Skip to content

Commit 68cc784

Browse files
committed
test: add #1849 fused-3D regression + shape-parametrized forward coverage
- test_experts4bit_1849_regression_...: build a fused-3D nn.Parameter expert module (the transformers v5 layout the Linear4bit walker skips), assert Experts4bit.from_float actually 4-bit-quantizes it (uint8-packed, >3x smaller than fp16). Guards the exact silent-skip #1849 reports. - test_experts4bit_shapes: forward correctness across a spread of (num_experts, hidden, intermediate). Co-Authored-By: Jordan Anderson <paul.jordan.anderson@gmail.com>
1 parent 9483b27 commit 68cc784

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

tests/test_experts4bit.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,58 @@ def test_experts4bit_lora_training_reduces_loss():
292292
# The frozen 4-bit base is bit-identical before and after training.
293293
assert torch.equal(base.gate_up_proj, gate_up_before)
294294
assert torch.equal(base.down_proj, down_before)
295+
296+
297+
# --- #1849 regression + shape coverage ------------------------------------------------------------
298+
299+
300+
def test_experts4bit_1849_regression_fused_experts_get_quantized():
301+
"""Regression for #1849. transformers v5 stores MoE experts as a single fused 3D ``nn.Parameter``
302+
(``[num_experts, out, in]``, e.g. ``Qwen3MoeExperts``), which the default 4-bit walker skips because
303+
there is no ``nn.Linear`` to replace — so the experts stay full-precision and dominate memory.
304+
``Experts4bit`` is the fix: it actually 4-bit-quantizes the fused stack. Assert (a) the fused module
305+
exposes no ``nn.Linear`` for the walker to catch, and (b) ``from_float`` yields ``uint8``-packed 4-bit
306+
weights materially smaller than the fp16 originals."""
307+
num_experts, hidden, inter = 4, 128, 256
308+
309+
class FusedExperts(torch.nn.Module): # mirrors OlmoeExperts / Qwen3MoeExperts from #1849
310+
def __init__(self):
311+
super().__init__()
312+
self.gate_up_proj = torch.nn.Parameter(torch.randn(num_experts, 2 * inter, hidden) * 0.1)
313+
self.down_proj = torch.nn.Parameter(torch.randn(num_experts, hidden, inter) * 0.1)
314+
315+
fused = FusedExperts()
316+
# (a) the walker's target type is absent -> a Linear4bit conversion would be a silent no-op here.
317+
assert not any(isinstance(m, torch.nn.Linear) for m in fused.modules())
318+
fp16_bytes = (fused.gate_up_proj.numel() + fused.down_proj.numel()) * 2
319+
320+
# (b) Experts4bit quantizes the fused stack: uint8-packed 4-bit weights + small fp32 absmax.
321+
q = Experts4bit.from_float(
322+
fused.gate_up_proj.data.half(), fused.down_proj.data.half(), compute_dtype=torch.float16
323+
)
324+
assert q.gate_up_proj.dtype == torch.uint8 and q.down_proj.dtype == torch.uint8
325+
quantized_bytes = (
326+
q.gate_up_proj.numel() + q.down_proj.numel() # uint8 packed
327+
+ (q.gate_up_absmax.numel() + q.down_absmax.numel()) * 4 # fp32 absmax
328+
)
329+
assert quantized_bytes < fp16_bytes / 3 # ~4x on the weights, minus small absmax overhead
330+
331+
332+
@pytest.mark.parametrize("device", get_available_devices())
333+
@pytest.mark.parametrize(
334+
"num_experts,hidden,inter",
335+
[(2, 64, 64), (8, 128, 256), (4, 192, 320)],
336+
ids=["e2_h64_i64", "e8_h128_i256", "e4_h192_i320"],
337+
)
338+
def test_experts4bit_shapes(device, num_experts, hidden, inter):
339+
"""Forward is correct across a spread of MoE dims (all multiples of the blocksize)."""
340+
gate_up = torch.randn(num_experts, 2 * inter, hidden, dtype=torch.float32, device=device) * 0.1
341+
down = torch.randn(num_experts, hidden, inter, dtype=torch.float32, device=device) * 0.1
342+
module = Experts4bit.from_float(gate_up, down, compute_dtype=torch.float32)
343+
344+
n_tok = 10
345+
hidden_states = torch.randn(n_tok, hidden, device=device)
346+
top_k_index = torch.randint(0, num_experts, (n_tok, TOP_K), device=device)
347+
top_k_weights = torch.softmax(torch.randn(n_tok, TOP_K, device=device), dim=-1)
348+
out = module(hidden_states, top_k_index, top_k_weights)
349+
assert out.shape == (n_tok, hidden) and torch.isfinite(out).all()

0 commit comments

Comments
 (0)