Repository navigation
Reload prepared models from a state dict for all compressors #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Added a `state_dict` argument to `prepare()` for `Quantizer` (eager and graph mode) and `MagnitudePruner`, matching `KMeansPalettizer`. It reloads a previously prepared model's compression state (calibrated qparams, pruning masks and step count, centroids), validates it against the config, and leaves the model in the same state as a regular `prepare()` call. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Pruning modules now store their step count in a `step_count` buffer, so a `MagnitudePruner` state dict saved before this change cannot be reloaded with `prepare(state_dict=...)`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fixed `KMeansPalettizer.prepare(state_dict=...)` failing for configs with a quantized LUT, and `MagnitudePruner` restarting its sparsity schedule from step 0 after a model saved mid-schedule was reloaded. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ class PruneImplBase(CompressionSimulatorBase): | |
| """ | ||
|
|
||
| schedule: SparsityScheduleBase | None = None | ||
| step_count: torch.Tensor | ||
| _sparsity: float | ||
| _target_sparsity: float | ||
| _pruning_scheme: PruningScheme | ||
|
|
@@ -55,6 +56,38 @@ def __init__( | |
| self.schedule = None | ||
| self._dirty = True | ||
| self.register_buffer("mask", torch.empty(0)) | ||
| # Persisted so a reloaded model resumes mid-schedule; the sparsity is derived from it. | ||
| self.register_buffer("step_count", torch.tensor(0, dtype=torch.int64)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This addition of We could also consider checking if step_count is missing from the state_dict during loading and default the count to 0 in that case, but that may be overkill. Separately, do you plan to make similar step_count buffer additions to palettizers and quantizers too? Those would both benefit in then being able to easily pick up again if saved in the middle of QAT/PAT training
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, because this was an improbable scenario, I did not add the missing key check. I can include a note in the changelog regarding the incompatibility. |
||
|
|
||
| def _load_from_state_dict( | ||
| self, | ||
| state_dict: dict[str, Any], | ||
| prefix: str, | ||
| local_metadata: dict[str, Any], | ||
| strict: bool, | ||
| missing_keys: list[str], | ||
| unexpected_keys: list[str], | ||
| error_msgs: list[str], | ||
| ) -> None: | ||
| """Load a saved mask and step count, and derive the scheduled sparsity from the step. | ||
|
|
||
| A mask that has not been computed yet takes the saved shape. | ||
| """ | ||
| mask_key = prefix + "mask" | ||
| # The mask is empty until first computed; take the saved shape so it loads correctly | ||
| if mask_key in state_dict and self.mask.numel() == 0: | ||
| self.mask = torch.empty_like(state_dict[mask_key], device=self.mask.device) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can also consider adding a version checker to ensure compatibility: _version = 2
step_key = prefix + "step_count"
if local_metadata.get("version", 1) < 2 and step_key not in state_dict:
# Checkpoints saved before step_count existed keep the current step.
state_dict[step_key] = self.step_count |
||
| super()._load_from_state_dict( | ||
| state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs | ||
| ) | ||
| if self.schedule is not None: | ||
| # Replayed from step 0, since off-boundary steps of some schedules reuse the | ||
| # previous sparsity. Fine for a one-off restore. | ||
| for step in range(int(self.step_count) + 1): | ||
| self.update_sparsity(step) | ||
| if mask_key in state_dict and self.mask.numel() > 0: | ||
| # A loaded mask is current, this prevents it from being recomputed during forward pass | ||
| self._dirty = False | ||
|
|
||
| @property | ||
| def sparsity(self) -> float: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep
self._model.apply(_enable_fake_palett)inside the if case only. The responsibility of setting the buffers when state_dict is present should just automatically fall to the loading of the state dict. It also keeps things symmetrical where the earlierself._model.apply(_disable_fake_palett)only happened if state_dict is None.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think we should set the flags during
prepareno matter what the state_dict shows. A user could save a checkpoint mid calibration or training; observer flags are distinct in that they affect further calibration / training steps, so we should consistently return them in their documented state after prepare.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose saving the state dict in the middle of the context managers could lead to unintended compressor states being saved since the context manager cleanup wouldn't have happened. I'm ok then with resetting to post-prepare.
Could we then add a note to palettizer's prepare
state_dictdocstring saying that it resets to post-prepare states? Similar to what we have forquantization/quantizer.py.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure will update the docstring