Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
6652a67
build: bump mlx pin to 0.32.2, nanobind 2.15.0, add pinned-mlx behavi…
asher Aug 30, 2026
9ae7cf5
build: name mlx 0.32.2 and nanobind 2.15.0 in install docs and CI
asher Sep 28, 2026
e5b5666
build: tools extra and CI exclude only transformers 5.13.0, lifting t…
asher Sep 28, 2026
0e493c0
chore(release): 0.5.0
asher Sep 28, 2026
256fbf0
feat(qmm): KQ_TUNE_LIVE reads the NAX tile levers per dispatch
asher Sep 28, 2026
cead1df
feat(qmm): KQ_NAX_TILE and KQ_QMM_TILE pin the quantized matmul tile
asher Sep 28, 2026
bf27f00
feat(ops): max_threadgroup_memory reports the GPU threadgroup memory …
asher Sep 28, 2026
1c0cd17
feat(bench): diffusion transformer shape harness with paired tile arm…
asher Sep 28, 2026
05164e8
feat(bench): thermal curve, first-dispatch cost and float64 numerics …
asher Sep 28, 2026
ebc731a
fix(bench): discard the first call of each shape visit in the DiT har…
asher Sep 28, 2026
c80be78
fix(bench): pairs follow the Thue-Morse bit, 3 inner calls, per-op A …
asher Sep 28, 2026
95f732d
fix(bench): roofline bytes floor counts compulsory traffic, with Q6_K…
asher Sep 28, 2026
36f82e7
fix(bench): rename the first-round column to round 0 and point to the…
asher Sep 28, 2026
24855c6
fix(bench): evaluate a sample's calls together and give fast shapes m…
asher Sep 28, 2026
062d392
feat(bench): numerics probe adds a same-sign reduction, R2 references…
asher Sep 28, 2026
33c42b3
fix(bench): dense and affine tile labels follow MLX's rules on GPUs w…
asher Sep 28, 2026
fd84c62
test(qmm): every qmm_t tile kernel name outside split-K and gather ma…
asher Sep 28, 2026
5326837
docs(qmm): KQ_QMM_TILE runs only 64,64,2,2 today, KQ_TUNE_LIVE is for…
asher Sep 28, 2026
6ec382e
fix(bench): modulation linears run at one row per image, M 1 and 2, w…
asher Sep 28, 2026
a759bd5
fix(bench): median CI sums the binomial tail in integers for thousand…
asher Sep 28, 2026
d3e6f4f
feat(bench): shape samples reach a raw log as each slot ends, --reagg…
asher Sep 28, 2026
d8fc85c
feat(bench): route numerics probe shows which quantized matmul routes…
asher Sep 28, 2026
04dc76d
feat(qmm): BM=128 tile runs at every M from 4096 rows behind a row-li…
asher Sep 28, 2026
6b67928
fix(qmm): split-K routes store float partials so the fold rounds once…
asher Sep 28, 2026
c13f685
test(nax): take the bm128 codec list from a static set when no metall…
asher Sep 29, 2026
badf3d2
docs(nax): mark the odd-M bm128 threshold provisional and list what K…
asher Sep 29, 2026
dca04cf
test(nax): tell split routes apart by their logged kernel and slice c…
asher Sep 29, 2026
7b32994
fix(bench): probe the ALU split and verify_qmv routes and raise on er…
asher Sep 29, 2026
3c8ed7d
feat(qmm): KQ_SPLITK_CAP caps the small-M split-K slice target by gri…
asher Sep 29, 2026
626263b
fix(verify): verify_mma keeps activations in their dtype and sums eac…
asher Sep 29, 2026
7cb6247
fix(qmm): KQ_SPLITK_F32 defaults to 0 and float split-K partials beco…
asher Sep 29, 2026
086dcc2
test(qmm): check that every split-K kernel name the host can build ex…
asher Sep 29, 2026
1615b7e
docs(verify): verify_mma float kernels with activation-dtype partials…
asher Sep 29, 2026
4015e9e
fix(qmm): float16 split-K matmuls store float partials by default so …
asher Sep 29, 2026
94f6941
docs(qmm): a capped split call runs what it runs with the split route…
asher Sep 29, 2026
8e13e04
fix(qmm): drop the float32 split-K kernels and fold, which no call re…
asher Sep 29, 2026
e11bd40
test(qmm): the split-K name enumerator takes its tiles, NAX tile rows…
asher Sep 29, 2026
cb24fb7
docs(qmm): split-K partial comments name T for the plain kernels and …
asher Sep 29, 2026
5bb7009
test(qmm): skip the split-K name tests when no metallib loads
asher Sep 29, 2026
c32b96e
fix(prism): pq2_0 mat-vec kernels stage bfloat16 activations in float
asher Sep 29, 2026
05047d0
fix(moe): KQ_MOE_HALF dots each chunk in float so a grid code times a…
asher Sep 29, 2026
08d9b20
docs(verify): KQ_VERIFY_MMA_HALF block sums overflow on pq2_0 and ptq…
asher Sep 29, 2026
1ef95aa
fix(qmm): drop the mxfp4 and nvfp4 split-K kernels, which the split-K…
asher Sep 29, 2026
9093681
fix(qmm): drop the float activation qmm, qmv, gather and mv_ext kerne…
asher Sep 29, 2026
8bf93bb
fix(env): an empty or unrecognized lever value keeps the default inst…
asher Sep 29, 2026
f47f412
docs(kernels): the pq2_0 half2 dot runs on float16 activations only, …
asher Sep 29, 2026
cbc32b0
docs(changelog): pq2_0 fix covers MTP verify, KQ_SPLITK_F32=0 can ret…
asher Sep 29, 2026
362c753
perf(verify): pq2_0 verify_mma runs one row tile per simdgroup, so th…
asher Sep 29, 2026
da86d92
refactor(verify): drop the ptq1_0 verify_mma half kernels, and KQ_VER…
asher Sep 29, 2026
e684f8a
perf(qmm): q4_0 and q8_0 mid-M split-K on float partials targets 10 s…
asher Sep 29, 2026
a28a3c1
perf(qmm): q4_0 and q8_0 mid-M split-K on float partials keeps 8 slic…
asher Sep 30, 2026
197ec94
perf(qmm): q6_k runs the BM=64 and BM=128 NAX tiles on a K tile of 12…
asher Oct 3, 2026
4e0309b
feat(kernel): qk_norm_rope normalizes and rotates DiT attention q and…
asher Sep 29, 2026
1629982
feat(bench): qk_norm_rope bench times the op against the unfused ops,…
asher Sep 29, 2026
bb0e89a
docs(kernels): qk_norm_rope section covers what it computes, its layo…
asher Sep 29, 2026
968f685
docs(changelog): qk_norm_rope runs DiT attention q and k norm and RoP…
asher Sep 29, 2026
8b844dd
test(qk_norm_rope): check eps on inputs of 1e-3, at eps 0.5 and on a …
asher Sep 29, 2026
2bea7d5
test(qk_norm_rope): run views with an odd head or row stride, and a p…
asher Sep 29, 2026
a553c12
docs(kernels): qk_norm_rope output is the exactly rounded value on ne…
asher Sep 29, 2026
92e06ad
docs(qk_norm_rope): the op is for inference, and on the GPU the fused…
asher Sep 29, 2026
396a31e
test(conftest): clear the mlx buffer cache after each test, so a sess…
asher Oct 3, 2026
b45b166
fix(loader): honor get_model_classes in loader.load and the patched m…
asher Oct 3, 2026
d177c21
perf(sdpa): the head dim 512 NAX decode pass takes starts, so left-pa…
asher Oct 3, 2026
596acbf
perf(qmm): q5_k and q3_k NAX tiles walk K 128 weights per step, as q6…
asher Oct 3, 2026
b0c4f25
perf(qmm): NAX split-K at M 32 and below takes one slice on grids tha…
asher Oct 3, 2026
7caa1d3
fix(bench): dit shape sweeps bound the mlx buffer cache, record memor…
asher Oct 3, 2026
ee60b99
fix(bench): dit shape samples time one dependent call chain, so calls…
asher Oct 3, 2026
380651d
feat(bench): dit shape sweeps take lever arms that pair with kq in on…
asher Oct 3, 2026
9e29da3
feat(qmm): nax tile policy carries a large-m row per codec, and KQ_NA…
asher Oct 3, 2026
f76be97
test(qmm): every codec runs each nax tile it carries and folded row o…
asher Oct 3, 2026
9357bd8
feat(bench): --time-budget-min splits a dit shape sweep into processe…
asher Oct 3, 2026
e8193cf
feat(qmm): q4_k, q5_k, q6_k and q8_0 carry measured large-m rows, and…
asher Oct 4, 2026
313de52
fix(bench): dit shape ratios balance the two call orders of each pair…
asher Oct 4, 2026
bf463c0
perf(qmm): large-m rows run the bm 64 tile above a per-codec k ceiling
asher Oct 5, 2026
cdbf7cb
docs(changelog): dit shape bench ratios balance call order
asher Oct 5, 2026
7d94446
docs(changelog): fold the unreleased entries into 0.5.0 and keep user…
asher Oct 5, 2026
d9fdb5c
docs(kernels): KQ_NAX_BM128_ODD_M=0 also turns off the large-m rows a…
asher Oct 5, 2026
8b3c884
test(qmm): rows above int16 cover an odd row-tile count at 32769 rows
asher Oct 5, 2026
6442a61
feat(qmm): q4_k carries bk 128 nax tiles behind KQ_NAX_Q4K_BK128
asher Oct 5, 2026
4eb386a
feat(qmm): flat and iq codecs carry bk 128 nax tiles behind per-codec…
asher Oct 5, 2026
11aafe9
perf(qmm): q4_k runs bk 128 nax tiles by default
asher Oct 5, 2026
dc616e1
perf(qmm): flat and iq codecs that measured faster run bk 128 nax til…
asher Oct 5, 2026
5d4823d
feat(kernel): quantized_matmul_glu computes a gated feed-forward prod…
asher Oct 5, 2026
6bb4ff3
fix(bench): bench_dit_shapes leaves out the ratio of a visit whose re…
asher Oct 5, 2026
17158a1
docs(kernels): quantized_matmul_glu in the kernel reference and the c…
asher Oct 5, 2026
922e6e9
feat(kernel): quantized_matmul_glu runs q5_k and q6_k weights, and q8…
asher Oct 5, 2026
7270761
fix(ops): an unevaluated strided weight view is no longer read as den…
asher Oct 6, 2026
ec40985
fix(kernel): the float16 quantized_matmul_glu kernel passes a NaN and…
asher Oct 6, 2026
14b4a12
fix(kernel): quantized_matmul_glu reads a row band of a wider weight …
asher Oct 6, 2026
a3b7934
fix(kernel): the unfused path of quantized_matmul_glu builds its acti…
asher Oct 6, 2026
1bc6a0b
fix(bench): bench_dit_shapes leaves out a visit whose reference sampl…
asher Oct 6, 2026
db20de7
fix(bench): bench_glu reads its peak baseline after the arm before ha…
asher Oct 6, 2026
6782e05
fix(kernel): mx.grad through quantized_matmul_glu gives the gradient …
asher Oct 6, 2026
aaf959d
fix(build): every build names the probe kernel option, so a build aft…
asher Oct 6, 2026
3cddde6
fix(kernel): a release build reads no quantized_matmul_glu tile lever…
asher Oct 6, 2026
cb22fde
docs(kernels): the quantized_matmul_glu docstring and header comment …
asher Oct 6, 2026
b1b9246
docs(kernels): the quantized_matmul_glu changelog entry is two lines,…
asher Oct 6, 2026
fe77c14
fix(ops): dequantize, quantize and the bias of quantized_matmul_qmv_b…
asher Oct 6, 2026
a7074e3
test(glu): name the kernel each call runs, and cover an unsaturated g…
asher Oct 6, 2026
5ae94a5
fix(ops): quantized_matmul returns zeros for an inner dim of 0, and o…
asher Oct 6, 2026
997ec1c
test(bench): the lever arm test counts pairs plus dropped visits, so …
asher Oct 6, 2026
e437520
docs(kernels): the GLU table has no q8_0 cell on float16, where the o…
asher Oct 6, 2026
d5ad4c5
fix(ops): quantized_matmul checks its LoRA arguments before it return…
asher Oct 6, 2026
e26c518
docs(changelog): the weight view fix names quantized_matmul_qmv_bias too
asher Oct 6, 2026
e2c2a16
fix(glu): the unfused ops follow mx's compile switch on every call, a…
asher Oct 6, 2026
8c7cdd4
docs(glu): with float32 x the fused gradient sums its two terms in bf…
asher Oct 6, 2026
4687b13
refactor(dense): layout helpers an op calls in its eval, where every …
asher Oct 6, 2026
298ed90
fix(route_shed): strided views of indices, scores and the slot table …
asher Oct 6, 2026
cb07434
fix(moe): the fused MoE ops and the router copy a strided activation …
asher Oct 6, 2026
c11901c
fix(sdpa): sdpa_prefill_block_sparse refuses keys and values with a s…
asher Oct 6, 2026
6fb6fe1
fix(sdpa): the sparse attention ops copy a window, pool or scales vie…
asher Oct 6, 2026
0f4b63b
fix(dsa): dsa_indexer_score_decode copies keys whose rows are not dense
asher Oct 6, 2026
8ab7919
fix(sdpa): quantized cache scales and biases with two layouts or a st…
asher Oct 6, 2026
e29d228
refactor(ops): quantized_matmul drops a stride read on x that its lay…
asher Oct 6, 2026
79556d6
fix(moe): the limited GLU activations pass a NaN gate or up value ins…
asher Oct 6, 2026
5e05ff4
test(views): a weight view with reversed columns
asher Oct 6, 2026
a41b1e9
test(views): weight views in quantized_matmul_qmv_bias
asher Oct 6, 2026
2a1fb79
test(views): gather_qmm weight views with and without sorted_indices
asher Oct 6, 2026
1f0ab27
test(views): 3-D views in dequantize and quantize
asher Oct 6, 2026
53b6bcf
test(glu): a fused bfloat16 result above the float16 range
asher Oct 6, 2026
f7825b1
test(empty): the dtype of the zeros for an inner dim of 0
asher Oct 6, 2026
d087b95
test(glu): an unfused call on a stream that is not the default
asher Oct 6, 2026
f57eb0b
test(glu): the release lever test sets KQ_GLU_BK and KQ_GLU_DUAL128
asher Oct 6, 2026
13fa553
test(glu): the stream test names silu as the case whose bits tell the…
asher Oct 6, 2026
140a6a3
feat(lora): quantized_matmul_lora runs the low-rank term as more K st…
asher Oct 6, 2026
b3b0ff9
perf(lora): the tail loads its z and B tiles from their buffers, with…
asher Oct 6, 2026
428e0fb
test(lora): quantized_matmul_lora against float64 on both streams
asher Oct 6, 2026
d3e862f
perf(lora): the low-rank walk runs before the K walk, and one row tak…
asher Oct 6, 2026
1e2bb9c
refactor(lora): the composition adds a 1-D row with addmm, with no re…
asher Oct 6, 2026
5390335
test(lora): node equality, the stream of the call, jvp and vmap, the …
asher Oct 6, 2026
2a5fe8f
docs(lora): quantized_matmul_lora in the kernel reference and the cha…
asher Oct 6, 2026
46f9f00
test(lora): the tile name tests leave the low-rank kernels to test_ma…
asher Oct 6, 2026
b0486ad
feat(lora): q5_k, q6_k and q8_0 kernels, bands that end on a 32-colum…
asher Oct 6, 2026
d908cb5
docs(lora): the codecs, the bounds on shape and rows, and the lever m…
asher Oct 6, 2026
a893ee0
fix(lora): a band of one column, bands=[] as no band, jvp and vmap of…
asher Oct 7, 2026
dc0b6e8
test(lora): one-column bands against float64, every gradient subset, …
asher Oct 7, 2026
d8b8761
docs(lora): when the levers are read, jvp and vmap, bands=[], strengt…
asher Oct 7, 2026
c926f83
perf(lora): the shape rule leaves a rank above 128 on N below 8192 to…
asher Oct 7, 2026
2278ce5
test(lora): the rank bound of the shape rule on both sides, with and …
asher Oct 7, 2026
3996b7c
docs(lora): the rank bound of the shape rule, and timings at ranks 12…
asher Oct 7, 2026
f09532a
chore(merge): main 0.4.17 into the image kernel branch
asher Oct 9, 2026
73cff7e
fix(bench): an env arm value may hold commas, as a tile override does
asher Oct 9, 2026
e796c67
feat(nax): quantized matmul with a gated residual epilogue
asher Oct 9, 2026
4b54521
feat(nax): 3x3 convolution as an implicit gemm
asher Oct 9, 2026
22aeb1f
test(nax): the name enumerating tests leave the residual kernels to t…
asher Oct 9, 2026
478ba63
test(nax): hold each element of the residual and convolution kernels …
asher Oct 9, 2026
5f381b0
docs(kernels): the output channels where winograd is faster, and the …
asher Oct 9, 2026
4cc7299
refactor(kernels): the plain qmm_t tile and its simdgroup grid are te…
asher Oct 10, 2026
621d94d
feat(matmul): q4_k, q5_k and q6_k matmuls past a row floor run as deq…
asher Oct 10, 2026
436f4f6
feat(matmul): nvfp4, ptq1_0 and q3_k matmuls from 2048 rows run as de…
asher Oct 10, 2026
8c0f9f6
docs(kernels): the dequantize rows of the plain matmul on gpus withou…
asher Oct 10, 2026
99ebd07
docs(kernels): the measured gain of the dequantize rows on m3 max
asher Oct 10, 2026
7c0d9e6
fix(matmul): the dequantize route runs on every gpu without nax befor…
asher Oct 10, 2026
6b6e0ee
docs(kernels): the dequantize route on gpus without nax before m5, it…
asher Oct 10, 2026
e12089e
fix(matmul): a gradient wrt the weight or scales of a dequantize-rout…
asher Oct 10, 2026
a2c6be5
docs(kernels): the dequantize route's weight gradient, its glu bound …
asher Oct 10, 2026
8acad67
fix(matmul): a dequantize-routed matmul refuses jvp and vmap by the k…
asher Oct 10, 2026
820b411
docs(kernels): routed calls hold their dense copy until the command b…
asher Oct 10, 2026
fe5dbe3
fix(hc): the fused hyper-connection cycle hands off through device-co…
asher Oct 10, 2026
088d345
fix(hc): the fused cycle passes h between threadgroups as uint2 bits,…
asher Oct 10, 2026
dc00ddf
build: bump mlx pin to 0.32.3 and nanobind 3.0.1
asher Oct 11, 2026
53cee00
chore(merge): main 0.4.18 into the 0.5 branch, whose hc fix it alread…
asher Oct 11, 2026
2e94d4b
test(matmul): the jvp and vmap refusal test expects the glu node each…
asher Oct 11, 2026
2b2999c
test(glu): the peak bounds follow the stock activation's own peak, so…
asher Oct 11, 2026
f099606
test(glu): the compile switch test puts back the switch it found, so …
asher Oct 11, 2026
832f5c9
test(matmul): the refusal test's off case turns the lora and residual…
asher Oct 11, 2026
7e6812a
test(compile): tests of mx.compile turn the switch on through a fixtu…
asher Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Build extension + test deps
run: |
python -m pip install --upgrade pip
pip install "setuptools>=77" wheel "mlx==0.32.1" "nanobind==2.13.0" "cmake>=3.27" gguf numpy pytest "mlx-lm>=0.27" "transformers<5.13"
pip install "setuptools>=77" wheel "mlx==0.32.3" "nanobind==3.0.1" "cmake>=3.27" gguf numpy pytest "mlx-lm>=0.27" "transformers!=5.13.0"
pip install -e . --no-build-isolation

