ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
kv_quant.cu
Go to the documentation of this file.
1// Quantised KV cache kernels (kv_quant.h; EMBER_KV=f16|q8|q4). The default f32 cache never reaches
2// this file: graph_ir.cpp calls it only when EMBER_KV names another format.
3//
4// The decode kernel is decode_attn_tiled.cuh's tiled split-K kernel with its loads replaced: the same
5// chunking (32-key chunks below 2,048 keys, at most 64 chunks per head), lane layout and fixed-order
6// reductions, and its merge (tiled_merge_block) unchanged, so a format's result is the same run to run
7// and depends only on L. Each float4 slice a lane loads is dequantised from the codes as it is loaded.
8#include "kv_quant.h"
9
10#include <cuda_fp16.h>
11#include <cuda_runtime.h>
12
13#include <stdexcept>
14#include <string>
15
16#include "decode_attn_tiled.cuh"
17
18namespace ember_kvq {
19namespace {
20
21void check(cudaError_t e, const char* what) {
22 if (e != cudaSuccess) throw std::runtime_error(std::string("kv_quant ") + what + ": " + cudaGetErrorString(e));
23}
24
25// ---------------------------------------------------------------------------
26// Writes. q8/q4: one warp per block of 32 elements (the warp's lanes its elements); f16: one thread per element.
27
28template <int F>
30 uint8_t* __restrict__ Vs, const float* __restrict__ k, const float* __restrict__ v,
31 int T, int row, int start_pos, const int* __restrict__ dpos) {
32 const int nb = row / kBlock;
33 const long long per = static_cast<long long>(T) * nb;
34 const long long gw = (static_cast<long long>(blockIdx.x) * blockDim.x + threadIdx.x) >> 5;
35 const int lane = threadIdx.x & 31;
36 if (gw >= 2 * per) return; // the whole warp: gw is the warp's
37 const bool isv = gw >= per;
38 const long long w = isv ? gw - per : gw;
39 const int t = static_cast<int>(w / nb), b = static_cast<int>(w - static_cast<long long>(t) * nb);
40 const size_t pos = static_cast<size_t>((dpos ? *dpos : start_pos) + t);
41 const float x = (isv ? v : k)[static_cast<size_t>(t) * row + static_cast<size_t>(b) * kBlock + lane];
42 uint8_t* D = isv ? Vd : Kd;
43 uint8_t* S = isv ? Vs : Ks;
44 if (F == kQ8) {
45 float a = fabsf(x);
46 for (int o = 16; o > 0; o >>= 1) a = fmaxf(a, __shfl_xor_sync(0xffffffffu, a, o));
47 const __half dh = __float2half_rn(__fdiv_rn(a, 127.0f));
48 const float d = __half2float(dh);
49 float q = d != 0.0f ? rintf(__fdiv_rn(x, d)) : 0.0f;
50 q = fminf(127.0f, fmaxf(-127.0f, q));
51 reinterpret_cast<int8_t*>(D)[pos * row + static_cast<size_t>(b) * kBlock + lane] = static_cast<int8_t>(q);
52 if (lane == 0) reinterpret_cast<__half*>(S)[pos * nb + b] = dh;
53 } else { // kQ4
54 float mn = x, mx = x;
55 for (int o = 16; o > 0; o >>= 1) {
56 mn = fminf(mn, __shfl_xor_sync(0xffffffffu, mn, o));
57 mx = fmaxf(mx, __shfl_xor_sync(0xffffffffu, mx, o));
58 }
59 const __half dh = __float2half_rn(__fdiv_rn(__fsub_rn(mx, mn), 15.0f));
60 const __half mh = __float2half_rn(mn);
61 const float d = __half2float(dh), m = __half2float(mh);
62 float q = d != 0.0f ? rintf(__fdiv_rn(__fsub_rn(x, m), d)) : 0.0f;
63 q = fminf(15.0f, fmaxf(0.0f, q));
64 const int qi = static_cast<int>(q);
65 const int hi = __shfl_down_sync(0xffffffffu, qi, 1);
66 if ((lane & 1) == 0)
67 D[(pos * row + static_cast<size_t>(b) * kBlock + lane) / 2] = static_cast<uint8_t>(qi | (hi << 4));
68 if (lane == 0) reinterpret_cast<__half2*>(S)[pos * nb + b] = __halves2half2(dh, mh);
69 }
70}
71
73 const float* __restrict__ v, int T, int row, int start_pos,
74 const int* __restrict__ dpos) {
75 const size_t n = static_cast<size_t>(T) * row;
76 const size_t i = static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
77 if (i >= 2 * n) return;
78 const bool isv = i >= n;
79 const size_t e = isv ? i - n : i;
80 const size_t at = static_cast<size_t>(dpos ? *dpos : start_pos) * row + e;
81 (isv ? Vd : Kd)[at] = __float2half_rn((isv ? v : k)[e]);
82}
83
84// ---------------------------------------------------------------------------
85// Loads: elements e .. e + 3 of cache row pos (e % 4 == 0, inside one head), dequantised.
86
87template <int F>
89 int row, int e) {
90 const size_t at = pos * row + e;
91 if (F == kF16) {
92 const uint2 u = __ldg(reinterpret_cast<const uint2*>(D + at * 2));
93 const float2 a = __half22float2(*reinterpret_cast<const __half2*>(&u.x));
94 const float2 b = __half22float2(*reinterpret_cast<const __half2*>(&u.y));
95 return make_float4(a.x, a.y, b.x, b.y);
96 } else if (F == kQ8) {
97 const char4 c = __ldg(reinterpret_cast<const char4*>(D + at));
98 const float d = __half2float(
99 __ushort_as_half(__ldg(reinterpret_cast<const unsigned short*>(S) + pos * (row / kBlock) + e / kBlock)));
100 return make_float4(__fmul_rn(static_cast<float>(c.x), d), __fmul_rn(static_cast<float>(c.y), d),
101 __fmul_rn(static_cast<float>(c.z), d), __fmul_rn(static_cast<float>(c.w), d));
102 } else { // kQ4
103 const unsigned u = __ldg(reinterpret_cast<const unsigned short*>(D + at / 2));
104 const unsigned dm = __ldg(reinterpret_cast<const unsigned*>(S) + pos * (row / kBlock) + e / kBlock);
105 const float d = __half2float(__ushort_as_half(static_cast<unsigned short>(dm & 0xffffu)));
106 const float m = __half2float(__ushort_as_half(static_cast<unsigned short>(dm >> 16)));
107 return make_float4(__fmaf_rn(static_cast<float>(u & 15u), d, m), __fmaf_rn(static_cast<float>((u >> 4) & 15u), d, m),
108 __fmaf_rn(static_cast<float>((u >> 8) & 15u), d, m),
109 __fmaf_rn(static_cast<float>((u >> 12) & 15u), d, m));
110 }
111}
112
113// ---------------------------------------------------------------------------
114// Decode: ember_tiled::tiled_split_block with load4 in place of the float4 cache loads.
115
118
119template <int F>
122 const uint8_t* __restrict__ Vs, int seq_len, int head_dim, float scale,
123 int num_kv_heads, int num_heads, const int* __restrict__ dpos,
124 float* __restrict__ partial) {
125 const int L = dpos ? *dpos + 1 : seq_len;
126 const int h = blockIdx.x, c = blockIdx.y, nc = gridDim.y;
127 const int kvh = h / (num_heads / num_kv_heads);
128 const int row = num_kv_heads * head_dim, hoff = kvh * head_dim;
129 float* __restrict__ part = partial + (static_cast<size_t>(h) * nc + c) * (head_dim + 2);
131 const int k0 = c * chunk;
132 if (k0 >= L) return;
133 const int k1 = min(L, k0 + chunk);
134 const int n = k1 - k0;
135 const int d4n = head_dim >> 2;
136 extern __shared__ float4 kvq_sm4[];
137 float4* q4 = kvq_sm4;
138 float* s = reinterpret_cast<float*>(q4 + d4n);
139 __shared__ float red[kThreads / 32];
141 const int tid = threadIdx.x, lane = tid & 31, warp = tid >> 5;
142 const float4* Q4 = reinterpret_cast<const float4*>(Q + static_cast<size_t>(h) * head_dim);
143 for (int j = tid; j < d4n; j += kThreads) q4[j] = Q4[j];
145 const int quad = tid >> 2, ql = tid & 3;
146 for (int kb = k0; kb < k1; kb += kThreads / 4) {
147 const int k = kb + quad;
148 float dot = 0.0f;
149 if (k < k1) {
150#pragma unroll 4
151 for (int j = ql; j < d4n; j += 4) {
152 const float4 a = q4[j];
153 const float4 b = load4<F>(Kd, Ks, static_cast<size_t>(k), row, hoff + 4 * j);
154 dot = fmaf(a.x, b.x, dot);
155 dot = fmaf(a.y, b.y, dot);
156 dot = fmaf(a.z, b.z, dot);
157 dot = fmaf(a.w, b.w, dot);
158 }
159 }
160 dot += __shfl_xor_sync(0xffffffff, dot, 1);
161 dot += __shfl_xor_sync(0xffffffff, dot, 2);
162 if (k < k1 && ql == 0) s[k - k0] = dot * scale;
163 }
165 float mx = -INFINITY;
166 for (int i = tid; i < n; i += kThreads) mx = fmaxf(mx, s[i]);
167 for (int o = 16; o > 0; o >>= 1) mx = fmaxf(mx, __shfl_xor_sync(0xffffffff, mx, o));
168 if (lane == 0) red[warp] = mx;
170 mx = red[0];
171 for (int w = 1; w < kThreads / 32; ++w) mx = fmaxf(mx, red[w]);
173 float sum = 0.0f;
174 for (int i = tid; i < n; i += kThreads) {
175 const float e = expf(s[i] - mx);
176 s[i] = e;
177 sum += e;
178 }
179 for (int o = 16; o > 0; o >>= 1) sum += __shfl_xor_sync(0xffffffff, sum, o);
180 if (lane == 0) red[warp] = sum;
182 if (tid == 0) {
183 float l = 0.0f;
184 for (int w = 0; w < kThreads / 32; ++w) l += red[w];
185 part[0] = mx;
186 part[1] = l;
187 }
188 const int G = kThreads / d4n;
189 const int g = tid / d4n, j = tid - g * d4n;
190 if (g < G) {
191 float4 acc = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
192#pragma unroll 4
193 for (int i = g; i < n; i += G) {
194 const float w = s[i];
195 const float4 v = load4<F>(Vd, Vs, static_cast<size_t>(k0 + i), row, hoff + 4 * j);
196 acc.x = fmaf(w, v.x, acc.x);
197 acc.y = fmaf(w, v.y, acc.y);
198 acc.z = fmaf(w, v.z, acc.z);
199 acc.w = fmaf(w, v.w, acc.w);
200 }
201 pacc4[g * d4n + j] = acc;
202 }
204 const float* pacc = reinterpret_cast<const float*>(pacc4);
205 for (int d = tid; d < head_dim; d += kThreads) {
206 float a = 0.0f;
207 for (int gg = 0; gg < G; ++gg) a += pacc[gg * head_dim + d];
208 part[2 + d] = a;
209 }
210}
211
212__global__ void decode_merge_kernel(const float* __restrict__ partial, float* __restrict__ out, int seq_len,
213 int head_dim, int nc, const int* __restrict__ dpos) {
214 const int L = dpos ? *dpos + 1 : seq_len;
215 const int h = blockIdx.x;
216 ember_tiled::tiled_merge_block(partial + static_cast<size_t>(h) * nc * (head_dim + 2),
217 out + static_cast<size_t>(h) * head_dim, L, head_dim, kMin, kMax);
218}
219
220template <int F>
222 const uint8_t* __restrict__ Vd, const uint8_t* __restrict__ Vs, float* __restrict__ K,
223 float* __restrict__ V, int n, int row) {
224 const int r4 = row / 4;
225 const size_t per = static_cast<size_t>(n) * r4;
226 const size_t i = static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
227 if (i >= 2 * per) return;
228 const bool isv = i >= per;
229 const size_t w = isv ? i - per : i;
230 const size_t pos = w / r4;
231 const int e = static_cast<int>(w - pos * r4) * 4;
232 reinterpret_cast<float4*>(isv ? V : K)[w] = isv ? load4<F>(Vd, Vs, pos, row, e) : load4<F>(Kd, Ks, pos, row, e);
233}
234
235template <int F>
236void decode_launch(const float* q, const void* Kd, const void* Ks, const void* Vd, const void* Vs, float* out, int hq,
237 int hkv, int hd, float scale, int max_len, int seq_len, float* part, cudaStream_t st,
238 const int* dpos) {
239 const int nc = ember_tiled::tiled_grid_chunks(max_len, kMin, kMax);
240 const size_t shm = ember_tiled::tiled_shm_bytes(hd, max_len, kMin, kMax);
242 q, static_cast<const uint8_t*>(Kd), static_cast<const uint8_t*>(Ks), static_cast<const uint8_t*>(Vd),
243 static_cast<const uint8_t*>(Vs), seq_len, hd, scale, hkv, hq, dpos, part);
245 check(cudaGetLastError(), "decode");
246}
247
248template <int F>
249void dequant_launch(const void* Kd, const void* Ks, const void* Vd, const void* Vs, float* K, float* V, int n, int row,
251 const size_t total = 2 * static_cast<size_t>(n) * (row / 4);
252 dequant_kernel<F><<<static_cast<unsigned>((total + 255) / 256), 256, 0, st>>>(
253 static_cast<const uint8_t*>(Kd), static_cast<const uint8_t*>(Ks), static_cast<const uint8_t*>(Vd),
254 static_cast<const uint8_t*>(Vs), K, V, n, row);
255 check(cudaGetLastError(), "dequant");
256}
257
258} // namespace
259
260void write_cuda(int f, void* Kd, void* Ks, void* Vd, void* Vs, const float* k, const float* v, int T, int row,
261 int start_pos, cudaStream_t st, const int* dpos) {
262 if (T <= 0) return;
263 if (f == kF16) {
264 const size_t total = 2 * static_cast<size_t>(T) * row;
265 write_f16_kernel<<<static_cast<unsigned>((total + 255) / 256), 256, 0, st>>>(
266 static_cast<__half*>(Kd), static_cast<__half*>(Vd), k, v, T, row, start_pos, dpos);
267 } else {
268 const size_t threads = 2 * static_cast<size_t>(T) * (row / kBlock) * 32;
269 const unsigned grid = static_cast<unsigned>((threads + 127) / 128);
270 auto* kd = static_cast<uint8_t*>(Kd);
271 auto* ks = static_cast<uint8_t*>(Ks);
272 auto* vd = static_cast<uint8_t*>(Vd);
273 auto* vs = static_cast<uint8_t*>(Vs);
274 if (f == kQ8)
275 write_block_kernel<kQ8><<<grid, 128, 0, st>>>(kd, ks, vd, vs, k, v, T, row, start_pos, dpos);
276 else if (f == kQ4)
277 write_block_kernel<kQ4><<<grid, 128, 0, st>>>(kd, ks, vd, vs, k, v, T, row, start_pos, dpos);
278 else
279 throw std::runtime_error("kv_quant write: format " + std::to_string(f));
280 }
281 check(cudaGetLastError(), "write");
282}
283
284size_t partial_floats(int hq, int hd, int max_len) {
285 return static_cast<size_t>(hq) * ember_tiled::tiled_grid_chunks(max_len, kMin, kMax) * (hd + 2);
286}
287
288void decode_cuda(int f, const float* q, const void* Kd, const void* Ks, const void* Vd, const void* Vs, float* out,
289 int hq, int hkv, int hd, float scale, int max_len, int seq_len, float* part, cudaStream_t st,
290 const int* dpos) {
291 if (!ember_tiled::tiled_shape_ok(hq, hd, max_len, 1 << 20, kMin, kMax) || hkv <= 0 || hq % hkv != 0)
292 throw std::runtime_error("kv_quant decode: shape not supported (heads " + std::to_string(hq) + "/" +
293 std::to_string(hkv) + ", head_dim " + std::to_string(hd) + ", max_len " +
294 std::to_string(max_len) + ")");
295 switch (f) {
296 case kF16: decode_launch<kF16>(q, Kd, Ks, Vd, Vs, out, hq, hkv, hd, scale, max_len, seq_len, part, st, dpos); break;
297 case kQ8: decode_launch<kQ8>(q, Kd, Ks, Vd, Vs, out, hq, hkv, hd, scale, max_len, seq_len, part, st, dpos); break;
298 case kQ4: decode_launch<kQ4>(q, Kd, Ks, Vd, Vs, out, hq, hkv, hd, scale, max_len, seq_len, part, st, dpos); break;
299 default: throw std::runtime_error("kv_quant decode: format " + std::to_string(f));
300 }
301}
302
303void dequant_rows_cuda(int f, const void* Kd, const void* Ks, const void* Vd, const void* Vs, float* K, float* V, int n,
304 int row, cudaStream_t st) {
305 if (n <= 0) return;
306 switch (f) {
307 case kF16: dequant_launch<kF16>(Kd, Ks, Vd, Vs, K, V, n, row, st); break;
308 case kQ8: dequant_launch<kQ8>(Kd, Ks, Vd, Vs, K, V, n, row, st); break;
309 case kQ4: dequant_launch<kQ4>(Kd, Ks, Vd, Vs, K, V, n, row, st); break;
310 default: throw std::runtime_error("kv_quant dequant: format " + std::to_string(f));
311 }
312}
313
314} // namespace ember_kvq
int n
std::vector< float > v
float c
std::vector< E > a
node out
node scale
size_t j
void write_cuda(int f, void *Kd, void *Ks, void *Vd, void *Vs, const float *k, const float *v, int T, int row, int start_pos, cudaStream_t st, const int *dpos)
Definition kv_quant.cu:260
size_t partial_floats(int hq, int hd, int max_len)
Definition kv_quant.cu:284
void decode_cuda(int f, const float *q, const void *Kd, const void *Ks, const void *Vd, const void *Vs, float *out, int hq, int hkv, int hd, float scale, int max_len, int seq_len, float *part, cudaStream_t st, const int *dpos)
Definition kv_quant.cu:288
void dequant_rows_cuda(int f, const void *Kd, const void *Ks, const void *Vd, const void *Vs, float *K, float *V, int n, int row, cudaStream_t st)
Definition kv_quant.cu:303
constexpr int kBlock
Definition kv_quant.h:34
__host__ bool tiled_shape_ok(int heads, int head_dim, int max_len, int heads_max, int min_chunk, int max_chunks)
__host__ size_t tiled_shm_bytes(int head_dim, int max_len, int min_chunk, int max_chunks)
constexpr int kTiledThreads
constexpr int kTiledMaxChunks
__host__ __device__ int tiled_chunk(int L, int min_chunk, int max_chunks)
constexpr int kTiledMinChunk
__device__ __forceinline__ void tiled_merge_block(const float *__restrict__ base, float *__restrict__ out_h, int L, int head_dim, int min_chunk, int max_chunks)
__host__ __device__ int tiled_grid_chunks(int max_len, int min_chunk, int max_chunks)