Skip to content

Commit e3fa491

Browse files
authored
Unify parallel and sequential KMeansPalettizer centroid computation (#66)
Unify parallel and sequential KMeansPalettizer centroid computation. This change adopts the parallel path's per-fake-palettize computation for the sequential path, eliminating the need to retain example_inputs. Sequential just does the same thing as parallel using a loop instead of a worker pool. Tested using existing unit tests, as there's coverage for both the sequential and parallel centroid computation.
1 parent 1813541 commit e3fa491

1 file changed

Lines changed: 52 additions & 55 deletions

File tree

src/coreai_opt/palettization/kmeans/palettizer.py

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
)
2828
from coreai_opt._utils.spec_utils import PartialConstructor as _PartialConstructor
2929
from coreai_opt._utils.torch_utils import (
30-
move_model_to_eval as _move_model_to_eval,
3130
remove_compression_parametrizations as _remove_compression_parametrizations,
3231
)
3332
from coreai_opt.common import ExportBackend
@@ -100,9 +99,6 @@ def _calculate_centroids_for_module(
10099
except Exception as e:
101100
raise RuntimeError(f"Centroid calculation failed for layer {layer_name!r}") from e
102101

103-
if fp_module._disabled:
104-
fp_module._disabled_reason = f"layer {layer_name!r}"
105-
106102
return fp_module
107103

108104

@@ -141,9 +137,6 @@ def __init__(self, model: torch.nn.Module, config: KMeansPalettizerConfig | None
141137
optimization_type_name="palettize",
142138
)
143139

144-
# Store example inputs for sensitivity-based centroid recomputation
145-
self._example_inputs = None
146-
147140
self._num_workers = 1
148141

149142
@classmethod
@@ -198,9 +191,6 @@ def prepare(
198191
logger.info("Preparing model for palettization")
199192
prepared_model = self._handler.prepare(self._model, example_inputs=example_inputs)
200193

201-
# Save example inputs for later use in calibration
202-
self._example_inputs = tuple([ip.detach().clone() for ip in example_inputs])
203-
204194
# Load precomputed sensitivities if provided
205195
if sensitivity_path is not None:
206196
logger.info(
@@ -216,7 +206,7 @@ def prepare(
216206
if self._num_workers > 1:
217207
self._calculate_centroids_parallel(num_workers)
218208
else:
219-
self._calculate_centroids_sequential(example_inputs)
209+
self._calculate_centroids_sequential()
220210

221211
# Remove FakePalettize modules that were disabled during the forward
222212
# pass due to incompatible granularity or cluster dimensions.
@@ -335,7 +325,7 @@ def step(self, output: torch.Tensor, target: torch.Tensor):
335325
if self._num_workers > 1:
336326
self._calculate_centroids_parallel(self._num_workers)
337327
else:
338-
self._calculate_centroids_sequential(self._example_inputs)
328+
self._calculate_centroids_sequential()
339329

340330
# Restore normal operation
341331
self._model.apply(_enable_fake_palett)
@@ -438,49 +428,24 @@ def _spec_to_partial(
438428
args.update(module_config._get_compressor_specific_settings())
439429
return _KMeansFakePalettize.with_args(**args)
440430

441-
def _calculate_centroids_sequential(self, example_inputs: tuple[torch.Tensor]) -> None:
442-
"""Run a forward pass to calculate centroids, with a per-layer progress bar."""
443-
fp_modules: list[_KMeansFakePalettize] = []
444-
for _, module in self._model.named_modules(remove_duplicate=True):
445-
if not P.is_parametrized(module):
446-
continue
447-
for parametrizations in module.parametrizations.values():
448-
for p in parametrizations:
449-
if isinstance(p, _KMeansFakePalettize):
450-
fp_modules.append(p)
451-
break
452-
453-
progress = tqdm(total=len(fp_modules), desc="Palettizing layers (num_workers=1)")
454-
seen: set[int] = set()
431+
def _collect_fake_palett_info(self, *, to_cpu: bool) -> list[_FakePalettInfo]:
432+
"""Collect one ``_FakePalettInfo`` per ``_KMeansFakePalettize`` parametrization.
455433
456-
def _tick(module, _inputs, _output):
457-
if id(module) not in seen:
458-
seen.add(id(module))
459-
progress.update(1)
460-
461-
handles = [m.register_forward_hook(_tick) for m in fp_modules]
462-
try:
463-
with _move_model_to_eval(self._model):
464-
with torch.no_grad():
465-
self._model(*example_inputs)
466-
finally:
467-
for h in handles:
468-
h.remove()
469-
progress.close()
470-
471-
def _calculate_centroids_parallel(self, num_workers: int) -> None:
472-
"""Compute centroids for all _KMeansFakePalettize modules in parallel."""
473-
# Track parametrization slot (module, attr_name, idx) so the worker's
474-
# mutated module can be swapped back in. Whole-module swap means every
475-
# buffer and plain attribute round-trips automatically.
434+
Records the parametrization slot (module, attr_name, idx) so a
435+
(worker-mutated) module can be swapped back in, plus the layer's dense
436+
weight. ``to_cpu`` moves each weight to CPU, required when the weight is
437+
shipped to a spawned worker process.
438+
"""
476439
fp_info: list[_FakePalettInfo] = []
477440
for module_name, module in self._model.named_modules(remove_duplicate=True):
478441
if not P.is_parametrized(module):
479442
continue
480443
for attr_name, parametrizations in module.parametrizations.items():
481444
for idx, p in enumerate(parametrizations):
482445
if isinstance(p, _KMeansFakePalettize):
483-
weight = parametrizations.original.detach().cpu()
446+
weight = parametrizations.original.detach()
447+
if to_cpu:
448+
weight = weight.cpu()
484449
fp_info.append(
485450
_FakePalettInfo(
486451
module=module,
@@ -492,7 +457,30 @@ def _calculate_centroids_parallel(self, num_workers: int) -> None:
492457
)
493458
)
494459
break
460+
return fp_info
495461

462+
def _calculate_centroids_sequential(self) -> None:
463+
"""Compute centroids for every ``_KMeansFakePalettize`` module in-process.
464+
465+
Mirrors the parallel path but runs in the current process (no worker
466+
pool): each layer's centroids are computed by invoking
467+
``fp_module(weight)`` directly. Palettization centroids depend only on
468+
the layer weight, so this needs no model forward — and therefore no
469+
example inputs.
470+
"""
471+
fp_info = self._collect_fake_palett_info(to_cpu=False)
472+
if not fp_info:
473+
return
474+
475+
results = [
476+
_calculate_centroids_for_module((info.fp_module, info.weight, info.layer_name))
477+
for info in tqdm(fp_info, desc="Palettizing layers (num_workers=1)")
478+
]
479+
self._apply_centroid_results(fp_info, results)
480+
481+
def _calculate_centroids_parallel(self, num_workers: int) -> None:
482+
"""Compute centroids for all _KMeansFakePalettize modules in parallel."""
483+
fp_info = self._collect_fake_palett_info(to_cpu=True)
496484
if not fp_info:
497485
return
498486

@@ -516,15 +504,24 @@ def _calculate_centroids_parallel(self, num_workers: int) -> None:
516504
)
517505
)
518506

507+
self._apply_centroid_results(fp_info, results)
508+
509+
def _apply_centroid_results(
510+
self,
511+
fp_info: list[_FakePalettInfo],
512+
results: list[_KMeansFakePalettize],
513+
) -> None:
514+
"""Swap each computed ``_KMeansFakePalettize`` back into its slot.
515+
516+
``ParametrizationList`` supports item assignment, so this swaps the
517+
module into the live model without touching the surrounding
518+
parametrization registration. In the parallel path ``results`` holds the
519+
workers' returned copies; in-process they are the same live objects
520+
(mutated in place), so the assignment is a harmless no-op there.
521+
"""
519522
for info, new_fp in zip(fp_info, results, strict=True):
520-
if getattr(new_fp, "_disabled", False):
521-
logger.warning(
522-
f"Disabling palettization for a module: "
523-
f"{getattr(new_fp, '_disabled_reason', '')}"
524-
)
525-
# ParametrizationList supports item assignment; this swaps the
526-
# worker's mutated module into the live model without touching
527-
# the surrounding parametrization registration.
523+
if new_fp.is_disabled():
524+
logger.warning("Disabling palettization for layer %r", info.layer_name)
528525
info.module.parametrizations[info.attr_name][info.idx] = new_fp
529526

530527
@staticmethod

0 commit comments

Comments
 (0)