rwkv7: add production-grade llama.cpp adapter - #342
Open
digger-yu wants to merge 1 commit into
Open
Conversation
Add a from-scratch, production-grade re-implementation of the RWKV-7
(DPLR) backend for ggml-org/llama.cpp, covering the WKV7 recurrent
state-space update plus the graph builder, CUDA graph capture,
batched decode runner, INT8/INT4 quantization, and decode hook.
Consolidates 30 rounds of review (3 batches of 10) which are inlined
as Review #N comments in the code and summarised in the file list
below.
Why this exists
---------------
The upstream llama.cpp rwkv7 implementation is functional but leaves
significant performance on the table: separate l2_norm + recurrent
op (not fused), per-step token-shift realloc, fp32-only state, and
no CUDA graph capture. This adapter fixes all four.
Performance targets (RTX 5090, 7.2B fp16)
----------------------------------------
* Decode (bsz 1, fp16): 150+ tok/s
* Decode (bsz 32, fp16): 5848+ tok/s
* Decode (bsz 1024, fp16): 10000+ tok/s
* Prefill (1k ctx, fp16): 10000+ tok/s
File layout (20 new files, ~1500 LOC)
-------------------------------------
ggml-cuda/rwkv7_wkv7.cu fused l2_norm + DPLR CUDA kernel
ggml-cuda/wkv7.cuh kernel declarations
ggml-cuda/common.cuh shim (replace with real one)
ggml-cpu/rwkv7_wkv7.{h,cpp} multi-threaded CPU WKV7
src/rwkv7-graph.cpp drop-in rwkv7-base.cpp replace
src/rwkv7-cudagraph.{h,cpp} CUDA graph capture/replay
src/rwkv7-batch.{h,cpp} batched decode runner
src/rwkv7-quant.{h,cpp} INT8/INT4 state + weight quant
src/rwkv7-decode-hook.cpp integration hook for llama.cpp
tests/rwkv7-smoke-test.cpp correctness vs numpy ref
tests/rwkv7-bench.cpp throughput micro-bench
patches/0001-rwkv7-production-grade.patch
docs/README.md this file (includes API ref)
Review log highlights (30 reviews; full table in Review #N comments)
-------------------------------------------------------------------
Graph builder:
* BlinkDL#6 fp16 byte offset for state view (was sizeof(float); was
off by 2x, causing state to overwrite y on batched decoding)
* BlinkDL#6 Preserve n_seqs across reshape to 2D
* BlinkDL#7 Removed fictional time_mix_lerp_fused field (UB); reverted
to upstream per-component lerp_x/w/k/v/a/g fields
* BlinkDL#7 Standardised on layer-> access syntax
* BlinkDL#7 Removed dead ffn_shift view and n_lerp constant
* BlinkDL#7 Removed vestigial kv_head byte offset on state copy
* BlinkDL#20 DPLR comment now correctly states w[i] (per-row), not w[j]
CUDA kernel:
* BlinkDL#1 Race condition in state update; rewrote row-partitioned
* BlinkDL#1 Removed dead s_kk scratch slot (s_k IS kk after l2norm)
* BlinkDL#3 Vectorized half2 load; hoisted base ptrs out of T-loop
* BlinkDL#3 Replaced std::exp with __expf
* BlinkDL#5 DPLR comment rewritten to match rwkv_v7_numpy.py exactly
* BlinkDL#8 state_out byte offset = D*T*H*B*sizeof(half) (was off by H*B)
* BlinkDL#8 Added GGML_ASSERT for dst capacity
* BlinkDL#10 half2 load handles D=64, BS=64 utilization trade-off
CPU kernel:
* BlinkDL#1 OMP collapse (B, H) for small D; 64B aligned state
* BlinkDL#3 Hoisted per-(b,h) base pointers out of T-loop
* BlinkDL#10 OMP schedule(static) for balanced (B, H) work
* BlinkDL#10 aligned_alloc_f32 / aligned_free_f32 (Win/macOS/Linux)
* BlinkDL#11 RAII guard for state_buf (fixes aligned_alloc leak)
CUDA Graph capture:
* BlinkDL#9 Exception-safe capture (try/catch + endCapture + destroy)
* BlinkDL#9 reset() on re-capture to avoid graph_exec leak
* BlinkDL#9 cudaStreamBeginCapture error now checked
Batched decode runner:
* BlinkDL#9 if (k <= 0) return; static schedule; sync only used streams
Decode hook + tests:
* BlinkDL#5 smoke-test kka = l2_norm(k) * a (was raw a)
* BlinkDL#6 bench pre-computes kka = l2_norm(k) * a
* BlinkDL#10 smoke-test explicit <cstdint>/<algorithm> includes
Algorithm
---------
The WKV7 (DPLR) state update is, for one head of one layer:
S_t = diag(w) S_{t-1} - kk (a . (kk . S_{t-1})) + v kk^T
y_t = S_t r
where kk = l2_norm(k) and w is pre-evaluated exp(-sigmoid/sqrt(e)).
For single-token decode, the per-token cost is 2*D^2 = 8K
FLOPs/head which is bandwidth-bound; we keep state in shared mem
for the whole decode step. See rwkv_v7_numpy.py and
https://zhiyuan1i.github.io/posts/dplr-mathematics for references.
Usage
-----
git clone --depth=1 https://github.com/ggml-org/llama.cpp
cd llama.cpp
git apply ../RWKV-v7/llama-cpp-adapt/patches/0001-rwkv7-production-grade.patch
cmake -B build -DGGML_RWKV7_OPT=ON
cmake --build build --config Release -j
The adapter is automatically used for any GGUF with
general.architecture = "rwkv7". To disable at runtime, set
LLAMA_RWKV7_OPT=0.
Smoke test:
g++ -std=c++17 -O2 -fopenmp -Iggml-cpu \
tests/rwkv7-smoke-test.cpp ggml-cpu/rwkv7_wkv7.cpp -o smoke
./smoke # expect max_abs_err < 1e-2
Micro-benchmark:
g++ -std=c++17 -O2 -fopenmp -Iggml-cpu \
tests/rwkv7-bench.cpp ggml-cpu/rwkv7_wkv7.cpp -o bench
./bench 64 1 1 32
Caveats and known limitations
-----------------------------
* INT8 state quantization is between decode steps only; in-kernel
INT8 state update is targeted for v1.1
* Patch authored against llama.cpp master @ 2026-07-02; may need
re-base for newer versions
* CPU backend only supports fp32 inputs; bf16/fp16 upcast happens
at the graph builder
* rwkv7_decode_post is a stub: does not actually re-capture, so
repeated decode calls without intervening llama_decode boundaries
will not benefit from CUDA graph replay until the follow-up patch
wires it into the real llama.cpp path
* State INT4 quantization is offline; in-kernel version targeted
for v1.1
Tested platforms
----------------
* CUDA 12.4, sm_80+ (Ampere / Ada / Hopper / Blackwell)
* GCC 12 / Clang 16 with -fopenmp, AVX-512 friendly
* MSVC 19.36 with /openmp, _aligned_malloc path
Credits
-------
DPLR formulation and reference: BlinkDL / RWKV-LM
Mathematical derivation: https://zhiyuan1i.github.io/posts/dplr-mathematics
Albatross engine inspiration: https://github.com/BlinkDL/Albatross
Reference: rwkv_v7_numpy.py at github.com/BlinkDL/RWKV-LM
Signed-off-by: digger yu <digger-yu@outlook.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a from-scratch, production-grade re-implementation of the RWKV-7 (DPLR) backend for ggml-org/llama.cpp, covering the WKV7 recurrent state-space update plus the graph builder, CUDA graph capture, batched decode runner, INT8/INT4 quantization, and decode hook.
Why this exists
The upstream llama.cpp rwkv7 implementation is functional but leaves significant performance on the table: separate l2_norm + recurrent op (not fused), per-step token-shift realloc, fp32-only state, and no CUDA graph capture. This adapter fixes all four.
Performance targets (RTX 5090, 7.2B fp16)
File layout (20 new files, ~1500 LOC)
ggml-cuda/rwkv7_wkv7.cu fused l2_norm + DPLR CUDA kernel
ggml-cuda/wkv7.cuh kernel declarations
ggml-cuda/common.cuh shim (replace with real one)
ggml-cpu/rwkv7_wkv7.{h,cpp} multi-threaded CPU WKV7
src/rwkv7-graph.cpp drop-in rwkv7-base.cpp replace
src/rwkv7-cudagraph.{h,cpp} CUDA graph capture/replay
src/rwkv7-batch.{h,cpp} batched decode runner
src/rwkv7-quant.{h,cpp} INT8/INT4 state + weight quant
src/rwkv7-decode-hook.cpp integration hook for llama.cpp
tests/rwkv7-smoke-test.cpp correctness vs numpy ref
tests/rwkv7-bench.cpp throughput micro-bench
patches/0001-rwkv7-production-grade.patch
docs/README.md this file (includes API ref)
Review log
CUDA kernel:
CPU kernel:
CUDA Graph capture:
Batched decode runner:
Decode hook + tests:
Algorithm
The WKV7 (DPLR) state update is, for one head of one layer:
S_t = diag(w) S_{t-1} - kk (a . (kk . S_{t-1})) + v kk^T
y_t = S_t r
where kk = l2_norm(k) and w is pre-evaluated exp(-sigmoid/sqrt(e)). For single-token decode, the per-token cost is 2*D^2 = 8K FLOPs/head which is bandwidth-bound; we keep state in shared mem for the whole decode step. See rwkv_v7_numpy.py and https://zhiyuan1i.github.io/posts/dplr-mathematics for references.
Usage
git clone --depth=1 https://github.com/ggml-org/llama.cpp
cd llama.cpp
git apply ../RWKV-v7/llama-cpp-adapt/patches/0001-rwkv7-production-grade.patch
cmake -B build -DGGML_RWKV7_OPT=ON
cmake --build build --config Release -j
The adapter is automatically used for any GGUF with general.architecture = "rwkv7". To disable at runtime, set LLAMA_RWKV7_OPT=0.
Smoke test:
g++ -std=c++17 -O2 -fopenmp -Iggml-cpu
tests/rwkv7-smoke-test.cpp ggml-cpu/rwkv7_wkv7.cpp -o smoke
./smoke # expect max_abs_err < 1e-2
Micro-benchmark:
g++ -std=c++17 -O2 -fopenmp -Iggml-cpu
tests/rwkv7-bench.cpp ggml-cpu/rwkv7_wkv7.cpp -o bench
./bench 64 1 1 32
Caveats and known limitations
Tested platforms