|
| 1 | +Align split-K partitions to the qmm K-tile (BK=32), fixing nvfp4. |
| 2 | + |
| 3 | +MLX v0.32.0's qmm_splitk caps split_k only by the quantization group count |
| 4 | +(K / group_size), not by the kernel's K-tile width BK (=32). For nvfp4 |
| 5 | +(group_size=16) this yields a per-partition K of 16 < BK, and fp_qmm_t_splitk's |
| 6 | +tile load reads a full BK-wide K-tile with no K bound -- spilling 16 columns |
| 7 | +past the partition into the next group's packed weights and fp8 scales. That |
| 8 | +corrupts every partial (non-uniform ~2x error) and reads past the buffer on the |
| 9 | +last partition (NaN/inf). Affine (group_size >= 32) is unaffected because its |
| 10 | +partitions are already >= BK. |
| 11 | + |
| 12 | +Fix in the dispatch (MLX's pattern for tile-alignment constraints): require each |
| 13 | +K partition to be a whole number of BK-wide tiles as well as whole groups, i.e. |
| 14 | +align split_k to max(group_size, BK). Only changes behavior for group_size < 32. |
| 15 | + |
| 16 | +Upstream candidate. |
| 17 | + |
| 18 | +diff --git a/mlx/backend/metal/quantized.cpp b/mlx/backend/metal/quantized.cpp |
| 19 | +index 62d48714..94c56307 100644 |
| 20 | +--- a/mlx/backend/metal/quantized.cpp |
| 21 | ++++ b/mlx/backend/metal/quantized.cpp |
| 22 | +@@ -884,11 +884,15 @@ void qmm_splitk( |
| 23 | + int current_tgs = n_tiles * m_tiles; |
| 24 | + int split_k = std::max(1, 512 / current_tgs); |
| 25 | + |
| 26 | +- // Cap split_k by the number of quantization groups |
| 27 | +- split_k = std::min(split_k, K / group_size); |
| 28 | +- |
| 29 | +- // Ensure K divides evenly by split_k * group_size |
| 30 | +- while (split_k > 1 && (K % (split_k * group_size) != 0)) { |
| 31 | ++ // Each K partition must be a whole number of BK-wide (32) K-tiles as well as |
| 32 | ++ // whole quantization groups. The qmm_t_splitk kernels tile K by BK=32 and do |
| 33 | ++ // not bound the K dimension, so a partition smaller than BK (e.g. nvfp4's |
| 34 | ++ // group_size=16) would over-read into the next group's weights/scales. |
| 35 | ++ int k_align = group_size > 32 ? group_size : 32; |
| 36 | ++ split_k = std::min(split_k, K / k_align); |
| 37 | ++ |
| 38 | ++ // Ensure K divides evenly by split_k * k_align |
| 39 | ++ while (split_k > 1 && (K % (split_k * k_align) != 0)) { |
| 40 | + split_k--; |
| 41 | + } |
| 42 | + if (split_k <= 1) { |
0 commit comments