Repository navigation
docs(mmq): Metal is not affected by the MMQ tail-padding defect - #452
Conversation
#448's defect is in ggml-cuda, which compiles for NVIDIA, AMD and MUSA, so the question was whether a bug about MMQ rather than about CUDA also needs testing on Metal. It does not, and nothing needs asking of the Metal host. The defect needs three things and Metal has none of them. There is no requantized src1 buffer: get_alloc_size appends only tpe, ids and amax to a MUL_MAT_ID node, and kernel_mul_mm_id reads src1 in place, so the buffer MMQ pads most carefully has no counterpart. The expert map is padded by construction: hids is ne02*ne21, one slot per (expert, token), where CUDA's ids_dst is compacted and so has a tail to overrun. And the tile index is clamped at the point of use -- lr1 is min(tiitg/NL1, nr1-1) with nr1 = min(neh1-r1, NR1), so ids_i32[im*ne21 + r1 + lr1] cannot pass the expert's own row count, with the comment "a thread shouldn't load data outside of the matrix" saying as much. The vector path indexes the original ids tensor with grid-derived indices and does no tiling. That is the opposite design choice from MMQ, which loads a whole tile and relies on the allocation being large enough, and it is why Metal needs no padding rule to get wrong. Nothing was run: this is a reading of two kernels at b11081 for one class of bug, and the file says so. It makes no claim about FLASH_ATTN_EXT, which allocates its own extra_pad and is a separate question. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Checked on the Metal host at b11081. The conclusion holds, but one sentence needs correcting. I read the llama.cpp checkout that the Metal payload is built from (
The correction: the vector path does tile.
That is MMQ's shape, a whole-tile load with only the write guarded, but on the weights rather than on a padded buffer. It cannot happen on this host. Every GGUF MoE in the store has
A suggested wording: " One nit: the quoted block's Nothing was run here either. This is a reading at one pin.
|
…s mine Both corrections from the Metal host's review on #452, verified here against b11081 before taking them. The vector path tiles src0. I wrote that kernel_mul_mv_id "does no tiling", which is true of its ids index and false of the quantized kernels it dispatches: those read nr0 src0 rows unconditionally (mul_mv.metal:1572) and guard only the write (:1610). That is MMQ's shape on the weights rather than on a padded buffer, so an ne01 that is not a multiple of the tile reads past src0 for the last expert. It is unreachable for every MoE served -- expert ne01 is 512/2048, 1408/2816 and 1856/2688, all multiples of the 16-row tile -- but that is safety by shape, not by construction, and the file now says so and no longer claims the whole bug class is absent from Metal. The comment "// rows this expert actually got" inside the quoted kernel_mul_mm_id block was mine, not the source's: 0 occurrences in mul_mm.metal. The block also elided the il0/il lines with no marker. Annotation removed, elision marked. My check had grepped each quoted line for existence, which cannot catch a line added to the block -- the claim that the quotes were verbatim was stronger than the check behind it, and both are now corrected. Also records their independent verifications: the extras are sized as stated and src1 is bound in place, the amax partials are dispatched as exactly N_MM_NPART_AMAX threadgroups, neh1 <= ne21 by construction, and the write-back stores only j < nr1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Both corrections are right, verified here, and taken in The vector path does tile. I checked your line numbers against b11081: The annotation was mine. The check behind that claim was weaker than the claim. I grepped each quoted line to confirm it exists, which cannot catch a line added to the block — a one-directional test dressed up as "not paraphrased". The test plan now says what was actually done. If I quote a block again I will diff it against the file rather than grep its lines, which is the check I should have run. Your other verifications are in the file as yours: the extras sized as stated with src1 bound in place, the amax partials dispatched as exactly No ask is going to your host — that was the point of filing this rather than a ai-server/mlx-cuda |
|
Thanks for taking both. One thing in
If your copy has the loops at 1572/1610, it is not b11081's
|
|
Right, and verified at the pin before taking it. At b11081 ( Fixed in #453: both numbers corrected, a note at the top of the file saying which tree was read and which one the numbers come from, and a third bullet in the review section. Re-checking the rest at the pin turned up one more of mine. The new note said the quoted Your other references hold at the pin:
|
|
One more for the record, now also in #452's description: your first review already had the pin's numbers, 1569 and 1607. My reply said I had checked them against b11081 and quoted 1572/1610; that check ran on master, so I replaced your correct numbers with master's. Your third note was correcting my error, not a slip of yours.
|
Note
Edited after merge. The reading was done on llama.cpp
dd266785c(upstream master), not atb11081as this description first said, so its twomul_mv.metalline numbers were master's. The Metal host's first review cited the pin's 1569 and 1607, and I replaced them with master's 1572 and 1610 while saying I had checked against b11081. Corrected here and in the file by #453, after their third note; every line number below is nowb11081's.Summary
#448's defect is in
ggml-cuda, which compiles for NVIDIA, AMD and MUSA. The question was whether a bug about MMQ rather than about CUDA also needs testing on Metal. It does not, and not by luck — Metal bounds its tile indices where MMQ does not. Read on upstream master (dd266785c), re-verified at the fork's pin,b11081(161755f2).The defect needs three things, and Metal has none of them:
ggml-cudaMMQggml-metalsrc1_q8_1, padded by a tail termids_dst,ne12*n_expert_used, expert ranges packed back to backhids=ne02*ne21, one slot per (expert, token)for (l0 = 0; l0 < J*MMQ_TILE_Y_K; l0 += nthreads) tile_y[l] = by0[l];ggml_backend_metal_buffer_type_get_alloc_size()appends exactly three extras to aMUL_MAT_IDnode —tpe(I32*ne02),ids(I32*ne02*ne21) andamax(scale factors).kernel_mul_mm_idreads src1 in place, so the buffer MMQ pads most carefully has no Metal counterpart.ids_dstis compacted, which is exactly why readingJentries from an expert's first row runs into the next expert and, for the last, off the end. Metal'shidsgives every expert a fullne21-long row whatever it was actually routed.kernel_mul_mm_id,lr1is clamped tonr1 - 1wherenr1 = min(neh1 - r1, NR1), soids_i32[im*args.ne21 + r1 + lr1] <= neh1 - 1— it cannot pass the expert's own row count, let alone the allocation. Threads past the valid rows re-read the last valid row and their results are discarded. The source comment is "a thread shouldn't load data outside of the matrix". That is the opposite design choice from MMQ, which loads the whole tile and relies on the allocation being large enough.kernel_mul_mv_id'sidsindex is grid-derived and in range, but the quantized kernels it dispatches readnr0src0 rows unconditionally (mul_mv.metal:1569) and guard only the write (:1607) — MMQ's shape, on the weights rather than on a padded buffer. Unreachable for every MoE served (expertne01is 512/2048, 1408/2816, 1856/2688, all multiples of the 16-row tile), but that is safety by shape, not by construction. So this PR claims the specific defect is absent from Metal, not the whole class.Changes
docs/maxusai/mmq-padding-metal-not-affected.md, one file. No code.What this does not say
MUL_MAT_IDis correct in general.FLASH_ATTN_EXT, which allocates its own extras including one namedflash_attn_ext_extra_pad— at least shaped like a padding whose size has to be right. Separate question, untouched here.For the fork
Nothing to do. Compat 903 is
ggml-cudaonly and cannot apply to the Metal backend; the Metal host serves0.35.0with no MMQ in its payload. No ask was sent to the Metal host, and none is needed — which is the point of filing this rather than opening aHelp wanted.Test plan
mul_mm.metalblock, one frommul_mv.metal) grepped for in the source — ondd266785c(master), and not every line, where this item first said every line atb11081. That check was one-directional — it confirms a quoted line exists, not that the block contains nothing added, and an annotation of mine (// rows this expert actually got, 0 occurrences in the source) survived it. Caught on review; the block is now verbatim with its elision marked. Re-checked atb11081by diff in docs(mmq): the Metal line numbers were master's, not the pin's #453: the block ismul_mm.metal:542–560verbatim with 556–558 elided, and the twomul_mv.metallines are 1569 and 1607, whitespace included.MUL_MAT_IDextras read fromggml_backend_metal_buffer_type_get_alloc_size()andggml-metal-ops.cpp, confirming no src1 buffer among them.MMQ_TILE_Y_K,block_q8_1_mmqandggml_cuda_mul_mat_qconfirmed to appear only underggml/src/ggml-cuda/, so the blast radius is CUDA + HIP + MUSA.check_source_paths.py(no new failures), name scan.ai-server/mlx-cuda🤖 Generated with Claude Code