Skip to content

Commit d2fbc2b

Browse files
committed
fix(qwen36): restore MoE device-tilemap (MOE_GPU) correctness — masked down dequant no-op
SPARKINFER_PREFILL_MOE_GPU=1 (device tilemap, skips the per-layer D2H counts sync) is silently broken on main: the down projection's masked Q->int8 dequant has cols=mffn=512, which the vectorized fast-mask kernel declines (cols not a multiple of 1024), and launch_gguf_dequant_rows_i8_mask ignored that false return — so the int8 down weights were never written and the down GEMM ran on stale/garbage data. Batched-vs-token-loop prefill_check collapses to TOP1 ~0.375 / KL ~0.35 @512 (this is the #586 corruption that forced the path default-off). Fix: launch_gguf_dequant_rows_i8_mask now falls back to a correct masked slow kernel (single-pass, register-resident, same dequant/amax/round as the host deq_rows_i8_kernel; dead experts exit via the counts mask) when the fast path declines. Restores prefill_check to TOP1 0.94 / KL ~0.01 @512 with MOE_GPU=1. Keeps SPARKINFER_PREFILL_MOE_GPU default OFF: the device path is not currently a prefill speedup (it must dequant all experts per group rather than the host path's live-only coalesced runs), so this is a correctness fix for the opt-in flag, not a perf change. The default host-tilemap path is unaffected.
1 parent 86cfb59 commit d2fbc2b

1 file changed

Lines changed: 71 additions & 3 deletions

File tree

kernels/csrc/cuda/quant/dequant_gguf.cu

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,58 @@ __global__ void deq_rows_i8_twopass_kernel(const unsigned char* __restrict__ src
224224
}
225225
}
226226

