Skip to content

Commit ae8aa89

Browse files
pjordanandrsnclaude
andcommitted
fix: retarget compute_dtype on float dtype casts instead of casting quant state
nn.Module dtype casts (.to(dtype), .half(), .bfloat16()) convert every floating-point tensor. The packed uint8 weights are naturally immune, but the fp32 absmax/code buffers would be silently cast, degrading every subsequent dequantization. Override _apply so a float dtype cast retargets compute_dtype while the quantization state stays fp32 (re-derived from the pre-cast tensors — a cast round-trip would be lossy). Device movement is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d5d8c92 commit ae8aa89

1 file changed

Lines changed: 21 additions & 0 deletions

File tree

bitsandbytes/nn/experts.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,27 @@ def from_float(
212212
module.down_absmax = down_absmax
213213
return module
214214

215+
def _apply(self, fn, recurse=True):
216+
"""Shield the quantization state from floating-point dtype casts.
217+
218+
``nn.Module`` dtype casts (``.to(dtype)``, ``.half()``, ``.bfloat16()``, …) convert
219+
every floating-point tensor. The packed ``uint8`` weights are naturally untouched,
220+
but the fp32 ``absmax``/``code`` state would be silently cast, degrading every
221+
subsequent dequantization. Instead, a float dtype cast retargets ``compute_dtype``
222+
while the quantization state stays fp32; device movement is unaffected.
223+
"""
224+
requested_dtype = fn(torch.empty(0, dtype=torch.float32)).dtype
225+
preserved = {name: getattr(self, name) for name in ("gate_up_absmax", "down_absmax", "code")}
226+
module = super()._apply(fn, recurse)
227+
if requested_dtype != torch.float32 and requested_dtype.is_floating_point:
228+
module.compute_dtype = requested_dtype
229+
for name, old in preserved.items():
230+
new = getattr(module, name)
231+
if new is not None and new.dtype != old.dtype:
232+
# Re-derive from the pre-cast tensor — a cast round-trip would be lossy.
233+
setattr(module, name, old.to(new.device))
234+
return module
235+
215236
def _quantize_stack(self, weights: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
216237
"""Quantize a ``[num_experts, out, in]`` stack to packed bytes + per-expert absmax."""
217238
packed = []

0 commit comments

Comments
 (0)