Repository navigation
Failed to quant MoE models with fused expert weights in transformers v5 #1849
Description
Description
At transformer v4, experts were implemented using nn.ModuleList of nn.Linear layers. However, starting from transformer v5, newer efficient implementations (likely optimizing for Grouped GEMM or CUDA kernels) are moving towards storing all expert weights in a single fused nn.Parameter tensor (e.g., with shape [num_experts, in_features, out_features]) instead of separate nn.Linear modules.
One typical model is Qwen3MoeForCausalLM, with expert implementations shown as below:
@use_experts_implementation
class Qwen3MoeExperts(nn.Module):
"""Collection of expert weights stored as 3D tensors."""
def __init__(self, config):
super().__init__()
self.num_experts = config.num_experts
self.hidden_dim = config.hidden_size
self.intermediate_dim = config.moe_intermediate_size
self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim))
self.down_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim))
self.act_fn = ACT2FN[config.hidden_act]It's worth noticing use_experts_implementation allow switching MOE implementation to the more efficent group_gemm version. Bitsandbytes library may also consider implementing a grouped forward version to improve speed on Moe models.
Reproduction
transformers 5.0.0rc3 should be used.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True
)
model_path = "Qwen/Qwen3-30B-A3B"
model = AutoModelForCausalLM.from_pretrained(
model_path,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
print(f"Memory: {model.get_memory_footprint() / 1024**3:.2f} GB")It prints Memory: 55.60 GB, showing quantizing is not working correctly.
Expected behavior
As in transformer v4, it prints Memory: 15.09 GB, which is properly quantized.
Fix proposal
I've opened a feature request in transformers to address the root cause of this issue: huggingface/transformers#43472.
Instead of handling raw parameters, I proposed introducing a standardized BatchLinear module. If adopted, downstream libraries would only need to support replacing this specific module type, ensuring compatibility for Qwen3-MoE, DeepSeek, and future MoE architectures without model-specific hacks.
Upvoting or commenting on that RFC would help prioritize a unified solution and significantly reduce the maintenance burden for bitsandbytes.
Activity
yurkoff-mv commentedon Feb 9, 2026
Hi!
On the Qwen3-30B-A3B-Instruct-2507 model, I get the
Memory: 15.09 GBoutput, butnvidia-smishows the consumption as 46.75 GB.My environment:
pjordanandrsn commentedon Jun 5, 2026
The walker in
transformers/integrations/bitsandbytes.pycould be extended to match the pattern already infinegrained_fp8.py(the.expertsbranch dispatching viause_experts_implementation), with a newLinear4bitExpertsmodule on the bnb side wrapping a 3DParams4bit. Happy to draft both PRs against the current walker if there's appetite — or is the preferred direction to wait on transformers#43472 (BatchLinear RFC) so all quant backends converge on one swap target? Asking before writing code so the work doesn't land in the wrong abstraction.cc @matthewdouglas @SunMarc
matthewdouglas commentedon Jun 5, 2026
Hi @pjordanandrsn
My plan after we release v0.50.0 was to take a look at exactly this feature. However, I was considering a different approach, where I would not use the existing
Params4bitand instead use a plainnn.Parameterfor the weights. The additional data (e.g. absmax) would also be stored within theExperts4bitmodule.There's a lot of nuance to consider here, but I'm open to suggestions or looking at an initial PR.
pjordanandrsn commentedon Jun 5, 2026
Thanks @matthewdouglas — that's cleaner than what I'd sketched, and it makes sense. Keeping the weights as a plain
nn.Parameterand hangingabsmax/quant metadata offExperts4bititself avoids bendingParams4bit's tensor-subclass + device-movement machinery around a 3D[num_experts, out_features, in_features]stack, and it serializes naturally through the module'sstate_dict. Glad to dropParams4bithere.Two things I'd like to pin down before writing code, so it lands in the right shape:
absmaxas a per-expert buffer (e.g.[num_experts, blocks_per_expert]), and do you wantcompress_statistics(double-quant) supported per-expert in v1, or deferred?dequantize_4bit+matmul_4bitloop, or do you want it aimed at the grouped-GEMM path from your recent 4bit inference kernels ([CUDA] New 4bit GEMM kernels for inference #1949) from the start?I'm happy to put up an initial bnb-side PR against that shape, paired with the transformers-side
.expertswalker branch (mirroringreplace_with_fp8_linear's.endswith(".experts")dispatch). No rush on your end with v0.50.0 still in flight — I'll have a draft ready for whenever you pick it up. I can validate quant-correctness and the memory footprint on a small fused-expert MoE (OLMoE-1B-7B) that fits a single 12 GB card.matthewdouglas commentedon Jun 5, 2026
Initially I didn't think it would be worth the effort to support
compress_statistics. And we'd also want to be enforcing that thein_featuresdimension is exactly divisible by the blocksize (default 64), so that to your point we can properly slice around expert boundaries.An initial loop around experts is fine as a PoC. I did not implement grouped-GEMM yet, but kernels for MoE inference are on the radar.
IMHO, avoid opening a PR on transformers side; that's best left for a point in time when it's closer to being shipped here.
Add Experts4bit for 4-bit quantization of fused MoE experts (bitsandb…
test: add bitsandbytes-foundation#1849 fused-3D regression + shape-pa…
pjordanandrsn commentedon Jul 2, 2026
Root cause confirmed: transformers v5 stores fused-MoE experts as single 3-D
nn.Parameters, and the 4-bit walker only replacesnn.Linear— so the experts silently stay 16-bit.Working solve today:
pip install experts4bit-qlora(quantizes the fused stacks directly, per-expert LoRA on top). Measured on a 12 GB RTX A2000: OLMoE-1B-7B loads at ~4.7 GB and trains; with expert CPU-offload (OFFLOAD_EXPERTS=1), Qwen3-30B-A3B trains at 7.16 GB peak and Gemma-4-26B at 8.47 GB — both OOM without it.Upstream path: the quantization primitive is drafted in #1965 (held for the post-v0.50.0 cycle per the maintainers). Scope: three architectures (OLMoE / Qwen3-MoE / Gemma-4), dequantize-path forward, offload costs ~+11 % s/step.
feat: generalize Experts4bit to ExpertsNbit with 8-bit and 16-bit sto…
Add Experts4bit for 4-bit quantization of fused MoE experts (bitsandb…
test: add bitsandbytes-foundation#1849 fused-3D regression + shape-pa…
aka-mnaf-zariche commentedon Jul 16, 2026
Another data point from a consumer 8 GB card (RTX 3050), confirming the silent-skip failure mode is still present on current releases — transformers 5.13.1, bitsandbytes 0.49.2, torch 2.13.0+cu126:
allenai/OLMoE-1B-7B-0924withBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4"): the 16OlmoeExpertsmodules keep their fused 3-Dgate_up_proj/down_projparameters in bf16 — ~6.4 B of 6.9 B params (~95 %) stay unquantized. The model loads at 13.45 GB (silently spilling to CPU RAM) while everything reports success. We ran several experiments over multiple days before noticing.bitsandbytes.nn.parametrize.replace_parameter_4bit(experts, "gate_up_proj" / "down_proj", quant_type="nf4")over all 16 layers → 13.45 GB → 4.19 GB in ~2 s. Perplexity delta NF4-vs-bf16 on our eval sets: ~1–4 %. Router-only training then runs at 5.35 GB peak, ~1.0 s/step.gradient_checkpointing_enable()leaks memory until OOM. Filed separately with root cause + validated workaround: Memory leak: nn.parametrize.replace_parameter_4bit + HF gradient_checkpointing_enable() — dequant cache not released on early-stopped recompute #2005+1 to the
Experts4bitdirection in #1965. Independent of that, a load-time warning when the quantization walker leaves a large fraction of parameters unquantized (95 % in this case) would have surfaced this immediately.Disclosure: found and drafted with AI assistance (Claude); measurements are from real runs on our hardware, posted and vouched for by the account owner.
pjordanandrsn commentedon Jul 16, 2026
Following up on the grouped-GEMM note above, since it's now concrete: I've built and published the compute half — grouped-nf4-gemm (MIT), a fused single-launch grouped GEMM that decodes NF4 in-register inside the mainloop (fp32 accumulation; no dequantized tensor is ever materialized). It consumes the plain-
nn.Parameter+ absmax storage from your note above, as implemented in #1965, so it drops onto theExperts4bitsurface unchanged.Headline numbers, each from a preregistered protocol with blind confirmatories on fresh instances:
The full confirmatory record is in-repo, including the protocols that failed. Sharing it now so it's on the table for your MoE-inference-kernel work post-v0.50.0 — take it, adapt it, or treat it as prior art; happy to align with whichever direction you choose. No change to #1965's scope, and still no rush on my end.
Add Experts4bit for 4-bit quantization of fused MoE experts (bitsandb…
test: add bitsandbytes-foundation#1849 fused-3D regression + shape-pa…
kvnloo commentedon Sep 28, 2026
The
Experts4bitdirection seems more appropriate than forcing fused 3-D expert parameters throughParams4bit.For a first correctness-focused version I would preserve the constraints already discussed:
in_featuresdivisible by block size;nn.Parameterstorage plus module-owned quant metadata;compress_statisticsrequirement initially;I can help validate this on a 12 GB-class NVIDIA GPU with a small fused-expert MoE and report:
The important first milestone is that the fused expert bulk is actually quantized and correct. A specialized grouped NF4 GEMM can remain a later performance layer.