Source code for coreai_opt.palettization.spec.training_strategy

# Copyright 2026 Apple Inc.
#
# Use of this source code is governed by a BSD-3-Clause license that can
# be found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause

"""Pluggable training-time behavior for fake-palettize modules."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, ClassVar

import torch
from pydantic import BaseModel, ConfigDict, model_serializer

from coreai_opt._utils.registry_utils import ConfigRegistryMixin as _ConfigRegistryMixin

if TYPE_CHECKING:
    from coreai_opt.palettization.spec.fake_palettize import _FakePalettizeImplBase


[docs] class TrainingStrategy(ABC): """Contract for a fake-palettize module's training-time forward pass."""
[docs] @abstractmethod def train_forward(self, module: _FakePalettizeImplBase, weight: torch.Tensor) -> torch.Tensor: """Return the training-time output for ``weight``. Defines how the palettized weight behaves during training (e.g. frozen, straight-through, soft assignment). """ raise NotImplementedError
class _DefaultTrainingStrategy(TrainingStrategy): """Post-training, one-shot k-means — today's KMeansPalettizer behavior. This strategy does not train the palettized weights. Inside a ``training_mode()`` loop it reconstructs each forward from the frozen centroids/indices computed at ``prepare()`` time, so no gradient flows back to a palettized weight — palettized weights stay fixed at their one-shot k-means values. Once a module's ``pat_schedule`` has enabled fake palettization, the rest of the model still trains normally and its forward pass sees the palettized weights, so non-palettized parameters adapt around them (palettization-aware fine-tuning of the rest of the network). To learn the palettized weights/centroids themselves, register a custom ``TrainingStrategy``. """ def train_forward(self, module: _FakePalettizeImplBase, weight: torch.Tensor) -> torch.Tensor: return module.hard_assign(weight)
[docs] class TrainingStrategySpec(BaseModel, _ConfigRegistryMixin): """Base class for a fake-palettize module's training-strategy settings. Each subclass points ``_strategy_cls`` at its paired ``TrainingStrategy`` behavior class; ``build_strategy()`` constructs that strategy from this spec's own fields. """ model_config = ConfigDict(frozen=True, extra="forbid") # Each subclass points this at its paired TrainingStrategy behavior class. _strategy_cls: ClassVar[type[TrainingStrategy]] @model_serializer def _serialize_model(self) -> dict[str, Any]: """Custom serializer that includes the registry type.""" data = {} for field_name in type(self).model_fields: data[field_name] = getattr(self, field_name) # Find the registry key for this class type registry_key = None # Use the base class registry instead of instance registry for key, registered_class in TrainingStrategySpec.REGISTRY.items(): if registered_class is type(self): registry_key = key break if registry_key is not None: data["type"] = registry_key return data
[docs] def build_strategy(self) -> TrainingStrategy: """Construct this spec's paired ``TrainingStrategy`` behavior instance.""" kwargs = {k: v for k, v in self.model_dump().items() if k != "type"} return self._strategy_cls(**kwargs)
[docs] @TrainingStrategySpec.register("default") class DefaultTrainingSpec(TrainingStrategySpec): """Settings for the default, post-training one-shot k-means strategy. No fields.""" _strategy_cls = _DefaultTrainingStrategy