char-fork/llama.cpp

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

Ruben Ortlamvulkan: top_k radix select for k >= 1024 for Qwen 3.8 Flash Next (#28032)daef7b687

char/aliceai-t5-moe
4.5 KiB144 linesraw
1#version 450
2
3#extension GL_EXT_control_flow_attributes : enable
4#extension GL_EXT_shader_16bit_storage : require
5
6#include "types.glsl"
7
8layout(constant_id = 0) const int BLOCK_SIZE = 1024;
9layout(constant_id = 1) const int QSA = 0; // 1: fuse the qwen4 QSA indexer gather + f16 mask
10
11layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
12
13layout (binding = 0) readonly buffer A  {float     data_a[];};   // input values, or QSA block scores [n_tps, n_blocks, n_stream]
14layout (binding = 1) writeonly buffer D {int       data_d[];};   // [k, ...]
15layout (binding = 2) readonly buffer CB {int       cell_blk[];}; // QSA: cell->block map [n_kv, n_stream]
16layout (binding = 3) readonly buffer M  {float16_t mask[];};     // QSA: raw f16 kq_mask [n_kv, n_tps, n_stream]
17layout (binding = 4) buffer S           {float     scratch[];};  // QSA: [nrows, n_kv] gathered inputs
18
19layout (push_constant) uniform parameter {
20    uint ncols;
21    uint k;
22    uint nrows;
23    uint n_tps;    // QSA only
24    uint n_blocks; // QSA only
25    uint n_stream; // QSA only
26} p;
27
28#define RADIX_BITS 8
29#define RADIX_SIZE (1 << RADIX_BITS)
30
31shared uint histo[RADIX_SIZE];
32shared uint sh_bucket;
33shared uint sh_above;
34shared uint out_count;
35
36// order-preserving float -> uint mapping
37uint f2ui(float x) {
38    uint y = floatBitsToUint(x);
39    if ((y & 0x80000000u) != 0u) {
40        y ^= 0xFFFFFFFFu;
41    } else {
42        y |= 0x80000000u;
43    }
44    return y;
45}
46
47// QSA element i of row (t,s): score[cell_blk[i,s], t, s] + mask[i,t,s]
48float gather(uint row, uint i) {
49    const uint t = row % p.n_tps;
50    const uint s = row / p.n_tps;
51    const uint block = uint(cell_blk[s * p.ncols + i]);
52    const float a = data_a[(s * p.n_blocks + block) * p.n_tps + t];
53    const float m = float(mask[(s * p.n_tps + t) * p.ncols + i]);
54    return a + m;
55}
56
57float load(uint row, uint i, bool first) {
58    if (QSA == 0) {
59        return data_a[row * p.ncols + i];
60    }
61    // materialize the scattered gather on the first pass and reuse it after; each
62    // invocation only touches its own scratch entries, so no barrier is needed
63    const uint off = row * p.ncols + i;
64    if (first) {
65        const float v = gather(row, i);
66        scratch[off] = v;
67        return v;
68    }
69    return scratch[off];
70}
71
72// one workgroup per row: radix-select the K-th largest, then compact it plus enough ties
73void topk(const uint row) {
74    const uint tid = gl_LocalInvocationID.x;
75    const uint ncols = p.ncols;
76    const uint row_out = row * p.k;
77
78    uint prefix  = 0;     // fixed high bits of the threshold key
79    uint desired = p.k;   // count still needed from the candidate range
80
81    [[unroll]] for (int shift = 32 - RADIX_BITS; shift >= 0; shift -= RADIX_BITS) {
82        for (uint i = tid; i < RADIX_SIZE; i += BLOCK_SIZE) {
83            histo[i] = 0;
84        }
85        barrier();
86
87        const bool first     = (shift == 32 - RADIX_BITS);
88        const uint hi_mask   = (shift + RADIX_BITS >= 32) ? 0u : (0xFFFFFFFFu << uint(shift + RADIX_BITS));
89        const uint prefix_hi = prefix & hi_mask;
90        for (uint i = tid; i < ncols; i += BLOCK_SIZE) {
91            const uint key = f2ui(load(row, i, first));
92            if ((key & hi_mask) == prefix_hi) {
93                atomicAdd(histo[(key >> uint(shift)) & (RADIX_SIZE - 1)], 1u);
94            }
95        }
96        barrier();
97
98        // top-down scan for the bucket holding the K-th value
99        if (tid == 0) {
100            uint acc = 0;
101            uint b   = 0;
102            for (int bb = RADIX_SIZE - 1; bb >= 0; --bb) {
103                const uint c = histo[bb];
104                if (acc + c >= desired) { b = uint(bb); break; }
105                acc += c;
106            }
107            sh_bucket = b;
108            sh_above  = acc;
109        }
110        barrier();
111
112        prefix  |= sh_bucket << uint(shift);
113        desired -= sh_above;
114        barrier();
115    }
116
117    if (tid == 0) {
118        out_count = 0;
119    }
120    barrier();
121
122    // emit everything above the threshold, then fill the rest from ties
123    const uint threshold = prefix;
124    for (uint i = tid; i < ncols; i += BLOCK_SIZE) {
125        if (f2ui(load(row, i, false)) > threshold) {
126            data_d[row_out + atomicAdd(out_count, 1u)] = int(i);
127        }
128    }
129    barrier();
130    for (uint i = tid; i < ncols; i += BLOCK_SIZE) {
131        if (f2ui(load(row, i, false)) == threshold) {
132            const uint pos = atomicAdd(out_count, 1u);
133            if (pos < p.k) {
134                data_d[row_out + pos] = int(i);
135            }
136        }
137    }
138}
139
140void main() {
141    for (uint row = gl_WorkGroupID.y; row < p.nrows; row += gl_NumWorkGroups.y) {
142        topk(row);
143    }
144}