char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Pranesh GonegandlaCUDA: branchless Q4_K/Q5_K unpack to speed up mmvq, L2 prefetch on DGX Spark (#26705)73ab7599b

char/aliceai-t5-moe
71.2 KiB1551 linesraw
1#include "mmvq.cuh"
2#include "quantize.cuh"
3#include "unary.cuh"
4#include "vecdotq.cuh"
5
6#include <cstdint>
7#include <type_traits>
8
9// only enabled on DGX Spark, where it is a gain on every type below. On the higher-bandwidth parts the kernel
10// has little exposed latency left to hide and the extra requests cost more than they save.
11// For perf data, see https://github.com/ggml-org/llama.cpp/pull/26705#issuecomment-5569335031
12#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
13// returns true only for those quants that benefit from prefetch and false otherwise
14static constexpr __host__ __device__ bool mmvq_should_prefetch(ggml_type type) {
15    switch (type) {
16        case GGML_TYPE_Q4_0:
17        case GGML_TYPE_Q5_0:
18        case GGML_TYPE_Q8_0:
19        case GGML_TYPE_MXFP4:
20        case GGML_TYPE_Q3_K:
21        case GGML_TYPE_Q4_K:
22        case GGML_TYPE_Q5_K:
23        case GGML_TYPE_Q6_K:
24        case GGML_TYPE_IQ1_M:
25        case GGML_TYPE_IQ4_NL:
26        case GGML_TYPE_IQ4_XS:
27            return true;
28        default:
29            return false;
30    }
31}
32
33static __device__ __forceinline__ void mmvq_prefetch_l2(const void * p) {
34    asm volatile("prefetch.global.L2 [%0];" :: "l"(p));
35}
36#endif
37
38typedef float (*vec_dot_q_cuda_t)(const void * __restrict__ vbq, const block_q8_1 * __restrict__ bq8_1, const int & kbx, const int & iqs);
39
40static constexpr __device__ vec_dot_q_cuda_t get_vec_dot_q_cuda(ggml_type type) {
41    switch (type) {
42        case GGML_TYPE_Q1_0:    return vec_dot_q1_0_q8_1;
43        case GGML_TYPE_Q2_0:    return vec_dot_q2_0_q8_1;
44        case GGML_TYPE_Q4_0:    return vec_dot_q4_0_q8_1;
45        case GGML_TYPE_Q4_1:    return vec_dot_q4_1_q8_1;
46        case GGML_TYPE_Q5_0:    return vec_dot_q5_0_q8_1;
47        case GGML_TYPE_Q5_1:    return vec_dot_q5_1_q8_1;
48        case GGML_TYPE_Q8_0:    return vec_dot_q8_0_q8_1;
49        case GGML_TYPE_MXFP4:   return vec_dot_mxfp4_q8_1;
50        case GGML_TYPE_NVFP4:   return vec_dot_nvfp4_q8_1;
51        case GGML_TYPE_Q2_K:    return vec_dot_q2_K_q8_1;
52        case GGML_TYPE_Q3_K:    return vec_dot_q3_K_q8_1;
53        case GGML_TYPE_Q4_K:    return vec_dot_q4_K_q8_1;
54        case GGML_TYPE_Q5_K:    return vec_dot_q5_K_q8_1;
55        case GGML_TYPE_Q6_K:    return vec_dot_q6_K_q8_1;
56        case GGML_TYPE_IQ2_XXS: return vec_dot_iq2_xxs_q8_1;
57        case GGML_TYPE_IQ2_XS:  return vec_dot_iq2_xs_q8_1;
58        case GGML_TYPE_IQ2_S:   return vec_dot_iq2_s_q8_1;
59        case GGML_TYPE_IQ3_XXS: return vec_dot_iq3_xxs_q8_1;
60        case GGML_TYPE_IQ1_S:   return vec_dot_iq1_s_q8_1;
61        case GGML_TYPE_IQ1_M:   return vec_dot_iq1_m_q8_1;
62        case GGML_TYPE_IQ4_NL:  return vec_dot_iq4_nl_q8_1;
63        case GGML_TYPE_IQ4_XS:  return vec_dot_iq4_xs_q8_1;
64        case GGML_TYPE_IQ3_S:   return vec_dot_iq3_s_q8_1;
65        default:                return nullptr;
66    }
67}
68
69static constexpr __host__ __device__ int get_vdr_mmvq(ggml_type type) {
70    switch (type) {
71        case GGML_TYPE_Q1_0:    return VDR_Q1_0_Q8_1_MMVQ;
72        case GGML_TYPE_Q2_0:    return VDR_Q2_0_Q8_1_MMVQ;
73        case GGML_TYPE_Q4_0:    return VDR_Q4_0_Q8_1_MMVQ;
74        case GGML_TYPE_Q4_1:    return VDR_Q4_1_Q8_1_MMVQ;
75        case GGML_TYPE_Q5_0:    return VDR_Q5_0_Q8_1_MMVQ;
76        case GGML_TYPE_Q5_1:    return VDR_Q5_1_Q8_1_MMVQ;
77        case GGML_TYPE_Q8_0:    return VDR_Q8_0_Q8_1_MMVQ;
78        case GGML_TYPE_MXFP4:   return VDR_MXFP4_Q8_1_MMVQ;
79        case GGML_TYPE_NVFP4:   return VDR_NVFP4_Q8_1_MMVQ;
80        case GGML_TYPE_Q2_K:    return VDR_Q2_K_Q8_1_MMVQ;
81        case GGML_TYPE_Q3_K:    return VDR_Q3_K_Q8_1_MMVQ;
82        case GGML_TYPE_Q4_K:    return VDR_Q4_K_Q8_1_MMVQ;
83        case GGML_TYPE_Q5_K:    return VDR_Q5_K_Q8_1_MMVQ;
84        case GGML_TYPE_Q6_K:    return VDR_Q6_K_Q8_1_MMVQ;
85        case GGML_TYPE_IQ2_XXS: return VDR_IQ2_XXS_Q8_1_MMVQ;
86        case GGML_TYPE_IQ2_XS:  return VDR_IQ2_XS_Q8_1_MMVQ;
87        case GGML_TYPE_IQ2_S:   return VDR_IQ2_S_Q8_1_MMVQ;
88        case GGML_TYPE_IQ3_XXS: return VDR_IQ3_XXS_Q8_1_MMVQ;
89        case GGML_TYPE_IQ3_S:   return VDR_IQ3_S_Q8_1_MMVQ;
90        case GGML_TYPE_IQ4_NL:  return VDR_IQ4_NL_Q8_1_MMVQ;
91        case GGML_TYPE_IQ4_XS:  return VDR_IQ4_XS_Q8_1_MMVQ;
92        default:                return 1;
93    }
94}
95
96enum mmvq_parameter_table_id {
97    MMVQ_PARAMETERS_GENERIC = 0,
98    MMVQ_PARAMETERS_TURING,
99    MMVQ_PARAMETERS_GCN,
100    MMVQ_PARAMETERS_RDNA2,
101    MMVQ_PARAMETERS_RDNA3_0,
102    MMVQ_PARAMETERS_RDNA4,
103    MMVQ_PARAMETERS_GB10
104};
105
106static constexpr __device__ mmvq_parameter_table_id get_device_table_id() {
107#if defined(RDNA4)
108    return MMVQ_PARAMETERS_RDNA4;
109#elif defined(RDNA3_0)
110    return MMVQ_PARAMETERS_RDNA3_0;
111#elif defined(RDNA2) || defined(RDNA3_5)
112    return MMVQ_PARAMETERS_RDNA2;
113#elif defined(GCN) || defined(CDNA)
114    return MMVQ_PARAMETERS_GCN;
115#elif defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= GGML_CUDA_CC_TURING && __CUDA_ARCH__ < GGML_CUDA_CC_AMPERE
116    return MMVQ_PARAMETERS_TURING;
117#elif defined(__CUDA_ARCH__) && __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
118    return MMVQ_PARAMETERS_GB10;
119#else
120    return MMVQ_PARAMETERS_GENERIC;
121#endif
122}
123
124static __host__ mmvq_parameter_table_id get_device_table_id(int cc) {
125    if (GGML_CUDA_CC_IS_RDNA4(cc)) {
126        return MMVQ_PARAMETERS_RDNA4;
127    }
128    if (GGML_CUDA_CC_IS_RDNA3_0(cc)) {
129        return MMVQ_PARAMETERS_RDNA3_0;
130    }
131    if (GGML_CUDA_CC_IS_RDNA2(cc) || GGML_CUDA_CC_IS_RDNA3_5(cc)) {
132        return MMVQ_PARAMETERS_RDNA2;
133    }
134    if (GGML_CUDA_CC_IS_GCN(cc) || GGML_CUDA_CC_IS_CDNA(cc)) {
135        return MMVQ_PARAMETERS_GCN;
136    }
137    if (GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) >= GGML_CUDA_CC_TURING && ggml_cuda_highest_compiled_arch(cc) < GGML_CUDA_CC_AMPERE) {
138        return MMVQ_PARAMETERS_TURING;
139    }
140    if (GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) == GGML_CUDA_CC_DGX_SPARK) {
141        return MMVQ_PARAMETERS_GB10;
142    }
143    return MMVQ_PARAMETERS_GENERIC;
144}
145
146// Per-architecture maximum batch size for which MMVQ should be used for MUL_MAT_ID.
147// Returns a value <= MMVQ_MAX_BATCH_SIZE. Default is MMVQ_MAX_BATCH_SIZE.
148// Check https://github.com/ggml-org/llama.cpp/pull/20905#issuecomment-4145835627 for details
149
150static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_pascal_older(ggml_type type) {
151    switch (type) {
152        case GGML_TYPE_IQ1_S:   return 6;
153        case GGML_TYPE_IQ1_M:   return 6;
154        case GGML_TYPE_IQ2_S:   return 4;
155        case GGML_TYPE_IQ2_XS:  return 5;
156        case GGML_TYPE_IQ2_XXS: return 5;
157        case GGML_TYPE_IQ3_S:   return 4;
158        case GGML_TYPE_IQ3_XXS: return 4;
159        case GGML_TYPE_IQ4_NL:  return 6;
160        case GGML_TYPE_IQ4_XS:  return 5;
161        case GGML_TYPE_MXFP4:   return 4;
162        case GGML_TYPE_NVFP4:   return 4;
163        case GGML_TYPE_Q2_K:    return 4;
164        case GGML_TYPE_Q3_K:    return 4;
165        case GGML_TYPE_Q4_0:    return 6;
166        case GGML_TYPE_Q4_1:    return 6;
167        case GGML_TYPE_Q4_K:    return 5;
168        case GGML_TYPE_Q5_0:    return 6;
169        case GGML_TYPE_Q5_1:    return 6;
170        case GGML_TYPE_Q5_K:    return 5;
171        case GGML_TYPE_Q6_K:    return 4;
172        case GGML_TYPE_Q8_0:    return 4;
173        default:                return MMVQ_MAX_BATCH_SIZE;
174    }
175}
176
177static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_turing_plus(ggml_type type) {
178    switch (type) {
179        case GGML_TYPE_IQ2_S:   return 7;
180        case GGML_TYPE_IQ3_S:   return 6;
181        case GGML_TYPE_IQ3_XXS: return 7;
182        case GGML_TYPE_MXFP4:   return 7;
183        case GGML_TYPE_NVFP4:   return 8;
184        case GGML_TYPE_Q2_K:    return 7;
185        case GGML_TYPE_Q3_K:    return 5;
186        default:                return MMVQ_MAX_BATCH_SIZE;
187    }
188}
189
190static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_gcn(ggml_type type) {
191    switch (type) {
192        case GGML_TYPE_IQ1_S:   return 5;
193        case GGML_TYPE_IQ1_M:   return 5;
194        case GGML_TYPE_IQ2_S:   return 4;
195        case GGML_TYPE_IQ2_XS:  return 4;
196        case GGML_TYPE_IQ2_XXS: return 4;
197        case GGML_TYPE_IQ3_S:   return 4;
198        case GGML_TYPE_IQ3_XXS: return 4;
199        case GGML_TYPE_IQ4_NL:  return 6;
200        case GGML_TYPE_IQ4_XS:  return 4;
201        case GGML_TYPE_Q2_K:    return 4;
202        case GGML_TYPE_Q3_K:    return 4;
203        case GGML_TYPE_Q4_0:    return 5;
204        case GGML_TYPE_Q4_1:    return 5;
205        case GGML_TYPE_Q4_K:    return 4;
206        case GGML_TYPE_Q5_K:    return 4;
207        case GGML_TYPE_Q6_K:    return 4;
208        case GGML_TYPE_Q8_0:    return 4;
209        default:                return MMVQ_MAX_BATCH_SIZE;
210    }
211}
212
213static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_cdna(ggml_type type) {
214    switch (type) {
215        case GGML_TYPE_IQ2_S:   return 5;
216        case GGML_TYPE_IQ2_XS:  return 5;
217        case GGML_TYPE_IQ2_XXS: return 5;
218        case GGML_TYPE_IQ3_S:   return 4;
219        case GGML_TYPE_IQ3_XXS: return 5;
220        default:                return MMVQ_MAX_BATCH_SIZE;
221    }
222}
223
224static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna1_rdna2(ggml_type type) {
225    switch (type) {
226        case GGML_TYPE_IQ2_S:   return 4;
227        case GGML_TYPE_IQ2_XS:  return 4;
228        case GGML_TYPE_IQ2_XXS: return 4;
229        case GGML_TYPE_IQ3_S:   return 4;
230        case GGML_TYPE_IQ3_XXS: return 4;
231        case GGML_TYPE_Q2_K:    return 7;
232        case GGML_TYPE_Q3_K:    return 4;
233        case GGML_TYPE_Q4_K:    return 5;
234        case GGML_TYPE_Q5_K:    return 6;
235        case GGML_TYPE_Q6_K:    return 5;
236        default:                return MMVQ_MAX_BATCH_SIZE;
237    }
238}
239
240static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna3(ggml_type type) {
241    switch (type) {
242        case GGML_TYPE_IQ1_S:   return 6;
243        case GGML_TYPE_IQ1_M:   return 6;
244        case GGML_TYPE_IQ2_S:   return 4;
245        case GGML_TYPE_IQ2_XS:  return 4;
246        case GGML_TYPE_IQ2_XXS: return 4;
247        case GGML_TYPE_IQ3_S:   return 4;
248        case GGML_TYPE_IQ3_XXS: return 4;
249        case GGML_TYPE_IQ4_NL:  return 6;
250        case GGML_TYPE_IQ4_XS:  return 6;
251        case GGML_TYPE_Q4_K:    return 4;
252        case GGML_TYPE_Q5_K:    return 4;
253        case GGML_TYPE_Q6_K:    return 4;
254        default:                return MMVQ_MAX_BATCH_SIZE;
255    }
256}
257
258static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna4(ggml_type type) {
259    switch (type) {
260        case GGML_TYPE_IQ1_S:   return 7;
261        case GGML_TYPE_IQ1_M:   return 7;
262        case GGML_TYPE_IQ2_S:   return 4;
263        case GGML_TYPE_IQ2_XS:  return 4;
264        case GGML_TYPE_IQ2_XXS: return 4;
265        case GGML_TYPE_IQ3_S:   return 4;
266        case GGML_TYPE_IQ3_XXS: return 4;
267        case GGML_TYPE_IQ4_NL:  return 7;
268        case GGML_TYPE_IQ4_XS:  return 5;
269        case GGML_TYPE_MXFP4:   return 5;
270        case GGML_TYPE_NVFP4:   return 5;
271        case GGML_TYPE_Q3_K:    return 4;
272        case GGML_TYPE_Q4_0:    return 7;
273        case GGML_TYPE_Q4_1:    return 7;
274        case GGML_TYPE_Q4_K:    return 4;
275        case GGML_TYPE_Q5_0:    return 7;
276        case GGML_TYPE_Q5_1:    return 7;
277        case GGML_TYPE_Q5_K:    return 5;
278        case GGML_TYPE_Q6_K:    return 5;
279        case GGML_TYPE_Q8_0:    return 7;
280        default:                return MMVQ_MAX_BATCH_SIZE;
281    }
282}
283
284// Host function: returns the max batch size for the current arch+type at runtime.
285int get_mmvq_mmid_max_batch(ggml_type type, int cc) {
286    // NVIDIA: Volta, Ada Lovelace, and Blackwell always use MMVQ for MUL_MAT_ID.
287    if (GGML_CUDA_CC_IS_NVIDIA(cc)) {
288        if (cc == GGML_CUDA_CC_VOLTA || cc >= GGML_CUDA_CC_ADA_LOVELACE) {
289            return MMVQ_MAX_BATCH_SIZE;
290        }
291        if (cc >= GGML_CUDA_CC_TURING) {
292            return get_mmvq_mmid_max_batch_turing_plus(type);
293        }
294        return get_mmvq_mmid_max_batch_pascal_older(type);
295    }
296
297    // AMD
298    if (GGML_CUDA_CC_IS_AMD(cc)) {
299        if (GGML_CUDA_CC_IS_RDNA4(cc)) {
300            return get_mmvq_mmid_max_batch_rdna4(type);
301        }
302        if (GGML_CUDA_CC_IS_RDNA3(cc)) {
303            return get_mmvq_mmid_max_batch_rdna3(type);
304        }
305        if (GGML_CUDA_CC_IS_RDNA1(cc) || GGML_CUDA_CC_IS_RDNA2(cc)) {
306            return get_mmvq_mmid_max_batch_rdna1_rdna2(type);
307        }
308        if (GGML_CUDA_CC_IS_CDNA(cc)) {
309            return get_mmvq_mmid_max_batch_cdna(type);
310        }
311        if (GGML_CUDA_CC_IS_GCN(cc)) {
312            return get_mmvq_mmid_max_batch_gcn(type);
313        }
314    }
315    return MMVQ_MAX_BATCH_SIZE;
316}
317
318bool ggml_cuda_should_use_mmvq(enum ggml_type type, int cc, int64_t ne11) {
319    if (!ggml_is_quantized(type)) {
320        return false;
321    }
322    // k-quants cost more to decode and mvq redoes that per column, so MMQ wins sooner.
323    // Only list quant-types MMQ supports, others would fall back to cuBLAS.
324    if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_ADA_LOVELACE) {
325        switch (type) { // tuned on RTX 4090
326            case GGML_TYPE_Q2_K:
327                return ne11 <= 4;
328            case GGML_TYPE_Q3_K:
329                return ne11 <= 6;
330            default:
331                return ne11 <= MMVQ_MAX_BATCH_SIZE;
332        }
333    }
334    if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_BLACKWELL) {
335        switch (type) { // tuned on RTX 5090
336            case GGML_TYPE_Q2_K:
337            case GGML_TYPE_Q3_K:
338            case GGML_TYPE_Q4_K:
339                return ne11 <= 5;
340            case GGML_TYPE_Q5_K:
341                return ne11 <= 6;
342            case GGML_TYPE_Q6_K:
343                return ne11 <= 7;
344            default:
345                return ne11 <= MMVQ_MAX_BATCH_SIZE;
346        }
347    }
348    if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_DGX_SPARK) {
349        switch (type) { // tuned on DGX Spark GB10
350            case GGML_TYPE_Q2_K:
351                return ne11 <= 6;
352            default:
353                return ne11 <= MMVQ_MAX_BATCH_SIZE;
354        }
355    }
356    if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_ORIN) {
357        switch (type) { // tuned for Jetson Orin
358            case GGML_TYPE_Q2_K:
359            case GGML_TYPE_Q3_K:
360            case GGML_TYPE_Q4_K:
361            case GGML_TYPE_Q5_K:
362            case GGML_TYPE_Q6_K:
363                return ne11 <= 1;
364            default:
365                return ne11 <= MMVQ_MAX_BATCH_SIZE;
366        }
367    }
368    if (GGML_CUDA_CC_IS_CDNA(cc)) {
369        if (GGML_CUDA_CC_IS_CDNA1(cc)) {
370            switch (type) {
371                case GGML_TYPE_Q4_0:
372                case GGML_TYPE_Q4_1:
373                    return ne11 <= 7;
374                case GGML_TYPE_Q5_1:
375                    return ne11 <= 7;
376                case GGML_TYPE_Q8_0:
377                    return ne11 <= 6;
378                case GGML_TYPE_Q2_K:
379                    return ne11 <= 4;
380                case GGML_TYPE_Q3_K:
381                    return ne11 <= 3;
382                case GGML_TYPE_Q4_K:
383                    return ne11 <= 2;
384                case GGML_TYPE_Q5_K:
385                    return ne11 <= 3;
386                case GGML_TYPE_Q6_K:
387                    return ne11 <= 4;
388                case GGML_TYPE_IQ1_S:
389                    return ne11 <= 5;
390                case GGML_TYPE_IQ2_XXS:
391                case GGML_TYPE_IQ3_S:
392                case GGML_TYPE_IQ4_XS:
393                    return ne11 <= 6;
394                default:
395                    return ne11 <= MMVQ_MAX_BATCH_SIZE;
396            }
397        }
398        switch (type) { // tuned for CDNA2
399            case GGML_TYPE_Q2_K:
400                return ne11 <= 5;
401            case GGML_TYPE_Q3_K:
402            case GGML_TYPE_Q4_K:
403            case GGML_TYPE_Q5_K:
404                return ne11 <= 3;
405            case GGML_TYPE_Q6_K:
406                return ne11 <= 5;
407            default:
408                return ne11 <= MMVQ_MAX_BATCH_SIZE;
409        }
410    }
411    return ne11 <= MMVQ_MAX_BATCH_SIZE;
412}
413
414// Device constexpr: returns the max batch size for the current arch+type at compile time.
415template <ggml_type type>
416static constexpr __device__ int get_mmvq_mmid_max_batch_for_device() {
417#if defined(RDNA4)
418    return get_mmvq_mmid_max_batch_rdna4(type);
419#elif defined(RDNA3)
420    return get_mmvq_mmid_max_batch_rdna3(type);
421#elif defined(RDNA2) || defined(RDNA1)
422    return get_mmvq_mmid_max_batch_rdna1_rdna2(type);
423#elif defined(CDNA)
424    return get_mmvq_mmid_max_batch_cdna(type);
425#elif defined(GCN)
426    return get_mmvq_mmid_max_batch_gcn(type);
427#elif defined(__CUDA_ARCH__) && (__CUDA_ARCH__ == GGML_CUDA_CC_VOLTA || __CUDA_ARCH__ >= GGML_CUDA_CC_ADA_LOVELACE)
428    return MMVQ_MAX_BATCH_SIZE;
429#elif defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= GGML_CUDA_CC_TURING
430    return get_mmvq_mmid_max_batch_turing_plus(type);
431#else
432    return get_mmvq_mmid_max_batch_pascal_older(type);
433#endif
434}
435
436static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_dst, mmvq_parameter_table_id table_id, bool small_k = false, bool halve_iters = false) {
437    if (table_id == MMVQ_PARAMETERS_GENERIC) {
438        switch (ncols_dst) {
439            case 1:
440            case 2:
441            case 3:
442            case 4:
443                return 4;
444            case 5:
445            case 6:
446            case 7:
447            case 8:
448                return 2;
449            default:
450                return 1;
451        }
452    } else if (table_id == MMVQ_PARAMETERS_GCN) {
453        switch (ncols_dst) {
454            case 1:
455            case 2:
456            case 3:
457            case 4:
458                return 2;
459            case 5:
460            case 6:
461            case 7:
462            case 8:
463            default:
464                return 1;
465        }
466    }
467    if (table_id == MMVQ_PARAMETERS_RDNA4) {
468        // nwarps=8 benefits types with simple vec_dot on RDNA4 (ncols_dst=1).
469        // Types with complex vec_dot (Q3_K, IQ2_*, IQ3_*) regress due to register
470        // pressure and lookup table contention at higher thread counts.
471        if (ncols_dst == 1) {
472            switch (type) {
473                case GGML_TYPE_Q4_0:
474                case GGML_TYPE_Q4_1:
475                case GGML_TYPE_Q5_0:
476                case GGML_TYPE_Q5_1:
477                case GGML_TYPE_Q8_0:
478                case GGML_TYPE_Q2_K:
479                case GGML_TYPE_Q4_K:
480                case GGML_TYPE_Q5_K:
481                case GGML_TYPE_Q6_K:
482                case GGML_TYPE_IQ4_NL:
483                case GGML_TYPE_IQ4_XS:
484                    return 8;
485                default:
486                    return 1;
487            }
488        }
489        return 1;
490    }
491    if (table_id == MMVQ_PARAMETERS_RDNA3_0) {
492        // RDNA3 (W7900): stricter whitelist than RDNA4.
493        // Q2_K / Q5_K / IQ4_XS regress in full quant sweeps.
494        if (ncols_dst == 1) {
495            switch (type) {
496                case GGML_TYPE_Q4_0:
497                case GGML_TYPE_Q4_1:
498                case GGML_TYPE_Q5_0:
499                case GGML_TYPE_Q5_1:
500                case GGML_TYPE_Q8_0:
501                    return 8;
502                case GGML_TYPE_Q6_K:
503                    return 2;
504                case GGML_TYPE_IQ4_NL:
505                    return 8;
506                default:
507                    return 1;
508            }
509        }
510        return 1;
511    }
512    if (table_id == MMVQ_PARAMETERS_TURING) {
513        if (ncols_dst == 1) {
514            switch (type) {
515                case GGML_TYPE_Q2_K:
516                case GGML_TYPE_Q3_K:
517                case GGML_TYPE_Q4_K:
518                case GGML_TYPE_Q5_K:
519                case GGML_TYPE_Q6_K:
520                    return 2;
521                default:
522                    return 4;
523            }
524        }
525        switch (ncols_dst) {
526            case 2:
527            case 3:
528            case 4:
529                return 4;
530            case 5:
531            case 6:
532            case 7:
533            case 8:
534                return 2;
535            default:
536                return 1;
537        }
538    }
539    if (table_id == MMVQ_PARAMETERS_GB10) {
540        const int generic = calc_nwarps(type, ncols_dst, MMVQ_PARAMETERS_GENERIC);
541        // Only worth the wider block when it actually retires the K loop in half the trips (Observation)
542        if (ncols_dst == 1 && !small_k && halve_iters) {
543            switch (type) {
544                case GGML_TYPE_Q4_0:
545                case GGML_TYPE_Q4_1:
546                case GGML_TYPE_Q5_0:
547                case GGML_TYPE_Q5_1:
548                case GGML_TYPE_Q8_0:
549                case GGML_TYPE_Q4_K:
550                case GGML_TYPE_Q5_K:
551                case GGML_TYPE_Q6_K:
552                case GGML_TYPE_IQ4_NL:
553                    return 2 * generic;
554                default:
555                    break;
556            }
557        }
558        return generic;
559    }
560    return 1;
561}
562
563static constexpr __host__ __device__ int calc_rows_per_block(int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
564    if (table_id == MMVQ_PARAMETERS_GENERIC || table_id == MMVQ_PARAMETERS_GCN || table_id == MMVQ_PARAMETERS_TURING || table_id == MMVQ_PARAMETERS_GB10) {
565        switch (ncols_dst) {
566            case 1:
567                return small_k ? nwarps : 1;
568            case 2:
569            case 3:
570            case 4:
571            case 5:
572            case 6:
573            case 7:
574            case 8:
575                return 2;
576            default:
577                return 1;
578        }
579    }
580    return 1;
581}
582
583template <ggml_type type, int ncols_dst, bool has_fusion, bool small_k = false, bool halve_iters = false>
584__launch_bounds__(calc_nwarps(type, ncols_dst, get_device_table_id(), small_k, halve_iters)*ggml_cuda_get_physical_warp_size(), 1)
585static __global__ void mul_mat_vec_q(
586        const void * vx_ptr, const void * vy_ptr, const int32_t * ids_ptr, const ggml_cuda_mm_fusion_args_device fusion, float * dst_ptr,
587        const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t stride_row_x, const uint32_t stride_col_y,
588        const uint32_t stride_col_dst, const uint3 channel_ratio, const uint32_t stride_channel_x,
589        const uint32_t stride_channel_y, const uint32_t stride_channel_dst, const uint3 sample_ratio,
590        const uint32_t stride_sample_x, const uint32_t stride_sample_y, const uint32_t stride_sample_dst,
591        const uint32_t ids_stride) {
592    const void    * GGML_CUDA_RESTRICT vx  = vx_ptr;
593    const void    * GGML_CUDA_RESTRICT vy  = vy_ptr;
594    const int32_t * GGML_CUDA_RESTRICT ids = ids_ptr;
595    float         * GGML_CUDA_RESTRICT dst = dst_ptr;
596
597    constexpr int qk  = ggml_cuda_type_traits<type>::qk;
598    constexpr int qi  = ggml_cuda_type_traits<type>::qi;
599    constexpr int vdr = get_vdr_mmvq(type);
600    constexpr mmvq_parameter_table_id table_id = get_device_table_id();
601    constexpr int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
602    constexpr int rows_per_cuda_block = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
603    constexpr int warp_size = ggml_cuda_get_physical_warp_size();
604
605    constexpr vec_dot_q_cuda_t vec_dot_q_cuda = get_vec_dot_q_cuda(type);
606
607    const     int tid = warp_size*threadIdx.y + threadIdx.x;
608    const     int row0 = rows_per_cuda_block*blockIdx.x;
609    const     int blocks_per_row_x = ncols_x / qk;
610    constexpr int blocks_per_iter = vdr * nwarps*warp_size / qi;
611
612    const uint32_t channel_dst = blockIdx.y;
613
614    uint32_t channel_x;
615    uint32_t channel_y;
616    uint32_t sample_dst;
617
618    ggml_cuda_pdl_sync();
619    channel_x  = ncols_dst == 1 && ids ? ids[channel_dst]                     : fastdiv(channel_dst, channel_ratio);
620    channel_y  = ncols_dst == 1 && ids ? fastmodulo(channel_dst, nchannels_y) : channel_dst;
621    sample_dst = blockIdx.z;
622
623    const uint32_t sample_x    = fastdiv(sample_dst, sample_ratio);
624    const uint32_t sample_y    = sample_dst;
625
626    bool use_gate = false;
627    bool use_bias = false;
628    bool use_gate_bias = false;
629    bool use_scale = false;
630    bool use_gate_scale = false;
631    [[maybe_unused]] const void * vgate = nullptr;
632    const float * x_bias = nullptr;
633    const float * gate_bias = nullptr;
634    const float * x_scale = nullptr;
635    const float * gate_scale = nullptr;
636    ggml_glu_op active_glu;
637    float glu_limit = 0.0f;
638
639    if constexpr (has_fusion) {
640        use_gate      = fusion.gate      != nullptr;
641        use_bias      = fusion.x_bias    != nullptr;
642        use_gate_bias = fusion.gate_bias != nullptr && use_gate;
643        vgate         = fusion.gate;
644        x_bias        = (const float *) fusion.x_bias;
645        gate_bias     = (const float *) fusion.gate_bias;
646        active_glu    = fusion.glu_op;
647        glu_limit     = fusion.glu_limit;
648        if constexpr (type == GGML_TYPE_NVFP4) {
649            use_scale      = fusion.x_scale    != nullptr;
650            use_gate_scale = fusion.gate_scale != nullptr && use_gate;
651            x_scale        = (const float *) fusion.x_scale;
652            gate_scale     = (const float *) fusion.gate_scale;
653        }
654    }
655
656
657    [[maybe_unused]] float x_biases[ncols_dst]    = { 0.0f };
658    [[maybe_unused]] float gate_biases[ncols_dst] = { 0.0f };
659    [[maybe_unused]] float x_scales = 1.0f;
660    [[maybe_unused]] float gate_scales = 1.0f;
661    if constexpr (has_fusion) {
662        // 1. Hide latency by prefetching bias, gates and scales here
663        // 2. load only on threads that won't die after partial sum calculation
664        const uint32_t channel_bias = ids ? channel_x : channel_dst;
665        if (threadIdx.x < rows_per_cuda_block && threadIdx.y == 0 &&
666            (rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) {
667            if (use_bias) {
668                x_bias = x_bias + sample_dst * stride_sample_dst + channel_bias * stride_channel_dst + row0;
669#pragma unroll
670                for (int j = 0; j < ncols_dst; ++j) {
671                    x_biases[j] = x_bias[j * stride_col_dst + threadIdx.x];
672                }
673            }
674            if (use_gate_bias) {
675                gate_bias = gate_bias + sample_dst * stride_sample_dst + channel_bias * stride_channel_dst + row0;
676#pragma unroll
677                for (int j = 0; j < ncols_dst; ++j) {
678                    gate_biases[j] = gate_bias[j * stride_col_dst + threadIdx.x];
679                }
680            }
681            if constexpr (type == GGML_TYPE_NVFP4) {
682                if (use_scale) {
683                    x_scales = x_scale[ids ? channel_x : 0];
684                }
685                if (use_gate_scale) {
686                    gate_scales = gate_scale[ids ? channel_x : 0];
687                }
688            }
689        }
690    }
691
692    // partial sum for each thread
693    float tmp[ncols_dst][rows_per_cuda_block] = {{0.0f}};
694    float tmp_gate[ncols_dst][rows_per_cuda_block] = {{0.0f}};
695
696    const block_q8_1 * y = ((const block_q8_1 *) vy) + sample_y*stride_sample_y + channel_y*stride_channel_y;
697    const int kbx_offset = sample_x*stride_sample_x + channel_x*stride_channel_x + row0*stride_row_x;
698
699    for (int kbx = tid / (qi/vdr); kbx < blocks_per_row_x; kbx += blocks_per_iter) {
700        const int kby = kbx * (qk/QK8_1); // y block index that aligns with kbx
701
702        // x block quant index when casting the quants to int
703        const int kqs = vdr * (tid % (qi/vdr));
704
705#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
706        // start the next iterations' weight loads early
707        if constexpr (mmvq_should_prefetch(type)) {
708            constexpr int pf_dist = 2; // loop iterations, not blocks
709            const int kbx_pf = kbx + pf_dist*blocks_per_iter;
710            if (kbx_pf < blocks_per_row_x) {
711#pragma unroll
712                for (int i = 0; i < rows_per_cuda_block; ++i) {
713                    const size_t off = (size_t)(kbx_offset + i*stride_row_x + kbx_pf) * ggml_cuda_type_traits<type>::bs;
714                    mmvq_prefetch_l2((const char *) vx + off);
715                    if constexpr (has_fusion) {
716                        if (use_gate) {
717                            mmvq_prefetch_l2((const char *) vgate + off);
718                        }
719                    }
720                }
721            }
722        }
723#endif
724
725#pragma unroll
726        for (int j = 0; j < ncols_dst; ++j) {
727#pragma unroll
728            for (int i = 0; i < rows_per_cuda_block; ++i) {
729                tmp[j][i] += vec_dot_q_cuda(
730                    vx, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs);
731                if constexpr (has_fusion) {
732                    if (use_gate) {
733                        tmp_gate[j][i] += vec_dot_q_cuda(
734                            vgate, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs);
735                    }
736                }
737            }
738        }
739    }
740
741    __shared__ float tmp_shared[nwarps-1 > 0 ? nwarps-1 : 1][ncols_dst][rows_per_cuda_block][warp_size];
742    [[maybe_unused]] __shared__ float tmp_shared_gate[(has_fusion && (nwarps-1 > 0)) ? nwarps-1 : 1][ncols_dst][rows_per_cuda_block][warp_size];
743
744    if (threadIdx.y > 0) {
745#pragma unroll
746        for (int j = 0; j < ncols_dst; ++j) {
747#pragma unroll
748            for (int i = 0; i < rows_per_cuda_block; ++i) {
749                tmp_shared[threadIdx.y-1][j][i][threadIdx.x] = tmp[j][i];
750                if constexpr (has_fusion) {
751                    if (use_gate) {
752                        tmp_shared_gate[threadIdx.y-1][j][i][threadIdx.x] = tmp_gate[j][i];
753                    }
754                }
755            }
756        }
757    }
758    __syncthreads();
759    if (threadIdx.y > 0) {
760        return;
761    }
762
763    dst += sample_dst*stride_sample_dst + channel_dst*stride_channel_dst + row0;
764
765    // sum up partial sums and write back result
766#pragma unroll
767    for (int j = 0; j < ncols_dst; ++j) {
768#pragma unroll
769        for (int i = 0; i < rows_per_cuda_block; ++i) {
770#pragma unroll
771            for (int l = 0; l < nwarps-1; ++l) {
772                tmp[j][i] += tmp_shared[l][j][i][threadIdx.x];
773                if constexpr (has_fusion) {
774                    if (use_gate) {
775                        tmp_gate[j][i] += tmp_shared_gate[l][j][i][threadIdx.x];
776                    }
777                }
778            }
779            tmp[j][i] = warp_reduce_sum<warp_size>(tmp[j][i]);
780            if constexpr (has_fusion) {
781                if (use_gate) {
782                    tmp_gate[j][i] = warp_reduce_sum<warp_size>(tmp_gate[j][i]);
783                }
784            }
785
786            if (threadIdx.x == i && (rows_per_cuda_block == 1 || uint32_t(row0 + i) < stride_col_dst)) {
787                float result = tmp[j][i];
788                if constexpr (has_fusion) {
789                    if constexpr (type == GGML_TYPE_NVFP4) {
790                        result *= x_scales;
791                    }
792                    result += x_biases[j];
793                    if (use_gate) {
794                        float gate_value = tmp_gate[j][i];
795                        if constexpr (type == GGML_TYPE_NVFP4) {
796                            gate_value *= gate_scales;
797                        }
798                        gate_value += gate_biases[j];
799                        switch (active_glu) {
800                            case GGML_GLU_OP_SWIGLU:
801                                result *= ggml_cuda_op_silu_single(gate_value);
802                                break;
803                            case GGML_GLU_OP_GEGLU:
804                                result *= ggml_cuda_op_gelu_single(gate_value);
805                                break;
806                            case GGML_GLU_OP_SWIGLU_OAI:
807                                result = ggml_cuda_op_swiglu_oai_single(gate_value, result);
808                                break;
809                            case GGML_GLU_OP_SWIGLU_CLAMP:
810                                result = ggml_cuda_op_swiglu_clamp_single(gate_value, result, glu_limit);
811                                break;
812                            default:
813                                result = result * gate_value;
814                                break;
815                        }
816                    }
817                }
818                dst[j*stride_col_dst + i] = result;
819            }
820        }
821    }
822
823    if constexpr (!has_fusion) {
824        GGML_UNUSED_VARS(use_gate, use_bias, use_gate_bias, use_scale, use_gate_scale, active_glu, glu_limit, gate_bias, x_bias, x_scale, gate_scale, tmp_gate);
825    }
826    if constexpr (type != GGML_TYPE_NVFP4) {
827        GGML_UNUSED_VARS(use_scale, use_gate_scale, x_scale, gate_scale, x_scales, gate_scales);
828    }
829}
830
831// Dedicated MoE multi-token kernel.
832// Grid: (ceil(nrows_x / c_rows_per_block), nchannels_dst)
833// Block: (warp_size, ncols_dst) - each warp handles one token independently.
834// No shared memory reduction needed since each warp works alone.
835template <ggml_type type, int c_rows_per_block, bool has_fusion = false>
836__launch_bounds__(get_mmvq_mmid_max_batch_for_device<type>()*ggml_cuda_get_physical_warp_size(), 1)
837static __global__ void mul_mat_vec_q_moe(
838        const void * vx_ptr, const void * vy_ptr, const int32_t * ids_ptr, const ggml_cuda_mm_fusion_args_device fusion,
839        float * dst_ptr,
840        const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t nrows_x,
841        const uint32_t stride_row_x, const uint32_t stride_col_y, const uint32_t stride_col_dst,
842        const uint32_t stride_channel_x, const uint32_t stride_channel_y, const uint32_t stride_channel_dst,
843        const uint32_t ncols_dst, const uint32_t ids_stride) {
844    const void    * GGML_CUDA_RESTRICT vx  = vx_ptr;
845    const void    * GGML_CUDA_RESTRICT vy  = vy_ptr;
846    const int32_t * GGML_CUDA_RESTRICT ids = ids_ptr;
847    float         * GGML_CUDA_RESTRICT dst = dst_ptr;
848
849    constexpr int qk  = ggml_cuda_type_traits<type>::qk;
850    constexpr int qi  = ggml_cuda_type_traits<type>::qi;
851    constexpr int vdr = get_vdr_mmvq(type);
852    constexpr int warp_size = ggml_cuda_get_physical_warp_size();
853
854    constexpr vec_dot_q_cuda_t vec_dot_q_cuda = get_vec_dot_q_cuda(type);
855
856    // fuse gate, bias, scales, and glu_op into the up projection
857    bool use_gate = false;
858    const void  * vgate      = nullptr;
859    const float * x_bias     = nullptr;
860    const float * gate_bias  = nullptr;
861    const float * x_scale    = nullptr;
862    const float * gate_scale = nullptr;
863    ggml_glu_op   active_glu = GGML_GLU_OP_SWIGLU;
864    float         glu_limit  = 0.0f;
865
866    if constexpr (has_fusion) {
867        use_gate   = fusion.gate != nullptr;
868        vgate      = fusion.gate;
869        x_bias     = (const float *) fusion.x_bias;
870        gate_bias  = (const float *) fusion.gate_bias;
871        active_glu = fusion.glu_op;
872        glu_limit  = fusion.glu_limit;
873        if constexpr (type == GGML_TYPE_NVFP4) {
874            x_scale    = (const float *) fusion.x_scale;
875            gate_scale = (const float *) fusion.gate_scale;
876        }
877    }
878
879    const uint32_t token_idx   = threadIdx.y;
880    const int      row0        = c_rows_per_block*blockIdx.x;
881    const int      blocks_per_row_x = ncols_x / qk;
882    constexpr int  blocks_per_iter  = vdr * warp_size / qi;
883
884    const uint32_t channel_dst = blockIdx.y;
885
886    if (token_idx >= ncols_dst) {
887        return;
888    }
889
890    ggml_cuda_pdl_sync();
891    const uint32_t channel_x = ids[channel_dst + token_idx * ids_stride];
892    const uint32_t channel_y = fastmodulo(channel_dst, nchannels_y);
893
894    const block_q8_1 * y = ((const block_q8_1 *) vy) + channel_y*stride_channel_y + token_idx*stride_col_y;
895    const int kbx_offset  = channel_x*stride_channel_x + row0*stride_row_x;
896
897    // partial sum for each thread
898    float tmp[c_rows_per_block] = {0.0f};
899    float tmp_gate[c_rows_per_block] = {0.0f};
900
901    for (int kbx = threadIdx.x / (qi/vdr); kbx < blocks_per_row_x; kbx += blocks_per_iter) {
902        const int kby = kbx * (qk/QK8_1);
903        const int kqs = vdr * (threadIdx.x % (qi/vdr));
904
905#pragma unroll
906        for (int i = 0; i < c_rows_per_block; ++i) {
907            tmp[i] += vec_dot_q_cuda(vx, &y[kby], kbx_offset + i*stride_row_x + kbx, kqs);
908            if constexpr (has_fusion) {
909                if (use_gate) {
910                    tmp_gate[i] += vec_dot_q_cuda(vgate, &y[kby], kbx_offset + i*stride_row_x + kbx, kqs);
911                }
912            }
913        }
914    }
915
916    ggml_cuda_pdl_lc();
917
918    // Warp-level reduction only - no shared memory needed
919#pragma unroll
920    for (int i = 0; i < c_rows_per_block; ++i) {
921        tmp[i] = warp_reduce_sum<warp_size>(tmp[i]);
922        if constexpr (has_fusion) {
923            if (use_gate) {
924                tmp_gate[i] = warp_reduce_sum<warp_size>(tmp_gate[i]);
925            }
926        }
927    }
928
929    // Write results
930    if (threadIdx.x < c_rows_per_block && (c_rows_per_block == 1 || uint32_t(row0 + threadIdx.x) < nrows_x)) {
931        float result = tmp[threadIdx.x];
932        if constexpr (has_fusion) {
933            const uint32_t bias_idx = channel_x*stride_channel_dst + row0 + threadIdx.x;
934
935            if constexpr (type == GGML_TYPE_NVFP4) {
936                if (x_scale) {
937                    result *= x_scale[channel_x];
938                }
939            }
940            if (x_bias) {
941                result += x_bias[bias_idx];
942            }
943            if (use_gate) {
944                float gate_value = tmp_gate[threadIdx.x];
945                if constexpr (type == GGML_TYPE_NVFP4) {
946                    if (gate_scale) {
947                        gate_value *= gate_scale[channel_x];
948                    }
949                }
950                if (gate_bias) {
951                    gate_value += gate_bias[bias_idx];
952                }
953                switch (active_glu) {
954                    case GGML_GLU_OP_SWIGLU:
955                        result *= ggml_cuda_op_silu_single(gate_value);
956                        break;
957                    case GGML_GLU_OP_GEGLU:
958                        result *= ggml_cuda_op_gelu_single(gate_value);
959                        break;
960                    case GGML_GLU_OP_SWIGLU_OAI:
961                        result = ggml_cuda_op_swiglu_oai_single(gate_value, result);
962                        break;
963                    case GGML_GLU_OP_SWIGLU_CLAMP:
964                        result = ggml_cuda_op_swiglu_clamp_single(gate_value, result, glu_limit);
965                        break;
966                    default:
967                        result = result * gate_value;
968                        break;
969                }
970            }
971        }
972        dst[channel_dst*stride_channel_dst + token_idx*stride_col_dst + row0 + threadIdx.x] = result;
973    }
974
975    if constexpr (!has_fusion) {
976        GGML_UNUSED_VARS(use_gate, tmp_gate, vgate, x_bias, gate_bias, active_glu, glu_limit, x_scale, gate_scale);
977    } else if constexpr (type != GGML_TYPE_NVFP4) {
978        GGML_UNUSED_VARS(x_scale, gate_scale);
979    }
980}
981
982template<ggml_type type>
983static std::pair<dim3, dim3> calc_launch_params(
984        const int ncols_dst, const int nrows_x, const int nchannels_dst, const int nsamples_or_ntokens,
985        const int warp_size, const mmvq_parameter_table_id table_id, const bool small_k = false, const bool halve_iters = false) {
986    const int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
987    const int rpb = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
988    const int64_t nblocks = (nrows_x + rpb - 1) / rpb;
989    const dim3 block_nums(nblocks, nchannels_dst, nsamples_or_ntokens);
990    const dim3 block_dims(warp_size, nwarps, 1);
991    return {block_nums, block_dims};
992}
993
994template<ggml_type type, int c_ncols_dst, bool small_k = false, bool halve_iters = false>
995static void mul_mat_vec_q_switch_fusion(
996        const void * vx, const void * vy, const int32_t * ids, const ggml_cuda_mm_fusion_args_device fusion, float * dst,
997        const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t stride_row_x, const uint32_t stride_col_y,
998        const uint32_t stride_col_dst, const uint3 channel_ratio, const uint32_t stride_channel_x,
999        const uint32_t stride_channel_y, const uint32_t stride_channel_dst, const uint3 sample_ratio,
1000        const uint32_t stride_sample_x, const uint32_t stride_sample_y, const uint32_t stride_sample_dst,
1001        const dim3 & block_nums, const dim3 & block_dims, const int nbytes_shared,
1002        const uint32_t ids_stride, cudaStream_t stream) {
1003
1004    const bool has_fusion = fusion.gate != nullptr || fusion.x_bias != nullptr || fusion.gate_bias != nullptr ||
1005                            fusion.x_scale != nullptr || fusion.gate_scale != nullptr;
1006    if constexpr (c_ncols_dst == 1) {
1007        if (has_fusion) {
1008            const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, nbytes_shared, stream);
1009            ggml_cuda_kernel_launch(mul_mat_vec_q<type, c_ncols_dst, true, small_k, halve_iters>, launch_params,
1010                 vx, vy, ids, fusion, dst, ncols_x, nchannels_y, stride_row_x, stride_col_y, stride_col_dst,
1011                 channel_ratio, stride_channel_x, stride_channel_y, stride_channel_dst,
1012                 sample_ratio, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride);
1013            return;
1014        }
1015    }
1016
1017    GGML_ASSERT(!has_fusion && "fusion only supported for ncols_dst=1");
1018
1019    const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, nbytes_shared, stream);
1020    ggml_cuda_kernel_launch(mul_mat_vec_q<type, c_ncols_dst, false, small_k, halve_iters>, launch_params,
1021        vx, vy, ids, fusion, dst, ncols_x, nchannels_y, stride_row_x, stride_col_y, stride_col_dst,
1022        channel_ratio, stride_channel_x, stride_channel_y, stride_channel_dst,
1023        sample_ratio, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride);
1024}
1025
1026template <ggml_type type>
1027static void mul_mat_vec_q_moe_launch(
1028        const void * vx, const void * vy, const int32_t * ids, const ggml_cuda_mm_fusion_args_device fusion, float * dst,
1029        const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t nrows_x,
1030        const uint32_t stride_row_x, const uint32_t stride_col_y, const uint32_t stride_col_dst,
1031        const uint32_t stride_channel_x, const uint32_t stride_channel_y, const uint32_t stride_channel_dst,
1032        const uint32_t ncols_dst, const uint32_t ids_stride,
1033        const int warp_size, const int nchannels_dst, cudaStream_t stream) {
1034
1035    constexpr int rows_per_block = 2; // 2 gives best perf based on tuning
1036    const int64_t nblocks_rows = (nrows_x + rows_per_block - 1) / rows_per_block;
1037    const dim3 block_nums(nblocks_rows, nchannels_dst);
1038    const dim3 block_dims(warp_size, ncols_dst);
1039    const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
1040
1041    const bool has_fusion = fusion.gate != nullptr || fusion.x_bias != nullptr || fusion.gate_bias != nullptr ||
1042                            fusion.x_scale != nullptr || fusion.gate_scale != nullptr;
1043
1044    if (has_fusion) {
1045        ggml_cuda_kernel_launch(mul_mat_vec_q_moe<type, rows_per_block, true>, launch_params,
1046            vx, vy, ids, fusion, dst, ncols_x, nchannels_y, nrows_x,
1047            stride_row_x, stride_col_y, stride_col_dst,
1048            stride_channel_x, stride_channel_y, stride_channel_dst,
1049            ncols_dst, ids_stride);
1050    } else {
1051        ggml_cuda_kernel_launch(mul_mat_vec_q_moe<type, rows_per_block, false>, launch_params,
1052            vx, vy, ids, fusion, dst, ncols_x, nchannels_y, nrows_x,
1053            stride_row_x, stride_col_y, stride_col_dst,
1054            stride_channel_x, stride_channel_y, stride_channel_dst,
1055            ncols_dst, ids_stride);
1056    }
1057}
1058
1059template <ggml_type type>
1060static void mul_mat_vec_q_switch_ncols_dst(
1061        const void * vx, const void * vy, const int32_t * ids, const ggml_cuda_mm_fusion_args_device fusion, float * dst,
1062        const int ncols_x, const int nrows_x, const int ncols_dst,
1063        const int stride_row_x, const int stride_col_y, const int stride_col_dst,
1064        const int nchannels_x, const int nchannels_y, const int nchannels_dst,
1065        const int stride_channel_x, const int stride_channel_y, const int stride_channel_dst,
1066        const int nsamples_x, const int nsamples_dst, const int stride_sample_x, const int stride_sample_y, const int stride_sample_dst,
1067        const int ids_stride, cudaStream_t stream) {
1068
1069    GGML_ASSERT(ncols_x % ggml_blck_size(type) == 0);
1070    GGML_ASSERT(ncols_dst <= MMVQ_MAX_BATCH_SIZE);
1071
1072    const uint3 nchannels_y_fd   = ids ? init_fastdiv_values(nchannels_y) : make_uint3(0, 0, 0);
1073    const uint3 channel_ratio_fd = ids ? make_uint3(0, 0, 0)              : init_fastdiv_values(nchannels_dst / nchannels_x);
1074    const uint3 sample_ratio_fd  = init_fastdiv_values(nsamples_dst  / nsamples_x);
1075
1076    const int device = ggml_cuda_get_device();
1077    const int                     cc        = ggml_cuda_info().devices[device].cc;
1078    const int warp_size = ggml_cuda_info().devices[device].warp_size;
1079    const mmvq_parameter_table_id table_id  = get_device_table_id(cc);
1080
1081    const bool has_ids = ids != nullptr;
1082
1083    // How the K loop divides up at the baseline block width, both decisions below use these.
1084    constexpr int qk                    = ggml_cuda_type_traits<type>::qk;
1085    constexpr int qi                    = ggml_cuda_type_traits<type>::qi;
1086    constexpr int vdr                   = get_vdr_mmvq(type);
1087    const int     blocks_per_row_x      = ncols_x / qk;
1088    const int     blocks_per_iter_1warp = vdr * warp_size / qi;
1089
1090    const auto should_use_small_k = [&](int c_ncols_dst) {
1091        // When K is small, increase rows_per_block to match nwarps so each warp has more work to do
1092        // Trigger when the full thread block covers all K blocks in a single loop iteration and few threads remain idle.
1093        const int  nwarps = calc_nwarps(type, c_ncols_dst, table_id);
1094        bool       use    = nwarps > 1 && blocks_per_row_x < nwarps * blocks_per_iter_1warp;
1095
1096        constexpr std::array<ggml_type, 2> iq_slow_turing = {
1097            GGML_TYPE_IQ3_XXS,
1098            GGML_TYPE_IQ3_S,
1099        };
1100        constexpr std::array<ggml_type, 8> iq_slow_other = {
1101            GGML_TYPE_IQ1_S, GGML_TYPE_IQ1_M,   GGML_TYPE_IQ2_XXS, GGML_TYPE_IQ2_XS,
1102            GGML_TYPE_IQ2_S, GGML_TYPE_IQ3_XXS, GGML_TYPE_IQ3_S,   GGML_TYPE_IQ4_XS,
1103        };
1104        constexpr std::array<ggml_type, 3> slow_pascal = {
1105            GGML_TYPE_IQ3_S,
1106            GGML_TYPE_Q2_K,
1107            GGML_TYPE_Q3_K,
1108        };
1109
1110        const bool is_nvidia_turing_plus  = GGML_CUDA_CC_IS_NVIDIA(cc) && cc >= GGML_CUDA_CC_TURING;
1111        const bool is_nvidia_pascal_older = GGML_CUDA_CC_IS_NVIDIA(cc) && cc < GGML_CUDA_CC_VOLTA;
1112
1113        if (is_nvidia_turing_plus) {
1114            if (ncols_dst == 1 &&
1115                    std::find(iq_slow_turing.begin(), iq_slow_turing.end(), type) != iq_slow_turing.end()) {
1116                use = false;
1117            }
1118        } else if ((ncols_dst == 1 && std::find(iq_slow_other.begin(), iq_slow_other.end(), type) != iq_slow_other.end()) ||
1119                (is_nvidia_pascal_older && std::find(slow_pascal.begin(), slow_pascal.end(), type) != slow_pascal.end()) ||
1120                GGML_CUDA_CC_IS_RDNA(cc)) {
1121            use = false;
1122        }
1123
1124        return use;
1125    };
1126
1127    // Whether doubling nwarps pays off on the ncols_dst == 1 path, where K sets the K loop trip count.
1128    const auto should_halve_iters = [&] {
1129        if (table_id != MMVQ_PARAMETERS_GB10) {
1130            return false;
1131        }
1132
1133        // Expert rows are gathered per token, so a wider block adds reduction work without reuse.
1134        if (has_ids) {
1135            return false;
1136        }
1137
1138        const int blocks_per_iter = calc_nwarps(type, 1, table_id) * blocks_per_iter_1warp;
1139        const int iters           = (blocks_per_row_x + blocks_per_iter - 1) /  blocks_per_iter;
1140        const int iters_wide      = (blocks_per_row_x + blocks_per_iter * 2 - 1) / (blocks_per_iter * 2);
1141
1142        // An odd trip count leaves half the wider block idle for its last iteration, that tail is
1143        // only affordable once the loop is long enough to dilute it to an eighth of the work (observation).
1144        const int idle = iters_wide * 2 - iters;
1145
1146        return idle * 8 <= iters_wide * 2;
1147    };
1148
1149    if (has_ids && ncols_dst > 1) {
1150        // Multi-token MUL_MAT_ID path - dedicated MoE kernel
1151        mul_mat_vec_q_moe_launch<type>(
1152            vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x,
1153            stride_row_x, stride_col_y, stride_col_dst,
1154            stride_channel_x, stride_channel_y, stride_channel_dst,
1155            ncols_dst, ids_stride, warp_size, nchannels_dst, stream);
1156        return;
1157    }
1158
1159    switch (ncols_dst) {
1160        case 1: {
1161            // static, else MSVC lambda capture breaks the constexpr uses below
1162            static constexpr int c_ncols_dst = 1;
1163
1164            // Tag types keep the flags compile-time, so __launch_bounds__ matches what is launched.
1165            const auto launch = [&](auto small_k_tag, auto halve_iters_tag) {
1166                constexpr bool c_small_k = decltype(small_k_tag)::value;
1167                // Types the table does not promote would compile a second, identical kernel.
1168                constexpr bool c_promoted =
1169                    calc_nwarps(type, c_ncols_dst, MMVQ_PARAMETERS_GB10, false, true) !=
1170                    calc_nwarps(type, c_ncols_dst, MMVQ_PARAMETERS_GB10, false, false);
1171
1172                constexpr bool c_halve_iters = decltype(halve_iters_tag)::value && c_promoted;
1173
1174                const std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst,
1175                                                                              nsamples_dst, warp_size, table_id, c_small_k, c_halve_iters);
1176                mul_mat_vec_q_switch_fusion<type, c_ncols_dst, c_small_k, c_halve_iters>(
1177                    vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1178                    channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst, sample_ratio_fd,
1179                    stride_sample_x, stride_sample_y, stride_sample_dst, dims.first, dims.second, 0, ids_stride,
1180                    stream);
1181            };
1182
1183            if (should_use_small_k(c_ncols_dst)) {
1184                launch(std::true_type{},  std::false_type{});
1185            } else if (should_halve_iters()) {
1186                launch(std::false_type{}, std::true_type{});
1187            } else {
1188                launch(std::false_type{}, std::false_type{});
1189            }
1190        } break;
1191        case 2: {
1192            constexpr int c_ncols_dst = 2;
1193            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1194            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1195                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1196                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1197                 dims.first, dims.second, 0, ids_stride, stream);
1198        } break;
1199        case 3: {
1200            constexpr int c_ncols_dst = 3;
1201            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1202            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1203                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1204                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1205                 dims.first, dims.second, 0, ids_stride, stream);
1206        } break;
1207        case 4: {
1208            constexpr int c_ncols_dst = 4;
1209            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1210            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1211                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1212                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1213                 dims.first, dims.second, 0, ids_stride, stream);
1214        } break;
1215        case 5: {
1216            constexpr int c_ncols_dst = 5;
1217            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1218            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1219                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1220                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1221                 dims.first, dims.second, 0, ids_stride, stream);
1222        } break;
1223        case 6: {
1224            constexpr int c_ncols_dst = 6;
1225            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1226            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1227                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1228                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1229                 dims.first, dims.second, 0, ids_stride, stream);
1230        } break;
1231        case 7: {
1232            constexpr int c_ncols_dst = 7;
1233            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1234            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1235                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1236                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1237                 dims.first, dims.second, 0, ids_stride, stream);
1238        } break;
1239        case 8: {
1240            constexpr int c_ncols_dst = 8;
1241            std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
1242            mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
1243                 channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
1244                 sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
1245                 dims.first, dims.second, 0, ids_stride, stream);
1246        } break;
1247        default:
1248            GGML_ABORT("fatal error");
1249            break;
1250    }
1251}
1252static void mul_mat_vec_q_switch_type(
1253        const void * vx, const ggml_type type_x, const void * vy, const int32_t * ids, const ggml_cuda_mm_fusion_args_device fusion, float * dst,
1254        const int ncols_x, const int nrows_x, const int ncols_dst,
1255        const int stride_row_x, const int stride_col_y, const int stride_col_dst,
1256        const int nchannels_x, const int nchannels_y, const int nchannels_dst,
1257        const int stride_channel_x, const int stride_channel_y, const int stride_channel_dst,
1258        const int nsamples_x, const int nsamples_dst, const int stride_sample_x, const int stride_sample_y, const int stride_sample_dst,
1259        const int ids_stride, cudaStream_t stream) {
1260    switch (type_x) {
1261        case GGML_TYPE_Q1_0:
1262            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q1_0>
1263                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1264                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1265                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1266            break;
1267        case GGML_TYPE_Q2_0:
1268            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q2_0>
1269                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1270                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1271                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1272            break;
1273        case GGML_TYPE_Q4_0:
1274            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q4_0>
1275                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1276                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1277                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1278            break;
1279        case GGML_TYPE_Q4_1:
1280            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q4_1>
1281                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1282                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1283                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1284            break;
1285        case GGML_TYPE_Q5_0:
1286            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q5_0>
1287                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1288                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1289                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1290            break;
1291        case GGML_TYPE_Q5_1:
1292            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q5_1>
1293                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1294                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1295                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1296            break;
1297        case GGML_TYPE_Q8_0:
1298            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q8_0>
1299                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1300                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1301                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1302            break;
1303        case GGML_TYPE_MXFP4:
1304            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_MXFP4>
1305                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1306                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1307                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1308            break;
1309        case GGML_TYPE_NVFP4:
1310            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_NVFP4>
1311                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1312                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1313                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1314            break;
1315        case GGML_TYPE_Q2_K:
1316            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q2_K>
1317                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1318                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1319                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1320            break;
1321        case GGML_TYPE_Q3_K:
1322            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q3_K>
1323                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1324                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1325                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1326            break;
1327        case GGML_TYPE_Q4_K:
1328            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q4_K>
1329                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1330                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1331                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1332            break;
1333        case GGML_TYPE_Q5_K:
1334            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q5_K>
1335                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1336                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1337                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1338            break;
1339        case GGML_TYPE_Q6_K:
1340            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_Q6_K>
1341                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1342                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1343                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1344            break;
1345        case GGML_TYPE_IQ2_XXS:
1346            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ2_XXS>
1347                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1348                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1349                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1350            break;
1351        case GGML_TYPE_IQ2_XS:
1352            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ2_XS>
1353                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1354                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1355                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1356            break;
1357        case GGML_TYPE_IQ2_S:
1358            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ2_S>
1359                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1360                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1361                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1362            break;
1363        case GGML_TYPE_IQ3_XXS:
1364            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ3_XXS>
1365                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1366                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1367                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1368            break;
1369        case GGML_TYPE_IQ1_S:
1370            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ1_S>
1371                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1372                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1373                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1374            break;
1375        case GGML_TYPE_IQ1_M:
1376            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ1_M>
1377                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1378                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1379                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1380            break;
1381        case GGML_TYPE_IQ4_NL:
1382            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ4_NL>
1383                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1384                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1385                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1386            break;
1387        case GGML_TYPE_IQ4_XS:
1388            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ4_XS>
1389                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1390                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1391                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1392            break;
1393        case GGML_TYPE_IQ3_S:
1394            mul_mat_vec_q_switch_ncols_dst<GGML_TYPE_IQ3_S>
1395                (vx, vy, ids, fusion, dst, ncols_x, nrows_x, ncols_dst, stride_row_x, stride_col_y, stride_col_dst,
1396                 nchannels_x, nchannels_y, nchannels_dst, stride_channel_x, stride_channel_y, stride_channel_dst,
1397                 nsamples_x, nsamples_dst, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride, stream);
1398            break;
1399        default:
1400            GGML_ABORT("fatal error");
1401            break;
1402    }
1403}
1404
1405void ggml_cuda_mul_mat_vec_q(
1406        ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst,
1407        const ggml_cuda_mm_fusion_args_host * fusion) {
1408    GGML_ASSERT(        src1->type == GGML_TYPE_F32);
1409    GGML_ASSERT(        dst->type  == GGML_TYPE_F32);
1410    GGML_ASSERT(!ids || ids->type  == GGML_TYPE_I32); // Optional, used for batched GGML_MUL_MAT_ID.
1411
1412    GGML_TENSOR_BINARY_OP_LOCALS;
1413
1414    cudaStream_t stream = ctx.stream();
1415
1416    const size_t ts_src0 = ggml_type_size(src0->type);
1417    const size_t ts_src1 = ggml_type_size(src1->type);
1418    const size_t ts_dst  = ggml_type_size(dst->type);
1419
1420    GGML_ASSERT(        nb00       == ts_src0);
1421    GGML_ASSERT(        nb10       == ts_src1);
1422    GGML_ASSERT(        nb0        == ts_dst);
1423    GGML_ASSERT(!ids || ids->nb[0] == ggml_type_size(ids->type));
1424
1425    GGML_ASSERT(!ids || ne12 <= MMVQ_MAX_BATCH_SIZE);
1426
1427    const float   * src1_d =       (const float   *) src1->data;
1428    const int32_t *  ids_d = ids ? (const int32_t *)  ids->data : nullptr;
1429    float         *  dst_d =       (float         *)  dst->data;
1430
1431    ggml_cuda_mm_fusion_args_device fusion_local{};
1432
1433    if (fusion) {
1434        const int cc = ggml_cuda_info().devices[ggml_cuda_get_device()].cc;
1435        GGML_ASSERT( !ids || dst->ne[2] <= get_mmvq_mmid_max_batch(src0->type, cc));
1436        GGML_ASSERT(  ids || dst->ne[1] == 1);
1437        // Scale fusion is only allowed for NVFP4 currently as the cost of checking this at run-time in the prologue is
1438        // non-negligible for some models such as gpt-oss-20b
1439        GGML_ASSERT((fusion->x_scale == nullptr && fusion->gate_scale == nullptr) || src0->type == GGML_TYPE_NVFP4);
1440
1441        if (fusion->x_bias) {
1442            GGML_ASSERT(fusion->x_bias->type == GGML_TYPE_F32);
1443            GGML_ASSERT(fusion->x_bias->ne[0] == dst->ne[0]);
1444            GGML_ASSERT(!ids || fusion->x_bias->ne[1] == src0->ne[2]);
1445            fusion_local.x_bias = fusion->x_bias->data;
1446        }
1447        if (fusion->gate) {
1448            GGML_ASSERT(fusion->gate->type == src0->type && ggml_are_same_stride(fusion->gate, src0));
1449            fusion_local.gate = fusion->gate->data;
1450        }
1451        if (fusion->gate_bias) {
1452            GGML_ASSERT(fusion->gate_bias->type == GGML_TYPE_F32);
1453            GGML_ASSERT(fusion->gate_bias->ne[0] == dst->ne[0]);
1454            GGML_ASSERT(!ids || fusion->gate_bias->ne[1] == src0->ne[2]);
1455            fusion_local.gate_bias = fusion->gate_bias->data;
1456        }
1457        if (fusion->x_scale) {
1458            GGML_ASSERT(fusion->x_scale->type == GGML_TYPE_F32);
1459            GGML_ASSERT(ggml_is_contiguous(fusion->x_scale));
1460            GGML_ASSERT(ggml_nelements(fusion->x_scale) == (ids ? src0->ne[2] : 1));
1461            fusion_local.x_scale = fusion->x_scale->data;
1462        }
1463        if (fusion->gate_scale) {
1464            GGML_ASSERT(fusion->gate_scale->type == GGML_TYPE_F32);
1465            GGML_ASSERT(ggml_is_contiguous(fusion->gate_scale));
1466            GGML_ASSERT(ggml_nelements(fusion->gate_scale) == (ids ? src0->ne[2] : 1));
1467            fusion_local.gate_scale = fusion->gate_scale->data;
1468        }
1469        fusion_local.glu_op = fusion->glu_op;
1470        fusion_local.glu_limit = fusion->glu_limit;
1471    }
1472
1473    // If src0 is a temporary compute buffer, clear any potential padding.
1474    if (ggml_backend_buffer_get_usage(src0->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
1475        const size_t size_data  = ggml_nbytes(src0);
1476        const size_t size_alloc = ggml_backend_buffer_get_alloc_size(src0->buffer, src0);
1477        if (size_alloc > size_data) {
1478            GGML_ASSERT(ggml_is_contiguously_allocated(src0));
1479            GGML_ASSERT(!src0->view_src);
1480            CUDA_CHECK(cudaMemsetAsync((char *) src0->data + size_data, 0, size_alloc - size_data, stream));
1481        }
1482    }
1483
1484    const int64_t ne10_padded = GGML_PAD(ne10, MATRIX_ROW_PADDING);
1485    ggml_cuda_pool_alloc<char> src1_q8_1(ctx.pool(), ne13*ne12 * ne11*ne10_padded * sizeof(block_q8_1)/QK8_1);
1486    {
1487        const int64_t s11 = src1->nb[1] / ts_src1;
1488        const int64_t s12 = src1->nb[2] / ts_src1;
1489        const int64_t s13 = src1->nb[3] / ts_src1;
1490        quantize_row_q8_1_cuda(src1_d, nullptr, src1_q8_1.get(), src0->type, ne10, s11, s12, s13, ne10_padded, ne11, ne12, ne13, stream);
1491    }
1492
1493    const int64_t s01 = src0->nb[1] / ts_src0;
1494    const int64_t s11 = ne10_padded / QK8_1;
1495    const int64_t s1  =  dst->nb[1] / ts_dst;
1496    const int64_t s02 = src0->nb[2] / ts_src0;
1497    const int64_t s2  =  dst->nb[2] / ts_dst;
1498    const int64_t s03 = src0->nb[3] / ts_src0;
1499    const int64_t s3  =  dst->nb[3] / ts_dst;
1500
1501    const int64_t s12 = ne11*s11;
1502    const int64_t s13 = ne12*s12;
1503
1504    // For MUL_MAT_ID the memory layout is different than for MUL_MAT:
1505    const int64_t ncols_dst          = ids ? ne2  : ne1;
1506    const int64_t nchannels_y        = ids ? ne11 : ne12;
1507    const int64_t nchannels_dst      = ids ? ne1  : ne2;
1508    const int64_t stride_col_dst     = ids ? s2   : s1;
1509    const int64_t stride_col_y       = ids ? s12  : s11;
1510    const int64_t stride_channel_dst = ids ? s1   : s2;
1511    const int64_t stride_channel_y   = ids ? s11  : s12;
1512
1513    const int64_t ids_stride = ids ? ids->nb[1] / ggml_type_size(ids->type) : 0;
1514
1515    mul_mat_vec_q_switch_type(
1516        src0->data, src0->type, src1_q8_1.get(), ids_d, fusion_local, dst_d, ne00,
1517        ne01,              ncols_dst,     s01, stride_col_y,     stride_col_dst,
1518        ne02, nchannels_y, nchannels_dst, s02, stride_channel_y, stride_channel_dst,
1519        ne03,              ne3,           s03, s13,              s3,               ids_stride, stream);
1520}
1521
1522void ggml_cuda_op_mul_mat_vec_q(
1523    ggml_backend_cuda_context & ctx,
1524    const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, const char * src0_dd_i, const float * src1_ddf_i,
1525    const char * src1_ddq_i, float * dst_dd_i, const int64_t row_low, const int64_t row_high, const int64_t src1_ncols,
1526    const int64_t src1_padded_row_size, cudaStream_t stream) {
1527
1528    const int64_t ne00 = src0->ne[0];
1529    const int64_t row_diff = row_high - row_low;
1530
1531    const int64_t ne10 = src1->ne[0];
1532    GGML_ASSERT(ne10 % QK8_1 == 0);
1533
1534    const int64_t ne0 = dst->ne[0];
1535
1536    int id = ggml_cuda_get_device();
1537
1538    // the main device has a larger memory buffer to hold the results from all GPUs
1539    // nrows_dst == nrows of the matrix that the kernel writes into
1540    const int64_t nrows_dst = id == ctx.device ? ne0 : row_diff;
1541
1542    const int stride_row_x = ne00 / ggml_blck_size(src0->type);
1543    const int stride_col_y = src1_padded_row_size / QK8_1;
1544
1545    ggml_cuda_mm_fusion_args_device fusion_local{};
1546    mul_mat_vec_q_switch_type(
1547        src0_dd_i, src0->type, src1_ddq_i, nullptr, fusion_local, dst_dd_i, ne00, row_diff, src1_ncols, stride_row_x, stride_col_y, nrows_dst,
1548        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, stream);
1549
1550    GGML_UNUSED_VARS(src1, dst, src1_ddf_i, src1_ncols, src1_padded_row_size);
1551}