- name: Codec lint (incl. runtime kq.codecs() leg)
Expand Down Expand Up @@ -155,7 +155,7 @@ jobs:
- name: Build extension (CPU-only) + test deps
run: |
python -m pip install --upgrade pip
pip install "setuptools>=77" wheel "mlx[cpu]==0.32.1" "nanobind==2.13.0" "cmake>=3.27" gguf numpy pytest "mlx-lm>=0.27" "transformers<5.13"
pip install "setuptools>=77" wheel "mlx[cpu]==0.32.3" "nanobind==3.0.1" "cmake>=3.27" gguf numpy pytest "mlx-lm>=0.27" "transformers!=5.13.0"
pip install -e . --no-build-isolation

# A non-Metal build must report no metallib (and not throw doing so).
Expand Down
96 changes: 95 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,101 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project aims to
adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.5.0]

### Added
- `kq.qk_norm_rope` normalizes and rotates DiT attention q and k in one
dispatch, about 25x faster than the unfused ops at Z-Image shapes.
- `kq.quantized_matmul_glu` computes a gated feed-forward product of two packed
weights. On NAX GPUs (M5) a call holds one output buffer, not two or three.
- `kq.quantized_matmul_lora` adds a low-rank adapter term to a packed matmul in
one call. On NAX GPUs (M5) the call is 0 to 11% faster than the unfused ops on
the layer shapes of an image model, 4% at the median.
- `kq.quantized_matmul_residual` adds a residual, gated or not, to a packed
matmul in one call. On NAX GPUs (M5) it costs what the matmul alone costs.
- `kq.conv2d_3x3` runs the 3x3 convolutions of an image VAE decoder. On NAX
GPUs (M5) a bfloat16 call takes 0.25 to 0.34 of the time of MLX's convolution.
- `KQ_TUNE_LIVE=1` makes the NAX tile levers take effect on the next call, so
tile A/B runs share one process. `KQ_QMM_LOG` now logs NAX tile dispatches.
- `KQ_NAX_TILE` and `KQ_QMM_TILE` pin the quantized matmul tile, and raise
with the kernel name on a tile that is not built; `KQ_QMM_TILE` accepts
only `64,64,2,2` for now. `kq.metallib_kernels()` lists the built kernels.
- `kq.max_threadgroup_memory()` reports the GPU's threadgroup memory limit.
- `benchmarks/bench_dit_shapes.py` and `benchmarks/bench_roofline.py` time
matmul tiles against stock and MLX affine arms at diffusion model shapes.
- `KQ_SPLITK_CAP` caps the K slices of the NAX and ALU split-K routes at 32 or
fewer rows by GPU core count, for A/B runs on wide ones such as a vocab head.
- `KQ_DEQUANT_ROUTE` turns the dequantize route off or forces it on any GPU,
and `KQ_QMM_LOG` logs each call that takes it.
- `KQ_SPLITK_F32=1` makes bfloat16 split-K matmuls, MTP verify among them,
round once instead of once per K slice. `0` stores float16 partials again,
which can return inf.

