Skip to content

BLOCKER: CUDA grouped MoE writes NOTHING for IQ1_S/IQ1_XXXS - silent wrong output on the default path #1029

Description

@localai-bot

Found by independent review of PR #967, which was self-reviewed and merged with --admin. Live on main today. Row ENG-EXPERT-STREAM, issue #912.

The defect

IsCudaKeepQuantSupported (src/vt/cuda/cuda_quant_dot.cu:1600-1601) now returns true for kIQ1_S and kIQ1_XXXS. Three dispatch switches consume that predicate; #967 extended only the dense one.

MatmulBTQuantGroupedKernelCuda (:1948-1957) uses the predicate at :1916 to SKIP its CPU fallback (:1917-1921), then dispatches through a switch (w) that has no case for either new dtype and no default:. It quantizes the activation, launches nothing, and returns. CheckCuda(cudaGetLastError()) at :1958 sees no error because there was no launch. The output tensor is never written.

Before #967 these dtypes returned false and took the CPU fallback: correct, just slow. #967 converted correct-but-slow into silently wrong, which is the exact inversion of its stated purpose.

Measured (GB10, reviewer probe, poisoned output buffer)

case device out[0] CPU oracle out[0] nmse vs CPU
iq1_s -12345 (poison, untouched) -2.18233 4.58e6
iq1_xxxs -12345 (poison, untouched) -4.11988 9.96e6
iq2_s (control, has a case) matches pass

Why it is reachable by default

qwen3_5_gguf_weights.cpp:62 accepts any KeepQuantDType, which now includes ggml 19 and 66 -> qwen3_5.cpp:6091 -> :5186 KqGrouped -> vt::MatmulBTQuantGrouped (ops.cpp:214) -> the broken switch. VT_QWEN35_GROUPED_MOE defaults ON.

#985 ("loads and generates") ran on --device cpu, so nothing has exercised the CUDA arm end to end yet. The defect is waiting at exactly the next step of this row.

Why it compiled silently

cmake/CompilerWarnings.cmake:28 gives CUDA only -Werror=all-warnings and no -Wall, so the host pass never runs -Wswitch. A missing enum case in a switch without a default is therefore not an error here.

F2 (HIGH): the same omission in the fused MoE SwiGLU seam

MoeGateUpSwiGLUGroupedCuda (:2117-2126) guards with the same predicate at :2092-2096 and previously threw a NAMED error. #967 made the guard pass while the switch still matches nothing, so a loud refusal became silence. Latent for IQ1 today (reachable via cuda_deepseek_v4.cu:2082 / vt::MergedGemm), but any keep-quant MoE arch binding the shared seam inherits it.

F3 (HIGH): the CUDA grouped dispatch has no test at all

grep -rl MatmulBTQuantGrouped tests/ finds two files, neither with any kCUDA reference. The whole grouped dispatch is ungated on device, which is why F1 and F2 landed green.

F5 (MEDIUM): device grid tables are unsealed, and 13% of one is untested

cuda_quant_iq_tables.cuh:41-43 claims "the digests the CPU tests re-derive cover these bytes too". They do not: the tests digest the HOST symbols vt::cpu::kIq1sGrid / kIq1xxxsGrid; nothing reads vt::cuda::d_iq1s_grid. The header at :4 also claims a runtime memcmp test against the CPU tables that does not exist.

Replaying the gate's fixed-seed generator: 266 of 2048 d_iq1s_grid entries (13.0%) are never addressed by any test shape. Drifting entry 0 is caught; drifting entry 3 is GREEN, 150032/150032.

Remediation

  1. Add the two LaunchGroupedGemm<> cases (the templates are generic in W; DotSuperblock/FinalFactor already specialize, so no new kernel code) and a default: that THROWS naming the dtype, in both grouped switches.
  2. Extend the CUDA gate to the grouped seam over the same case table.
  3. Add the cudaMemcpyFromSymbol + memcmp seal the header already claims, and fix both comments.
  4. Consider giving CUDA -Wswitch so the compiler catches the next one.

Owning row: ENG-EXPERT-STREAM.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions