← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ckernel_rwkv7_decode.h
Go to the documentation of this file.
1#ifndef CKERNEL_RWKV7_DECODE_H
2#define CKERNEL_RWKV7_DECODE_H
3
4#include <stddef.h>
5
6#ifdef __cplusplus
7extern "C" {
8#endif
9
10/*
11 * RWKV-7 single-token (T=1, B=1) FP32 decode reference.
12 *
13 * Token shift: xm = x + mu*(shift-x) with shift_out = x.
14 *
15 * WKV generalised delta rule, multiplicative decay, per head with state
16 * [N,N] in (value,key) axes:
17 * decay[j] = exp(w_log[j])
18 * b[j] = kk[j]*a[j]
19 * S_new[i,j] = S[i,j]*decay[j] - (S[i,:].kk)*b[j] + v[i]*k[j]
20 * y[i] = sum_l S_new[i,l]*r[l]
21 * GroupNorm eps is norm_eps*head_dim. Bonus is per head:
22 * s_h = sum(r_h*k_h*rk_h), y_h += s_h*v_h, with rk [H,N].
23 * Output is y_out = (groupnorm(y)+bonus)*g elementwise; the final CxC
24 * output projection stays with the caller's GEMV.
25 * v-mixing towards v_first and the LoRA chains are separate units below;
26 * the WKV takes final w_log/k/v/kk/a/g (kk L2-normalised, k scaled by
27 * 1+(k_a-1)*a per the RWKV-7-rc4a diagram's Lerp(k_a,1) box; this
28 * intentionally differs from modeling_rwkv7.py:501's 1+(a-1)*k_a).
29 *
30 * Layouts (all FP32, row-major, flat C = H*N):
31 * - vectors: [C]; rk: [H,N] (row per head)
32 * - state: [H,N,N], head h at offset h*N*N, row i at +i*N+j
33 * - LoRA down factors [C,R], up factors [R,C]; biases [C] (g has none)
34 * - channelmix Wk [K,C] (expand), Wv [C,K] (project)
35 *
36 * Time-mix tail order (single entry point below, mirrors
37 * modeling_rwkv7.py Rwkv7Attention.forward without its GEMVs/LayerNorms):
38 * lora_gates -> vgate_vmix -> kk_raw = k_raw*k_k -> norm_kk ->
39 * scale_k -> wkv_decode. The kk factor multiply is also exposed alone;
40 * the final CxC output projection stays with the caller's GEMV.
41 *
42 * Limits (violations are hard errors): head_dim <= 512, num_heads <= 256,
43 * LoRA ranks <= 512, hidden_dim <= 8192, norm_eps > 0.
44 *
45 * All functions return 0 on success, -1 on bad args, except
46 * ck_rwkv7_channelmix_scratch_elems, which returns the required scratch
47 * element count (0 on bad args). No malloc/free.
48 * Unless an alias is explicitly documented, outputs must be disjoint from
49 * inputs and each other; callers own storage sizes. State-step/state-decode
50 * permit state_out == state_in, and norm_kk permits kk_out == kk_in.
51 */
52
53int ck_rwkv7_token_shift_lerp(const float *x,
54 const float *shift_in,
55 const float *mu,
56 float *xm,
57 float *shift_out,
58 int dim);
59/* Each output may exactly alias an input. The two outputs must be disjoint;
60 * shifted/partial output-input overlaps are rejected before any write. */
61
62/* Six-way time-mix shift bundle (one entry point for the diagram's bottom
63 * Lerp row): xr/xw/xk/xv/xa/xg[i] = x[i] + mu_*[i]*(shift_in[i]-x[i]) with
64 * shift_out = x. The fused per-index loop reads x[i]/shift_in[i] once, so a
65 * single pass replaces six ck_rwkv7_token_shift_lerp calls and the shared
66 * shift is written exactly once. The seven outputs must be pairwise
67 * disjoint; shift_out may exactly alias shift_in (in-place carry). All other
68 * output-input overlaps (including exact output == x aliasing, which would
69 * let sibling mixes collide) are rejected before any write. Read-only inputs
70 * may overlap each other. */
71int ck_rwkv7_time_mix_shifts(const float *x,
72 const float *shift_in,
73 const float *mu_r,
74 const float *mu_w,
75 const float *mu_k,
76 const float *mu_v,
77 const float *mu_a,
78 const float *mu_g,
79 float *xr,
80 float *xw,
81 float *xk,
82 float *xv,
83 float *xa,
84 float *xg,
85 float *shift_out,
86 int dim);
87
88/* kk_raw -> per-head L2-normalised kk (divisor max(norm,1e-12)). */
89int ck_rwkv7_norm_kk(const float *kk_in,
90 float *kk_out,
91 int num_heads,
92 int head_dim);
93
94/* k_out = k_in * (1 + (k_a-1) * a), elementwise over [C] (diagram Lerp(k_a,1)
95 * box with mix weight a. */
96int ck_rwkv7_scale_k(const float *k_in,
97 const float *a,
98 const float *k_a,
99 float *k_out,
100 int dim);
101
102/* kk_raw = k .* k_k, elementwise over [C] (reference: kk = k * k_k before
103 * per-head L2 normalisation). kk_raw may exactly alias k or k_k. */
104int ck_rwkv7_apply_kk_factor(const float *k,
105 const float *k_k,
106 float *kk_raw,
107 int dim);
108
109/* Unfused FP32 LoRA chains (decode, B=1,T=1).
110 * Computes w_log = -e^-0.5*sigmoid(tanh(xw@w1)@w2+w0),
111 * a = sigmoid((xa@a1)@a2+a0), g = sigmoid(xg@g1)@g2.
112 * Fusion is a later perf change behind its own parity gate. */
113int ck_rwkv7_lora_gates(const float *xw,
114 const float *xa,
115 const float *xg,
116 const float *w1,
117 const float *w2,
118 const float *w0,
119 const float *a1,
120 const float *a2,
121 const float *a0,
122 const float *g1,
123 const float *g2,
124 int dim,
125 int rank_w,
126 int rank_a,
127 int rank_g,
128 float *w_log,
129 float *a_out,
130 float *g_out);
131
132/* Value-residual gate and mix. Layer 0: pass xv/v1/v2/v0 as NULL to produce
133 * v_first (v_out = v_first_out = v_in copy). Layer >0: v_gate =
134 * sigmoid((xv@v1)@v2+v0), v_out = v_in+(v_first-v_in)*v_gate,
135 * v_first_out = v_first copy. v_gate may be NULL if unneeded. */
136int ck_rwkv7_vgate_vmix(const float *v_in,
137 const float *v_first,
138 const float *xv,
139 const float *v1,
140 const float *v2,
141 const float *v0,
142 int dim,
143 int rank_v,
144 float *v_out,
145 float *v_first_out,
146 float *v_gate);
147
148/* Single-head state transition (the unit a SIMD/chunked variant replaces):
149 * S_new[i,j] = S[i,j]*exp(w_log[j]) - (S[i,:].kk)*(kk[j]*a[j]) + v[i]*k[j],
150 * state axes (value,key), all vectors [N], states [N,N] row-major. */
151int ck_rwkv7_state_step(const float *w_log,
152 const float *k,
153 const float *v,
154 const float *kk,
155 const float *a,
156 const float *state_in,
157 float *state_out,
158 int head_dim);
159
160/* Single-head readout y = S_new.r, state [N,N] row-major, r/y [N]. */
161int ck_rwkv7_state_readout(const float *state,
162 const float *r,
163 float *y,
164 int head_dim);
165
166/* Full single-token time-mix step (composition): per head, ck_rwkv7_state_step
167 * then ck_rwkv7_state_readout; then GroupNorm over C (eps = norm_eps*head_dim),
168 * per-head bonus s_h = sum(r_h*k_h*rk_h) added as s_h*v_h, and y *= g.
169 * The final CxC output projection stays with the caller's GEMV. */
170int ck_rwkv7_wkv_decode(const float *r,
171 const float *w_log,
172 const float *k,
173 const float *v,
174 const float *kk,
175 const float *a,
176 const float *g,
177 const float *rk,
178 const float *ln_w,
179 const float *ln_b,
180 float norm_eps,
181 const float *state_in,
182 float *state_out,
183 float *y_out,
184 int num_heads,
185 int head_dim);
186
187/* Scratch elements needed by ck_rwkv7_channelmix_decode (xk + hidden);
188 * 0 on bad args. */
189size_t ck_rwkv7_channelmix_scratch_elems(int dim, int hidden_dim);
190
191/* Scratch elements needed by ck_rwkv7_time_mix_tail (w_log + a + g + v +
192 * kk_raw + kk + k_scaled = 7*dim floats); 0 on bad args. */
193size_t ck_rwkv7_time_mix_scratch_elems(int dim);
194
195/* Single-token time-mix tail (composition of the units above, no GEMV/LayerNorm).
196 * Inputs are post-shift/post-projection vectors: r/k_raw/v_in [C], LoRA inputs
197 * xw/xa/xg/xv [C] (xv == NULL selects the layer-0 produce-only v path, in
198 * which case v_first_in/v1/v2/v0 must also be NULL and rank_v is unchecked).
199 * Computes w_log/a/g via lora_gates, v via vgate_vmix, kk_raw = k_raw*k_k,
200 * kk via norm_kk, k via scale_k, then wkv_decode into state_out/y_preproj.
201 * y_preproj is (groupnorm(y)+bonus)*g; the final CxC output projection stays
202 * with the caller's GEMV. Requires dim == num_heads*head_dim.
203 * `scratch` must hold ck_rwkv7_time_mix_scratch_elems(dim) floats and be
204 * disjoint from every other argument. state_out may exactly alias state_in;
205 * v_first_out may exactly alias v_first_in. All other output-input and
206 * output-output overlaps are rejected before any write. */
207int ck_rwkv7_time_mix_tail(const float *r,
208 const float *k_raw,
209 const float *v_in,
210 const float *v_first_in,
211 const float *xw,
212 const float *xa,
213 const float *xg,
214 const float *xv,
215 const float *w1,
216 const float *w2,
217 const float *w0,
218 const float *a1,
219 const float *a2,
220 const float *a0,
221 const float *g1,
222 const float *g2,
223 const float *v1,
224 const float *v2,
225 const float *v0,
226 const float *k_k,
227 const float *k_a,
228 const float *rk,
229 const float *ln_w,
230 const float *ln_b,
231 float norm_eps,
232 const float *state_in,
233 float *state_out,
234 float *y_preproj,
235 float *v_first_out,
236 int dim,
237 int num_heads,
238 int head_dim,
239 int rank_w,
240 int rank_a,
241 int rank_g,
242 int rank_v,
243 float *scratch,
244 size_t scratch_elems);
245
246/* Squared-ReLU feedforward decode step with caller-provided scratch.
247 * xk = x + mu*(shift_in-x); hidden = relu(Wk@xk)^2, Wk [K,C] row-major;
248 * out = Wv@hidden, Wv [C,K] row-major; shift_out = x.
249 * `scratch` must hold ck_rwkv7_channelmix_scratch_elems(dim,hidden_dim)
250 * floats. Either shift_out or out may exactly alias x, but the two outputs
251 * must be disjoint. Other output-input overlap and all scratch overlap are
252 * rejected before any write. Read-only inputs may overlap each other. */
253int ck_rwkv7_channelmix_decode(const float *x,
254 const float *shift_in,
255 const float *mu,
256 const float *Wk,
257 const float *Wv,
258 float *shift_out,
259 float *out,
260 int dim,
261 int hidden_dim,
262 float *scratch,
263 size_t scratch_elems);
264
265/* GEMV/LayerNorm callbacks for ck_rwkv7_time_mix_block. GEMV computes
266 * y = W x with W [dim,dim] row-major (one row per output channel), matching
267 * the engine's NT decode path; LayerNorm computes y = norm(x;gamma,beta).
268 * Both return 0 on success, nonzero on failure (propagated as -1). The
269 * block never calls them with overlapping arguments. */
270typedef int (*ck_rwkv7_gemv_fn)(const float *W,
271 const float *x,
272 float *y,
273 void *ctx);
274typedef int (*ck_rwkv7_layer_norm_fn)(const float *x,
275 const float *gamma,
276 const float *beta,
277 float *y,
278 int dim,
279 void *ctx);
280
281/* Scratch elements needed by ck_rwkv7_time_mix_block (attn_in + 6 mixes +
282 * r/k/v + y_pre + attn_out + 7*dim tail scratch = 19*dim floats);
283 * 0 on bad args. */
285
286/* Full single-token time-mix block (composition, no weight ownership).
287 * Computes attn_in = LN(h), six shifts, r/k/v GEMVs, the time-mix tail,
288 * the W_o GEMV, and h_out = h + attn_out, updating att_shift/state/v_first.
289 * is_layer_zero selects the produce-only v path (v_first_in/v1/v2/v0 must be
290 * NULL, rank_v unchecked); otherwise the residual v path. Requires
291 * dim == num_heads*head_dim. `scratch` must hold
292 * ck_rwkv7_time_mix_block_scratch_elems(dim) floats and be disjoint from
293 * every other argument. h_out may exactly alias h, att_shift_out may exactly
294 * alias att_shift_in, state_out may exactly alias state_in, and v_first_out
295 * may exactly alias v_first_in; the four outputs must otherwise be pairwise
296 * disjoint and disjoint from all inputs. Read-only inputs may overlap each
297 * other. */
298int ck_rwkv7_time_mix_block(const float *h,
299 const float *att_shift_in,
300 const float *v_first_in,
301 const float *state_in,
302 const float *ln_g,
303 const float *ln_b,
304 const float *mu_r,
305 const float *mu_w,
306 const float *mu_k,
307 const float *mu_v,
308 const float *mu_a,
309 const float *mu_g,
310 const float *Wr,
311 const float *Wk,
312 const float *Wv,
313 const float *Wo,
314 const float *w1,
315 const float *w2,
316 const float *w0,
317 const float *a1,
318 const float *a2,
319 const float *a0,
320 const float *g1,
321 const float *g2,
322 const float *v1,
323 const float *v2,
324 const float *v0,
325 const float *k_k,
326 const float *k_a,
327 const float *rk,
328 const float *gn_w,
329 const float *gn_b,
330 float norm_eps,
331 int dim,
332 int num_heads,
333 int head_dim,
334 int rank_w,
335 int rank_a,
336 int rank_g,
337 int rank_v,
338 int is_layer_zero,
339 ck_rwkv7_gemv_fn gemv,
340 ck_rwkv7_layer_norm_fn layer_norm,
341 void *ctx,
342 float *h_out,
343 float *att_shift_out,
344 float *state_out,
345 float *v_first_out,
346 float *scratch,
347 size_t scratch_elems);
348
349#ifdef __cplusplus
350}
351#endif
352
353#endif
int ck_rwkv7_lora_gates(const float *xw, const float *xa, const float *xg, const float *w1, const float *w2, const float *w0, const float *a1, const float *a2, const float *a0, const float *g1, const float *g2, int dim, int rank_w, int rank_a, int rank_g, float *w_log, float *a_out, float *g_out)
int ck_rwkv7_apply_kk_factor(const float *k, const float *k_k, float *kk_raw, int dim)
int ck_rwkv7_vgate_vmix(const float *v_in, const float *v_first, const float *xv, const float *v1, const float *v2, const float *v0, int dim, int rank_v, float *v_out, float *v_first_out, float *v_gate)
int ck_rwkv7_channelmix_decode(const float *x, const float *shift_in, const float *mu, const float *Wk, const float *Wv, float *shift_out, float *out, int dim, int hidden_dim, float *scratch, size_t scratch_elems)
int(* ck_rwkv7_gemv_fn)(const float *W, const float *x, float *y, void *ctx)
size_t ck_rwkv7_time_mix_scratch_elems(int dim)
int(* ck_rwkv7_layer_norm_fn)(const float *x, const float *gamma, const float *beta, float *y, int dim, void *ctx)
size_t ck_rwkv7_time_mix_block_scratch_elems(int dim)
int ck_rwkv7_scale_k(const float *k_in, const float *a, const float *k_a, float *k_out, int dim)
int ck_rwkv7_state_readout(const float *state, const float *r, float *y, int head_dim)
int ck_rwkv7_token_shift_lerp(const float *x, const float *shift_in, const float *mu, float *xm, float *shift_out, int dim)
int ck_rwkv7_time_mix_shifts(const float *x, const float *shift_in, const float *mu_r, const float *mu_w, const float *mu_k, const float *mu_v, const float *mu_a, const float *mu_g, float *xr, float *xw, float *xk, float *xv, float *xa, float *xg, float *shift_out, int dim)
size_t ck_rwkv7_channelmix_scratch_elems(int dim, int hidden_dim)
int ck_rwkv7_state_step(const float *w_log, const float *k, const float *v, const float *kk, const float *a, const float *state_in, float *state_out, int head_dim)
int ck_rwkv7_time_mix_block(const float *h, const float *att_shift_in, const float *v_first_in, const float *state_in, const float *ln_g, const float *ln_b, const float *mu_r, const float *mu_w, const float *mu_k, const float *mu_v, const float *mu_a, const float *mu_g, const float *Wr, const float *Wk, const float *Wv, const float *Wo, const float *w1, const float *w2, const float *w0, const float *a1, const float *a2, const float *a0, const float *g1, const float *g2, const float *v1, const float *v2, const float *v0, const float *k_k, const float *k_a, const float *rk, const float *gn_w, const float *gn_b, float norm_eps, int dim, int num_heads, int head_dim, int rank_w, int rank_a, int rank_g, int rank_v, int is_layer_zero, ck_rwkv7_gemv_fn gemv, ck_rwkv7_layer_norm_fn layer_norm, void *ctx, float *h_out, float *att_shift_out, float *state_out, float *v_first_out, float *scratch, size_t scratch_elems)
int ck_rwkv7_wkv_decode(const float *r, const float *w_log, const float *k, const float *v, const float *kk, const float *a, const float *g, const float *rk, const float *ln_w, const float *ln_b, float norm_eps, const float *state_in, float *state_out, float *y_out, int num_heads, int head_dim)
int ck_rwkv7_norm_kk(const float *kk_in, float *kk_out, int num_heads, int head_dim)
int ck_rwkv7_time_mix_tail(const float *r, const float *k_raw, const float *v_in, const float *v_first_in, const float *xw, const float *xa, const float *xg, const float *xv, const float *w1, const float *w2, const float *w0, const float *a1, const float *a2, const float *a0, const float *g1, const float *g2, const float *v1, const float *v2, const float *v0, const float *k_k, const float *k_a, const float *rk, const float *ln_w, const float *ln_b, float norm_eps, const float *state_in, float *state_out, float *y_preproj, float *v_first_out, int dim, int num_heads, int head_dim, int rank_w, int rank_a, int rank_g, int rank_v, float *scratch, size_t scratch_elems)