227+
// Masked single-pass Q->int8 for the MoE device-tilemap (MOE_GPU) path. Correct for ANY cols
228+
// (the vectorized fast-mask kernel declines cols not a multiple of 1024 — e.g. the down proj
229+
// cols=mffn=512 — and its false return was ignored, so down weights silently stayed stale;
230+
// #586 corruption). grid = n_in*rows_per_expert; one block per (local expert, row); dead
231+
// experts (counts[e_base+le]<=0) exit. Same dequant/amax/round as deq_rows_i8_kernel.
232+
template <int QT>
233+
__global__ void deq_rows_i8_mask_slow_kernel(
234+
const unsigned char* __restrict__ src0, signed char* __restrict__ q0, float* __restrict__ scale0,
235+
const int* __restrict__ counts, int e_base, int n_in, int rows_per_expert, int cols,
236+
size_t expert_bytes) {
237+
constexpr int BS = (QT == 12) ? 144 : (QT == 13) ? 176 : 210;
238+
const int flat = blockIdx.x;
239+
const int le = flat / rows_per_expert;
240+
const int row_in = flat - le * rows_per_expert;
241+
if (le >= n_in || counts[e_base + le] <= 0) return;
242+
const int t = threadIdx.x;
243+
const int nsb = cols >> 8;
244+
const size_t row_bytes = (size_t)nsb * (size_t)BS;
245+
const unsigned char* rbase = src0 + (size_t)le * expert_bytes + (size_t)row_in * row_bytes;
246+
float* srow = scale0 + (size_t)le * (size_t)rows_per_expert + (size_t)row_in;
247+
signed char* qrow = q0 + ((size_t)le * (size_t)rows_per_expert + (size_t)row_in) * (size_t)cols;
248+
249+
// Single-pass: keep the decoded row in registers (nsb <= 8 for MoE cols <= 2048) so Q5_K is
250+
// decoded once, matching the host path's deq_rows_i8_kernel. cols=512 (down) => nsb=2.
251+
float vals[kDeqRowsI8MaxNsb];
252+
float amax = 0.f;
253+
for (int sb = 0; sb < nsb; sb++) {
254+
const unsigned char* blk = rbase + (size_t)sb * BS;
255+
const float v = (QT == 12) ? deq_q4k_val(blk, t)
256+
: (QT == 13) ? deq_q5k_val(blk, t) : deq_q6k_val(blk, t);
257+
vals[sb] = v;
258+
amax = fmaxf(amax, fabsf(v));
259+
}
260+
__shared__ float swarp[8];
261+
#pragma unroll
262+
for (int o = 16; o > 0; o >>= 1) amax = fmaxf(amax, __shfl_xor_sync(0xffffffffu, amax, o));
263+
if ((t & 31) == 0) swarp[t >> 5] = amax;
264+
__syncthreads();
265+
if (t < 32) {
266+
float v = (t < 8) ? swarp[t] : 0.f;
267+
#pragma unroll
268+
for (int o = 4; o > 0; o >>= 1) v = fmaxf(v, __shfl_xor_sync(0xffffffffu, v, o));
269+
if (t == 0) swarp[0] = v;
270+
}
271+
__syncthreads();
272+
const float d = swarp[0] / 127.f;
273+
if (t == 0) *srow = d;
274+
const float inv = (d > 0.f) ? (1.f / d) : 0.f;
275+
for (int sb = 0; sb < nsb; sb++)
276+
qrow[sb * 256 + t] = (signed char)(int)roundf(vals[sb] * inv);
277+
}
278+
227279
__global__ void deq_q8_0_kernel(const unsigned char* __restrict__ src, __nv_bfloat16* __restrict__ y, long nblocks) {
228280
long b = (long)blockIdx.x * blockDim.x + threadIdx.x; if (b >= nblocks) return;
229281
const unsigned char* blk = src + b * 34; float d = gg_h2f(blk);
@@ -352,9 +404,25 @@ bool launch_gguf_dequant_rows_i8_mask(
352404
int ggml_type, const void* src0, signed char* q0, float* scale0,
353405
const int* counts, int e_base, int n_in, int rows_per_expert, int cols,
354406
size_t expert_bytes, cudaStream_t stream) {
355-
return launch_gguf_dequant_rows_i8_fast_mask(
356-
ggml_type, src0, q0, scale0, counts, e_base, n_in, rows_per_expert, cols, expert_bytes,
357-
stream);
407+
if (launch_gguf_dequant_rows_i8_fast_mask(
408+
ggml_type, src0, q0, scale0, counts, e_base, n_in, rows_per_expert, cols, expert_bytes,
409+
stream))
410+
return true;
411+
// Fast path declined (e.g. down proj cols=512 not a multiple of 1024). Fall back to the
412+
// correct masked slow kernel instead of silently no-opping (the #586 stale-weight bug).
413+
if ((ggml_type != GGML_Q4_K && ggml_type != GGML_Q5_K && ggml_type != GGML_Q6_K) ||
414+
n_in <= 0 || rows_per_expert <= 0 || (cols & 255) != 0 ||
415+
(cols >> 8) > kDeqRowsI8MaxNsb) // nsb must fit the register array
416+
return false;
417+
auto s = reinterpret_cast<const unsigned char*>(src0);
418+
const int grid = n_in * rows_per_expert;
419+
if (ggml_type == GGML_Q4_K)
420+
deq_rows_i8_mask_slow_kernel<12><<<grid, 256, 0, stream>>>(s, q0, scale0, counts, e_base, n_in, rows_per_expert, cols, expert_bytes);
421+
else if (ggml_type == GGML_Q5_K)
422+
deq_rows_i8_mask_slow_kernel<13><<<grid, 256, 0, stream>>>(s, q0, scale0, counts, e_base, n_in, rows_per_expert, cols, expert_bytes);
423+
else
424+
deq_rows_i8_mask_slow_kernel<14><<<grid, 256, 0, stream>>>(s, q0, scale0, counts, e_base, n_in, rows_per_expert, cols, expert_bytes);
425+
return true;
358426
}
359427

360428
bool launch_gguf_dequant_rows_i8_mask_pair(

0 commit comments

Comments
 (0)