Skip to content

fix: make reset_model null-safe to handle study cancellations (#77)#367

Merged
p-e-w merged 6 commits into
p-e-w:masterfrom
umran666:fix/study-cancellation-reload
Jun 11, 2026
Merged

fix: make reset_model null-safe to handle study cancellations (#77)#367
p-e-w merged 6 commits into
p-e-w:masterfrom
umran666:fix/study-cancellation-reload

Conversation

@umran666

@umran666 umran666 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

When a study is cancelled (via Ctrl+C) during a model reload, self.model is left as None. Subsequent calls to reset_model() fail with an AttributeError when trying to read self.model.config or self.model.dtype.

This PR introduces a cached self.dtype attribute on the Model class, populates it upon successful load, and updates reset_model() to safely check for nullity and fall back to the cached self.dtype when self.model is None. This allows the CLI to recover and successfully reload the model when selecting a new trial after a cancellation.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/heretic/model.py Outdated
Comment on lines +324 to +328
current_model = (
getattr(self.model.config, "name_or_path", None)
if self.model is not None
else None
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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
)

Comment thread src/heretic/model.py Outdated
Comment on lines +337 to +341
if self.model is not None:
dtype = self.model.dtype
self.dtype = dtype
else:
dtype = self.dtype or "auto"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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 p-e-w left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

Comment thread src/heretic/model.py Outdated
else:
dtype = self.dtype or (
self.settings.dtypes[0] if self.settings.dtypes else "auto"
)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply always user self.dtype here? It's always set, and it's always the right dtype, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I just pushed an update to use self.dtype directly.

Comment thread src/heretic/model.py Outdated
**self.revision_kwargs,
**extra_kwargs,
)
if self.model is not None:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can self.model be None here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yea It can't be. from_pretrained never returns None. I've removed the redundant None checks in the latest commit.

@umran666 umran666 force-pushed the fix/study-cancellation-reload branch from 32d462e to b0dac6f Compare June 10, 2026 15:29
@umran666

Copy link
Copy Markdown
Contributor Author

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.

Comment thread src/heretic/model.py Outdated
def __init__(self, settings: Settings):
self.settings = settings
self.needs_reload = False
self.dtype = None

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this either. The dtype will always be set below, otherwise an exception bubbles up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Removed the redundant initialization.

Comment thread src/heretic/model.py Outdated
**self.revision_kwargs,
**extra_kwargs,
)
self.dtype = self.model.dtype

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this. The dtype must remain constant throughout the run.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Removed this re-assignment since self.dtype is already set from the initial load.

Comment thread src/heretic/model.py Outdated
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

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a separate variable, just inline self.dtype below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, inlined self.dtype directly.

Comment thread src/heretic/model.py Outdated
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

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that was completely redundant. I've removed the assertion.

Comment thread src/heretic/model.py Outdated
# Set for multimodal models, None for text-only ones.
processor: ProcessorMixin | None
peft_config: LoraConfig
dtype: torch.dtype | str | None

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I've updated the Model.dtype type annotation to strictly be torch.dtype and confirmed it passes ty check.

Comment thread pyproject.toml Outdated
"tomli-w~=1.2",
"tqdm~=4.67",
"transformers[kernels]~=5.6",
"torch",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add this dependency?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't let agents blindly do stuff. It's such a hassle to review.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@p-e-w p-e-w merged commit e735203 into p-e-w:master Jun 11, 2026
4 checks passed
@p-e-w

p-e-w commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants