File size: 14,105 Bytes
dacd8a1
 
 
 
 
 
e8ca8bd
f0e7465
 
 
 
 
 
 
 
e8ca8bd
 
 
 
 
 
 
 
 
 
0d72ea4
 
91fdc3c
 
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
0d72ea4
91fdc3c
 
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d37f5a3
 
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d72ea4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91fdc3c
 
 
 
 
 
 
 
0d72ea4
 
 
 
 
e8ca8bd
 
91fdc3c
e8ca8bd
0d72ea4
 
91fdc3c
e8ca8bd
91fdc3c
 
 
 
 
 
 
 
 
0d72ea4
e8ca8bd
 
 
 
 
 
 
 
 
 
 
91fdc3c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8ca8bd
 
0d72ea4
91fdc3c
 
e8ca8bd
91fdc3c
 
 
 
e8ca8bd
 
 
 
 
 
 
 
0d72ea4
 
 
 
 
 
 
 
91fdc3c
 
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d72ea4
 
 
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91fdc3c
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
---
license: apache-2.0
base_model:
- BlinkDL/rwkv7-g1
- allenai/Olmo-3-1025-7B
---
# RWKV-7 "g1d" 0.1B — HuggingFace port (OLMo tokenizer)
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
# If it keeps falling back, check the cache folder first!!!!!!!!!!!!!!!!!!!!Basically, download the 'cuda' folder and place it inside the 'cache' folder.
A `trust_remote_code=True` HuggingFace wrapper around the BlinkDL
**RWKV-7 "Goose" g1d 0.1B** checkpoint
(`rwkv7-g1d-0.1b-20260129-ctx8192.pth`), re-headed for the **OLMo tokenizer**.

