char-fork/llama.cpp

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

Frosty40sycl: fuse rms_norm+mul+add and add+add residual chains (#27610)6703d7894

char/aliceai-t5-moe
8.0 KiB212 linesraw
1#include "fusion.hpp"
2#include "binbcast.hpp"
3
4#include <algorithm>
5
6// mul_mat(gate) + mul_mat(up) + GLU: graph shape and tensor properties only. Backend state
7// (weight layout, split buffers, DMMV) is checked by ggml_sycl_mul_mat_glu_mmvq_fused().
8static bool ggml_sycl_should_fuse_mul_mat_glu(const ggml_tensor * gate, const ggml_tensor * up,
9                                              const ggml_tensor * glu) {
10    // the fused epilogue implements these two; the rest fall back to the standalone GLU kernels
11    const ggml_glu_op glu_op = ggml_get_glu_op(glu);
12    if (glu_op != GGML_GLU_OP_SWIGLU && glu_op != GGML_GLU_OP_GEGLU) {
13        return false;
14    }
15
16    // the kernel always treats src[0] as the activated operand and src[1] as the multiplier
17    if (ggml_get_op_params_i32(glu, 1) /* swapped */) {
18        return false;
19    }
20
21    const ggml_tensor * wu  = up->src[0];
22    const ggml_tensor * wg  = gate->src[0];
23    const ggml_tensor * act = up->src[1];
24
25    // one set of block offsets and one quantized activation must serve both weights
26    if (wu->type != wg->type || !ggml_are_same_shape(wu, wg) || !ggml_are_same_stride(wu, wg)) {
27        return false;
28    }
29    if (act != gate->src[1]) {
30        return false;
31    }
32
33    // only q4_K has a fused reorder GEMV so far, and it walks whole super-blocks
34    if (wu->type != GGML_TYPE_Q4_K || wu->ne[0] % QK_K != 0) {
35        return false;
36    }
37
38    // one 2D reorder-layout matrix in, a plain column stride out: no broadcast or padding
39    if (!ggml_is_contiguous(wu) || !ggml_is_contiguous(wg) || !ggml_is_contiguous(act) ||
40        !ggml_is_contiguous(glu)) {
41        return false;
42    }
43    if (act->type != GGML_TYPE_F32 || glu->type != GGML_TYPE_F32) {
44        return false;
45    }
46    if (act->ne[2] != 1 || act->ne[3] != 1 || wu->ne[2] != 1 || wu->ne[3] != 1) {
47        return false;
48    }
49    // the kernel writes rows [0, wu->ne[1]) of each glu column, strided by glu->ne[0]
50    if (glu->ne[0] != wu->ne[1] || glu->ne[1] != act->ne[1]) {
51        return false;
52    }
53    // mat-vec only: one column per decoded token, up to the batch the reorder kernels cover
54    if (act->ne[1] > MMVQ_MAX_BATCH_SIZE) {
55        return false;
56    }
57
58    return true;
59}
60
61bool ggml_sycl_can_fuse(const ggml_cgraph * cgraph, int node_idx, std::initializer_list<enum ggml_op> ops,
62                        std::initializer_list<enum ggml_unary_op> unary_ops) {
63#ifndef NDEBUG
64    const size_t num_unary = std::count(ops.begin(), ops.end(), GGML_OP_UNARY);
65    GGML_ASSERT(unary_ops.size() == num_unary);
66#endif
67
68    if (!g_ggml_sycl_enable_fusion) {
69        return false;
70    }
71
72    // gate and up are siblings, not a chain, so ggml_can_fuse cannot express this: use the
73    // subgraph form with the GLU as the only materialised output.
74    if (ops.size() == 3 && ops.begin()[0] == GGML_OP_MUL_MAT && ops.begin()[1] == GGML_OP_MUL_MAT &&
75        ops.begin()[2] == GGML_OP_GLU) {
76        if (!ggml_can_fuse_subgraph(cgraph, node_idx, ops, { node_idx + 2 })) {
77            return false;
78        }
79
80        const ggml_tensor * glu  = cgraph->nodes[node_idx + 2];
81        const ggml_tensor * gate = glu->src[0];
82        const ggml_tensor * up   = glu->src[1];
83
84        // don't assume which of the two mat-muls is the gate; infer it from the GLU's operands
85        const bool ok = (gate == cgraph->nodes[node_idx] && up == cgraph->nodes[node_idx + 1]) ||
86                        (gate == cgraph->nodes[node_idx + 1] && up == cgraph->nodes[node_idx]);
87        if (!ok) {
88            return false;
89        }
90
91        return ggml_sycl_should_fuse_mul_mat_glu(gate, up, glu);
92    }
93
94    if (!ggml_can_fuse(cgraph, node_idx, ops)) {
95        return false;
96    }
97
98    if ((ops.size() == 2 || ops.size() == 3) && ops.begin()[0] == GGML_OP_RMS_NORM && ops.begin()[1] == GGML_OP_MUL) {
99        if (ops.size() == 3 && ops.begin()[2] != GGML_OP_ADD) {
100            return false;
101        }
102
103        const ggml_tensor * rms_norm = cgraph->nodes[node_idx];
104        const ggml_tensor * mul      = cgraph->nodes[node_idx + 1];
105        const ggml_tensor * add      = ops.size() == 3 ? cgraph->nodes[node_idx + 2] : nullptr;
106
107        GGML_ASSERT(rms_norm->src[0]->type == GGML_TYPE_F32);
108        GGML_ASSERT(rms_norm->type == GGML_TYPE_F32);
109
110        if (mul->src[0]->type != GGML_TYPE_F32 ||
111            mul->src[1]->type != GGML_TYPE_F32 ||
112            mul->type != GGML_TYPE_F32) {
113            return false;
114        }
115
116        // if rms norm is the B operand, then we don't handle broadcast
117        if (rms_norm == mul->src[1] && !ggml_are_same_shape(mul->src[0], rms_norm)) {
118            return false;
119        }
120
121        const ggml_tensor * mul_w = (mul->src[0] == rms_norm) ? mul->src[1] : mul->src[0];
122        // the fused kernel indexes the weight as mul[col], so it must span ncols contiguously
123        if (mul_w->ne[0] != rms_norm->ne[0] || mul_w->nb[0] != ggml_type_size(mul_w->type)) {
124            return false;
125        }
126
127        if (!ggml_is_contiguous_rows(mul->src[0]) || !ggml_is_contiguous_rows(mul->src[1])) {
128            return false;
129        }
130
131        if (add != nullptr) {
132            if (add->src[0]->type != GGML_TYPE_F32 ||
133                add->src[1]->type != GGML_TYPE_F32 ||
134                add->type != GGML_TYPE_F32) {
135                return false;
136            }
137
138            // the fused kernel indexes the residual as add[col] and does not broadcast it
139            const ggml_tensor * add_w = (add->src[0] == mul) ? add->src[1] : add->src[0];
140            if (!ggml_are_same_shape(add_w, add)) {
141                return false;
142            }
143
144            if (!ggml_is_contiguous(add->src[0]) || !ggml_is_contiguous_rows(add->src[1])) {
145                return false;
146            }
147        }
148
149        return true;
150    }
151
152    if (ops.size() == 2 && ops.begin()[0] == GGML_OP_ADD && ops.begin()[1] == GGML_OP_ADD) {
153        const ggml_tensor * add0 = cgraph->nodes[node_idx];
154        const ggml_tensor * add1 = cgraph->nodes[node_idx + 1];
155        // ggml_can_fuse already guarantees add1 consumes add0 and that add0 has a single use.
156        // Keep the CUDA association: the running sum is src0 of the next ADD so the fused
157        // float fold matches two sequential add() launches.
158        if (add1->src[0] != add0) {
159            return false;
160        }
161
162        const ggml_tensor * c = add1->src[1];
163        if (!ggml_sycl_add_kernel_supports(add0->src[0]->type, add0->src[1]->type, add0->type) ||
164            !ggml_sycl_add_kernel_supports(add0->type, c->type, add1->type)) {
165            return false;
166        }
167
168        return true;
169    }
170
171    if (ops.size() == 2 && ops.begin()[0] == GGML_OP_UNARY && ops.begin()[1] == GGML_OP_MUL &&
172        unary_ops.size() == 1) {
173        const ggml_tensor * unary = cgraph->nodes[node_idx];
174        const ggml_tensor * mul   = cgraph->nodes[node_idx + 1];
175
176        const ggml_unary_op unary_op = ggml_get_unary_op(unary);
177        if (unary_op != unary_ops.begin()[0]) {
178            return false;
179        }
180
181        // the ops ggml_sycl_op_unary_mul_fused() has a kernel for
182        if (unary_op != GGML_UNARY_OP_SILU && unary_op != GGML_UNARY_OP_SIGMOID &&
183            unary_op != GGML_UNARY_OP_SOFTPLUS) {
184            return false;
185        }
186
187        if (unary->type != GGML_TYPE_F32 && unary->type != GGML_TYPE_F16) {
188            return false;
189        }
190
191        const ggml_tensor * other = (mul->src[0] == unary) ? mul->src[1] : mul->src[0];
192        if (other->type != unary->type) {
193            return false;
194        }
195
196        // one row stride per source comes from nb[1], so rows must be contiguous and equally
197        // shaped; the destination is written flat, so it must be fully contiguous
198        if (!ggml_is_contiguous_1(unary->src[0]) || !ggml_is_contiguous_1(other) ||
199            !ggml_are_same_shape(other, unary) || !ggml_is_contiguous(mul)) {
200            return false;
201        }
202
203        // the 32-bit fastdiv is inexact past 2^31; decline, the unfused path handles it
204        if (ggml_nelements(mul) >= ((int64_t) 1 << 31)) {
205            return false;
206        }
207
208        return true;
209    }
210
211    return false;
212}