Update to Model Hub integration - #502
Conversation
okrochak
left a comment
There was a problem hiding this comment.
I've left some comments about the modelhub-related code organization. I've tried to run the tutorial, also with my own .env file, but didn't manage to (see the last comment).
There was a problem hiding this comment.
GitHub preview shows me a lot of text being underlined, which I think is not so good for readability.
There was a problem hiding this comment.
Maybe it's better to put this function into features.py ? I feel like 1 file with 1 function is too little, but it's up to you
There was a problem hiding this comment.
Again, a subjective thing, but maybe it's better to put ModelHubModelLoader into model-hub folder and import it in inference.py, to more clearly manage Model Hub code.
There was a problem hiding this comment.
I've tried to run this tutorial with a fresh install of itwinai from this branch modelhub-in-trainer. I don't think the .env file is uploaded, and when I try to use the .env file I have configured myself for the model hub, I still get an error even though .env file was parsed:
[ERROR]: API token not provided. Set it via:
- --api-token option
- HYPHA_TOKEN environment variable
- HYPHA_TOKEN in .env file```
I will try to look into this deeper
|
@matbun Could you please review this? |
I was waiting for you to iterate on Alex's comments, but I can review it in parallel. Will do by the end of the week! |
matbun
left a comment
There was a problem hiding this comment.
Nice PR, it definitely improves a lot the integration with the AI model hub!
Important points (the order is random):
- I would suggest moving the
model_hubpackage fromitwinai/torch/model_hubto one level up asitwinai/model_hub. The reason is that itwinai is naturally designed to support multiple frameworks, andmodel_hubshould be considered at the same level as pytorch. Also considering that model hub does not strictly depend on torch. This also simplifies the import fromitwinai.torch.model_hubtoitwinai.model_hub - As already mentioned inline, I would remove the change in
conftest.pyand rebase on #505. This way we don't risk of having broken code while the CI still passes. - No test touches
model_hub/*,ModelHubModelLoader, or the CLI changes. I would suggest adding some tests. - The Model Hub pull runs on every rank.
TorchPredictor.executecallsself.model = self.model()without a rank guard, so all workers download the same checkpoint to the same CWD-relativetmp/modelhub_downloads/<model_id>/path and race on the write. On HPC that path is usually the submit directory on a shared filesystem, so it's a cross-node corruption race, not just wasted bandwidth. The guard can't go insideModelHubModelLoadersince aModelLoaderis a bare callable with no strategy handle, butexecute()method ownsself.strategy: download under ifself.strategy.is_main_worker: thenself.strategy.barrier(), since every rank still needs the file beforedistribute_model()
Other points:
- As discussed in a previous thread this could be a good opportunity of moving some code from
upload_to_model_huband_load_env_fileto the newmodel_hubpackage to save some lines incli.py. A similar reasoning is true for other functions we added incli.py... If you agree I will open a separate issue for cleaning this up as well.
| os.environ.setdefault("MLFLOW_ALLOW_FILE_STORE", "true") | ||
|
|
There was a problem hiding this comment.
This change is making the CI pass, but it is hiding the root cause, which is being solved by #505
If the user doesn't remember to set this env var the code will break, which is something that the tests are not able to detect anymore.
I would suggest removing this, merging #505 first, and rebasing on main to bring in the fix
| def __init__(self, config: dict): | ||
| self.config = config or {} | ||
| self.enabled = self.config.get("enabled", False) | ||
| self.final_checkpoint_name = self.config.get("final_checkpoint_name", "best_model") |
There was a problem hiding this comment.
final_checkpoint_name is configurable here, but trainer.py:1280 hardcodes the directory it
passes in:
best_ckpt_dir = Path(self.checkpoints_location) / "best_model"So if a user sets final_checkpoint_name: my_ckpt, on_training_end hits the
ckpt_dir.name != self.final_checkpoint_name guard on line 29, returns early, and the whole
feature silently does nothing — no manifest, no upload, no message. The knob is only ever
correct at its default value.
Two ways out: drop the option and hardcode "best_model" in both places, or (better) have the
trainer ask the feature for the name it wants. The latter also removes the guard entirely:
# trainer.py
if self.strategy.is_main_worker and self._model_hub.enabled:
best_ckpt_dir = Path(self.checkpoints_location) / self._model_hub.final_checkpoint_name
if best_ckpt_dir.exists():
self._model_hub.on_training_end(self, best_ckpt_dir)Worth noting "best_model" is also hardcoded at trainer.py:1235 in the save_checkpoint
call, so a real fix probably wants a single constant both sites share.
| def upload(self, model_dir: Path): | ||
| subprocess.run( | ||
| ["itwinai", "upload-model-to-hub", str(model_dir)], | ||
| check=False, |
There was a problem hiding this comment.
check=False means a non-zero exit from itwinai upload-model-to-hub never raises, right? in that case, this would make
_safe_upload's except Exception at feature.py:47 unreachable. so on a failed upload the
user never sees "Model Hub upload failed…" or the "You can re-upload later with…" hint, and
training reports success.
| dst_dir = Path("tmp") / "modelhub_downloads" / self.model_id | ||
| ckpt_path = download_file(self.base_url, self.model_id, file_path, dst_dir) | ||
|
|
||
| checkpoint = torch.load(ckpt_path, weights_only=False) |
There was a problem hiding this comment.
I would suggest to set weights_only=True here, as it seems we are loading only the weights of the model. This has some important security implications considering we are unpicking a file we downloaded from the internet from a remote source which could have been compromised.
| if isinstance(checkpoint, dict) and "model_state_dict" in checkpoint: | ||
| model.load_state_dict(checkpoint["model_state_dict"], strict=False) | ||
| else: | ||
| model.load_state_dict(checkpoint, strict=False) |
There was a problem hiding this comment.
With strict=False, load_state_dict returns silently when no key matches at all. Combined
with pulling weights from a remote hub and requiring the user to supply model_class by hand,
this is the exact scenario where a mismatch is likely, and the result is a randomly
initialised model that runs inference and returns plausible-looking garbage... But if this is too strict please ignore this comment
|
Hi @r-sarma, I saw your last commits but I haven't started a review yet because I don't know if you are already done. Don't hesitate to re-request a review when ready. No rush, though |
Mostly done but I am adding some tests. I will re-request once its ready. |
This PR is an update to the RI-SCALE Model Hub integration with itwinai, which was a CLI-based implementation.