- The 12 transformer-style RWKV-7 blocks keep their **pretrained** weights.
- The **embedding** and **lm-head** are **re-initialized** (from RWKV's 65536-token
  vocab to OLMo's 100278-token vocab), so they are *untrained* and need fine-tuning.
- Time-mixing runs on a **fused CUDA kernel** (forward **and** backward) when a GPU
  + CUDA toolchain are available, and transparently **falls back to pure PyTorch**
  otherwise.
- `generate()` runs in **RNN mode with a recurrent state cache** — after the prompt
  is absorbed into the state, each new token is a single-token forward instead of a
  full-sequence recompute. Stateful inference runs on a second, **forward-only
  "wkv7s" CUDA kernel** (state in/out), so prefill and decode are kernel-speed too.

> Because emb/head are freshly initialized, `generate()` produces gibberish until
> you fine-tune on OLMo-tokenized data. The model *body* is pretrained; only the
> vocabulary projection is new.

---

## Files

| File | Purpose |
|------|---------|
| `config.json` | Serialized `RWKV7Config` (dims + `auto_map` to the remote-code classes). |
| `configuration_rwkv7.py` | `RWKV7Config` — all architecture hyperparameters. |
| `modeling_rwkv7.py` | Model code: CUDA-kernel dispatch + PyTorch fallback, recurrent state cache for generation, `RWKV7Model`, `RWKV7ForCausalLM`. |
| `cuda/wkv7_cuda.cu`, `cuda/wkv7_op.cpp` | The fused bf16 "wind_backstepping" RWKV-7 kernel (forward + backward), copied from `RWKV-v7/train_temp/cuda/`. Used on the stateless (training) path. |
| `cuda/wkv7s.cu`, `cuda/wkv7s_op.cpp` | The stateful, forward-only "wkv7s" inference kernel (fp32 state in/out, arbitrary `T`, no chunk padding), adapted from RWKV-LM's inference kernel — patched here for **bf16** (upstream typedef was fp16) and **B > 1** (batch-aware state indexing). Used on the stateful (generation) path. |
| `model.safetensors` | Converted weights (bf16, ~244M params). |
| `tokenizer.json`, `tokenizer_config.json` | OLMo tokenizer (vocab 100278, GPT2-style BPE). |
| `generation_config.json` | Default generation settings (eos/pad ids). |
| `convert.py` | Reproduces `model.safetensors` from the original `.pth`. |
| `verify.py` | End-to-end smoke test (load / forward / parity / backward / generate). |

## Architecture (from `config.json`)

| field | value | meaning |
|-------|-------|---------|
| `num_hidden_layers` | 12 | RWKV-7 blocks |
| `hidden_size` | 768 | embedding dim `C` |
| `head_size` | 64 | → 12 heads (`H = C / head_size`) |
| `intermediate_size` | 3072 | channel-mix (FFN) hidden |
| `decay_lora` / `aaa_lora` / `mv_lora` / `gate_lora` | 64 / 64 / 32 / 128 | LoRA ranks for `w` / `a` / `v` / `g` |
| `vocab_size` | 100278 | OLMo tokenizer size (re-initialized emb/head) |
| `chunk_len` | 16 | CUDA kernel chunk length; sequence is padded to a multiple of this |
| `use_cuda_kernel` | true | prefer the fused kernel when possible |

---

## Usage

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

PATH = "/workspace/rwkv7-g1d-olmo"

tok = AutoTokenizer.from_pretrained(PATH, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    PATH, trust_remote_code=True, dtype=torch.bfloat16
).cuda().eval()

ids = tok("The Eiffel tower is in the city of", return_tensors="pt").input_ids.cuda()
with torch.no_grad():
    logits = model(ids).logits          # (1, T, 100278)

# training / backward
model.train
#🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨labels[labels == tokenizer.pad_token_id] = -100🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
out = model(ids, labels=ids)            # shifted cross-entropy loss
out.loss.backward()                     # gradients flow through the CUDA kernel
```

### Forcing the PyTorch fallback

Set the flag on the config (useful on CPU, non-bf16, or to debug the kernel):

```python
model.config.use_cuda_kernel = False    # every RWKV-7 op now runs in pure PyTorch
```

The kernel is also skipped automatically when: no CUDA device, input dtype is not
bfloat16, or the kernel fails to compile — in all cases the fallback is used and a
one-line notice is printed.

### Stateful generation (RNN mode)

`generate()` uses the state cache automatically — nothing to configure:

```python
out_ids = model.generate(ids, max_new_tokens=64, do_sample=False)
```

You can also drive the state manually (streaming, chunked prefill, state
save/reuse, "infinite context" style loops):

```python
with torch.no_grad():
    out = model(ids, use_cache=True)          # prefill: prompt -> state
    next_tok = out.logits[:, -1].argmax(-1, keepdim=True)
    out = model(next_tok, state=out.state)    # passing `state` implies use_cache
```

`outputs.state` is a list of per-layer tuples `(att_x_prev, wkv_state, ffn_x_prev)`:

| tensor | shape | dtype | role |
|--------|-------|-------|------|
| `att_x_prev` | `(B, C)` | input dtype | last post-`ln1` token, time-shift for the time-mix |
| `wkv_state` | `(B, H, N, N)` | fp32 | the WKV matrix state |
| `ffn_x_prev` | `(B, C)` | input dtype | last post-`ln2` token, time-shift for the channel-mix |

States are ordinary tensors — you can `clone()` them to snapshot/branch a
context, or persist them to disk and resume later.

> **Which implementation runs when?** Stateful calls under `torch.no_grad()`
> (i.e. normal generation) use the forward-only **wkv7s** kernel — one kernel
> launch per forward, any `T`, no chunk padding — so both long-prompt prefill
> and per-token decode are fast. wkv7s has **no backward**, so a stateful call
> with autograd enabled (e.g. TBPTT-style training that carries state) silently
> drops to the sequential PyTorch path instead. The kernel mutates its state
> buffer in place; the wrapper always clones the incoming state first, so a
> `state` you're holding on to is never corrupted (snapshot/branching stays safe).

Zero token-shift state + zero WKV state is exactly equivalent to the zero-padded
non-cached forward, so prefill→decode with the cache reproduces the full-recompute
logits.

---

## How the CUDA kernels + fallback work (`modeling_rwkv7.py`)

The time-mixing recurrence is dispatched by
`run_rwkv7(r, w, k, v, a, b, config, initial_state=None, output_final_state=False)`,
which returns `(y, final_state_or_None)`. Dispatch is three-way:

| call | implementation |
|------|----------------|
| stateless (training / plain forward), CUDA + bf16 | `wind_backstepping` (fused fwd + bwd) |
| stateful (cache), CUDA + bf16, **autograd off** | `wkv7s` (fwd-only, state in/out) |
| everything else (CPU, non-bf16, grad-enabled stateful, compile failure) | pure PyTorch |

1. **Training kernel** (`_rwkv7_cuda`, "wind_backstepping") — used when
   `config.use_cuda_kernel` is set, the tensors are CUDA + bf16, the call is
   **stateless** (`initial_state is None` and no final state requested), and
   `_try_load_cuda_kernel()` succeeds. On first call it
   JIT-compiles `cuda/wkv7_op.cpp` + `cuda/wkv7_cuda.cu` via
   `torch.utils.cpp_extension.load` (flags `-D_C_=head_size`, `-D_CHUNK_LEN_=chunk_len`)
   and registers the `wind_backstepping` op. Compilation happens once per process and
   is cached; any failure is caught and flips the model to the fallback.
   - `_WindBackstepping` is a `torch.autograd.Function`:
     - **forward**`wind_backstepping.forward` (produces `y` plus the saved state
       `s` and `sa` needed for backprop).
     - **backward** → `wind_backstepping.backward` (returns gradients for all six
       inputs `w, q, k, v, z, b`), so training works end-to-end on the kernel.
   - The sequence length is padded to a multiple of `chunk_len` (16) and sliced back.

2. **Inference kernel** (`_rwkv7_cuda_stateful`, "wkv7s") — used for **stateful**
   calls (an incoming state and/or `output_final_state=True`) when the tensors are
   CUDA + bf16 **and autograd is disabled** (`torch.no_grad()` — the kernel has no
   backward). JIT-compiled from `cuda/wkv7s_op.cpp` + `cuda/wkv7s.cu`
   (flag `-D_N_=head_size`), registered as the `wkv7s` op.
   - The time loop lives inside the kernel: a single launch processes any `T`
     (no `chunk_len` padding), fully parallel over `B·H` blocks × `N` threads.
   - The fp32 state `(B, H, N, N)` is read at the start and written back at the
     end (in-place); the Python wrapper clones the incoming state so the caller's
     tensor is never mutated.
   - Patched relative to the upstream RWKV-LM inference kernel: `bf16` typedef
     was actually `at::Half` (fp16) upstream → switched to `at::BFloat16`, and
     the state indexing was `B = 1`-only → made batch-aware.

3. **Fallback path** (`_rwkv7_pytorch`) — a plain sequential-over-time
   implementation of the same recurrence
   `state = state*exp(-exp(w)) + state·aᵀ·b + vᵀ·k`, `y = state·r`, in fp32.
   It is fully differentiable through ordinary autograd (no custom backward needed),
   accepts an `initial_state`, and can return the final state — it covers every
   case the kernels can't (CPU, non-bf16, grad-enabled stateful calls).

All three paths take the **raw (pre-exp) decay `w`** and apply `exp(-exp(w))`
internally, and implement the identical per-head recurrence
(`s[i,j] = s[i,j]·w_j + v_i·k_j + (Σⱼa_j·s[i,j])·b_j`), so they are numerically
interchangeable. `verify.py` confirms kernel-vs-fallback
parity (identical bf16 logits and top-1 prediction).

### Model structure

`RWKV7ForCausalLM``.rwkv` (`RWKV7Model`) + `.head` (lm-head). `RWKV7Model` holds
`emb`, `blocks[0..11]` (`RWKV7Block` = `ln1` + `att` time-mix, `ln2` + `ffn`
channel-mix; block 0 also has `ln0`), and `ln_out`. State-dict keys mirror the
original RWKV layout under the `rwkv.` prefix (e.g. `rwkv.blocks.0.att.receptance.weight`).

Generation runs in **RNN mode**: the model carries a per-layer recurrent state
(see the state table above) through the `state=` kwarg / `outputs.state` field —
the same convention as `transformers`' Rwkv and Mamba models, so
`GenerationMixin` propagates it between steps automatically (`state` is in
`ALL_CACHE_NAMES`; a `_update_model_kwargs_for_generation` override covers older
versions). `prepare_inputs_for_generation` feeds only the last token once a state
exists. Passing `state=` implies `use_cache=True`; gradient checkpointing forces
it off during training. Stateful forwards run on the wkv7s kernel under
`no_grad`, PyTorch otherwise.

---

## Reproducing the conversion (`convert.py`)

```bash
# 1) download the original checkpoint (already done in /workspace)
wget https://huggingface.co/BlinkDL/rwkv7-g1/resolve/main/rwkv7-g1d-0.1b-20260129-ctx8192.pth \
     -O /workspace/rwkv7-g1d-0.1b-20260129-ctx8192.pth

# 2) convert -> /workspace/rwkv7-g1d-olmo
python convert.py
```

What `convert.py` does:

1. Loads the OLMo tokenizer to read the target vocab size (100278).
2. Loads the `.pth` and infers all dims from the tensor shapes → builds `RWKV7Config`.
3. Remaps the original RWKV keys to the HF module layout (`rwkv.` prefix), **dropping**
   `emb.weight` / `head.weight`, and loads them with `strict=False`
   (asserts there are **no** unexpected or unmatched keys besides emb/head).
4. **Re-initializes** the embedding (`uniform(±1e-4)`) and head
   (`orthogonal`, gain `0.5·√(vocab/hidden)`) for the new vocabulary — RWKV's own
   init scheme.
5. Saves weights (`safetensors`), config, tokenizer, and copies the remote-code files.

## Verifying (`verify.py`)

```bash
python verify.py
```

Checks: load via `AutoModelForCausalLM(trust_remote_code=True)`, CUDA-kernel forward,
kernel-vs-fallback parity, cached-vs-uncached parity (prefill + stateful decode vs
full recompute), a backward pass (gradients on emb / attention / decay-LoRA), and a
short greedy `generate()` through the state cache.

> Note: 399/402 parameters receive gradients — the 3 without are
> `blocks.0.att.v0/v1/v2` (the value-residual params are unused in layer 0 by design).

---

## Requirements

- PyTorch with CUDA (bf16-capable GPU; tested on RTX 3060 / CUDA 13.0) for the kernel;
  CPU/other works via the fallback.
- `transformers >= 5`, `safetensors`.
- A working CUDA toolchain (`nvcc`) for first-call kernel JIT compilation; if absent,
  the model still runs on the PyTorch fallback.

## Credits

RWKV-7 architecture and the original checkpoint/kernels by **BlinkDL**<https://github.com/BlinkDL/RWKV-LM>. Training kernel copied from
`RWKV-v7/train_temp/cuda/`; the stateful `wkv7s` inference kernel adapted from
RWKV-LM's inference code (patched here for bf16 + batched state).