char-fork/llama.cpp

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

ynankaniCUDA: extend MOE fusion to specdec, earlier MOE glu fusion and topk-router fusion were restricted to 1 token (#27621)41ef91f7c

char/aliceai-t5-moe
16.9 KiB442 linesraw
1#include "ggml-cuda/common.cuh"
2#include "ggml.h"
3#include "topk-moe.cuh"
4
5#include <cmath>
6#include <initializer_list>
7
8// Kernel config struct - passed by value to CUDA kernel
9struct topk_moe_config {
10    bool use_sigmoid;
11    bool use_sqrt_softplus;
12    bool with_norm;
13    bool delayed_softmax;
14};
15
16// Warp-local softmax used for both the pre-top-k logits and the post-top-k delayed path.
17template <int experts_per_thread, bool use_limit>
18__device__ void softmax_warp_inplace(float (&vals)[experts_per_thread], const int limit, const int lane) {
19    float max_val = -INFINITY;
20
21#pragma unroll
22    for (int i = 0; i < experts_per_thread; i++) {
23        const int  idx    = lane + i * WARP_SIZE;
24        const bool active = !use_limit || (idx < limit);
25        if (active) {
26            max_val = max(max_val, vals[i]);
27        }
28    }
29
30    max_val = warp_reduce_max(max_val);
31
32    float sum = 0.f;
33
34#pragma unroll
35    for (int i = 0; i < experts_per_thread; i++) {
36        const int  idx    = lane + i * WARP_SIZE;
37        const bool active = !use_limit || (idx < limit);
38        if (active) {
39            const float val = expf(vals[i] - max_val);
40            vals[i]         = val;
41            sum += val;
42        } else {
43            vals[i] = 0.f;
44        }
45    }
46
47    sum = warp_reduce_sum(sum);
48
49    const float inv_sum = 1.0f / sum;
50
51#pragma unroll
52    for (int i = 0; i < experts_per_thread; i++) {
53        const int  idx    = lane + i * WARP_SIZE;
54        const bool active = !use_limit || (idx < limit);
55        if (active) {
56            vals[i] *= inv_sum;
57        }
58    }
59}
60
61template <int experts_per_thread, bool use_limit>
62__device__ void sigmoid_warp_inplace(float (&vals)[experts_per_thread], const int limit, const int lane) {
63#pragma unroll
64    for (int i = 0; i < experts_per_thread; i++) {
65        const int  idx    = lane + i * WARP_SIZE;
66        const bool active = !use_limit || (idx < limit);
67        vals[i]           = active ? 1.f / (1.f + expf(-vals[i])) : -INFINITY;
68    }
69}
70
71template <int experts_per_thread, bool use_limit>
72__device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], const int limit, const int lane) {
73#pragma unroll
74    for (int i = 0; i < experts_per_thread; i++) {
75        const int  idx    = lane + i * WARP_SIZE;
76        const bool active = !use_limit || (idx < limit);
77        vals[i]           = active ? sqrtf(vals[i] > 20.0f ? vals[i] : logf(1.0f + expf(vals[i]))) : -INFINITY;
78    }
79}
80
81/*
82    This kernel does the following:
83    1. optionally softmax over the logits per token [n_experts, n_tokens]
84    2. argmax reduce over the top-k (n_experts_used) logits
85    3. write weights + ids to global memory
86    4. optionally normalize the weights or apply softmax over the selected logits
87
88    It is intended as fusion of softmax->top-k->get_rows pipeline for MoE models
89*/
90template <int n_experts, bool has_bias>
91__launch_bounds__(TOPK_MOE_ROWS_PER_BLOCK * WARP_SIZE, 1)
92__global__ void topk_moe_cuda(const float *         logits,
93                              float *               weights,
94                              int32_t *             ids,
95                              float *               bias,
96                              const int             n_rows,
97                              const int             n_expert_used,
98                              const float           clamp_val,
99                              const float           scale_val,
100                              const topk_moe_config config) {
101    const int row = blockIdx.x * blockDim.y + threadIdx.y;
102    if (row >= n_rows) {
103        return;
104    }
105
106    logits += n_experts * row;
107    weights += n_expert_used * row;
108    ids += n_experts * row;
109
110    constexpr int experts_per_thread = (n_experts > WARP_SIZE) ? n_experts / WARP_SIZE : 1;
111
112    float wt[experts_per_thread];
113
114    // Initialize all slots to -INFINITY
115#pragma unroll
116    for (int i = 0; i < experts_per_thread; i++) {
117        wt[i] = -INFINITY;
118    }
119
120    ggml_cuda_pdl_sync();
121#pragma unroll
122    for (int i = 0; i < n_experts; i += WARP_SIZE) {
123        const int expert  = i + threadIdx.x;
124        wt[i / WARP_SIZE] = (n_experts % WARP_SIZE == 0 || expert < n_experts) ? logits[expert] : -INFINITY;
125    }
126
127    // Weights and IDs can alias logits, so wait until every row in the block reads its logits.
128    __syncthreads();
129
130    if (!config.delayed_softmax) {
131        if (config.use_sigmoid) {
132           sigmoid_warp_inplace<experts_per_thread, false>(wt, n_experts, threadIdx.x);
133        } else if (config.use_sqrt_softplus) {
134           sqrt_softplus_warp_inplace<experts_per_thread, false>(wt, n_experts, threadIdx.x);
135        } else {
136           softmax_warp_inplace<experts_per_thread, false>(wt, n_experts, threadIdx.x);
137        }
138    }
139
140    // Sanitize NaN to -FLT_MAX so the iterative argmax produces unique expert IDs.
141    // NaN comparisons always return false, which would cause the same expert to be
142    // selected repeatedly. -FLT_MAX compares normally and is still excluded by the
143    // -INFINITY sentinel used after each selection round.
144    // More relevant for the cuBLAS path. See https://github.com/ggml-org/llama.cpp/issues/19659
145#pragma unroll
146    for (int i = 0; i < experts_per_thread; i++) {
147        if (__isnanf(wt[i])) {
148            wt[i] = -FLT_MAX;
149        }
150    }
151
152    // selection_wt is only needed when bias is present (selection uses wt + bias)
153    // when no bias, we use wt directly for both selection and weight values
154    [[maybe_unused]] float selection_wt[has_bias ? experts_per_thread : 1];
155
156    if constexpr (has_bias) {
157#pragma unroll
158        for (int i = 0; i < experts_per_thread; i++) {
159            selection_wt[i] = -INFINITY;
160        }
161#pragma unroll
162        for (int i = 0; i < n_experts; i += WARP_SIZE) {
163            const int expert = i + threadIdx.x;
164            selection_wt[i / WARP_SIZE] =
165                (n_experts % WARP_SIZE == 0 || expert < n_experts) ? wt[i / WARP_SIZE] + bias[expert] : -INFINITY;
166        }
167    }
168
169    //at this point, each thread holds either a portion of the softmax distribution
170    //or the raw logits. We do the argmax reduce over n_expert_used, each time marking
171    //the expert weight as -inf to exclude from the next iteration
172
173    float wt_sum = 0.f;
174
175    float output_weights[experts_per_thread];
176
177#pragma unroll
178    for (int i = 0; i < experts_per_thread; i++) {
179        output_weights[i] = 0.f;
180    }
181
182    ggml_cuda_pdl_lc();
183    for (int k = 0; k < n_expert_used; k++) {
184        float max_val    = wt[0];
185        int   max_expert = threadIdx.x;
186
187        if constexpr (has_bias) {
188            float max_val_s = selection_wt[0];
189
190#pragma unroll
191            for (int i = 1; i < experts_per_thread; i++) {
192                const int expert = threadIdx.x + i * WARP_SIZE;
193                if ((n_experts % WARP_SIZE == 0 || expert < n_experts) && selection_wt[i] > max_val_s) {
194                    max_val    = wt[i];
195                    max_val_s  = selection_wt[i];
196                    max_expert = expert;
197                }
198            }
199
200#pragma unroll
201            for (int mask = WARP_SIZE / 2; mask > 0; mask /= 2) {
202                const float val    = __shfl_xor_sync(0xFFFFFFFF, max_val, mask, WARP_SIZE);
203                const float val_s  = __shfl_xor_sync(0xFFFFFFFF, max_val_s, mask, WARP_SIZE);
204                const int   expert = __shfl_xor_sync(0xFFFFFFFF, max_expert, mask, WARP_SIZE);
205                if (val_s > max_val_s || (val_s == max_val_s && expert < max_expert)) {
206                    max_val    = val;
207                    max_val_s  = val_s;
208                    max_expert = expert;
209                }
210            }
211
212            if ((max_expert & (WARP_SIZE - 1)) == threadIdx.x) {
213                selection_wt[max_expert / WARP_SIZE] = -INFINITY;
214            }
215        } else {
216#pragma unroll
217            for (int i = 1; i < experts_per_thread; i++) {
218                const int expert = threadIdx.x + i * WARP_SIZE;
219                if ((n_experts % WARP_SIZE == 0 || expert < n_experts) && wt[i] > max_val) {
220                    max_val    = wt[i];
221                    max_expert = expert;
222                }
223            }
224
225#pragma unroll
226            for (int mask = WARP_SIZE / 2; mask > 0; mask /= 2) {
227                const float val    = __shfl_xor_sync(0xFFFFFFFF, max_val, mask, WARP_SIZE);
228                const int   expert = __shfl_xor_sync(0xFFFFFFFF, max_expert, mask, WARP_SIZE);
229                if (val > max_val || (val == max_val && expert < max_expert)) {
230                    max_val    = val;
231                    max_expert = expert;
232                }
233            }
234
235            if ((max_expert & (WARP_SIZE - 1)) == threadIdx.x) {
236                wt[max_expert / WARP_SIZE] = -INFINITY;
237            }
238        }
239
240        if ((k & (WARP_SIZE - 1)) == threadIdx.x) {
241            output_weights[k / WARP_SIZE] = max_val;
242        }
243
244        if ((max_expert & (WARP_SIZE - 1)) == threadIdx.x) {
245            ids[k] = max_expert;
246            if (config.with_norm) {
247                wt_sum += max_val;
248            }
249        }
250    }
251
252    if (config.with_norm) {
253        wt_sum              = warp_reduce_sum(wt_sum);
254        wt_sum              = max(wt_sum, clamp_val);
255        const float inv_sum = 1.0f / wt_sum;
256
257        for (int i = 0; i < experts_per_thread; i++) {
258            output_weights[i] *= inv_sum;
259        }
260    }
261
262    if (config.delayed_softmax) {
263        softmax_warp_inplace<experts_per_thread, true>(output_weights, n_expert_used, threadIdx.x);
264    }
265
266#pragma unroll
267    for (int i = 0; i < experts_per_thread; i++) {
268        const int idx = i * WARP_SIZE + threadIdx.x;
269        if (idx < n_expert_used) {
270            weights[idx] = output_weights[i] * scale_val;
271        }
272    }
273}
274
275template<bool has_bias>
276static void launch_topk_moe_cuda(ggml_backend_cuda_context & ctx,
277                                 const float *               logits,
278                                 float *                     weights,
279                                 int32_t *                   ids,
280                                 float *                     bias,
281                                 const int                   n_rows,
282                                 const int                   n_expert,
283                                 const int                   n_expert_used,
284                                 const float                 clamp_val,
285                                 const float                 scale_val,
286                                 const topk_moe_config       config) {
287    GGML_ASSERT(!(config.with_norm && config.delayed_softmax) &&
288                "delayed softmax is not supported with weight normalization");
289    const int    rows_per_block = TOPK_MOE_ROWS_PER_BLOCK;
290    dim3         grid_dims((n_rows + rows_per_block - 1) / rows_per_block, 1, 1);
291    dim3         block_dims(WARP_SIZE, rows_per_block, 1);
292    cudaStream_t stream = ctx.stream();
293    const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, stream);
294
295    switch (n_expert) {
296        case 1:
297            ggml_cuda_kernel_launch(topk_moe_cuda<1, has_bias>, launch_params,
298                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
299            break;
300        case 2:
301            ggml_cuda_kernel_launch(topk_moe_cuda<2, has_bias>, launch_params,
302                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
303            break;
304        case 4:
305            ggml_cuda_kernel_launch(topk_moe_cuda<4, has_bias>, launch_params,
306                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
307            break;
308        case 8:
309            ggml_cuda_kernel_launch(topk_moe_cuda<8, has_bias>, launch_params,
310                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
311            break;
312        case 16:
313            ggml_cuda_kernel_launch(topk_moe_cuda<16, has_bias>, launch_params,
314                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
315            break;
316        case 32:
317            ggml_cuda_kernel_launch(topk_moe_cuda<32, has_bias>, launch_params,
318                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
319            break;
320        case 64:
321            ggml_cuda_kernel_launch(topk_moe_cuda<64, has_bias>, launch_params,
322                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
323            break;
324        case 128:
325            ggml_cuda_kernel_launch(topk_moe_cuda<128, has_bias>, launch_params,
326                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
327            break;
328        case 256:
329            ggml_cuda_kernel_launch(topk_moe_cuda<256, has_bias>, launch_params,
330                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
331            break;
332        case 288: // StepFun 3.7
333            ggml_cuda_kernel_launch(topk_moe_cuda<288, has_bias>, launch_params,
334                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
335            break;
336        case 512:
337            ggml_cuda_kernel_launch(topk_moe_cuda<512, has_bias>, launch_params,
338                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
339            break;
340        case 576:
341            ggml_cuda_kernel_launch(topk_moe_cuda<576, has_bias>, launch_params,
342                logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
343            break;
344        default:
345            GGML_ASSERT(false && "fatal error");
346            break;
347    }
348}
349
350void ggml_cuda_op_topk_moe(ggml_backend_cuda_context &     ctx,
351                           const ggml_tensor *             logits,
352                           ggml_tensor *                   weights,
353                           ggml_tensor *                   ids,
354                           const ggml_tensor *             clamp,
355                           const ggml_tensor *             scale,
356                           const ggml_tensor *             bias,
357                           const ggml_cuda_topk_moe_args & args) {
358    GGML_ASSERT(logits->type == GGML_TYPE_F32);
359    GGML_ASSERT(weights->type == GGML_TYPE_F32);
360    GGML_ASSERT(ids->type == GGML_TYPE_I32);
361
362    const int n_experts = logits->ne[0];
363    const int n_rows    = logits->ne[1];
364
365    const float * logits_d  = (const float *) logits->data;
366    float *       weights_d = (float *) weights->data;
367    int32_t *     ids_d     = (int32_t *) ids->data;
368    float *       bias_d    = bias ? (float *) bias->data : nullptr;
369
370    float scale_val = scale ? ggml_get_op_params_f32(scale, 0) : 1.0f;
371
372    GGML_ASSERT(ids->nb[1] / ggml_type_size(ids->type) == (size_t) n_experts);
373
374    const int n_expert_used = weights->ne[1];
375
376    const bool with_norm = clamp != nullptr;
377
378    float clamp_val = -INFINITY;
379    if (clamp) {
380        clamp_val = ggml_get_op_params_f32(clamp, 0);
381    }
382
383    topk_moe_config config;
384    config.use_sigmoid       = args.sigmoid;
385    config.use_sqrt_softplus = args.sqrt_softplus;
386    config.with_norm         = with_norm;
387    config.delayed_softmax   = args.delayed_softmax;
388
389    if (bias) {
390        launch_topk_moe_cuda<true>(ctx, logits_d, weights_d, ids_d, bias_d, n_rows, n_experts, n_expert_used, clamp_val,
391                             scale_val, config);
392    } else {
393        launch_topk_moe_cuda<false>(ctx, logits_d, weights_d, ids_d, bias_d, n_rows, n_experts, n_expert_used, clamp_val,
394                             scale_val, config);
395    }
396}
397
398bool ggml_cuda_should_use_topk_moe(const ggml_tensor * gating_op,
399                                   const ggml_tensor * weights,
400                                   const ggml_tensor * logits,
401                                   const ggml_tensor * ids) {
402    // must match an instantiation of launch_topk_moe_cuda: a power of 2 up to 512,
403    // or one of the non-power-of-2 expert counts of supported models
404    const int n_expert = ids->nb[1] / ids->nb[0];
405    if (((n_expert & (n_expert - 1)) != 0 || n_expert > 512) && n_expert != 288 && n_expert != 576) {
406        return false;
407    }
408
409    if (!ggml_is_contiguous(weights) || !ggml_is_contiguous(logits)) {
410        return false;
411    }
412
413    if (gating_op->op == GGML_OP_SOFT_MAX) {
414        const ggml_tensor * softmax  = gating_op;
415        float               scale    = 1.0f;
416        float               max_bias = 0.0f;
417
418        memcpy(&scale, (const float *) softmax->op_params + 0, sizeof(float));
419        memcpy(&max_bias, (const float *) softmax->op_params + 1, sizeof(float));
420
421        if (!ggml_is_contiguous(softmax->src[0])) {
422            return false;
423        }
424
425        if (scale != 1.0f || max_bias != 0.0f) {
426            return false;
427        }
428
429        // don't fuse when masks or sinks are present
430        if (softmax->src[1] || softmax->src[2]) {
431            return false;
432        }
433    } else if (gating_op->op == GGML_OP_UNARY) {
434        ggml_unary_op op = ggml_get_unary_op(gating_op);
435
436        if (op != GGML_UNARY_OP_SIGMOID && op != GGML_UNARY_OP_SOFTPLUS) {
437            return false;
438        }
439    }
440
441    return true;
442}