Source code for coreai_opt.inspection.bits_per_weight

# 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

"""Compute the average bits-per-weight (bpw) of a prepared ``coreai-opt`` model."""

from collections import defaultdict
from dataclasses import dataclass

import torch
from torch.nn.utils import parametrize as _parametrize

from coreai_opt.base_model_compressor import _COREAI_OPT_PREPARED_ATTR as _PREPARED_MARKER

from ._bpw_utils import (
    ensure_not_pruned as _ensure_not_pruned,
    ensure_single_original as _ensure_single_original,
    ensure_supported_quantization as _ensure_supported_quantization,
    full_precision_bits as _full_precision_bits,
    get_weight_compressor as _get_weight_compressor,
    named_modules_excluding_compression_machinery as _named_modules_excluding_compression_machinery,
    tensor_storage_bits as _tensor_storage_bits,
)

__all__ = ["BitsPerWeightResult", "bits_per_weight"]


[docs] @dataclass class BitsPerWeightResult: """Result of a bits-per-weight computation. Attributes: bpw (float): Overall average bits per weight across all parameters (``total_bits / total_weights``). per_module_map (dict[str, float]): Map from module name to that module's own average bits per weight. Modules with no tensors are omitted. total_bits (int): Total storage cost in bits, including amortized compression overhead. total_weights (int): Total number of parameter elements. """ bpw: float per_module_map: dict[str, float] total_bits: int total_weights: int def __repr__(self) -> str: return ( f"BitsPerWeightResult(bpw={self.bpw:.4f}, " f"total_bits={self.total_bits}, total_weights={self.total_weights})" )
[docs] def bits_per_weight(model: torch.nn.Module) -> BitsPerWeightResult: """Compute the average bits-per-weight of a prepared ``coreai-opt`` model. Walks the module tree once. For each parametrized weight, the dense original tensor is counted at its effective compressed cost (eager mode quantization or palettization). Every other directly-owned parameter (biases, norms) and every buffer (BatchNorm running stats, RoPE caches, etc.) are counted at their full-precision dtype cost, regardless of ``persistent=``. Args: model (torch.nn.Module): A full-precision, eager-mode quantized, or palettized prepared model. Returns: BitsPerWeightResult: Overall bpw, per-module breakdown, and the total number of bits and weights used to derive them. Raises: NotImplementedError: If ``model`` is a graph-mode prepared model (a ``torch.fx.GraphModule``) or a ``torch.export.ExportedProgram``, or if it contains a weight compression whose storage cost this utility cannot compute. """ if isinstance(model, (torch.fx.GraphModule, torch.export.ExportedProgram)): raise NotImplementedError( f"Graph mode prepared models are not supported currently, got {type(model)}. " "Only full-precision, eager-mode quantized, and palettized " "nn.Modules are handled." ) module_bits: dict[str, int] = defaultdict(int) module_weights: dict[str, int] = defaultdict(int) # id() of every Parameter / Buffer already counted, so a tied tensor # is counted once seen_ids: set[int] = set() for name, module in _named_modules_excluding_compression_machinery(model): # Parametrized weights: count the dense original at its compressed cost. if _parametrize.is_parametrized(module): for tensor_name, param_list in module.parametrizations.items(): _ensure_single_original(param_list, name, tensor_name) _ensure_not_pruned(param_list, name, tensor_name) original = param_list.original if id(original) in seen_ids: continue seen_ids.add(id(original)) compressor = _get_weight_compressor(param_list) _ensure_supported_quantization(compressor, name, tensor_name) module_bits[name] += _tensor_storage_bits(original, compressor) module_weights[name] += original.numel() # Directly-owned plain parameters: bias, untargeted weights, norms, etc. for param in module.parameters(recurse=False): if id(param) in seen_ids: continue seen_ids.add(id(param)) module_bits[name] += _full_precision_bits(param) module_weights[name] += param.numel() # Buffers (BatchNorm running stats, RoPE caches, ...) # recurse=False keeps buf_name un-prefixed, so the marker comparison is # bare-to-bare. for buf_name, buf in module.named_buffers(recurse=False): if buf_name == _PREPARED_MARKER or id(buf) in seen_ids: continue seen_ids.add(id(buf)) module_bits[name] += _full_precision_bits(buf) module_weights[name] += buf.numel() total_bits = sum(module_bits.values()) total_weights = sum(module_weights.values()) per_module_map = { name: bits / module_weights[name] for name, bits in module_bits.items() if module_weights.get(name, 0) > 0 } bpw = total_bits / total_weights if total_weights > 0 else 0.0 return BitsPerWeightResult( bpw=bpw, per_module_map=per_module_map, total_bits=total_bits, total_weights=total_weights, )