quail-test / README.md
Ilikemechuri's picture
Update README.md
f0e7465 verified
|
Raw
History Blame Contribute Delete
14.1 kB
---
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).