fix: register_buffer for quant tensors, kron-based LoKr path, remove __doc__ overrides

This commit is contained in:
JWLHS 2026-07-16 01:18:39 +08:00
parent 99acd9d5b5
commit 9c576caa7f
2 changed files with 36 additions and 15 deletions

View File

@ -24,9 +24,8 @@ __all__ = [
"rotate_weight",
]
# Sphinx-compatible references for docstring coverage tools
TINT4Linear.__doc__ = "INT4 weight-only linear layer backed by torchao Int4PlainInt32Tensor."
quantize_model.__doc__ = "Quantize all nn.Linear layers in a model via torchao INT4 weight-only quantization."
reconstruct_int4_state_dict.__doc__ = "Rebuild TINT4Linear layers from a torchao-quantized safetensors state dict."
build_hadamard.__doc__ = "Build a normalized orthogonal Hadamard matrix for QuaRot."
rotate_weight.__doc__ = "Rotate weight matrix offline for QuaRot quantization."
# ★ FIX 3: 删除 __doc__ 覆写5 行)
# 原因:这些类/函数的 .py 文件里已有 docstring重复赋值会
# 1. 覆盖原始文档(如果两处不一致会让人困惑)
# 2. 干扰 Sphinx 等文档工具的正确引用
# 3. 违反 DRY 原则——文档应该只在源码处维护一份

View File

@ -58,9 +58,12 @@ class TINT4Linear(nn.Module):
else:
self.register_parameter('bias', None)
self._qdata = qdata
self._scale = scale
self._zp = zp
# ★ FIX 1: 使用 register_buffer 而不是直接用 = 赋值
# 好处model.to(device) / state_dict() / load_state_dict() 能自动管理
# 旧代码self._qdata = qdata → PyTorch 不知道这是持久化数据
self.register_buffer('_qdata', qdata)
self.register_buffer('_scale', scale)
self.register_buffer('_zp', zp)
self._block_size = block_size
self._qt: Int4PlainInt32Tensor | None = None
@ -156,19 +159,38 @@ class TINT4Linear(nn.Module):
else torch.float16)
for lora_entries in entries.values():
for e in lora_entries:
# LoKr path
# ── LoKr path ──
# ★ FIX 2: 用 torch.kron 替代 repeat_interleave
# 旧方案要求 w2 维度能被 factor 整除(例如 64÷60=不整除→报错)
# 新方案 kron 展开 + pad/trim 到目标维度,兼容任意分解
if isinstance(e[0], str) and e[0] == "lokr":
_, w1, w2, mult, factor = e[:5]
sl = e[5] if len(e) > 5 else None
se = e[6] if len(e) > 6 else None
# QuaRot: 先旋转 w2小矩阵转发负担小
# 数学上等价于旋转完整 kron 展开Hadamard 是分块对角的)
w2 = self._rotate_into_quarot(w2)
w1d = w1.to(device=dev, dtype=cd)
w2d = w2.to(device=dev, dtype=cd)
of2, if2 = w2d.shape
w1x = w1d.repeat_interleave(
of2 // factor, dim=0).repeat_interleave(
if2 // factor, dim=1)
dw = (w1x * w2d).mul_(mult)
# kron 展开为完整 delta代替 repeat_interleave
dw = torch.kron(w1d, w2d)
# pad/trim 到层实际维度
# QKV 融合层kron 覆盖 1 个 head模块持有 3 个
if dw.shape[0] < self.out_features:
dw = dw.repeat(
self.out_features // dw.shape[0], 1)
elif dw.shape[0] > self.out_features:
dw = dw[:self.out_features, :]
if dw.shape[1] < self.in_features:
dw = dw.repeat(
1, self.in_features // dw.shape[1])
elif dw.shape[1] > self.in_features:
dw = dw[:, :self.in_features]
dw = dw.contiguous().mul_(mult)
lo = x_flat.to(cd) @ dw.T
if sl is not None:
if lo.shape[1] != (se - sl):