perf: fuse quantized_dense onto mx::quantized_matmul kernel - #200
Merged
Conversation
Emily.Quantization.Layers.quantized_dense/4 dequantized the full weight
to bf16 and ran a dense Nx.dot on every call. Inside the compiled decode
while-loop that re-dequantized the entire model on every token, making
quantized inference slower than dense.
Lower to the fused mx::quantized_matmul kernel (via
Emily.Quantization.quantized_matmul_defn/2) instead, streaming the packed
low-bit weights directly. Layout/mode/bits/group_size are read off the
%QuantizedWeight{}; non-Emily backends keep the composed dequantize +
Nx.dot fallback via the block's default fun.
On 4-bit Qwen3-0.6B native greedy decode this is ~13x faster end-to-end
(2.7 -> 36 tok/s on the dev box), and quantized inference is now faster
than dense rather than slower. Per-matmul it is 2.3-4.6x faster than the
dequantize + Nx.dot path.
Add native-lane tests (native_fallback: :raise) proving quantized_dense
lowers to the fused opcode, plus bench/qmm_microbench.exs (per-matmul)
and bench/qwen3_quantized_tps.exs (end-to-end).
Closes #197.
Owner
Author
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
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
Emily.Quantization.Layers.quantized_dense/4— the Axon layer backing every quantized projection — now lowers to the fusedmx::quantized_matmulkernel (viaEmily.Quantization.quantized_matmul_defn/2) instead of dequantizing the full weight to bf16 and running a denseNx.dot. Inside the compiled decode:whileloop the old path re-dequantized the entire model on every token, making quantized inference slower than dense.Closes #197. This graduates the already-built-but-unwired fused quantized matmul (see
PLAN.mdM10.5 / M11 — "Option 2 remains available if the two-kernel-vs-fused gap materially hurts a real workload"). The motivating workload is the quantized gap highlighted in elixir-nx/emlx#124.Results
Same machine, native lane (
compiler: Emily.Compiler, native: true, native_fallback: :raise), Qwen/Qwen3-0.6B, 64-token greedy decode:(Absolute tok/s are from a box slower than the emlx PR's M4 Max, so they aren't directly comparable to that PR's numbers; the ratios and the "quantized now beats dense" result are the machine-independent findings.)
Changes
lib/emily/quantization/layers.ex— wire the layer toquantized_matmul_defn/2; layout/mode/bits/group_size are read off the%QuantizedWeight{}. Non-Emily backends keep the composeddequantize_defn+Nx.dotfallback via the block's default fun.test/emily/quantization/layers_test.exs— two native-lane tests (native_fallback: :raise) provingquantized_densegenuinely lowers to the fused opcode rather than falling back to op-by-op eval.bench/qmm_microbench.exs— per-matmul before/after (no model download needed).bench/qwen3_quantized_tps.exs— end-to-end quantized Qwen3-0.6B tok/s (runMIX_ENV=test mix run …).Testing
mix precommitclean:compile --warnings-as-errors,format,credo --strict, docs build, and 40 doctests / 79 properties / 737 tests, 0 failures.layers_testalready used the eager fused kernel as its oracle, so no tolerance changes were needed.