M10.5: Bumblebee quantized inference integration - #20
Merged
Conversation
Closes the gap M10 left open: `Native.quantized_matmul` is now
reachable from `Nx.Defn.jit`-traced Axon forward passes, and a
quantized Qwen3-0.6B greedy-decodes end-to-end on Emily through
stock `Bumblebee.Text.generation/4`.
Approach: defn-native dequantize (Option 1 from PLAN.md M10.5).
Rejected Option 2 (fork Evaluator — fragile) and Option 3 (upstream
Nx custom-op hook — too slow). Trade-off is two kernels per matmul
(dequantize + dot) instead of MLX's single fused quantized_matmul;
M11's fast-kernel work closes the gap.
- Emily.Quantization.dequantize_defn/1 — defn-native MLX affine
dequantize built from Nx.right_shift / bitwise_and / multiply /
add. Supports bits ∈ {2, 4, 8}; {3, 6} out of scope (cross-u32
packing). Uses Nx.flatten with negative axes and reshape :auto.
- Emily.Quantization.Layers.quantized_dense/4 — Axon-compatible
layer op. Pattern-matches on %QuantizedWeight{}; transpose flag
carried as compile-time constant via deftransform.
- Emily.Quantization.Transform — graph rewriter + state quantizer
modeled on Axon.Quantization.quantize/2. quantize/3 takes a dense
Axon model + dense ModelState and returns the quantized pair.
Lives under test/support/ because Axon is only: :test.
- qwen3_quant_full opt-in conformance — loads dense Qwen3-0.6B,
quantizes via Transform.quantize/3 (bits=4, group_size=128,
transpose=true), greedy-decodes 32 tokens, pins continuation text
as regression gate. ~14s after model cache.
- 26 new unit/property tests across quantization/ covering dequant
equality, layer op under both Nx.Defn.Evaluator and Emily.Compiler,
graph-rewrite round-trip, and validation.
Scope reductions from original PLAN.md M10.5:
- AWQ safetensors loader deferred. On closer inspection AWQ groups
along `in` while MLX transpose=false expects groups along the
stored last axis (out); correct conversion requires transposing
packed tensors, unpacking qzeros into biases, and mapping HF
param names to Bumblebee names. All tractable but additional
scope. The from-dense path exercises the same defn-integration
pipeline end-to-end.
- Conformance oracle is Emily's own first-run output (same
discipline as qwen3_full_test.exs) rather than MLX Python.
mix precommit: 263 tests + 66 properties + 1 doctest, 0 failures,
credo clean. qwen3_quant_full green (opt-in).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gap M10 left open:
Native.quantized_matmulis now reachable fromNx.Defn.jit-traced Axon forward passes, and a quantized Qwen3-0.6B greedy-decodes end-to-end through stockBumblebee.Text.generation/4.Emily.Quantization.dequantize_defn/1— defn-native MLX affine dequantize built fromNx.right_shift/bitwise_and/ multiply / add. Supportsbits ∈ {2, 4, 8}.Emily.Quantization.Layers.quantized_dense/4— Axon-compatible layer op.Nx.dot(x, dequantize_defn(qw))replaces MLX's fused quantized_matmul (two kernels vs. one; M11 closes the gap).Emily.Quantization.Transform.quantize/3— graph rewriter + model-state quantizer mirroringAxon.Quantization.quantize/2. Rewrites every:densenode to:quantized_denseand swaps kernels for%QuantizedWeight{}.:qwen3_quant_fullopt-in conformance — loads dense Qwen3-0.6B, quantizes viaTransform.quantize/3(bits=4, group_size=128, transpose=true), greedy-decodes 32 tokens, pins continuation text as regression gate.Approach chosen: defn-native dequantize (Option 1 from PLAN.md M10.5). Rejected Option 2 (fork
Nx.Defn.Evaluator— fragile) and Option 3 (upstream Nx custom-op hook — too slow).Scope reductions from original PLAN.md M10.5
inaxis while MLX'stranspose=falsepath expects groups along the stored last axis (out). Correct conversion requires transposing packed tensors, unpackingqzerosinto biases, and mapping HF param names to Bumblebee names — tractable but additional scope. The from-dense path validates the full defn-integration pipeline end-to-end.qwen3_full_test.exs) rather than MLX Python output on AWQ weights.Full details in PLAN.md §M10.5.
Test plan
mix precommit— 263 tests + 66 properties + 1 doctest, 0 failures, credo cleanmix test --only qwen3_quant_full— Qwen3-0.6B quantized greedy decode matches pinned reference (~14s after model cache)test/emily/quantization/:dequantize_defn_test.exs— element-wise equality withQuantizedWeight.to_dense/1across all(bits, group_size)comboslayers_test.exs—quantized_dense/4transpose=true/false, defn compositiontransform_test.exs— graph rewrite + state quantize + MLP round-trip under bothNx.Defn.EvaluatorandEmily.Compiler