### Changed
- Requires mlx 0.32.3, which fixes wrong values from dequantizing a strided
array and a crash at process exit.
- `mlx-kquant[tools]` now installs beside transformers 5.13.1 and later, as
mlx-vlm 0.7 requires. Only 5.13.0, which cannot import mlx-lm, is excluded.
- `KQ_NAX_SWIZZLE=n` folds 2^n row tiles on the BM=64 and BM=128 NAX tiles.
`1` now folds two row tiles, where it folded four.
- On NAX GPUs (M5), prefill and image steps of 4096 or more tokens no longer
change matmul kernels with the token count.
- On NAX GPUs (M5), `q6_k` matmuls in prefill and image steps run 5 to 20%
faster, with the same output.
- On NAX GPUs (M5), `q5_k` and `q3_k` matmuls in prefill and image steps run
up to 10% faster on most shapes, with the same output.
- On NAX GPUs (M5), `q4_k` matmuls in image steps run up to 5% faster, with
the same output.
- On NAX GPUs (M5), `q4_0`, `q4_1`, `q5_0`, `iq4_nl`, `iq3_xxs`, `iq3_s`,
`iq2_xs` and `iq2_s` matmuls in image steps run up to 8% faster, with the
same output.
- On NAX GPUs (M5), matmuls of 4096 or more rows at a large K, in image steps
such as the Qwen-Image MLP down projection and in text prefill of 4096 or
more tokens, run 1 to 8% faster on most codecs.
- On NAX GPUs (M5), output heads and `q4_0` and `q8_0` float16 matmuls at 9 to
32 rows, as in batched decode and speculative verify, run up to 30% faster.
- On NAX GPUs (M5), batched gemma-4 decode of prompts of different lengths
runs 1 to 2% faster. `KQ_GQA_NAX=0` restores the previous kernel.
- On M3 Max, `q3_k` to `q6_k`, `nvfp4` and `ptq1_0` matmuls from 2048 rows (`q4_k` 16384), K 3840-12288,
N 3840-24576, run up to 12% faster, `nvfp4` 39%; other pre-M5 GPUs without NAX take the rows unmeasured.
- The bundled metallib drops the float32-activation matmul kernels, which no
call reached, about 22 MB.

