1340 lines
49 KiB
C
1340 lines
49 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/*
|
|
* rfa.c — RWKV-7 + Full Attention hybrid inference engine
|
|
*
|
|
* Hybrid model: RWKV-7 recurrent blocks + Qwen2 dense attention (GQA + RoPE).
|
|
* All layers share RMSNorm + SwiGLU FFN; only the attention mechanism differs.
|
|
*
|
|
* Built on Foundry tensor ops. Designed for single-token decode (no batching).
|
|
*/
|
|
|
|
#include "rfa.h"
|
|
#include "safetensors/safetensors.h"
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#ifdef __ARM_NEON
|
|
#include <arm_neon.h>
|
|
#endif
|
|
|
|
#ifdef USE_BLAS
|
|
#ifdef __APPLE__
|
|
#include <Accelerate/Accelerate.h>
|
|
#else
|
|
#include <cblas.h>
|
|
#endif
|
|
#endif
|
|
|
|
/* ================================================================
|
|
* INT8 quantized weight matrix (per-row symmetric)
|
|
* ================================================================ */
|
|
|
|
typedef struct {
|
|
int8_t *data; /* [rows * cols] quantized data */
|
|
float *scales; /* [rows] per-row scale: float = int8 * scale */
|
|
int rows, cols;
|
|
} rfa_q8_weight;
|
|
|
|
/* Aligned malloc helper — 64-byte alignment for NEON cache line ops */
|
|
static void* rfa_aligned_malloc(size_t size) {
|
|
void *ptr = NULL;
|
|
if (posix_memalign(&ptr, 64, size) != 0) return NULL;
|
|
return ptr;
|
|
}
|
|
|
|
static rfa_q8_weight* rfa_q8_from_f32(const float *src, int rows, int cols) {
|
|
rfa_q8_weight *q = (rfa_q8_weight*)calloc(1, sizeof(rfa_q8_weight));
|
|
q->rows = rows;
|
|
q->cols = cols;
|
|
q->data = (int8_t*)rfa_aligned_malloc(rows * cols * sizeof(int8_t));
|
|
q->scales = (float*)rfa_aligned_malloc(rows * sizeof(float));
|
|
|
|
for (int i = 0; i < rows; i++) {
|
|
const float *row = &src[i * cols];
|
|
float amax = 0.0f;
|
|
for (int j = 0; j < cols; j++) {
|
|
float a = row[j] < 0 ? -row[j] : row[j];
|
|
if (a > amax) amax = a;
|
|
}
|
|
float scale = amax / 127.0f;
|
|
q->scales[i] = scale;
|
|
float inv_scale = (scale > 0.0f) ? 127.0f / amax : 0.0f;
|
|
int8_t *dst = &q->data[i * cols];
|
|
for (int j = 0; j < cols; j++) {
|
|
float v = row[j] * inv_scale;
|
|
int vi = (int)(v + (v >= 0 ? 0.5f : -0.5f));
|
|
if (vi > 127) vi = 127;
|
|
if (vi < -127) vi = -127;
|
|
dst[j] = (int8_t)vi;
|
|
}
|
|
}
|
|
return q;
|
|
}
|
|
|
|
static void rfa_q8_free(rfa_q8_weight *q) {
|
|
if (!q) return;
|
|
free(q->data);
|
|
free(q->scales);
|
|
free(q);
|
|
}
|
|
|
|
/* Quantize a float vector to int8. Returns scale factor. */
|
|
static float rfa_quantize_input(const float *x, int8_t *x_q, int n) {
|
|
float x_amax = 0.0f;
|
|
for (int j = 0; j < n; j++) {
|
|
float a = x[j] < 0 ? -x[j] : x[j];
|
|
if (a > x_amax) x_amax = a;
|
|
}
|
|
float x_scale = x_amax / 127.0f;
|
|
float x_inv_scale = (x_amax > 0.0f) ? 127.0f / x_amax : 0.0f;
|
|
for (int j = 0; j < n; j++) {
|
|
float v = x[j] * x_inv_scale;
|
|
int vi = (int)(v + (v >= 0 ? 0.5f : -0.5f));
|
|
if (vi > 127) vi = 127;
|
|
if (vi < -127) vi = -127;
|
|
x_q[j] = (int8_t)vi;
|
|
}
|
|
return x_scale;
|
|
}
|
|
|
|
/* INT8 matvec with pre-quantized input. NEON 4x unrolled. */
|
|
static void rfa_matvec_q8_preq(const int8_t *x_q, float x_scale,
|
|
const rfa_q8_weight *q, float *out,
|
|
int rows, int cols) {
|
|
#ifdef __ARM_NEON
|
|
int cols64 = cols & ~63;
|
|
int cols16 = cols & ~15;
|
|
#endif
|
|
for (int i = 0; i < rows; i++) {
|
|
const int8_t *row = &q->data[i * cols];
|
|
float w_scale = q->scales[i];
|
|
if (i + 1 < rows)
|
|
__builtin_prefetch(&q->data[(i + 1) * cols], 0, 1);
|
|
#ifdef __ARM_NEON
|
|
int32x4_t acc0 = vdupq_n_s32(0);
|
|
int32x4_t acc1 = vdupq_n_s32(0);
|
|
int32x4_t acc2 = vdupq_n_s32(0);
|
|
int32x4_t acc3 = vdupq_n_s32(0);
|
|
for (int j = 0; j < cols64; j += 64) {
|
|
acc0 = vdotq_s32(acc0, vld1q_s8(&x_q[j]), vld1q_s8(&row[j]));
|
|
acc1 = vdotq_s32(acc1, vld1q_s8(&x_q[j + 16]), vld1q_s8(&row[j + 16]));
|
|
acc2 = vdotq_s32(acc2, vld1q_s8(&x_q[j + 32]), vld1q_s8(&row[j + 32]));
|
|
acc3 = vdotq_s32(acc3, vld1q_s8(&x_q[j + 48]), vld1q_s8(&row[j + 48]));
|
|
}
|
|
int32x4_t acc = vaddq_s32(vaddq_s32(acc0, acc1), vaddq_s32(acc2, acc3));
|
|
for (int j = cols64; j < cols16; j += 16)
|
|
acc = vdotq_s32(acc, vld1q_s8(&x_q[j]), vld1q_s8(&row[j]));
|
|
int32_t dot = vaddvq_s32(acc);
|
|
for (int j = cols16; j < cols; j++)
|
|
dot += (int32_t)x_q[j] * (int32_t)row[j];
|
|
#else
|
|
int32_t dot = 0;
|
|
for (int j = 0; j < cols; j++)
|
|
dot += (int32_t)x_q[j] * (int32_t)row[j];
|
|
#endif
|
|
out[i] = (float)dot * w_scale * x_scale;
|
|
}
|
|
}
|
|
|
|
/* INT8 matvec: quantizes input on the fly, then dispatches. */
|
|
static void rfa_matvec_q8(const float *x, const rfa_q8_weight *q, float *out,
|
|
int rows, int cols, int8_t *q8_buf) {
|
|
float x_scale = rfa_quantize_input(x, q8_buf, cols);
|
|
rfa_matvec_q8_preq(q8_buf, x_scale, q, out, rows, cols);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Per-block INT8 quantized weights (populated when model->quantized)
|
|
* ================================================================ */
|
|
|
|
typedef struct {
|
|
/* Qwen attention (only for Qwen blocks) */
|
|
rfa_q8_weight *q_proj_q8, *k_proj_q8, *v_proj_q8, *o_proj_q8;
|
|
/* RWKV attention (only for RWKV blocks) */
|
|
rfa_q8_weight *receptance_q8, *key_q8, *value_q8, *output_q8;
|
|
/* SwiGLU FFN (all blocks) */
|
|
rfa_q8_weight *gate_q8, *up_q8, *down_q8;
|
|
} rfa_block_q8;
|
|
|
|
/* ================================================================
|
|
* Scratch workspace (pre-allocated, zero malloc in hot path)
|
|
* ================================================================ */
|
|
|
|
typedef struct {
|
|
float *buf[16]; /* general-purpose scratch buffers [C] */
|
|
float *ffn_buf; /* FFN intermediate buffer [intermediate_size] */
|
|
float *lora_buf; /* LoRA intermediate buffer [max_lora_dim] */
|
|
float *x_buf[2]; /* alternating input/output buffers [C] */
|
|
float *attn_buf; /* attention scores buffer [max_cache] */
|
|
float *rope_cos; /* precomputed RoPE cos [head_dim/2] */
|
|
float *rope_sin; /* precomputed RoPE sin [head_dim/2] */
|
|
int8_t *q8_buf; /* INT8 quantization scratch */
|
|
} rfa_workspace;
|
|
|
|
/* ================================================================
|
|
* Model struct
|
|
* ================================================================ */
|
|
|
|
struct rfa_model {
|
|
rfa_config config;
|
|
|
|
/* Embedding + head */
|
|
tm_tensor *emb_weight; /* [vocab, C] */
|
|
tm_tensor *ln_out_w; /* [C] final RMSNorm */
|
|
tm_tensor *head_weight; /* [C, vocab] transposed for fast matvec */
|
|
|
|
/* All blocks in execution order */
|
|
rfa_block *blocks; /* array of n_layers */
|
|
|
|
/* INT8 quantized weights (populated when quantized=true) */
|
|
rfa_block_q8 *blocks_q8; /* array of n_layers, NULL if not quantized */
|
|
rfa_q8_weight *head_q8; /* lm_head quantized */
|
|
|
|
/* Quantization flag */
|
|
bool quantized;
|
|
};
|
|
|
|
/* ================================================================
|
|
* State struct
|
|
* ================================================================ */
|
|
|
|
struct rfa_state {
|
|
rfa_rwkv_state *rwkv_states; /* array indexed by RWKV block sequence */
|
|
rfa_kv_cache *kv_caches; /* array indexed by Qwen block sequence */
|
|
float *v_first; /* [C] first-token value for RWKV-7 */
|
|
int n_rwkv; /* count of RWKV blocks */
|
|
int n_qwen; /* count of Qwen blocks */
|
|
int position; /* absolute position counter for RoPE */
|
|
rfa_config config;
|
|
rfa_workspace *workspace;
|
|
};
|
|
|
|
/* ================================================================
|
|
* Fast math helpers
|
|
* ================================================================ */
|
|
|
|
static inline float fast_silu(float x) {
|
|
return x / (1.0f + expf(-x));
|
|
}
|
|
|
|
/* RMSNorm: out = x * rsqrt(mean(x^2) + eps) * weight */
|
|
static void fast_rms_norm(const float *x, const float *w, float *out,
|
|
int n, float eps) {
|
|
float sum_sq = 0.0f;
|
|
for (int i = 0; i < n; i++)
|
|
sum_sq += x[i] * x[i];
|
|
float scale = 1.0f / sqrtf(sum_sq / n + eps);
|
|
for (int i = 0; i < n; i++)
|
|
out[i] = x[i] * scale * w[i];
|
|
}
|
|
|
|
/* Matrix-vector multiply: out[rows] = mat[rows, cols] @ x[cols] */
|
|
static void fast_matvec(const float *x, const float *mat, float *out,
|
|
int cols, int rows) {
|
|
#ifdef USE_BLAS
|
|
cblas_sgemv(CblasRowMajor, CblasNoTrans, rows, cols,
|
|
1.0f, mat, cols, x, 1, 0.0f, out, 1);
|
|
#else
|
|
for (int r = 0; r < rows; r++) {
|
|
float sum = 0.0f;
|
|
const float *row = &mat[r * cols];
|
|
for (int c = 0; c < cols; c++)
|
|
sum += row[c] * x[c];
|
|
out[r] = sum;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Add bias: out[i] += bias[i] */
|
|
static void fast_add_bias(float *out, const float *bias, int n) {
|
|
for (int i = 0; i < n; i++)
|
|
out[i] += bias[i];
|
|
}
|
|
|
|
/* Residual: out[i] += residual[i] */
|
|
static void fast_residual_add(float *out, const float *residual, int n) {
|
|
for (int i = 0; i < n; i++)
|
|
out[i] += residual[i];
|
|
}
|
|
|
|
/* Token shift: out[i] = x[i] + (prev[i] - x[i]) * lerp[i] */
|
|
static void fast_token_shift(const float *x, const float *prev,
|
|
const float *lerp, float *out, int n) {
|
|
for (int i = 0; i < n; i++)
|
|
out[i] = x[i] + (prev[i] - x[i]) * lerp[i];
|
|
}
|
|
|
|
/* L2 normalize per head */
|
|
static void fast_l2_normalize_per_head(const float *in, float *out,
|
|
int n_head, int head_size) {
|
|
for (int h = 0; h < n_head; h++) {
|
|
int off = h * head_size;
|
|
float sum_sq = 0.0f;
|
|
for (int j = 0; j < head_size; j++)
|
|
sum_sq += in[off + j] * in[off + j];
|
|
float scale = 1.0f / sqrtf(sum_sq + 1e-12f);
|
|
for (int j = 0; j < head_size; j++)
|
|
out[off + j] = in[off + j] * scale;
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* RoPE (Rotary Position Embedding)
|
|
* ================================================================ */
|
|
|
|
/* Apply RoPE to a single vector in-place.
|
|
* vec: [n_heads, head_dim], applied per-head.
|
|
* pos: absolute position index.
|
|
* theta: rope_theta (1e6 for Qwen2.5). */
|
|
static void apply_rope(float *vec, int n_heads, int head_dim,
|
|
int pos, float theta) {
|
|
int half = head_dim / 2;
|
|
for (int h = 0; h < n_heads; h++) {
|
|
float *head = &vec[h * head_dim];
|
|
for (int i = 0; i < half; i++) {
|
|
float freq = 1.0f / powf(theta, (float)(2 * i) / head_dim);
|
|
float angle = pos * freq;
|
|
float cos_a = cosf(angle);
|
|
float sin_a = sinf(angle);
|
|
float x0 = head[2 * i];
|
|
float x1 = head[2 * i + 1];
|
|
head[2 * i] = x0 * cos_a - x1 * sin_a;
|
|
head[2 * i + 1] = x0 * sin_a + x1 * cos_a;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* SwiGLU FFN forward
|
|
* ================================================================ */
|
|
|
|
/* SwiGLU: out = down_proj(silu(gate_proj(x)) * up_proj(x))
|
|
* gate_proj, up_proj: [intermediate, C]
|
|
* down_proj: [C, intermediate] */
|
|
static void swiglu_ffn(const float *x, const rfa_block *blk,
|
|
const rfa_block_q8 *bq8,
|
|
float *out, float *ffn_buf, float *ffn_buf2,
|
|
int c, int inter, int8_t *q8_buf) {
|
|
/* gate = gate_proj @ x */
|
|
if (bq8 && bq8->gate_q8)
|
|
rfa_matvec_q8(x, bq8->gate_q8, ffn_buf, inter, c, q8_buf);
|
|
else
|
|
fast_matvec(x, tm_data_f32(blk->gate_proj), ffn_buf, c, inter);
|
|
/* up = up_proj @ x */
|
|
if (bq8 && bq8->up_q8)
|
|
rfa_matvec_q8(x, bq8->up_q8, ffn_buf2, inter, c, q8_buf);
|
|
else
|
|
fast_matvec(x, tm_data_f32(blk->up_proj), ffn_buf2, c, inter);
|
|
/* act = silu(gate) * up */
|
|
for (int i = 0; i < inter; i++)
|
|
ffn_buf[i] = fast_silu(ffn_buf[i]) * ffn_buf2[i];
|
|
/* out = down_proj @ act */
|
|
if (bq8 && bq8->down_q8)
|
|
rfa_matvec_q8(ffn_buf, bq8->down_q8, out, c, inter, q8_buf);
|
|
else
|
|
fast_matvec(ffn_buf, tm_data_f32(blk->down_proj), out, inter, c);
|
|
}
|
|
|
|
/* ================================================================
|
|
* RWKV-7 Time Mix Forward (single token)
|
|
* ================================================================ */
|
|
|
|
/* WKV-7 kernel for a single token: updates state matrix in-place.
|
|
* State: [n_head, head_size, head_size]
|
|
* Inputs: r, k, v, w, a, b all [C] reshaped to [n_head, head_size] */
|
|
static void rfa_wkv7_one(const float *r, const float *w,
|
|
const float *k, const float *v,
|
|
const float *neg_kk, const float *kk_a,
|
|
float *state, float *y,
|
|
int n_head, int head_size) {
|
|
for (int h = 0; h < n_head; h++) {
|
|
int ho = h * head_size;
|
|
int so = h * head_size * head_size;
|
|
for (int i = 0; i < head_size; i++) {
|
|
float ri = r[ho + i];
|
|
float dot = 0.0f;
|
|
for (int j = 0; j < head_size; j++)
|
|
dot += state[so + i * head_size + j] * ri;
|
|
/* This isn't quite right — accumulate across i for y[ho+j] */
|
|
/* Actually WKV7 output: y[h,j] = sum_i(r[h,i] * state[h,i,j]) */
|
|
(void)dot; /* handled below */
|
|
}
|
|
/* Correct: y[h,j] = sum_i(r[h,i] * state[h,i,j]) */
|
|
for (int j = 0; j < head_size; j++) {
|
|
float sum = 0.0f;
|
|
for (int i = 0; i < head_size; i++)
|
|
sum += r[ho + i] * state[so + i * head_size + j];
|
|
y[ho + j] = sum;
|
|
}
|
|
/* State update: state[h,i,j] = state[h,i,j] * w[h,i]
|
|
* + k_mod[h,i] * v[h,j]
|
|
* + state[h,i,j] * neg_kk[h,i] * kk_a_sum... */
|
|
/* Simplified: state[h,i,j] = w[h,i] * state[h,i,j]
|
|
* + kk_a[h,i] * sum_k(neg_kk[h,k] * state[h,k,j])
|
|
* + k[h,i] * v[h,j] */
|
|
float sa_buf[head_size]; /* temp for state @ neg_kk per column */
|
|
for (int j = 0; j < head_size; j++) {
|
|
/* sa = sum_i(neg_kk[h,i] * state[h,i,j]) */
|
|
float sa = 0.0f;
|
|
for (int i = 0; i < head_size; i++)
|
|
sa += neg_kk[ho + i] * state[so + i * head_size + j];
|
|
sa_buf[j] = sa;
|
|
}
|
|
for (int i = 0; i < head_size; i++) {
|
|
float wi = w[ho + i];
|
|
float ki = k[ho + i];
|
|
float ai = kk_a[ho + i];
|
|
for (int j = 0; j < head_size; j++) {
|
|
int idx = so + i * head_size + j;
|
|
state[idx] = wi * state[idx]
|
|
+ ai * sa_buf[j]
|
|
+ ki * v[ho + j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rwkv7_time_mix(const rfa_rwkv_attn *attn, int layer_id,
|
|
const float *xx, rfa_rwkv_state *st,
|
|
float *v_first, rfa_config *cfg,
|
|
const rfa_block_q8 *bq8,
|
|
rfa_workspace *ws, float *output) {
|
|
int c = cfg->n_embd;
|
|
int n_head = cfg->n_wkv_heads;
|
|
int head_size = cfg->wkv_head_size;
|
|
|
|
float *prev = tm_data_f32(st->att_x_prev);
|
|
float *shifted = ws->buf[0];
|
|
float *r_vec = ws->buf[1];
|
|
float *k_vec = ws->buf[2];
|
|
float *v_vec = ws->buf[3];
|
|
float *w_vec = ws->buf[4];
|
|
float *a_vec = ws->buf[5];
|
|
float *kk = ws->buf[6];
|
|
float *k_mod = ws->buf[7];
|
|
float *v_final = ws->buf[8];
|
|
float *y = ws->buf[9];
|
|
float *tmp = ws->buf[10];
|
|
float *tmp2 = ws->buf[11];
|
|
float *xv_save = ws->buf[12];
|
|
float *lora = ws->lora_buf;
|
|
|
|
/* Token shift + projections */
|
|
fast_token_shift(xx, prev, tm_data_f32(attn->x_r), shifted, c);
|
|
if (bq8 && bq8->receptance_q8)
|
|
rfa_matvec_q8(shifted, bq8->receptance_q8, r_vec, c, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(shifted, tm_data_f32(attn->receptance_w), r_vec, c, c);
|
|
|
|
fast_token_shift(xx, prev, tm_data_f32(attn->x_k), shifted, c);
|
|
if (bq8 && bq8->key_q8)
|
|
rfa_matvec_q8(shifted, bq8->key_q8, k_vec, c, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(shifted, tm_data_f32(attn->key_w), k_vec, c, c);
|
|
|
|
fast_token_shift(xx, prev, tm_data_f32(attn->x_v), shifted, c);
|
|
memcpy(xv_save, shifted, c * sizeof(float));
|
|
if (bq8 && bq8->value_q8)
|
|
rfa_matvec_q8(shifted, bq8->value_q8, v_vec, c, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(shifted, tm_data_f32(attn->value_w), v_vec, c, c);
|
|
|
|
/* Decay: w = exp(-softplus(w0 + tanh(xw @ w1) @ w2) - 0.5) */
|
|
{
|
|
int ld = (int)tm_size(attn->w1, 1);
|
|
float *w0d = tm_data_f32(attn->w0);
|
|
fast_token_shift(xx, prev, tm_data_f32(attn->x_w), shifted, c);
|
|
fast_matvec(shifted, tm_data_f32(attn->w1), lora, c, ld);
|
|
for (int i = 0; i < ld; i++) lora[i] = tanhf(lora[i]);
|
|
fast_matvec(lora, tm_data_f32(attn->w2), w_vec, ld, c);
|
|
for (int i = 0; i < c; i++) {
|
|
float sp = logf(1.0f + expf(w0d[i] + w_vec[i]));
|
|
w_vec[i] = expf(-sp - 0.5f);
|
|
}
|
|
}
|
|
|
|
/* Gate a: a = sigmoid(a0 + (xa @ a1) @ a2) */
|
|
{
|
|
int ld = (int)tm_size(attn->a1, 1);
|
|
float *a0d = tm_data_f32(attn->a0);
|
|
fast_token_shift(xx, prev, tm_data_f32(attn->x_a), shifted, c);
|
|
fast_matvec(shifted, tm_data_f32(attn->a1), lora, c, ld);
|
|
fast_matvec(lora, tm_data_f32(attn->a2), a_vec, ld, c);
|
|
for (int i = 0; i < c; i++)
|
|
a_vec[i] = 1.0f / (1.0f + expf(-(a0d[i] + a_vec[i])));
|
|
}
|
|
|
|
/* Update prev (safe now — all shifts done) */
|
|
memcpy(prev, xx, c * sizeof(float));
|
|
|
|
/* Key normalization: kk = L2_normalize(k * k_k) per head */
|
|
{
|
|
float *kkd = tm_data_f32(attn->k_k);
|
|
for (int i = 0; i < c; i++) tmp[i] = k_vec[i] * kkd[i];
|
|
fast_l2_normalize_per_head(tmp, kk, n_head, head_size);
|
|
}
|
|
|
|
/* Modify key: k_mod = k * (1 + (a-1) * k_a) */
|
|
{
|
|
float *kad = tm_data_f32(attn->k_a);
|
|
for (int i = 0; i < c; i++)
|
|
k_mod[i] = k_vec[i] * (1.0f + (a_vec[i] - 1.0f) * kad[i]);
|
|
}
|
|
|
|
/* Value first-token gating */
|
|
if (layer_id == 0) {
|
|
memcpy(v_final, v_vec, c * sizeof(float));
|
|
memcpy(v_first, v_vec, c * sizeof(float));
|
|
} else {
|
|
int vld = (int)tm_size(attn->v1, 1);
|
|
float *v0d = tm_data_f32(attn->v0);
|
|
fast_matvec(xv_save, tm_data_f32(attn->v1), lora, c, vld);
|
|
fast_matvec(lora, tm_data_f32(attn->v2), tmp, vld, c);
|
|
for (int i = 0; i < c; i++) {
|
|
float blend = 1.0f / (1.0f + expf(-(v0d[i] + tmp[i])));
|
|
v_final[i] = v_vec[i] + (v_first[i] - v_vec[i]) * blend;
|
|
}
|
|
}
|
|
|
|
/* WKV-7 kernel */
|
|
for (int i = 0; i < c; i++) {
|
|
tmp[i] = -kk[i];
|
|
tmp2[i] = kk[i] * a_vec[i];
|
|
}
|
|
rfa_wkv7_one(r_vec, w_vec, k_mod, v_final,
|
|
tmp, tmp2, tm_data_f32(st->att_kv), y,
|
|
n_head, head_size);
|
|
|
|
/* Bonus term: y += (r * k * r_k).sum(-1, keepdim=True) * v */
|
|
{
|
|
float *rkd = tm_data_f32(attn->r_k);
|
|
for (int h = 0; h < n_head; h++) {
|
|
int ho = h * head_size;
|
|
float dot = 0.0f;
|
|
for (int j = 0; j < head_size; j++)
|
|
dot += r_vec[ho + j] * k_vec[ho + j] * rkd[h * head_size + j];
|
|
for (int j = 0; j < head_size; j++)
|
|
y[ho + j] += dot * v_final[ho + j];
|
|
}
|
|
}
|
|
|
|
/* Output projection */
|
|
if (bq8 && bq8->output_q8)
|
|
rfa_matvec_q8(y, bq8->output_q8, output, c, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(y, tm_data_f32(attn->output_w), output, c, c);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Qwen GQA Attention Forward (single token, decode mode)
|
|
* ================================================================ */
|
|
|
|
static void qwen_attention(const rfa_qwen_attn *attn, const float *x,
|
|
rfa_kv_cache *cache, rfa_config *cfg,
|
|
const rfa_block_q8 *bq8,
|
|
int position, rfa_workspace *ws, float *output) {
|
|
int c = cfg->n_embd;
|
|
int n_q = cfg->n_q_heads;
|
|
int n_kv = cfg->n_kv_heads;
|
|
int hd = cfg->head_dim;
|
|
int kv_dim = n_kv * hd;
|
|
int group_size = n_q / n_kv;
|
|
|
|
float *q = ws->buf[1]; /* [C] = [n_q * hd] */
|
|
float *k = ws->buf[2]; /* [kv_dim] = [n_kv * hd] */
|
|
float *v = ws->buf[3]; /* [kv_dim] */
|
|
float *y = ws->buf[9]; /* [C] output */
|
|
|
|
/* Q/K/V projections */
|
|
if (bq8 && bq8->q_proj_q8)
|
|
rfa_matvec_q8(x, bq8->q_proj_q8, q, c, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(x, tm_data_f32(attn->q_proj_w), q, c, c);
|
|
if (attn->q_proj_b) fast_add_bias(q, tm_data_f32(attn->q_proj_b), c);
|
|
|
|
if (bq8 && bq8->k_proj_q8)
|
|
rfa_matvec_q8(x, bq8->k_proj_q8, k, kv_dim, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(x, tm_data_f32(attn->k_proj_w), k, c, kv_dim);
|
|
if (attn->k_proj_b) fast_add_bias(k, tm_data_f32(attn->k_proj_b), kv_dim);
|
|
|
|
if (bq8 && bq8->v_proj_q8)
|
|
rfa_matvec_q8(x, bq8->v_proj_q8, v, kv_dim, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(x, tm_data_f32(attn->v_proj_w), v, c, kv_dim);
|
|
if (attn->v_proj_b) fast_add_bias(v, tm_data_f32(attn->v_proj_b), kv_dim);
|
|
|
|
/* Apply RoPE to Q and K */
|
|
apply_rope(q, n_q, hd, position, cfg->rope_theta);
|
|
apply_rope(k, n_kv, hd, position, cfg->rope_theta);
|
|
|
|
/* Append to KV cache */
|
|
int ct = cache->cache_len;
|
|
if (ct >= cache->max_cache_len) {
|
|
/* Simple: drop oldest half */
|
|
int keep = cache->max_cache_len / 2;
|
|
int drop = ct - keep;
|
|
memmove(cache->k_cache, &cache->k_cache[drop * kv_dim],
|
|
keep * kv_dim * sizeof(float));
|
|
memmove(cache->v_cache, &cache->v_cache[drop * kv_dim],
|
|
keep * kv_dim * sizeof(float));
|
|
ct = keep;
|
|
cache->cache_len = keep;
|
|
}
|
|
memcpy(&cache->k_cache[ct * kv_dim], k, kv_dim * sizeof(float));
|
|
memcpy(&cache->v_cache[ct * kv_dim], v, kv_dim * sizeof(float));
|
|
cache->cache_len = ct + 1;
|
|
int total = ct + 1;
|
|
|
|
/* GQA attention: per Q-head, dot with matching KV head across cache */
|
|
float scale = 1.0f / sqrtf((float)hd);
|
|
memset(y, 0, c * sizeof(float));
|
|
|
|
for (int qh = 0; qh < n_q; qh++) {
|
|
int kvh = qh / group_size; /* which KV head this Q head uses */
|
|
float *q_head = &q[qh * hd];
|
|
|
|
/* Compute attention scores: Q[qh] @ K[kvh, t]^T for all t */
|
|
float *scores = ws->attn_buf;
|
|
float max_s = -1e30f;
|
|
for (int t = 0; t < total; t++) {
|
|
float *kt = &cache->k_cache[t * kv_dim + kvh * hd];
|
|
float dot = 0.0f;
|
|
for (int j = 0; j < hd; j++)
|
|
dot += q_head[j] * kt[j];
|
|
scores[t] = dot * scale;
|
|
if (scores[t] > max_s) max_s = scores[t];
|
|
}
|
|
|
|
/* Softmax */
|
|
float sum_e = 0.0f;
|
|
for (int t = 0; t < total; t++) {
|
|
scores[t] = expf(scores[t] - max_s);
|
|
sum_e += scores[t];
|
|
}
|
|
float inv_sum = 1.0f / sum_e;
|
|
for (int t = 0; t < total; t++)
|
|
scores[t] *= inv_sum;
|
|
|
|
/* Value aggregation: y[qh] = sum_t(scores[t] * V[kvh, t]) */
|
|
float *y_head = &y[qh * hd];
|
|
for (int t = 0; t < total; t++) {
|
|
float *vt = &cache->v_cache[t * kv_dim + kvh * hd];
|
|
float s = scores[t];
|
|
for (int j = 0; j < hd; j++)
|
|
y_head[j] += s * vt[j];
|
|
}
|
|
}
|
|
|
|
/* Output projection */
|
|
if (bq8 && bq8->o_proj_q8)
|
|
rfa_matvec_q8(y, bq8->o_proj_q8, output, c, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(y, tm_data_f32(attn->o_proj_w), output, c, c);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Block forward (unified for RWKV and Qwen)
|
|
* ================================================================ */
|
|
|
|
static void rfa_block_forward(rfa_block *blk, int layer_id,
|
|
float *x, rfa_rwkv_state *rwkv_st,
|
|
rfa_kv_cache *kv_cache, float *v_first,
|
|
rfa_config *cfg, int position,
|
|
const rfa_block_q8 *bq8,
|
|
rfa_workspace *ws, float *x_out) {
|
|
int c = cfg->n_embd;
|
|
int inter = cfg->intermediate_size;
|
|
float eps = cfg->rms_norm_eps;
|
|
float *ln_out = ws->buf[15];
|
|
float *attn_out = ws->buf[14];
|
|
float *ffn_out = ws->buf[13];
|
|
|
|
/* Pre-attention RMSNorm */
|
|
fast_rms_norm(x, tm_data_f32(blk->ln1_w), ln_out, c, eps);
|
|
|
|
/* Attention (RWKV-7 or Qwen GQA) */
|
|
if (blk->is_qwen) {
|
|
qwen_attention(&blk->qwen, ln_out, kv_cache, cfg,
|
|
bq8, position, ws, attn_out);
|
|
} else {
|
|
rwkv7_time_mix(&blk->rwkv, layer_id, ln_out, rwkv_st,
|
|
v_first, cfg, bq8, ws, attn_out);
|
|
}
|
|
|
|
/* Residual: x1 = x + attn_out */
|
|
for (int i = 0; i < c; i++)
|
|
x_out[i] = x[i] + attn_out[i];
|
|
|
|
/* Pre-FFN RMSNorm */
|
|
fast_rms_norm(x_out, tm_data_f32(blk->ln2_w), ln_out, c, eps);
|
|
|
|
/* SwiGLU FFN */
|
|
swiglu_ffn(ln_out, blk, bq8, ffn_out, ws->ffn_buf,
|
|
ws->buf[0], /* reuse buf[0] as ffn_buf2 */
|
|
c, inter, ws->q8_buf);
|
|
|
|
/* Residual: x2 = x1 + ffn_out */
|
|
fast_residual_add(x_out, ffn_out, c);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Weight loading
|
|
* ================================================================ */
|
|
|
|
/* Convert BF16 raw bytes to F32 float array. Caller must free. */
|
|
static float* bf16_to_f32(const void *raw, int64_t numel) {
|
|
const uint16_t *src = (const uint16_t*)raw;
|
|
float *dst = (float*)malloc(numel * sizeof(float));
|
|
for (int64_t i = 0; i < numel; i++) {
|
|
uint32_t bits = (uint32_t)src[i] << 16;
|
|
memcpy(&dst[i], &bits, sizeof(float));
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
/* Load tensor from safetensors, handle BF16->F32 conversion and transpose.
|
|
* Returns owned tensor (caller must tm_free). NULL on missing. */
|
|
static tm_tensor* load_weight(fs_file *f, const char *name, bool required) {
|
|
if (!fs_has_tensor(f, name)) {
|
|
if (required)
|
|
fprintf(stderr, "rfa: missing weight: %s\n", name);
|
|
return NULL;
|
|
}
|
|
|
|
fs_tensor_info *info = fs_get_info(f, name);
|
|
if (!info) return NULL;
|
|
|
|
tm_tensor *t = NULL;
|
|
|
|
if (info->dtype == FS_DTYPE_F32) {
|
|
const float *data = fs_get_f32(f, name);
|
|
if (!data) { fs_free_info(info); return NULL; }
|
|
tm_tensor *t_view = tm_from_data((void*)data, info->shape, info->ndim, TM_F32);
|
|
t = tm_clone(t_view);
|
|
tm_free(t_view);
|
|
} else if (info->dtype == FS_DTYPE_BF16) {
|
|
const void *raw = fs_get_data(f, name);
|
|
if (!raw) { fs_free_info(info); return NULL; }
|
|
int64_t numel = 1;
|
|
for (int d = 0; d < info->ndim; d++) numel *= info->shape[d];
|
|
float *f32_data = bf16_to_f32(raw, numel);
|
|
tm_tensor *t_view = tm_from_data(f32_data, info->shape, info->ndim, TM_F32);
|
|
t = tm_clone(t_view);
|
|
tm_free(t_view);
|
|
free(f32_data);
|
|
} else {
|
|
fprintf(stderr, "rfa: unsupported dtype for %s\n", name);
|
|
fs_free_info(info);
|
|
return NULL;
|
|
}
|
|
|
|
/* Transpose 2D linear weights: [out, in] -> [in, out] for fast_matvec */
|
|
bool is_linear = strstr(name, ".weight") != NULL &&
|
|
(strstr(name, "proj") || strstr(name, "receptance") ||
|
|
strstr(name, "key.weight") || strstr(name, "value.weight") ||
|
|
strstr(name, "output.weight") || strstr(name, "head.weight") ||
|
|
strstr(name, "gate_proj") || strstr(name, "up_proj") ||
|
|
strstr(name, "down_proj"));
|
|
if (is_linear && info->ndim == 2) {
|
|
tm_tensor *tt = tm_t(t);
|
|
tm_tensor *tc = tm_contiguous(tt);
|
|
tm_free(t); tm_free(tt);
|
|
t = tc;
|
|
}
|
|
|
|
/* Squeeze singleton dims: [1,1,C] -> [C] */
|
|
for (int d = tm_ndim(t) - 1; d >= 0; d--) {
|
|
if (tm_size(t, d) == 1 && tm_ndim(t) > 1) {
|
|
tm_tensor *sq = tm_squeeze(t, d);
|
|
tm_free(t);
|
|
t = sq;
|
|
}
|
|
}
|
|
|
|
fs_free_info(info);
|
|
return t;
|
|
}
|
|
|
|
rfa_model* rfa_model_load(const char *path) {
|
|
fs_file *f = fs_open(path);
|
|
if (!f) {
|
|
fprintf(stderr, "rfa: cannot open %s\n", path);
|
|
return NULL;
|
|
}
|
|
|
|
rfa_config cfg = {0};
|
|
|
|
/* Parse config from metadata (simple sscanf-based) */
|
|
/* Default values for 1.5B */
|
|
cfg.n_embd = 1536;
|
|
cfg.n_q_heads = 12;
|
|
cfg.n_kv_heads = 2;
|
|
cfg.head_dim = 128;
|
|
cfg.n_wkv_heads = 24;
|
|
cfg.wkv_head_size = 64;
|
|
cfg.intermediate_size = 8960;
|
|
cfg.n_layers = 28;
|
|
cfg.vocab_size = 151936;
|
|
cfg.rope_theta = 1000000.0f;
|
|
cfg.rms_norm_eps = 1e-6f;
|
|
|
|
/* Override from metadata if present */
|
|
{
|
|
const char *v;
|
|
#define META_INT(key, field) do { \
|
|
v = fs_get_metadata(f, key); \
|
|
if (v) cfg.field = atoi(v); \
|
|
} while(0)
|
|
#define META_FLOAT(key, field) do { \
|
|
v = fs_get_metadata(f, key); \
|
|
if (v) cfg.field = strtof(v, NULL); \
|
|
} while(0)
|
|
META_INT("hidden_size", n_embd);
|
|
META_INT("num_attention_heads", n_q_heads);
|
|
META_INT("num_key_value_heads", n_kv_heads);
|
|
META_INT("head_dim", head_dim);
|
|
META_INT("num_wkv_heads", n_wkv_heads);
|
|
META_INT("wkv_head_size", wkv_head_size);
|
|
META_INT("intermediate_size", intermediate_size);
|
|
META_INT("num_hidden_layers", n_layers);
|
|
META_INT("vocab_size", vocab_size);
|
|
META_FLOAT("rope_theta", rope_theta);
|
|
META_FLOAT("rms_norm_eps", rms_norm_eps);
|
|
#undef META_INT
|
|
#undef META_FLOAT
|
|
}
|
|
|
|
/* Determine block types from metadata or tensor names */
|
|
int *block_types = (int*)calloc(cfg.n_layers, sizeof(int)); /* 0=rwkv, 1=qwen */
|
|
int n_rwkv = 0, n_qwen = 0;
|
|
|
|
/* Try metadata first */
|
|
const char *bt_str = fs_get_metadata(f, "block_types");
|
|
if (bt_str) {
|
|
/* Parse JSON array of "rwkv"/"qwen" strings */
|
|
const char *arr = strchr(bt_str, '[');
|
|
if (arr) {
|
|
arr++;
|
|
for (int i = 0; i < cfg.n_layers && *arr; i++) {
|
|
const char *q = strstr(arr, "qwen");
|
|
const char *next_comma = strchr(arr, ',');
|
|
const char *next_bracket = strchr(arr, ']');
|
|
const char *end = next_comma ? next_comma : next_bracket;
|
|
if (!end) break;
|
|
if (q && q < end) {
|
|
block_types[i] = 1;
|
|
n_qwen++;
|
|
} else {
|
|
block_types[i] = 0;
|
|
n_rwkv++;
|
|
}
|
|
arr = end + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fallback: detect from tensor names */
|
|
if (n_rwkv == 0 && n_qwen == 0) {
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
char qw_key[256];
|
|
snprintf(qw_key, sizeof(qw_key),
|
|
"model.layers.%d.self_attn.q_proj.weight", i);
|
|
if (fs_has_tensor(f, qw_key)) {
|
|
block_types[i] = 1;
|
|
n_qwen++;
|
|
} else {
|
|
block_types[i] = 0;
|
|
n_rwkv++;
|
|
}
|
|
}
|
|
}
|
|
|
|
cfg.n_rwkv_layers = n_rwkv;
|
|
cfg.n_qwen_layers = n_qwen;
|
|
|
|
printf("rfa: loading %d layers (%d RWKV, %d Qwen), %dd, vocab=%d\n",
|
|
cfg.n_layers, n_rwkv, n_qwen, cfg.n_embd, cfg.vocab_size);
|
|
|
|
/* Allocate model */
|
|
rfa_model *m = (rfa_model*)calloc(1, sizeof(rfa_model));
|
|
m->config = cfg;
|
|
m->blocks = (rfa_block*)calloc(cfg.n_layers, sizeof(rfa_block));
|
|
|
|
/* Global weights */
|
|
m->emb_weight = load_weight(f, "model.embed_tokens.weight", true);
|
|
m->ln_out_w = load_weight(f, "model.norm.weight", true);
|
|
|
|
/* Head weight: tied or separate */
|
|
m->head_weight = load_weight(f, "lm_head.weight", false);
|
|
if (!m->head_weight) {
|
|
/* Tied embeddings: head = embed_tokens transposed */
|
|
printf("rfa: using tied embeddings for lm_head\n");
|
|
m->head_weight = tm_clone(m->emb_weight);
|
|
/* Transpose [vocab, C] -> [C, vocab] for matvec */
|
|
int v = cfg.vocab_size, c = cfg.n_embd;
|
|
float *src = tm_data_f32(m->head_weight);
|
|
float *transposed = (float*)malloc(v * c * sizeof(float));
|
|
for (int i = 0; i < v; i++)
|
|
for (int j = 0; j < c; j++)
|
|
transposed[j * v + i] = src[i * c + j];
|
|
memcpy(src, transposed, v * c * sizeof(float));
|
|
free(transposed);
|
|
}
|
|
|
|
/* Load each block */
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
rfa_block *blk = &m->blocks[i];
|
|
blk->is_qwen = block_types[i];
|
|
char buf[256];
|
|
|
|
/* Norms (all layers) */
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.input_layernorm.weight", i);
|
|
blk->ln1_w = load_weight(f, buf, true);
|
|
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.post_attention_layernorm.weight", i);
|
|
blk->ln2_w = load_weight(f, buf, true);
|
|
|
|
/* SwiGLU FFN (all layers) */
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.mlp.gate_proj.weight", i);
|
|
blk->gate_proj = load_weight(f, buf, true);
|
|
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.mlp.up_proj.weight", i);
|
|
blk->up_proj = load_weight(f, buf, true);
|
|
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.mlp.down_proj.weight", i);
|
|
blk->down_proj = load_weight(f, buf, true);
|
|
|
|
if (blk->is_qwen) {
|
|
/* Qwen GQA attention */
|
|
rfa_qwen_attn *qa = &blk->qwen;
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.q_proj.weight", i);
|
|
qa->q_proj_w = load_weight(f, buf, true);
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.q_proj.bias", i);
|
|
qa->q_proj_b = load_weight(f, buf, false);
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.k_proj.weight", i);
|
|
qa->k_proj_w = load_weight(f, buf, true);
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.k_proj.bias", i);
|
|
qa->k_proj_b = load_weight(f, buf, false);
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.v_proj.weight", i);
|
|
qa->v_proj_w = load_weight(f, buf, true);
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.v_proj.bias", i);
|
|
qa->v_proj_b = load_weight(f, buf, false);
|
|
snprintf(buf, sizeof(buf), "model.layers.%d.self_attn.o_proj.weight", i);
|
|
qa->o_proj_w = load_weight(f, buf, true);
|
|
printf(" Layer %d: Qwen GQA\n", i);
|
|
} else {
|
|
/* RWKV-7 time mixer */
|
|
rfa_rwkv_attn *ra = &blk->rwkv;
|
|
#define LOAD_RWKV(field, fmt, ...) do { \
|
|
snprintf(buf, sizeof(buf), fmt, __VA_ARGS__); \
|
|
ra->field = load_weight(f, buf, true); \
|
|
} while(0)
|
|
|
|
LOAD_RWKV(x_r, "model.layers.%d.self_attn.time_mixer.x_r", i);
|
|
LOAD_RWKV(x_w, "model.layers.%d.self_attn.time_mixer.x_w", i);
|
|
LOAD_RWKV(x_k, "model.layers.%d.self_attn.time_mixer.x_k", i);
|
|
LOAD_RWKV(x_v, "model.layers.%d.self_attn.time_mixer.x_v", i);
|
|
LOAD_RWKV(x_a, "model.layers.%d.self_attn.time_mixer.x_a", i);
|
|
LOAD_RWKV(w0, "model.layers.%d.self_attn.time_mixer.w0", i);
|
|
LOAD_RWKV(w1, "model.layers.%d.self_attn.time_mixer.w1", i);
|
|
LOAD_RWKV(w2, "model.layers.%d.self_attn.time_mixer.w2", i);
|
|
LOAD_RWKV(a0, "model.layers.%d.self_attn.time_mixer.a0", i);
|
|
LOAD_RWKV(a1, "model.layers.%d.self_attn.time_mixer.a1", i);
|
|
LOAD_RWKV(a2, "model.layers.%d.self_attn.time_mixer.a2", i);
|
|
LOAD_RWKV(v0, "model.layers.%d.self_attn.time_mixer.v0", i);
|
|
LOAD_RWKV(v1, "model.layers.%d.self_attn.time_mixer.v1", i);
|
|
LOAD_RWKV(v2, "model.layers.%d.self_attn.time_mixer.v2", i);
|
|
LOAD_RWKV(k_k, "model.layers.%d.self_attn.time_mixer.k_k", i);
|
|
LOAD_RWKV(k_a, "model.layers.%d.self_attn.time_mixer.k_a", i);
|
|
LOAD_RWKV(r_k, "model.layers.%d.self_attn.time_mixer.r_k", i);
|
|
|
|
LOAD_RWKV(receptance_w, "model.layers.%d.self_attn.time_mixer.receptance.weight", i);
|
|
LOAD_RWKV(key_w, "model.layers.%d.self_attn.time_mixer.key.weight", i);
|
|
LOAD_RWKV(value_w, "model.layers.%d.self_attn.time_mixer.value.weight", i);
|
|
LOAD_RWKV(output_w, "model.layers.%d.self_attn.time_mixer.output.weight", i);
|
|
|
|
#undef LOAD_RWKV
|
|
}
|
|
}
|
|
|
|
free(block_types);
|
|
fs_close(f);
|
|
|
|
printf("rfa: model loaded successfully\n");
|
|
return m;
|
|
}
|
|
|
|
/* Quantize all linear weights to INT8 */
|
|
static void rfa_model_quantize_weights(rfa_model *m) {
|
|
rfa_config cfg = m->config;
|
|
int c = cfg.n_embd;
|
|
int inter = cfg.intermediate_size;
|
|
int kv_dim = cfg.n_kv_heads * cfg.head_dim;
|
|
|
|
m->blocks_q8 = (rfa_block_q8*)calloc(cfg.n_layers, sizeof(rfa_block_q8));
|
|
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
rfa_block *blk = &m->blocks[i];
|
|
rfa_block_q8 *bq8 = &m->blocks_q8[i];
|
|
|
|
/* FFN weights (all blocks) — [rows, cols] already transposed */
|
|
bq8->gate_q8 = rfa_q8_from_f32(tm_data_f32(blk->gate_proj), inter, c);
|
|
bq8->up_q8 = rfa_q8_from_f32(tm_data_f32(blk->up_proj), inter, c);
|
|
bq8->down_q8 = rfa_q8_from_f32(tm_data_f32(blk->down_proj), c, inter);
|
|
|
|
if (blk->is_qwen) {
|
|
/* Qwen attention projections */
|
|
bq8->q_proj_q8 = rfa_q8_from_f32(tm_data_f32(blk->qwen.q_proj_w), c, c);
|
|
bq8->k_proj_q8 = rfa_q8_from_f32(tm_data_f32(blk->qwen.k_proj_w), kv_dim, c);
|
|
bq8->v_proj_q8 = rfa_q8_from_f32(tm_data_f32(blk->qwen.v_proj_w), kv_dim, c);
|
|
bq8->o_proj_q8 = rfa_q8_from_f32(tm_data_f32(blk->qwen.o_proj_w), c, c);
|
|
} else {
|
|
/* RWKV attention projections */
|
|
bq8->receptance_q8 = rfa_q8_from_f32(tm_data_f32(blk->rwkv.receptance_w), c, c);
|
|
bq8->key_q8 = rfa_q8_from_f32(tm_data_f32(blk->rwkv.key_w), c, c);
|
|
bq8->value_q8 = rfa_q8_from_f32(tm_data_f32(blk->rwkv.value_w), c, c);
|
|
bq8->output_q8 = rfa_q8_from_f32(tm_data_f32(blk->rwkv.output_w), c, c);
|
|
}
|
|
}
|
|
|
|
/* Head weight: [vocab, C] transposed layout */
|
|
m->head_q8 = rfa_q8_from_f32(tm_data_f32(m->head_weight),
|
|
cfg.vocab_size, c);
|
|
|
|
m->quantized = true;
|
|
printf("rfa: quantized all weights to INT8\n");
|
|
}
|
|
|
|
rfa_model* rfa_model_load_q8(const char *path) {
|
|
rfa_model *m = rfa_model_load(path);
|
|
if (!m) return NULL;
|
|
rfa_model_quantize_weights(m);
|
|
return m;
|
|
}
|
|
|
|
void rfa_model_free(rfa_model *model) {
|
|
if (!model) return;
|
|
rfa_config cfg = model->config;
|
|
|
|
tm_free(model->emb_weight);
|
|
tm_free(model->ln_out_w);
|
|
tm_free(model->head_weight);
|
|
|
|
/* Free INT8 weights */
|
|
if (model->blocks_q8) {
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
rfa_block_q8 *bq8 = &model->blocks_q8[i];
|
|
rfa_q8_free(bq8->gate_q8);
|
|
rfa_q8_free(bq8->up_q8);
|
|
rfa_q8_free(bq8->down_q8);
|
|
rfa_q8_free(bq8->q_proj_q8);
|
|
rfa_q8_free(bq8->k_proj_q8);
|
|
rfa_q8_free(bq8->v_proj_q8);
|
|
rfa_q8_free(bq8->o_proj_q8);
|
|
rfa_q8_free(bq8->receptance_q8);
|
|
rfa_q8_free(bq8->key_q8);
|
|
rfa_q8_free(bq8->value_q8);
|
|
rfa_q8_free(bq8->output_q8);
|
|
}
|
|
free(model->blocks_q8);
|
|
}
|
|
rfa_q8_free(model->head_q8);
|
|
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
rfa_block *blk = &model->blocks[i];
|
|
tm_free(blk->ln1_w);
|
|
tm_free(blk->ln2_w);
|
|
tm_free(blk->gate_proj);
|
|
tm_free(blk->up_proj);
|
|
tm_free(blk->down_proj);
|
|
|
|
if (blk->is_qwen) {
|
|
tm_free(blk->qwen.q_proj_w);
|
|
tm_free(blk->qwen.q_proj_b);
|
|
tm_free(blk->qwen.k_proj_w);
|
|
tm_free(blk->qwen.k_proj_b);
|
|
tm_free(blk->qwen.v_proj_w);
|
|
tm_free(blk->qwen.v_proj_b);
|
|
tm_free(blk->qwen.o_proj_w);
|
|
} else {
|
|
rfa_rwkv_attn *ra = &blk->rwkv;
|
|
tm_free(ra->x_r); tm_free(ra->x_w); tm_free(ra->x_k);
|
|
tm_free(ra->x_v); tm_free(ra->x_a);
|
|
tm_free(ra->w0); tm_free(ra->w1); tm_free(ra->w2);
|
|
tm_free(ra->a0); tm_free(ra->a1); tm_free(ra->a2);
|
|
tm_free(ra->v0); tm_free(ra->v1); tm_free(ra->v2);
|
|
tm_free(ra->k_k); tm_free(ra->k_a); tm_free(ra->r_k);
|
|
tm_free(ra->receptance_w); tm_free(ra->key_w);
|
|
tm_free(ra->value_w); tm_free(ra->output_w);
|
|
}
|
|
}
|
|
free(model->blocks);
|
|
free(model);
|
|
}
|
|
|
|
rfa_config rfa_model_config(const rfa_model *model) {
|
|
return model->config;
|
|
}
|
|
|
|
/* ================================================================
|
|
* State lifecycle
|
|
* ================================================================ */
|
|
|
|
static rfa_workspace* workspace_new(rfa_config cfg, int max_cache) {
|
|
int c = cfg.n_embd;
|
|
int inter = cfg.intermediate_size;
|
|
rfa_workspace *ws = (rfa_workspace*)calloc(1, sizeof(rfa_workspace));
|
|
for (int i = 0; i < 16; i++)
|
|
ws->buf[i] = (float*)calloc(c, sizeof(float));
|
|
ws->ffn_buf = (float*)calloc(inter, sizeof(float));
|
|
ws->lora_buf = (float*)calloc(256, sizeof(float)); /* max LoRA dim */
|
|
ws->x_buf[0] = (float*)calloc(c, sizeof(float));
|
|
ws->x_buf[1] = (float*)calloc(c, sizeof(float));
|
|
ws->attn_buf = (float*)calloc(max_cache + 64, sizeof(float));
|
|
/* q8_buf needs to be large enough for max(C, intermediate_size) */
|
|
int q8_size = inter > c ? inter : c;
|
|
ws->q8_buf = (int8_t*)calloc(q8_size + 64, sizeof(int8_t));
|
|
return ws;
|
|
}
|
|
|
|
static void workspace_free(rfa_workspace *ws) {
|
|
if (!ws) return;
|
|
for (int i = 0; i < 16; i++) free(ws->buf[i]);
|
|
free(ws->ffn_buf);
|
|
free(ws->lora_buf);
|
|
free(ws->x_buf[0]);
|
|
free(ws->x_buf[1]);
|
|
free(ws->attn_buf);
|
|
free(ws->q8_buf);
|
|
free(ws);
|
|
}
|
|
|
|
rfa_state* rfa_state_new(const rfa_model *model, int max_cache_len) {
|
|
if (!model) return NULL;
|
|
rfa_config cfg = model->config;
|
|
if (max_cache_len <= 0) max_cache_len = 2048;
|
|
|
|
rfa_state *s = (rfa_state*)calloc(1, sizeof(rfa_state));
|
|
s->config = cfg;
|
|
s->position = 0;
|
|
|
|
/* Count RWKV and Qwen blocks */
|
|
s->n_rwkv = 0;
|
|
s->n_qwen = 0;
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
if (model->blocks[i].is_qwen) s->n_qwen++;
|
|
else s->n_rwkv++;
|
|
}
|
|
|
|
/* Allocate RWKV states */
|
|
s->rwkv_states = (rfa_rwkv_state*)calloc(s->n_rwkv, sizeof(rfa_rwkv_state));
|
|
for (int i = 0; i < s->n_rwkv; i++) {
|
|
int64_t prev_shape[] = {cfg.n_embd};
|
|
s->rwkv_states[i].att_x_prev = tm_zeros(prev_shape, 1, TM_F32);
|
|
|
|
int64_t kv_shape[] = {cfg.n_wkv_heads, cfg.wkv_head_size, cfg.wkv_head_size};
|
|
s->rwkv_states[i].att_kv = tm_zeros(kv_shape, 3, TM_F32);
|
|
}
|
|
|
|
/* Allocate Qwen KV caches */
|
|
int kv_dim = cfg.n_kv_heads * cfg.head_dim;
|
|
s->kv_caches = (rfa_kv_cache*)calloc(s->n_qwen, sizeof(rfa_kv_cache));
|
|
for (int i = 0; i < s->n_qwen; i++) {
|
|
s->kv_caches[i].k_cache = (float*)calloc(max_cache_len * kv_dim, sizeof(float));
|
|
s->kv_caches[i].v_cache = (float*)calloc(max_cache_len * kv_dim, sizeof(float));
|
|
s->kv_caches[i].cache_len = 0;
|
|
s->kv_caches[i].max_cache_len = max_cache_len;
|
|
}
|
|
|
|
/* v_first */
|
|
s->v_first = (float*)calloc(cfg.n_embd, sizeof(float));
|
|
|
|
/* Workspace */
|
|
s->workspace = workspace_new(cfg, max_cache_len);
|
|
|
|
return s;
|
|
}
|
|
|
|
void rfa_state_free(rfa_state *state) {
|
|
if (!state) return;
|
|
|
|
for (int i = 0; i < state->n_rwkv; i++) {
|
|
tm_free(state->rwkv_states[i].att_x_prev);
|
|
tm_free(state->rwkv_states[i].att_kv);
|
|
}
|
|
free(state->rwkv_states);
|
|
|
|
for (int i = 0; i < state->n_qwen; i++) {
|
|
free(state->kv_caches[i].k_cache);
|
|
free(state->kv_caches[i].v_cache);
|
|
}
|
|
free(state->kv_caches);
|
|
|
|
free(state->v_first);
|
|
workspace_free(state->workspace);
|
|
free(state);
|
|
}
|
|
|
|
void rfa_state_reset(rfa_state *state) {
|
|
if (!state) return;
|
|
rfa_config cfg = state->config;
|
|
|
|
state->position = 0;
|
|
|
|
for (int i = 0; i < state->n_rwkv; i++) {
|
|
memset(tm_data_f32(state->rwkv_states[i].att_x_prev), 0,
|
|
cfg.n_embd * sizeof(float));
|
|
memset(tm_data_f32(state->rwkv_states[i].att_kv), 0,
|
|
cfg.n_wkv_heads * cfg.wkv_head_size * cfg.wkv_head_size * sizeof(float));
|
|
}
|
|
|
|
for (int i = 0; i < state->n_qwen; i++) {
|
|
state->kv_caches[i].cache_len = 0;
|
|
}
|
|
|
|
memset(state->v_first, 0, cfg.n_embd * sizeof(float));
|
|
}
|
|
|
|
/* ================================================================
|
|
* Forward pass
|
|
* ================================================================ */
|
|
|
|
tm_tensor* rfa_forward(rfa_model *m, uint32_t token, rfa_state *s) {
|
|
return rfa_forward_ex(m, token, s, NULL, NULL);
|
|
}
|
|
|
|
tm_tensor* rfa_forward_ex(rfa_model *m, uint32_t token, rfa_state *s,
|
|
rfa_block_callback callback, void *user_data) {
|
|
if (!m || !s) return NULL;
|
|
rfa_config cfg = m->config;
|
|
int c = cfg.n_embd;
|
|
int vocab = cfg.vocab_size;
|
|
rfa_workspace *ws = s->workspace;
|
|
|
|
/* Embedding lookup */
|
|
float *emb = tm_data_f32(m->emb_weight);
|
|
float *x_cur = ws->x_buf[0];
|
|
float *x_next = ws->x_buf[1];
|
|
memcpy(x_cur, &emb[token * c], c * sizeof(float));
|
|
|
|
/* Block loop */
|
|
int rwkv_idx = 0, qwen_idx = 0;
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
rfa_block *blk = &m->blocks[i];
|
|
|
|
/* Determine which state to use */
|
|
rfa_rwkv_state *rst = NULL;
|
|
rfa_kv_cache *kvc = NULL;
|
|
int layer_id = 0;
|
|
|
|
if (blk->is_qwen) {
|
|
/* Pre-block callback for Qwen blocks */
|
|
if (callback)
|
|
callback(0, i, qwen_idx, x_cur, user_data);
|
|
|
|
kvc = &s->kv_caches[qwen_idx];
|
|
qwen_idx++;
|
|
} else {
|
|
rst = &s->rwkv_states[rwkv_idx];
|
|
layer_id = rwkv_idx; /* for v_first gating */
|
|
rwkv_idx++;
|
|
}
|
|
|
|
const rfa_block_q8 *bq8 = (m->quantized && m->blocks_q8) ?
|
|
&m->blocks_q8[i] : NULL;
|
|
rfa_block_forward(blk, layer_id, x_cur, rst, kvc,
|
|
s->v_first, &cfg, s->position,
|
|
bq8, ws, x_next);
|
|
|
|
/* Post-block callback for Qwen blocks */
|
|
if (blk->is_qwen && callback)
|
|
callback(1, i, qwen_idx - 1, x_next, user_data);
|
|
|
|
/* Swap buffers */
|
|
float *tmp = x_cur;
|
|
x_cur = x_next;
|
|
x_next = tmp;
|
|
}
|
|
|
|
/* Final RMSNorm */
|
|
fast_rms_norm(x_cur, tm_data_f32(m->ln_out_w), x_next,
|
|
c, cfg.rms_norm_eps);
|
|
|
|
/* Head projection: logits = x @ head_weight */
|
|
int64_t logits_shape[] = {vocab};
|
|
tm_tensor *logits = tm_empty(logits_shape, 1, TM_F32);
|
|
if (m->quantized && m->head_q8)
|
|
rfa_matvec_q8(x_next, m->head_q8, tm_data_f32(logits),
|
|
vocab, c, ws->q8_buf);
|
|
else
|
|
fast_matvec(x_next, tm_data_f32(m->head_weight), tm_data_f32(logits),
|
|
c, vocab);
|
|
|
|
s->position++;
|
|
return logits;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Weight accessors for training
|
|
* ================================================================ */
|
|
|
|
int rfa_model_get_qwen_weights(const rfa_model *model, int qwen_idx,
|
|
rfa_qwen_weights *out) {
|
|
if (!model || !out || qwen_idx < 0) return -1;
|
|
rfa_config cfg = model->config;
|
|
memset(out, 0, sizeof(*out));
|
|
|
|
/* Find the qwen_idx-th Qwen block */
|
|
int qi = 0;
|
|
for (int i = 0; i < cfg.n_layers; i++) {
|
|
const rfa_block *blk = &model->blocks[i];
|
|
if (!blk->is_qwen) continue;
|
|
if (qi == qwen_idx) {
|
|
out->ln1_w = tm_data_f32(blk->ln1_w);
|
|
out->ln2_w = tm_data_f32(blk->ln2_w);
|
|
out->q_proj_w = tm_data_f32(blk->qwen.q_proj_w);
|
|
out->q_proj_b = blk->qwen.q_proj_b ? tm_data_f32(blk->qwen.q_proj_b) : NULL;
|
|
out->k_proj_w = tm_data_f32(blk->qwen.k_proj_w);
|
|
out->k_proj_b = blk->qwen.k_proj_b ? tm_data_f32(blk->qwen.k_proj_b) : NULL;
|
|
out->v_proj_w = tm_data_f32(blk->qwen.v_proj_w);
|
|
out->v_proj_b = blk->qwen.v_proj_b ? tm_data_f32(blk->qwen.v_proj_b) : NULL;
|
|
out->o_proj_w = tm_data_f32(blk->qwen.o_proj_w);
|
|
out->gate_proj = tm_data_f32(blk->gate_proj);
|
|
out->up_proj = tm_data_f32(blk->up_proj);
|
|
out->down_proj = tm_data_f32(blk->down_proj);
|
|
out->inter_size = cfg.intermediate_size;
|
|
return 0;
|
|
}
|
|
qi++;
|
|
}
|
|
return -1; /* qwen_idx out of range */
|
|
}
|
|
|
|
const float* rfa_model_get_head_weight(const rfa_model *model) {
|
|
if (!model || !model->head_weight) return NULL;
|
|
return tm_data_f32(model->head_weight);
|
|
}
|
|
|
|
const float* rfa_model_get_ln_out_w(const rfa_model *model) {
|
|
if (!model || !model->ln_out_w) return NULL;
|
|
return tm_data_f32(model->ln_out_w);
|
|
}
|
|
|
|
int rfa_state_get_kv_cache(const rfa_state *state, int qwen_idx,
|
|
const float **k_cache, const float **v_cache) {
|
|
if (!state || qwen_idx < 0 || qwen_idx >= state->n_qwen) return 0;
|
|
const rfa_kv_cache *kvc = &state->kv_caches[qwen_idx];
|
|
if (k_cache) *k_cache = kvc->k_cache;
|
|
if (v_cache) *v_cache = kvc->v_cache;
|
|
return kvc->cache_len;
|
|
}
|
|
|
|
int rfa_state_get_position(const rfa_state *state) {
|
|
if (!state) return 0;
|
|
return state->position;
|
|
}
|