Pantheon/docs/architecture/rwkvx.md

5.8 KiB

RWKV-X Architecture

RWKV-X is a hybrid model architecture that interleaves RWKV-7 recurrent blocks (~75% of layers) with sparse attention blocks (~25%). It is fundamentally different from transformer models and uses safetensors format instead of GGUF.

Source

The RWKV-X engine in Pantheon (src/models/rwkvx.c, ~2,500 LOC) is copied from the battle-tested Foundry implementation (foundry/rwkvx/rwkvx.c), with Metal, CUDA, HybridMem, training, batch, instrumentation, and OpenMP support stripped. A thin adapter layer wraps it into Pantheon's pn_model/pn_state/pn_forward_fn interface.

Model Architecture

For the 0.2B model (12 RWKV + 4 attention layers), block execution order is:

R0, R1, R2, A0, R3, R4, R5, A1, R6, R7, R8, A2, R9, R10, R11, A3

Where R = RWKV-7 recurrent block, A = sparse attention block.

Forward Pass

embed(token)
  → for each block in interleaved order:
      if RWKV-7 block:
        LayerNorm → TimeMix (WKV-7 kernel) → residual
        → LayerNorm → ChannelMix FFN → residual
      if attention block:
        LayerNorm → Multi-Head Attention (with KV cache) → residual
        → LayerNorm → ChannelMix FFN → residual
  → LayerNorm → LM head → logits

WKV-7 Kernel (Generalized Delta Rule)

The core recurrent kernel, computed per-head per-timestep:

for each timestep t, for each head h:
  state *= diag(w[t])           // decay
  state += (a[t] @ state) * k^T // delta rule bonus
  state += v[t] @ k[t]^T       // key-value update
  output[t] = r[t] @ state     // readout

State is a [head_size, head_size] matrix per head per RWKV-7 layer — fixed size regardless of sequence length.

Attention Blocks

Standard multi-head attention with KV cache. Uses cached keys and values up to a configurable max length (default 2048). No RoPE — position information comes from the surrounding RWKV-7 blocks.

Weight Format

RWKV-X uses safetensors (not GGUF). Weights are loaded via Foundry's fs_open() (zero-copy mmap) and routed by prefix:

Prefix Block Type
rwkv.blocks.N.* RWKV-7 recurrent block N
moba.blocks.N.* Sparse attention block N
emb.weight Token embeddings
head.weight LM head
ln_out.* Final layer norm

Weight transformations at load time:

  • Linear weights are transposed (safetensors stores [out, in], inference needs [in, out])
  • Singleton dimensions are squeezed
  • att.r_k is flattened from [n_head, head_size] to [n_embd]
  • Embeddings are pre-normalized (L2 norm per row)

INT8 Quantization

When --quantize is passed, all linear projection weights are quantized to INT8 at load time using per-row symmetric quantization:

scale[row] = max(abs(row)) / 127
quantized[row][col] = round(float[row][col] / scale[row])

Inference uses a NEON-accelerated INT8 dot product kernel (fast_matvec_q8) that pre-quantizes the input vector once, then computes INT8 dot products for all output rows. This gives ~3x speedup on ARM.

Tokenizer

RWKV-X uses a trie-based World tokenizer with 65,536 tokens. The tokenizer operates on raw UTF-8 bytes with greedy longest-match — fundamentally different from BPE or SentencePiece.

The vocab is loaded from a text file (rwkv_vocab_v20230424.txt) via the --vocab CLI flag. Each line contains a token ID, token length, and the token's byte representation as a Python byte literal.

The tokenizer is stored inside the RWKV-X model struct (not in pn_model->tokenizer). The engine routes to it via pn_rwkvx_encode()/pn_rwkvx_decode() when config.arch == PN_ARCH_RWKVX.

Dual State

Each inference session maintains two state types in pn_state->kv_cache:

  • Recurrent state: Fixed-size per-layer matrix [n_heads, head_size, head_size] for RWKV-7 blocks
  • KV cache: Key-value store for sparse attention blocks (capped at max_cache_len)
  • Workspace: Pre-allocated scratch buffers (16 buffers + ping-pong pair + INT8 buffer)

All state is encapsulated in a single opaque struct. pn_state_reset() zeros both the recurrent state and KV cache.

Adapter API

The adapter layer in rwkvx.c maps Pantheon's interface to internal RWKV-X functions:

Pantheon API Internal Function
pn_rwkvx_load() rx_model_load_internal()
pn_rwkvx_free() rx_model_free_internal() + tokenizer free
pn_rwkvx_forward() rx_forward_internal()
pn_rwkvx_state_new() rx_state_new_internal()
pn_rwkvx_state_free() rx_state_free_internal()
pn_rwkvx_state_reset() rx_state_reset_internal()
pn_rwkvx_encode() rx_tokenizer_encode()
pn_rwkvx_decode() rx_tokenizer_decode()

Usage

# FP32 inference
./pantheon --model ../models/base/RWKV-X-0.2B-64k-Base.safetensors \
  --vocab foundry/rwkvx/data/rwkv_vocab_v20230424.txt \
  --prompt "Hello world" --max-tokens 32

# INT8 quantized inference (~3x faster decode on ARM)
./pantheon --model ../models/base/RWKV-X-0.2B-64k-Base.safetensors \
  --vocab foundry/rwkvx/data/rwkv_vocab_v20230424.txt \
  --prompt "Hello world" --max-tokens 32 --quantize

What Was Stripped

The Foundry RWKV-X engine has additional features not yet in Pantheon:

Feature Why Stripped
Metal GPU (fm_*) Pantheon doesn't have Metal model integration yet
CUDA GPU (fc_*) Pantheon doesn't have CUDA model integration yet
HybridMem (hm_*) Separate feature, not part of base inference
Training (rx_forward_ex, callbacks) Training not in Pantheon scope yet
Batch forward (rx_forward_batch) Prefill optimization, add later
Instrumentation (attn_log, embed_capture) Debug tooling, add later
OpenMP (#pragma omp) Pantheon uses threadpool, not OpenMP

These can be added incrementally in future phases.