### Fixed
- `quantized_matmul`, `quantized_matmul_qmv_bias` and `gather_qmm` return
correct values for a strided weight view that is not evaluated before the
call, such as `w[0::2]`.
- `dequantize`, `quantize` and the bias of `quantized_matmul_qmv_bias` take a
strided view that is not evaluated before the call, such as `w[::-1]`.
- `route_shed` returns correct slots for strided views of its indices,
scores or slot table.
- The fused MoE ops and `moe_router_topk` take a strided activation or logits
view that is not evaluated before the call, and raise for such a stack.
- `sdpa_prefill_block_sparse` raises for keys or values with a strided head
dim that are not evaluated before the call, instead of reading them as dense.
- `sdpa_sparse_decode` and `sdpa_sparse_prefill` return correct values for a
window, pool or pool scales view with a strided or reversed last dim.
- `dsa_indexer_score_decode` returns correct scores for one sequence whose
keys are a view with a strided or reversed last dim.
- `sdpa_decode_gqa` and the cascade raise for quantized-cache scales and biases
that differ in layout or have a strided last dim, instead of misreading them.
- The fused MoE ops with a limited activation (`silu_limit`, `swiglu_clamp`)
return NaN for a NaN gate or up value instead of the value of the limit.
- `quantized_matmul` returns zeros for an inner dim of 0. On a CPU stream a
weight with no rows ended the process at more than 16 rows of x.
- A float16 matmul of 128 or fewer rows split over K, MTP verify among them,
no longer returns inf where its output is finite.
- Matmuls of 2 to 8 rows on q4_0, pq2_0, q2_0 and ptq1_0 weights, MTP verify among
them, no longer return inf for activations past 65504, and fp16 error drops 3-5x.
- Decode and MTP verify steps on pq2_0 and q2_0 weights no longer return inf for
bfloat16 activations past 65504.
- `KQ_MOE_HALF=1` no longer returns inf for activations of a few hundred and
up; only activations past 65504 still do.
- Tuning levers that default on or to a per-codec rule, such as `KQ_QMV_SPLITK`
and `KQ_MOE_DEDUP`, keep that default when set empty instead of turning off.
- After `patch_mlx_lm_load()`, `mlx_lm.utils.load_model` builds a kquant
checkpoint with the caller's `get_model_classes` instead of the stock class.
`mlx_kquant.loader.load` takes it too.

