yongyizang's picture
TinyMOSS-Diarize: 2.911-bit packed weights, runtime, and model card
7ccb33d verified
Raw
History Blame Contribute Delete
12.2 kB
"""Symmetric round-to-nearest fake quantization for linear and embedding weights."""
from __future__ import annotations
from typing import Any
import weakref
import torch
import torch.nn as nn
import torch.nn.functional as F
def _validate_rtn_args(
weight: torch.Tensor,
bits: int,
granularity: str,
group_size: int,
) -> None:
if weight.ndim != 2:
raise ValueError(f"RTN only supports 2-D weights, got {weight.ndim}-D")
if not weight.is_floating_point():
raise ValueError("RTN fake quantization requires a floating-point weight")
if bits not in {3, 4, 8}:
raise ValueError("bits must be 3, 4, or 8")
if granularity not in {"per_channel", "per_group"}:
raise ValueError("granularity must be 'per_channel' or 'per_group'")
if not isinstance(group_size, int) or group_size <= 0:
raise ValueError("group_size must be a positive integer")
def _rtn_groups(
weight: torch.Tensor,
granularity: str,
group_size: int,
) -> tuple[torch.Tensor, int]:
"""Return padded row-local groups and the original column count."""
rows, columns = weight.shape
actual_group_size = columns if granularity == "per_channel" else group_size
number_of_groups = (columns + actual_group_size - 1) // actual_group_size
padded_columns = number_of_groups * actual_group_size
if padded_columns != columns:
weight = F.pad(weight, (0, padded_columns - columns))
return weight.reshape(rows, number_of_groups, actual_group_size), columns
def _quantize_groups(
groups: torch.Tensor,
bits: int,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Quantize groups in fp32 with deployment-canonical fp16 scales."""
qmax = 2 ** (bits - 1) - 1
scales = groups.float().abs().amax(dim=-1, keepdim=True) / qmax
# The physical RTN format stores one fp16 scale per group. Canonicalizing
# here makes QAT/inference consume exactly the scale precision that export
# can preserve, including when the latent master is fp32.
scales = scales.to(torch.float16).float()
safe_scales = torch.where(scales == 0, torch.ones_like(scales), scales)
integers = torch.round(groups.float() / safe_scales).clamp(-qmax, qmax)
return integers, scales
class _RTNQuantizeSTE(torch.autograd.Function):
"""Autograd implementation with the same pure identity STE as Sherry."""
@staticmethod
def forward(
ctx: Any,
weight: torch.Tensor,
bits: int,
granularity: str,
group_size: int,
) -> torch.Tensor:
del ctx
_validate_rtn_args(weight, bits, granularity, group_size)
groups, columns = _rtn_groups(weight, granularity, group_size)
integers, scales = _quantize_groups(groups, bits)
quantized = (integers * scales).reshape(weight.shape[0], -1)[:, :columns]
return quantized.to(weight.dtype)
@staticmethod
def backward(
ctx: Any,
grad_output: torch.Tensor,
) -> tuple[torch.Tensor, None, None, None]:
del ctx
return grad_output, None, None, None
def rtn_quantize(
weight: torch.Tensor,
bits: int = 8,
granularity: str = "per_channel",
group_size: int = 128,
) -> torch.Tensor:
"""Apply symmetric absmax RTN fake quantization with an identity STE.
``per_channel`` assigns one scale to every row (the output channel for a
linear weight, or one embedding vector). ``per_group`` divides each row
along its last dimension and permits a shorter final group. The signed
integer grid is ``[-127, 127]`` for W8, ``[-7, 7]`` for W4, or
``[-3, 3]`` for W3.
"""
return _RTNQuantizeSTE.apply(weight, bits, granularity, group_size)
class RTNLinear(nn.Linear):
"""``nn.Linear`` with an fp32 master weight and RTN fake-quant forward."""
def __init__(
self,
in_features: int,
out_features: int,
bias: bool = True,
*,
bits: int = 8,
granularity: str = "per_channel",
group_size: int = 128,
device: torch.device | str | None = None,
) -> None:
# Validate configuration without imposing a divisibility requirement.
_validate_rtn_args(
torch.empty(out_features, in_features), bits, granularity, group_size
)
super().__init__(
in_features,
out_features,
bias=bias,
device=device,
dtype=torch.float32,
)
self.bits = bits
self.granularity = granularity
self.group_size = group_size
@classmethod
def from_linear(
cls,
linear: nn.Linear,
*,
bits: int = 8,
granularity: str = "per_channel",
group_size: int = 128,
) -> "RTNLinear":
converted = cls(
linear.in_features,
linear.out_features,
bias=linear.bias is not None,
bits=bits,
granularity=granularity,
group_size=group_size,
device=linear.weight.device,
)
with torch.no_grad():
converted.weight.copy_(linear.weight.detach().float())
if linear.bias is not None and converted.bias is not None:
converted.bias.copy_(linear.bias.detach().float())
converted.weight.requires_grad_(linear.weight.requires_grad)
if linear.bias is not None and converted.bias is not None:
converted.bias.requires_grad_(linear.bias.requires_grad)
converted.train(linear.training)
return converted
def forward(self, input: torch.Tensor) -> torch.Tensor:
quantized_weight = rtn_quantize(
self.weight,
bits=self.bits,
granularity=self.granularity,
group_size=self.group_size,
).to(input.dtype)
bias = self.bias.to(input.dtype) if self.bias is not None else None
return F.linear(input, quantized_weight, bias)
class RTNEmbedding(nn.Embedding):
"""Embedding with an fp32 master and configurable RTN fake quantization.
A forward-local cache lets a tied output head consume the exact same
fake-quant tensor used by the lookup. This is important for a tied
embedding/head matrix: merely tying the fp32 master parameters would still
permit the two call sites to fake-quantize independently.
"""
def __init__(
self,
num_embeddings: int,
embedding_dim: int,
padding_idx: int | None = None,
max_norm: float | None = None,
norm_type: float = 2.0,
scale_grad_by_freq: bool = False,
sparse: bool = False,
*,
bits: int = 8,
granularity: str = "per_channel",
group_size: int = 128,
compute_dtype: torch.dtype = torch.float32,
device: torch.device | str | None = None,
) -> None:
_validate_rtn_args(
torch.empty(num_embeddings, embedding_dim), bits, granularity, group_size
)
super().__init__(
num_embeddings,
embedding_dim,
padding_idx=padding_idx,
max_norm=max_norm,
norm_type=norm_type,
scale_grad_by_freq=scale_grad_by_freq,
sparse=sparse,
device=device,
dtype=torch.float32,
)
self.bits = bits
self.granularity = granularity
self.group_size = group_size
self.compute_dtype = compute_dtype
object.__setattr__(self, "_forward_quantized_weight", None)
object.__setattr__(self, "_eval_quantized_weight", None)
@classmethod
def from_embedding(
cls,
embedding: nn.Embedding,
*,
bits: int = 8,
granularity: str = "per_channel",
group_size: int = 128,
) -> "RTNEmbedding":
converted = cls(
embedding.num_embeddings,
embedding.embedding_dim,
padding_idx=embedding.padding_idx,
max_norm=embedding.max_norm,
norm_type=embedding.norm_type,
scale_grad_by_freq=embedding.scale_grad_by_freq,
sparse=embedding.sparse,
bits=bits,
granularity=granularity,
group_size=group_size,
compute_dtype=embedding.weight.dtype,
device=embedding.weight.device,
)
with torch.no_grad():
converted.weight.copy_(embedding.weight.detach().float())
converted.weight.requires_grad_(embedding.weight.requires_grad)
converted.train(embedding.training)
return converted
def _make_quantized_weight(self) -> torch.Tensor:
return rtn_quantize(
self.weight,
bits=self.bits,
granularity=self.granularity,
group_size=self.group_size,
).to(self.compute_dtype)
def cache_eval_weight(self, quantized_weight: torch.Tensor | None) -> None:
"""Install/remove a detached persistent fake-quant matrix for eval."""
object.__setattr__(self, "_eval_quantized_weight", quantized_weight)
object.__setattr__(self, "_forward_quantized_weight", None)
def _lookup_quantized_weight(self) -> torch.Tensor:
quantized_weight = self._eval_quantized_weight
if quantized_weight is None:
quantized_weight = self._make_quantized_weight()
object.__setattr__(self, "_forward_quantized_weight", quantized_weight)
return quantized_weight
def take_tied_quantized_weight(self) -> torch.Tensor:
"""Consume the precise tensor produced by the preceding lookup."""
quantized_weight = self._forward_quantized_weight
if quantized_weight is None:
raise RuntimeError(
"tied RTN lm_head ran without a preceding embedding lookup; "
"the shared fake-quant invariant cannot be guaranteed"
)
object.__setattr__(self, "_forward_quantized_weight", None)
return quantized_weight
def forward(self, input: torch.Tensor) -> torch.Tensor:
quantized_weight = self._lookup_quantized_weight()
return F.embedding(
input,
quantized_weight,
self.padding_idx,
self.max_norm,
self.norm_type,
self.scale_grad_by_freq,
self.sparse,
)
class TiedRTNLMHead(nn.Module):
"""Bias-free projection tied to an :class:`RTNEmbedding` master and cache."""
def __init__(self, embedding: RTNEmbedding) -> None:
super().__init__()
self.in_features = embedding.embedding_dim
self.out_features = embedding.num_embeddings
# Register the same Parameter at the conventional checkpoint key while
# keeping the module reference weak/non-registered (no module cycle).
self.weight = embedding.weight
object.__setattr__(self, "_embedding_ref", weakref.ref(embedding))
@property
def embedding(self) -> RTNEmbedding:
embedding = self._embedding_ref()
if embedding is None:
raise RuntimeError("tied RTN embedding no longer exists")
return embedding
def forward(self, input: torch.Tensor) -> torch.Tensor:
embedding = self.embedding
if self.weight is not embedding.weight:
raise RuntimeError("RTN embedding/lm_head fp32 master tie was broken")
quantized_weight = embedding.take_tied_quantized_weight()
return F.linear(input, quantized_weight, None)
def tie_rtn_lm_head(model: nn.Module) -> tuple[RTNEmbedding, TiedRTNLMHead]:
"""Reconnect MOSS input/output weights through one shared fake-quant path."""
embedding = model.get_input_embeddings()
if not isinstance(embedding, RTNEmbedding):
raise TypeError(f"expected RTNEmbedding input, got {type(embedding).__name__}")
head = TiedRTNLMHead(embedding)
model.set_output_embeddings(head)
if model.get_output_embeddings() is not head:
raise RuntimeError("model rejected the tied RTN output head")
if head.weight is not embedding.weight or head.weight.data_ptr() != embedding.weight.data_ptr():
raise RuntimeError("embedding/lm_head fp32 master tie verification failed")
return embedding, head