fix: make reset_model null-safe to handle study cancellations (#77)#367
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates src/heretic/model.py to safely track and handle the model's dtype, especially when self.model is None during model resets. The review feedback suggests two improvements: first, to safely access the model's configuration using nested getattr to prevent potential AttributeErrors, and second, to fall back to the user's configured dtypes instead of a hardcoded "auto" string when the model's dtype is unavailable.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| current_model = ( | ||
| getattr(self.model.config, "name_or_path", None) | ||
| if self.model is not None | ||
| else None | ||
| ) |
There was a problem hiding this comment.
To prevent a potential AttributeError if self.model does not have a config attribute (for example, in certain mock or custom model configurations), it is safer to use getattr to access config as well.
| current_model = ( | |
| getattr(self.model.config, "name_or_path", None) | |
| if self.model is not None | |
| else None | |
| ) | |
| current_model = ( | |
| getattr(getattr(self.model, "config", None), "name_or_path", None) | |
| if self.model is not None | |
| else None | |
| ) |
| if self.model is not None: | ||
| dtype = self.model.dtype | ||
| self.dtype = dtype | ||
| else: | ||
| dtype = self.dtype or "auto" |
There was a problem hiding this comment.
If the initial model load was cancelled, self.dtype will be None. Falling back to a hardcoded "auto" string might bypass the user's custom dtypes configuration (defined in self.settings.dtypes). It is safer to fall back to the first configured dtype in self.settings.dtypes if available, and only use "auto" as a final fallback.
| if self.model is not None: | |
| dtype = self.model.dtype | |
| self.dtype = dtype | |
| else: | |
| dtype = self.dtype or "auto" | |
| if self.model is not None: | |
| dtype = self.model.dtype | |
| self.dtype = dtype | |
| else: | |
| dtype = self.dtype or (self.settings.dtypes[0] if self.settings.dtypes else "auto") |
p-e-w
left a comment
There was a problem hiding this comment.
Thanks for tackling this problem, though I think this PR can be substantially simplified.
BTW, the PR description is mangled with missing spaces, presumably from copy&paste or a broken agent. I don't mind contributors using LLMs (although I don't use them myself), but please do at least a basic sanity check before clicking "submit".
| else: | ||
| dtype = self.dtype or ( | ||
| self.settings.dtypes[0] if self.settings.dtypes else "auto" | ||
| ) |
There was a problem hiding this comment.
Why not simply always user self.dtype here? It's always set, and it's always the right dtype, no?
There was a problem hiding this comment.
Good catch. I just pushed an update to use self.dtype directly.
| **self.revision_kwargs, | ||
| **extra_kwargs, | ||
| ) | ||
| if self.model is not None: |
There was a problem hiding this comment.
Oh yea It can't be. from_pretrained never returns None. I've removed the redundant None checks in the latest commit.
32d462e to
b0dac6f
Compare
|
Ah, my apologies for the formatting. You're completely right—a local tokenizer bug stripped the spacing around the inline code backticks when the description text was formatted. I've edited the PR description to restore the spaces, and pushed the updates to simplify the code changes as we discussed. Thanks for catching that. |
| def __init__(self, settings: Settings): | ||
| self.settings = settings | ||
| self.needs_reload = False | ||
| self.dtype = None |
There was a problem hiding this comment.
I don't think we need this either. The dtype will always be set below, otherwise an exception bubbles up.
There was a problem hiding this comment.
Makes sense. Removed the redundant initialization.
| **self.revision_kwargs, | ||
| **extra_kwargs, | ||
| ) | ||
| self.dtype = self.model.dtype |
There was a problem hiding this comment.
We don't need this. The dtype must remain constant throughout the run.
There was a problem hiding this comment.
Agreed. Removed this re-assignment since self.dtype is already set from the initial load.
| dtype = self.model.dtype | ||
| # self.dtype is populated on successful load in __init__ or reload, | ||
| # providing a safe fallback if the model object is currently None. | ||
| dtype = self.dtype |
There was a problem hiding this comment.
No need for a separate variable, just inline self.dtype below.
There was a problem hiding this comment.
Done, inlined self.dtype directly.
| if current_model == self.settings.model and not self.needs_reload: | ||
| # Reset LoRA adapters to zero (identity transformation) | ||
| # Reset LoRA adapters to zero (identity transformation). | ||
| assert self.model is not None |
There was a problem hiding this comment.
Not sure what the purpose of this assertion is. This condition is guaranteed to be true by the logic above, because if self.model is None, current_model is also None, and this if branch will never be taken because self.settings.model cannot be None.
There was a problem hiding this comment.
You're right, that was completely redundant. I've removed the assertion.
| # Set for multimodal models, None for text-only ones. | ||
| processor: ProcessorMixin | None | ||
| peft_config: LoraConfig | ||
| dtype: torch.dtype | str | None |
There was a problem hiding this comment.
No, it can only be a torch.dtype. That's the type of model.dtype according to the docs, and it can never be None.
There was a problem hiding this comment.
Understood. I've updated the Model.dtype type annotation to strictly be torch.dtype and confirmed it passes ty check.
| "tomli-w~=1.2", | ||
| "tqdm~=4.67", | ||
| "transformers[kernels]~=5.6", | ||
| "torch", |
There was a problem hiding this comment.
Please don't let agents blindly do stuff. It's such a hassle to review.
There was a problem hiding this comment.
Apologies for the noise. The package and lockfile modifications were accidental environment pinning changes introduced by the coding agent during local test setup. I have reverted pyproject.toml and uv.lock to match upstream master exactly, leaving the PR focused solely on the cancel-reload bug fix.
|
Merged! |
When a study is cancelled (via Ctrl+C) during a model reload,
self.modelis left asNone. Subsequent calls toreset_model()fail with anAttributeErrorwhen trying to readself.model.configorself.model.dtype.This PR introduces a cached
self.dtypeattribute on theModelclass, populates it upon successful load, and updatesreset_model()to safely check for nullity and fall back to the cachedself.dtypewhenself.modelisNone. This allows the CLI to recover and successfully reload the model when selecting a new trial after a cancellation.