## [0.4.18]

Expand Down
34 changes: 31 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.27)
project(mlx_kquant LANGUAGES CXX C)

# ----------------------------- Setup -----------------------------
# C++20: MLX 0.32.1's public headers (device.h, stream.h) use defaulted
# C++20: MLX 0.32.3's public headers (device.h, stream.h) use defaulted
# comparison operators, a C++20 feature. Apple Clang accepts them as an
# extension under -std=c++17, but GCC (the Linux toolchain) rejects them, so a
# C++17 standard breaks the Metal-free Linux build. 20 builds cleanly on both.
Expand All @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

option(BUILD_SHARED_LIBS "Build extensions as a shared library" ON)
option(KQ_PROBE_KERNELS "Build the probe tiles of kernels under measurement" OFF)

# ----------------------------- Dependencies -----------------------------
# Pick the right interpreter. mlx.extension.CMakeBuild invokes cmake WITHOUT
Expand All @@ -37,7 +38,7 @@ find_package(
# "incompatible function arguments". Build isolation installs the pin;
# non-isolated dev builds (build_ext --inplace, -e --no-build-isolation) use
# whatever the venv has - guard that path here.
set(KQ_NANOBIND_PIN "2.13.0")
set(KQ_NANOBIND_PIN "3.0.1")
execute_process(
COMMAND "${Python_EXECUTABLE}" -c "import nanobind; print(nanobind.__version__)"
OUTPUT_STRIP_TRAILING_WHITESPACE
Expand Down Expand Up @@ -153,6 +154,12 @@ target_sources(
${CMAKE_CURRENT_LIST_DIR}/src/kquant_kvarn.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_moe_glu.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_norm_fused.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_qk_norm_rope.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_matmul_fused.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_matmul_lora.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_matmul_resid.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_conv_nax.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_dense.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_hadamard.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_lora_epilogue.cpp
${CMAKE_CURRENT_LIST_DIR}/src/kquant_hc_glue.cpp
Expand Down Expand Up @@ -221,12 +228,27 @@ endif()
target_link_libraries(mlx_kquant_ext PUBLIC gguflib)
target_link_options(mlx_kquant_ext PRIVATE ${KQ_WHOLE_ARCHIVE_GGUFLIB})

# A probe build carries the probe tiles and reads the levers that select
# them. A release build compiles those levers out. PUBLIC, because the
# sources above are PUBLIC and so are compiled into the Python module too,
# and that copy is the one the bindings call.
if(KQ_PROBE_KERNELS)
target_compile_definitions(mlx_kquant_ext PUBLIC KQ_PROBE_KERNELS)
endif()

# ----------------------------- Metal library -----------------------------
# The kq_* kernels compiled into a single mlx_kquant.metallib.
# The repo's metal/ dir is searched BEFORE the stock-wheel include tree so the
# repo's kq_*.h + quantized_utils.h (which adds load_vector) win;
# steel/gemm/*, utils.h, etc. fall through to the stock wheel headers.
if(MLX_BUILD_METAL)
# Probe tiles are measured against the shipped ones and stay out of a
# release metallib.
set(KQ_PROBE_METAL_SOURCES "")
if(KQ_PROBE_KERNELS)
list(APPEND KQ_PROBE_METAL_SOURCES
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized_nax_epi_probe.metal)
endif()
# The metallib has no depfile; header edits retrigger it only through DEPS.
file(GLOB KQ_METAL_KERNEL_HEADERS CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/metal/mlx/backend/metal/kernels/*.h)
Expand All @@ -240,6 +262,10 @@ if(MLX_BUILD_METAL)
SOURCES
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized_nax.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized_nax_epi.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized_nax_lora.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized_nax_resid.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_conv_nax.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_quantized_encode.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_sdpa.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_sdpa_nax.metal
Expand All @@ -256,12 +282,14 @@ if(MLX_BUILD_METAL)
${CMAKE_CURRENT_LIST_DIR}/metal/kq_moe_glu.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_moe_glu_kq.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_norm_fused.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_qk_norm_rope.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_hadamard.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_lora_epilogue.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_hc_glue.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_hc_lowrank.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_route_shed.metal
${CMAKE_CURRENT_LIST_DIR}/metal/kq_skinny_mv.metal
${KQ_PROBE_METAL_SOURCES}
INCLUDE_DIRS
${CMAKE_CURRENT_LIST_DIR}/metal
${MLX_INCLUDE_DIRS}
Expand Down Expand Up @@ -296,7 +324,7 @@ if(BUILD_SHARED_LIBS)
# installed mlx wheel at runtime. _ext.so lives in site-packages/mlx_kquant/,
# libmlx in site-packages/mlx/lib/, so ../mlx/lib reaches it. (The absolute
# build-tree rpath MLXConfig adds works only on the build machine; this is
# what makes a redistributed wheel find the ABI-pinned mlx==0.32.1. delocate
# what makes a redistributed wheel find the ABI-pinned mlx==0.32.3. delocate
# must therefore *exclude* libmlx from vendoring - see [tool.cibuildwheel].)
target_link_options(_ext PRIVATE -Wl,-rpath,@loader_path)
target_link_options(_ext PRIVATE -Wl,-rpath,@loader_path/../mlx/lib)
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ front-end. A few notes to get productive quickly.

```sh
python -m venv .venv && source .venv/bin/activate
pip install "mlx==0.32.1" "nanobind==2.13.0" "cmake>=3.27"
pip install "mlx==0.32.3" "nanobind==3.0.1" "cmake>=3.27"
pip install -e ".[dev,tools]" # builds _ext + mlx_kquant.metallib
```

Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ pip install mlx-kquant # the K-quant ops + precompiled metallib
pip install "mlx-kquant[tools]" # the CLI (quantize / run / chat / lora / fuse)
```

Either pulls the ABI-matched `mlx==0.32.1` automatically.
Either pulls the ABI-matched `mlx==0.32.3` automatically.

**From source** (or to develop) needs the Metal toolchain (`xcrun metal`); the metallib compiles at
install time, no runtime JIT:

```sh
git clone https://github.com/asher/mlx-kquant && cd mlx-kquant
pip install "mlx==0.32.1" # pinned, ABI-matched stock wheel (pulls the Metal backend)
pip install "mlx==0.32.3" # pinned, ABI-matched stock wheel (pulls the Metal backend)
pip install -e ".[tools]" # builds _ext + mlx_kquant.metallib; adds mlx-lm for the CLI
```

Expand All @@ -70,7 +70,7 @@ x86_64 stays on the scalar/threaded path.) The base `mlx` wheel ships no backend
install the CPU one explicitly:

```sh
pip install "mlx[cpu]==0.32.1" # base frontend + libmlx CPU backend
pip install "mlx[cpu]==0.32.3" # base frontend + libmlx CPU backend
pip install -e . --no-build-isolation
```

Expand Down Expand Up @@ -299,9 +299,10 @@ All optional; the defaults are right for normal use.
kernel. By default `sp` serves launches under 2048 threadgroups and `dd` serves wider ones at 2
to 8 rows. Read per call. `loop` and `sp` give identical bytes. A/B lever.
- `KQ_MOE_HALF=1` - on Metal, run the iq2_xs, iq2_xxs and iq3_xxs fused MoE decode gathers on the
half-dot kernels (half grid tables, half-staged activations, half chunk dots with a float sum per
chunk; shared-expert slots stay on the float path). Read per call. Default off: the outputs differ
from the float kernels at half rounding level. A/B lever.
half-dot kernels (half grid tables and half-staged activations, with each chunk dotted in float;
shared-expert slots stay on the float path). Read per call. Default off: the outputs differ from
the float kernels at half rounding level, and an activation past 65504 in magnitude turns to inf.
A/B lever.

- `KQ_SDPA_IDX_NAX=0` - run `sdpa_fa_indexed` on its simdgroup kernel even where the NAX tile
kernel is available. Read once per process. The simdgroup kernel matches `sdpa_fa_verify` over
Expand Down Expand Up @@ -376,7 +377,7 @@ are informed by our analysis of the mixed-precision quants that [Unsloth][unslot

## Version pinning

Pinned to `mlx==0.32.1`. The kernels include MLX's steel headers and the extension links `libmlx`,
Pinned to `mlx==0.32.3`. The kernels include MLX's steel headers and the extension links `libmlx`,
binding it to that release's ABI and header API. To move to a newer MLX: update the bundled headers
under `metal/mlx/backend/metal/kernels/` for that wheel, rebuild, and re-run the test suite.

Expand All @@ -390,10 +391,10 @@ python -m pytest tests/

- **macOS 26.2 (Tahoe) or later on Apple Silicon** (M-series). Building from source needs the Metal
toolchain (`xcrun metal`).
- **Linux** (x86_64 or aarch64) is supported CPU-only. Build against `mlx[cpu]==0.32.1`, no Metal
- **Linux** (x86_64 or aarch64) is supported CPU-only. Build against `mlx[cpu]==0.32.3`, no Metal
toolchain required. See [Install](#install) and [Limitations](#limitations).
- **Python >= 3.10** (the pinned `mlx==0.32.1` ships no cp39 wheel).
- **`mlx==0.32.1`** exactly - the kernels include MLX's steel headers and the extension links
- **Python >= 3.10** (the pinned `mlx==0.32.3` ships no cp39 wheel).
- **`mlx==0.32.3`** exactly - the kernels include MLX's steel headers and the extension links
`libmlx`, so the ABI is version-locked (see [Version pinning](#version-pinning)).

## Limitations
Expand Down
Loading
Loading