Text Generation
Transformers
Safetensors
RWKV
English
Chinese
rwkv7
goose
linear-attention
recurrent
conversational
custom_code
Instructions to use Hakureirm/rwkv7-0.1b-hf with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Hakureirm/rwkv7-0.1b-hf with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Hakureirm/rwkv7-0.1b-hf", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("Hakureirm/rwkv7-0.1b-hf", trust_remote_code=True, device_map="auto") - RWKV
How to use Hakureirm/rwkv7-0.1b-hf with RWKV:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Hakureirm/rwkv7-0.1b-hf with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Hakureirm/rwkv7-0.1b-hf" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Hakureirm/rwkv7-0.1b-hf", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/Hakureirm/rwkv7-0.1b-hf
- SGLang
How to use Hakureirm/rwkv7-0.1b-hf with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Hakureirm/rwkv7-0.1b-hf" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Hakureirm/rwkv7-0.1b-hf", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Hakureirm/rwkv7-0.1b-hf" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Hakureirm/rwkv7-0.1b-hf", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use Hakureirm/rwkv7-0.1b-hf with Docker Model Runner:
docker model run hf.co/Hakureirm/rwkv7-0.1b-hf
Retire the optional kernels, carry the tokenizer, drop dead config keys
Browse files- added_tokens.json +3 -0
- config.json +0 -4
- configuration_rwkv7.py +112 -166
- fused_wkv.py +0 -138
- hf_rwkv_tokenizer.py +279 -0
- modeling_rwkv7.py +29 -234
- rwkv_vocab_v20230424.txt +0 -0
- sparse_channel_mix.py +0 -175
- special_tokens_map.json +6 -0
- tokenizer_config.json +28 -0
added_tokens.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"<|rwkv_tokenizer_end_of_text|>": 0
|
| 3 |
+
}
|
config.json
CHANGED
|
@@ -9,13 +9,10 @@
|
|
| 9 |
},
|
| 10 |
"bos_token_id": 0,
|
| 11 |
"decay_low_rank_dim": 64,
|
| 12 |
-
"deep_embed_size": 768,
|
| 13 |
"dtype": "bfloat16",
|
| 14 |
"eos_token_id": 0,
|
| 15 |
"gate_low_rank_dim": 128,
|
| 16 |
"head_dim": 64,
|
| 17 |
-
"hidden_act": "sqrelu",
|
| 18 |
-
"hidden_ratio": 4.0,
|
| 19 |
"hidden_size": 768,
|
| 20 |
"intermediate_size": 3072,
|
| 21 |
"max_position_embeddings": 4096,
|
|
@@ -25,7 +22,6 @@
|
|
| 25 |
"num_heads": 12,
|
| 26 |
"num_hidden_layers": 12,
|
| 27 |
"pad_token_id": 0,
|
| 28 |
-
"sparse_channel_mix": false,
|
| 29 |
"tie_word_embeddings": false,
|
| 30 |
"transformers_version": "5.15.0.dev0",
|
| 31 |
"use_cache": true,
|
|
|
|
| 9 |
},
|
| 10 |
"bos_token_id": 0,
|
| 11 |
"decay_low_rank_dim": 64,
|
|
|
|
| 12 |
"dtype": "bfloat16",
|
| 13 |
"eos_token_id": 0,
|
| 14 |
"gate_low_rank_dim": 128,
|
| 15 |
"head_dim": 64,
|
|
|
|
|
|
|
| 16 |
"hidden_size": 768,
|
| 17 |
"intermediate_size": 3072,
|
| 18 |
"max_position_embeddings": 4096,
|
|
|
|
| 22 |
"num_heads": 12,
|
| 23 |
"num_hidden_layers": 12,
|
| 24 |
"pad_token_id": 0,
|
|
|
|
| 25 |
"tie_word_embeddings": false,
|
| 26 |
"transformers_version": "5.15.0.dev0",
|
| 27 |
"use_cache": true,
|
configuration_rwkv7.py
CHANGED
|
@@ -13,107 +13,88 @@
|
|
| 13 |
# limitations under the License.
|
| 14 |
"""RWKV-7 (Goose) model configuration."""
|
| 15 |
|
|
|
|
|
|
|
| 16 |
from transformers.configuration_utils import PreTrainedConfig
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
Configuration for [`Rwkv7Model`], an all-recurrent (attention-free) RWKV-7 "Goose"
|
| 22 |
model. Instantiating with the defaults yields the ~0.1B RWKV-7 configuration.
|
| 23 |
|
| 24 |
Parameter names follow the upstream RWKV reference implementation
|
| 25 |
(`BlinkDL/RWKV-LM`) rather than a renamed variant, so converting a native
|
| 26 |
`.pth` checkpoint is close to a rename-free copy.
|
| 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 |
-
low and 11.99% high -- and that projection is a third of the
|
| 96 |
-
model's bytes. Costs a
|
| 97 |
-
transposed copy of the value weight (about +30% weights), built lazily,
|
| 98 |
-
and only pays once launches are captured — see the model doc.
|
| 99 |
-
|
| 100 |
-
Exact but not bit-reproducible: the surviving channels are summed across
|
| 101 |
-
several partitions that combine through an atomic add, so the order the
|
| 102 |
-
partitions arrive in — and therefore the last unit in the last place of
|
| 103 |
-
each output — varies between runs of identical input. Same rounding
|
| 104 |
-
class as a split-K GEMM. Greedy decoding can turn that into a different
|
| 105 |
-
token at a near-tie, so leave this off when you need a run to reproduce
|
| 106 |
-
itself exactly.
|
| 107 |
-
bos_token_id (`int`, *optional*, defaults to 0):
|
| 108 |
-
Beginning-of-sequence id. The RWKV world tokenizer has no dedicated BOS
|
| 109 |
-
token and the reference implementation prepends nothing, so this exists to
|
| 110 |
-
satisfy `GenerationMixin` rather than to be emitted.
|
| 111 |
-
eos_token_id (`int`, *optional*, defaults to 0):
|
| 112 |
-
End-of-sequence id, id 0 in the RWKV world vocabulary.
|
| 113 |
-
pad_token_id (`int`, *optional*, defaults to 0):
|
| 114 |
-
Padding id, the same id 0. Set deliberately rather than left `None`:
|
| 115 |
-
`generate` needs one to pad a batch, and without it a batched call either
|
| 116 |
-
raised or fell back to the eos id with a warning on every step.
|
| 117 |
|
| 118 |
```python
|
| 119 |
>>> from transformers import Rwkv7Config, Rwkv7Model
|
|
@@ -126,81 +107,46 @@ class Rwkv7Config(PreTrainedConfig):
|
|
| 126 |
model_type = "rwkv7"
|
| 127 |
keys_to_ignore_at_inference = ["past_key_values"]
|
| 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 |
-
pad_token_id=0,
|
| 156 |
-
**kwargs,
|
| 157 |
-
):
|
| 158 |
-
self.vocab_size = vocab_size
|
| 159 |
-
self.hidden_size = hidden_size
|
| 160 |
-
self.num_hidden_layers = num_hidden_layers
|
| 161 |
-
self.head_dim = head_dim
|
| 162 |
-
self.num_heads = num_heads
|
| 163 |
-
self.decay_low_rank_dim = decay_low_rank_dim
|
| 164 |
-
self.a_low_rank_dim = a_low_rank_dim
|
| 165 |
-
self.v_low_rank_dim = v_low_rank_dim
|
| 166 |
-
self.gate_low_rank_dim = gate_low_rank_dim
|
| 167 |
-
self.hidden_ratio = hidden_ratio
|
| 168 |
-
# `None` rather than a number, so `hidden_ratio` is what actually decides. It
|
| 169 |
-
# used to default to 3072, which is exactly `768 * 4.0` -- correct for the
|
| 170 |
-
# default `hidden_size` and silently wrong for every other one. A config built
|
| 171 |
-
# as `Rwkv7Config(hidden_size=4096, num_heads=64)` came back with a channel-mix
|
| 172 |
-
# four times narrower than the architecture it names, and `hidden_ratio` was
|
| 173 |
-
# dead code that nothing could reach without passing `intermediate_size=None`
|
| 174 |
-
# explicitly. Serialisation is unaffected: the resolved value is written to
|
| 175 |
-
# `config.json` either way.
|
| 176 |
-
self.intermediate_size = (
|
| 177 |
-
intermediate_size if intermediate_size is not None else int(hidden_size * hidden_ratio)
|
| 178 |
-
)
|
| 179 |
-
self.hidden_act = hidden_act
|
| 180 |
-
self.norm_eps = norm_eps
|
| 181 |
-
self.norm_bias = norm_bias
|
| 182 |
-
self.max_position_embeddings = max_position_embeddings
|
| 183 |
-
self.use_cache = use_cache
|
| 184 |
-
self.use_deep_embed = use_deep_embed
|
| 185 |
-
self.deep_embed_size = deep_embed_size if deep_embed_size is not None else hidden_size
|
| 186 |
-
if wkv_state_dtype not in ("float32", "float16", "bfloat16"):
|
| 187 |
-
raise ValueError(f"wkv_state_dtype must be float32/float16/bfloat16, got {wkv_state_dtype}")
|
| 188 |
-
self.wkv_state_dtype = wkv_state_dtype
|
| 189 |
-
self.wkv_implementation = wkv_implementation
|
| 190 |
-
self.sparse_channel_mix = sparse_channel_mix
|
| 191 |
-
|
| 192 |
-
if hidden_size % head_dim != 0:
|
| 193 |
-
raise ValueError(f"hidden_size {hidden_size} must be divisible by head_dim {head_dim}")
|
| 194 |
-
if num_heads != hidden_size // head_dim:
|
| 195 |
-
raise ValueError(f"num_heads must be hidden_size // head_dim = {hidden_size // head_dim}, got {num_heads}")
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
|
| 206 |
__all__ = ["Rwkv7Config"]
|
|
|
|
| 13 |
# limitations under the License.
|
| 14 |
"""RWKV-7 (Goose) model configuration."""
|
| 15 |
|
| 16 |
+
from huggingface_hub.dataclasses import strict
|
| 17 |
+
|
| 18 |
from transformers.configuration_utils import PreTrainedConfig
|
| 19 |
+
from transformers.utils import auto_docstring
|
| 20 |
|
| 21 |
|
| 22 |
+
@auto_docstring(
|
| 23 |
+
custom_intro="""
|
| 24 |
Configuration for [`Rwkv7Model`], an all-recurrent (attention-free) RWKV-7 "Goose"
|
| 25 |
model. Instantiating with the defaults yields the ~0.1B RWKV-7 configuration.
|
| 26 |
|
| 27 |
Parameter names follow the upstream RWKV reference implementation
|
| 28 |
(`BlinkDL/RWKV-LM`) rather than a renamed variant, so converting a native
|
| 29 |
`.pth` checkpoint is close to a rename-free copy.
|
| 30 |
+
""",
|
| 31 |
+
checkpoint="Hakureirm/rwkv7-168m-pile-hf",
|
| 32 |
+
)
|
| 33 |
+
@strict
|
| 34 |
+
class Rwkv7Config(PreTrainedConfig):
|
| 35 |
+
r"""
|
| 36 |
+
vocab_size (`int`, *optional*, defaults to 65536):
|
| 37 |
+
Vocabulary size (RWKV "world" tokenizer).
|
| 38 |
+
hidden_size (`int`, *optional*, defaults to 768):
|
| 39 |
+
Model width `C`.
|
| 40 |
+
num_hidden_layers (`int`, *optional*, defaults to 12):
|
| 41 |
+
Number of blocks.
|
| 42 |
+
head_dim (`int`, *optional*, defaults to 64):
|
| 43 |
+
Width of one WKV head. `hidden_size` must be divisible by it.
|
| 44 |
+
num_heads (`int`, *optional*, defaults to 12):
|
| 45 |
+
Number of WKV heads; must equal `hidden_size // head_dim`.
|
| 46 |
+
decay_low_rank_dim (`int`, *optional*, defaults to 64):
|
| 47 |
+
Rank of the decay (`w`) LoRA.
|
| 48 |
+
a_low_rank_dim (`int`, *optional*, defaults to 64):
|
| 49 |
+
Rank of the in-context-learning-rate (`a`) LoRA.
|
| 50 |
+
v_low_rank_dim (`int`, *optional*, defaults to 32):
|
| 51 |
+
Rank of the value-residual (`v`) LoRA. Unused on layer 0, which
|
| 52 |
+
*produces* `v_first` instead of mixing towards it.
|
| 53 |
+
gate_low_rank_dim (`int`, *optional*, defaults to 128):
|
| 54 |
+
Rank of the output-gate (`g`) LoRA.
|
| 55 |
+
intermediate_size (`int`, *optional*):
|
| 56 |
+
Channel-mix inner width. Defaults to `4 * hidden_size`.
|
| 57 |
+
norm_eps (`float`, *optional*, defaults to 1e-05):
|
| 58 |
+
Epsilon of every LayerNorm/GroupNorm in the model.
|
| 59 |
+
norm_bias (`bool`, *optional*, defaults to `True`):
|
| 60 |
+
Whether the norms carry a bias.
|
| 61 |
+
max_position_embeddings (`int`, *optional*, defaults to 8192):
|
| 62 |
+
Training context length. RWKV is recurrent and not bounded by it at
|
| 63 |
+
inference; it only sizes generation defaults.
|
| 64 |
+
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
| 65 |
+
Whether to tie the input embedding and the LM head.
|
| 66 |
+
use_cache (`bool`, *optional*, defaults to `True`):
|
| 67 |
+
Whether to return the recurrent state.
|
| 68 |
+
use_deep_embed (`bool`, *optional*, defaults to `False`):
|
| 69 |
+
Enable the RWKV-8 "DeepEmbed" hook: a per-layer, per-token vector that
|
| 70 |
+
channelwise-modulates the channel-mix. The table is deliberately NOT a
|
| 71 |
+
model weight. It is meant to live in RAM/SSD and be prefetched per
|
| 72 |
+
token, which is the whole point of the design (VRAM savings), so it is
|
| 73 |
+
passed to the forward as `deep_embeds` instead. No RWKV-7 checkpoint
|
| 74 |
+
carries one; this is an extension point, off by default.
|
| 75 |
+
wkv_state_dtype (`str`, *optional*, defaults to `"float32"`):
|
| 76 |
+
Precision the recurrent WKV state is carried and accumulated in,
|
| 77 |
+
independently of the activation dtype. The recurrence is unrolled over
|
| 78 |
+
the whole sequence, so a narrow state drifts; `"float32"` with fp16
|
| 79 |
+
activations is the combination the reference implementation uses.
|
| 80 |
+
`"float16"`/`"bfloat16"` trade that for a smaller state.
|
| 81 |
+
wkv_implementation (`str`, *optional*, defaults to `"eager"`):
|
| 82 |
+
Which WKV recurrence to use, by name, from
|
| 83 |
+
`models.rwkv7.modeling_rwkv7.RWKV7_WKV_FUNCTIONS`. `"eager"` is the
|
| 84 |
+
portable PyTorch path: the sequential step when decoding, the
|
| 85 |
+
chunk-parallel form otherwise, and per-segment when a packed batch is
|
| 86 |
+
passed. Register an entry in that mapping to plug in a fused or varlen
|
| 87 |
+
kernel without forking the model.
|
| 88 |
+
bos_token_id (`int`, *optional*, defaults to 0):
|
| 89 |
+
Beginning-of-sequence id. The RWKV world tokenizer has no dedicated BOS
|
| 90 |
+
token and the reference implementation prepends nothing, so this exists to
|
| 91 |
+
satisfy `GenerationMixin` rather than to be emitted.
|
| 92 |
+
eos_token_id (`int`, *optional*, defaults to 0):
|
| 93 |
+
End-of-sequence id, id 0 in the RWKV world vocabulary.
|
| 94 |
+
pad_token_id (`int`, *optional*, defaults to 0):
|
| 95 |
+
Padding id, the same id 0. Set deliberately rather than left `None`:
|
| 96 |
+
`generate` needs one to pad a batch, and without it a batched call either
|
| 97 |
+
raised or fell back to the eos id with a warning on every step.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
```python
|
| 100 |
>>> from transformers import Rwkv7Config, Rwkv7Model
|
|
|
|
| 107 |
model_type = "rwkv7"
|
| 108 |
keys_to_ignore_at_inference = ["past_key_values"]
|
| 109 |
|
| 110 |
+
vocab_size: int = 65536
|
| 111 |
+
hidden_size: int = 768
|
| 112 |
+
num_hidden_layers: int = 12
|
| 113 |
+
head_dim: int = 64
|
| 114 |
+
num_heads: int = 12
|
| 115 |
+
decay_low_rank_dim: int = 64
|
| 116 |
+
a_low_rank_dim: int = 64
|
| 117 |
+
v_low_rank_dim: int = 32
|
| 118 |
+
gate_low_rank_dim: int = 128
|
| 119 |
+
# `None` rather than a number: a literal default is correct for the default
|
| 120 |
+
# `hidden_size` and silently wrong for every other one, so a config built as
|
| 121 |
+
# `Rwkv7Config(hidden_size=4096, num_heads=64)` would come back with a channel-mix
|
| 122 |
+
# four times narrower than the architecture it names. `__post_init__` resolves it,
|
| 123 |
+
# and the resolved value is written to `config.json` either way.
|
| 124 |
+
intermediate_size: int | None = None
|
| 125 |
+
norm_eps: float = 1e-5
|
| 126 |
+
norm_bias: bool = True
|
| 127 |
+
max_position_embeddings: int = 8192
|
| 128 |
+
tie_word_embeddings: bool = False
|
| 129 |
+
use_cache: bool = True
|
| 130 |
+
use_deep_embed: bool = False
|
| 131 |
+
wkv_state_dtype: str = "float32"
|
| 132 |
+
wkv_implementation: str = "eager"
|
| 133 |
+
bos_token_id: int | None = 0
|
| 134 |
+
eos_token_id: int | None = 0
|
| 135 |
+
pad_token_id: int | None = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
+
def __post_init__(self, **kwargs):
|
| 138 |
+
if self.intermediate_size is None:
|
| 139 |
+
self.intermediate_size = 4 * self.hidden_size
|
| 140 |
+
if self.wkv_state_dtype not in ("float32", "float16", "bfloat16"):
|
| 141 |
+
raise ValueError(f"wkv_state_dtype must be float32/float16/bfloat16, got {self.wkv_state_dtype}")
|
| 142 |
+
if self.hidden_size % self.head_dim != 0:
|
| 143 |
+
raise ValueError(f"hidden_size {self.hidden_size} must be divisible by head_dim {self.head_dim}")
|
| 144 |
+
if self.num_heads != self.hidden_size // self.head_dim:
|
| 145 |
+
raise ValueError(
|
| 146 |
+
f"num_heads must be hidden_size // head_dim = {self.hidden_size // self.head_dim}, "
|
| 147 |
+
f"got {self.num_heads}"
|
| 148 |
+
)
|
| 149 |
+
super().__post_init__(**kwargs)
|
| 150 |
|
| 151 |
|
| 152 |
__all__ = ["Rwkv7Config"]
|
fused_wkv.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The RWKV team and The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
"""Single-token WKV in one Triton kernel: the state is read once and written once.
|
| 15 |
-
|
| 16 |
-
The portable recurrence in `modeling_rwkv7.rwkv7_recurrent` touches the state four
|
| 17 |
-
times for one decoded token -- once to form `sa = (-kk) @ S`, once to update `S`,
|
| 18 |
-
once to read it back for `r @ S`, once to store it. That is the natural way to write
|
| 19 |
-
it and it is free at batch 1, where the state is a megabyte a layer and invisible
|
| 20 |
-
next to the weights being streamed.
|
| 21 |
-
|
| 22 |
-
It stops being free with batch. At batch 256 and 7.2B's shape the state is
|
| 23 |
-
`[256, 64, 64, 64]` -- 268 MB a layer, 8.6 GB across 32 layers -- and profiling put
|
| 24 |
-
77% of a decode step inside the time-mix while its own projections were 5.5% of it.
|
| 25 |
-
The cost was the state, not the weights, and four passes over it rather than two.
|
| 26 |
-
|
| 27 |
-
This does the whole update with the `[head_dim, head_dim]` tile resident. Measured on
|
| 28 |
-
one RTX 5090 at 7.2B: 5.7x on the recurrence at batch 256, and the whole model's
|
| 29 |
-
batched decode goes from 55% of the albatross reference to 85%.
|
| 30 |
-
|
| 31 |
-
Called as a plain function rather than wrapped in a `custom_op`. Dynamo traces a
|
| 32 |
-
`@triton.jit` launch natively, and the wrapper is opaque to it -- with the state
|
| 33 |
-
declared as mutated, inductor stops issuing CUDA graphs for the region. That is
|
| 34 |
-
invisible at batch 256, where the kernel wins anyway, and costs everything at batch
|
| 35 |
-
1: 138.9 tok/s wrapped becomes 57.9, which is eager speed. `sparse_channel_mix` is a
|
| 36 |
-
`custom_op` because its three kernels and its compaction buffers are genuinely
|
| 37 |
-
opaque; one traced kernel is better off without the wrapper.
|
| 38 |
-
"""
|
| 39 |
-
|
| 40 |
-
import torch
|
| 41 |
-
from transformers.utils.import_utils import is_triton_available
|
| 42 |
-
|
| 43 |
-
# Guarded rather than bare, although the module itself is only ever imported
|
| 44 |
-
# under the same `is_triton_available()` condition: import scanners that parse
|
| 45 |
-
# this file in isolation (transformers' remote-code `check_imports` is one)
|
| 46 |
-
# exempt imports inside an availability-guarded block, and demand triton on
|
| 47 |
-
# every machine otherwise.
|
| 48 |
-
if is_triton_available():
|
| 49 |
-
import triton
|
| 50 |
-
import triton.language as tl
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
@triton.jit
|
| 54 |
-
def _wkv_one_kernel(
|
| 55 |
-
state_ptr,
|
| 56 |
-
r_ptr,
|
| 57 |
-
w_ptr,
|
| 58 |
-
k_ptr,
|
| 59 |
-
v_ptr,
|
| 60 |
-
kk_ptr,
|
| 61 |
-
a_ptr,
|
| 62 |
-
out_ptr,
|
| 63 |
-
stride_sb,
|
| 64 |
-
stride_sh,
|
| 65 |
-
stride_si,
|
| 66 |
-
stride_sj,
|
| 67 |
-
stride_vb,
|
| 68 |
-
stride_vh,
|
| 69 |
-
N: tl.constexpr,
|
| 70 |
-
):
|
| 71 |
-
"""One program per (batch, head).
|
| 72 |
-
|
| 73 |
-
`N` is `head_dim` and is a compile-time constant, so the `[N, N]` tile lives in
|
| 74 |
-
registers for the whole body: 64x64 fp32 is 16 KB, which is what makes reading
|
| 75 |
-
the state once sufficient.
|
| 76 |
-
"""
|
| 77 |
-
batch = tl.program_id(0)
|
| 78 |
-
head = tl.program_id(1)
|
| 79 |
-
|
| 80 |
-
i = tl.arange(0, N)
|
| 81 |
-
j = tl.arange(0, N)
|
| 82 |
-
vec = batch * stride_vb + head * stride_vh
|
| 83 |
-
r = tl.load(r_ptr + vec + i).to(tl.float32)
|
| 84 |
-
w = tl.load(w_ptr + vec + i).to(tl.float32)
|
| 85 |
-
k = tl.load(k_ptr + vec + i).to(tl.float32)
|
| 86 |
-
v = tl.load(v_ptr + vec + j).to(tl.float32)
|
| 87 |
-
kk = tl.load(kk_ptr + vec + i).to(tl.float32)
|
| 88 |
-
a = tl.load(a_ptr + vec + i).to(tl.float32)
|
| 89 |
-
|
| 90 |
-
# Every axis of the state is strided, the last one included. Hardcoding unit
|
| 91 |
-
# stride there reads the tile transposed when the caller hands over anything
|
| 92 |
-
# that is not contiguous, and the result is wrong by 80-98% with nothing
|
| 93 |
-
# raised: the shape is right, so neither the kernel nor Triton notices.
|
| 94 |
-
off = batch * stride_sb + head * stride_sh + i[:, None] * stride_si + j[None, :] * stride_sj
|
| 95 |
-
state = tl.load(state_ptr + off).to(tl.float32)
|
| 96 |
-
|
| 97 |
-
# Same three lines as the reference, in the same order and in fp32: `sa` uses the
|
| 98 |
-
# PRE-update state and the output uses the POST-update one.
|
| 99 |
-
sa = tl.sum((-kk)[:, None] * state, axis=0)
|
| 100 |
-
state = tl.exp(w)[:, None] * state + (kk * a)[:, None] * sa[None, :] + k[:, None] * v[None, :]
|
| 101 |
-
out = tl.sum(r[:, None] * state, axis=0)
|
| 102 |
-
|
| 103 |
-
tl.store(state_ptr + off, state.to(state_ptr.dtype.element_ty))
|
| 104 |
-
tl.store(out_ptr + vec + j, out.to(out_ptr.dtype.element_ty))
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
def fused_wkv_one(
|
| 108 |
-
r: torch.Tensor,
|
| 109 |
-
w_log: torch.Tensor,
|
| 110 |
-
k: torch.Tensor,
|
| 111 |
-
v: torch.Tensor,
|
| 112 |
-
kk: torch.Tensor,
|
| 113 |
-
a: torch.Tensor,
|
| 114 |
-
state: torch.Tensor,
|
| 115 |
-
) -> torch.Tensor:
|
| 116 |
-
batch, _, heads, head_dim = r.shape
|
| 117 |
-
# The grid comes from `r`, so a state that disagrees with it is read at offsets
|
| 118 |
-
# belonging to another row and returns a plausible answer for the wrong sequence.
|
| 119 |
-
# Checked rather than assumed: the kernel cannot tell, and neither can the caller.
|
| 120 |
-
if tuple(state.shape) != (batch, heads, head_dim, head_dim):
|
| 121 |
-
raise ValueError(
|
| 122 |
-
f"state has shape {tuple(state.shape)}, but the vectors imply {(batch, heads, head_dim, head_dim)}"
|
| 123 |
-
)
|
| 124 |
-
out = torch.empty(batch, heads, head_dim, device=r.device, dtype=r.dtype)
|
| 125 |
-
flat = [t.reshape(batch, heads, head_dim).contiguous() for t in (r, w_log, k, v, kk, a)]
|
| 126 |
-
_wkv_one_kernel[(batch, heads)](
|
| 127 |
-
state,
|
| 128 |
-
*flat,
|
| 129 |
-
out,
|
| 130 |
-
state.stride(0),
|
| 131 |
-
state.stride(1),
|
| 132 |
-
state.stride(2),
|
| 133 |
-
state.stride(3),
|
| 134 |
-
out.stride(0),
|
| 135 |
-
out.stride(1),
|
| 136 |
-
N=head_dim,
|
| 137 |
-
)
|
| 138 |
-
return out.view(batch, 1, heads, head_dim)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_rwkv_tokenizer.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2024 The HuggingFace Inc. team.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
"""Tokenization classes for RWKV."""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import re
|
| 19 |
+
from typing import TYPE_CHECKING, List, Optional, Tuple
|
| 20 |
+
|
| 21 |
+
from transformers.tokenization_utils import AddedToken, PreTrainedTokenizer
|
| 22 |
+
from transformers.utils import logging
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
if TYPE_CHECKING:
|
| 26 |
+
pass
|
| 27 |
+
|
| 28 |
+
logger = logging.get_logger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
VOCAB_FILES_NAMES = {
|
| 32 |
+
"vocab_file": "rwkv_vocab_v20230424.txt",
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
class TRIE:
|
| 36 |
+
__slots__ = tuple("ch,to,values,front".split(","))
|
| 37 |
+
to: list
|
| 38 |
+
values: set
|
| 39 |
+
|
| 40 |
+
def __init__(self, front=None, ch=None):
|
| 41 |
+
self.ch = ch
|
| 42 |
+
self.to = [None for ch in range(256)]
|
| 43 |
+
self.values = set()
|
| 44 |
+
self.front = front
|
| 45 |
+
|
| 46 |
+
def __repr__(self):
|
| 47 |
+
fr = self
|
| 48 |
+
ret = []
|
| 49 |
+
while fr != None:
|
| 50 |
+
if fr.ch != None:
|
| 51 |
+
ret.append(fr.ch)
|
| 52 |
+
fr = fr.front
|
| 53 |
+
return "<TRIE %s %s>" % (ret[::-1], self.values)
|
| 54 |
+
|
| 55 |
+
def add(self, key: bytes, idx: int = 0, val=None):
|
| 56 |
+
if idx == len(key):
|
| 57 |
+
if val is None:
|
| 58 |
+
val = key
|
| 59 |
+
self.values.add(val)
|
| 60 |
+
return self
|
| 61 |
+
ch = key[idx]
|
| 62 |
+
if self.to[ch] is None:
|
| 63 |
+
self.to[ch] = TRIE(front=self, ch=ch)
|
| 64 |
+
return self.to[ch].add(key, idx=idx + 1, val=val)
|
| 65 |
+
|
| 66 |
+
def find_longest(self, key: bytes, idx: int = 0):
|
| 67 |
+
u: TRIE = self
|
| 68 |
+
ch: int = key[idx]
|
| 69 |
+
|
| 70 |
+
while u.to[ch] is not None:
|
| 71 |
+
u = u.to[ch]
|
| 72 |
+
idx += 1
|
| 73 |
+
if u.values:
|
| 74 |
+
ret = idx, u, u.values
|
| 75 |
+
if idx == len(key):
|
| 76 |
+
break
|
| 77 |
+
ch = key[idx]
|
| 78 |
+
return ret
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class RWKV_TOKENIZER:
|
| 82 |
+
def __init__(self, file_name):
|
| 83 |
+
self.idx2token = {}
|
| 84 |
+
sorted = [] # must be already sorted
|
| 85 |
+
with open(file_name, "r", encoding="utf-8") as f:
|
| 86 |
+
lines = f.readlines()
|
| 87 |
+
for l in lines:
|
| 88 |
+
idx = int(l[: l.index(" ")])
|
| 89 |
+
x = eval(l[l.index(" ") : l.rindex(" ")])
|
| 90 |
+
x = x.encode("utf-8") if isinstance(x, str) else x
|
| 91 |
+
assert isinstance(x, bytes)
|
| 92 |
+
|
| 93 |
+
assert len(x) == int(l[l.rindex(" ") :])
|
| 94 |
+
sorted += [x]
|
| 95 |
+
self.idx2token[idx] = x
|
| 96 |
+
|
| 97 |
+
self.token2idx = {}
|
| 98 |
+
for k, v in self.idx2token.items():
|
| 99 |
+
self.token2idx[v] = int(k)
|
| 100 |
+
|
| 101 |
+
self.root = TRIE()
|
| 102 |
+
for t, i in self.token2idx.items():
|
| 103 |
+
_ = self.root.add(t, val=(t, i))
|
| 104 |
+
|
| 105 |
+
def encodeBytes(self, src: bytes):
|
| 106 |
+
idx: int = 0
|
| 107 |
+
tokens = []
|
| 108 |
+
while idx < len(src):
|
| 109 |
+
_idx: int = idx
|
| 110 |
+
idx, _, values = self.root.find_longest(src, idx)
|
| 111 |
+
assert idx != _idx
|
| 112 |
+
_, token = next(iter(values))
|
| 113 |
+
tokens.append(token)
|
| 114 |
+
return tokens
|
| 115 |
+
|
| 116 |
+
def decodeBytes(self, tokens):
|
| 117 |
+
return b"".join(map(lambda i: self.idx2token[i], tokens))
|
| 118 |
+
|
| 119 |
+
def encode(self, src):
|
| 120 |
+
if isinstance(src, str):
|
| 121 |
+
return [self.encodeBytes(src.encode("utf-8"))]
|
| 122 |
+
elif isinstance(src, list):
|
| 123 |
+
return [self.encodeBytes(s.encode("utf-8")) for s in src]
|
| 124 |
+
|
| 125 |
+
def decode(self, tokens):
|
| 126 |
+
return [self.decodeBytes(batch).decode("utf-8") for batch in tokens]
|
| 127 |
+
# try:
|
| 128 |
+
# return self.decodeBytes(tokens).decode('utf-8')
|
| 129 |
+
# except:
|
| 130 |
+
# return '\ufffd' # bad utf-8
|
| 131 |
+
|
| 132 |
+
def printTokens(self, tokens):
|
| 133 |
+
for i in tokens:
|
| 134 |
+
s = self.idx2token[i]
|
| 135 |
+
try:
|
| 136 |
+
s = s.decode("utf-8")
|
| 137 |
+
except:
|
| 138 |
+
pass
|
| 139 |
+
print(f"{repr(s)}{i}", end=" ")
|
| 140 |
+
print()
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class RwkvTokenizer(PreTrainedTokenizer):
|
| 144 |
+
vocab_files_names = VOCAB_FILES_NAMES
|
| 145 |
+
model_input_names = ["input_ids", "attention_mask"]
|
| 146 |
+
|
| 147 |
+
def __init__(
|
| 148 |
+
self, vocab_file, bos_token="<|rwkv_tokenizer_end_of_text|>", eos_token="<|rwkv_tokenizer_end_of_text|>", unk_token="<|rwkv_tokenizer_end_of_text|>", **kwargs
|
| 149 |
+
):
|
| 150 |
+
if not os.path.isfile(vocab_file):
|
| 151 |
+
raise ValueError(
|
| 152 |
+
f"Can't find a vocabulary file at path '{vocab_file}'."
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
with open(vocab_file, "r", encoding="utf-8") as reader:
|
| 156 |
+
tokens = reader.readlines()
|
| 157 |
+
|
| 158 |
+
if "add_bos_token" in kwargs:
|
| 159 |
+
self.add_bos_token = kwargs["add_bos_token"]
|
| 160 |
+
else:
|
| 161 |
+
self.add_bos_token = False
|
| 162 |
+
self.trie_tokenizer = RWKV_TOKENIZER(vocab_file)
|
| 163 |
+
vocab = self.trie_tokenizer.token2idx
|
| 164 |
+
self.encoder = vocab
|
| 165 |
+
self.decoder = {v: k for k, v in vocab.items()}
|
| 166 |
+
self._added_tokens_decoder = {0: AddedToken(str(bos_token))}
|
| 167 |
+
super().__init__(
|
| 168 |
+
bos_token=bos_token, eos_token=eos_token, unk_token=unk_token, **kwargs
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
@property
|
| 172 |
+
def vocab_size(self):
|
| 173 |
+
return len(self.encoder)
|
| 174 |
+
|
| 175 |
+
def get_vocab(self):
|
| 176 |
+
vocab = self.encoder
|
| 177 |
+
vocab.update(self.added_tokens_encoder)
|
| 178 |
+
vocab = dict(sorted(vocab.items(), key=lambda item: item[1]))
|
| 179 |
+
return vocab
|
| 180 |
+
|
| 181 |
+
def _tokenize(self, text, split_special_tokens=False):
|
| 182 |
+
# return self.wordpiece_tokenizer.tokenize(text.encode("utf-8"))
|
| 183 |
+
return self.trie_tokenizer.encode(text)[0]
|
| 184 |
+
|
| 185 |
+
def _convert_token_to_id(self, token):
|
| 186 |
+
return token
|
| 187 |
+
|
| 188 |
+
def _convert_id_to_token(self, index):
|
| 189 |
+
"""Converts an index (integer) in a token (byte) using the vocab."""
|
| 190 |
+
token = self.decoder.get(index, self.unk_token)
|
| 191 |
+
if isinstance(token, (bytes)):
|
| 192 |
+
token = token.decode("utf-8", errors="replace")
|
| 193 |
+
return token
|
| 194 |
+
|
| 195 |
+
def convert_tokens_to_string(self, tokens):
|
| 196 |
+
"""Converts a sequence of tokens (bytes) in a single string. Additional tokens are encoded to bytes"""
|
| 197 |
+
out_string = b"".join(
|
| 198 |
+
[k.encode(errors="replace") if isinstance(k, str) else k for k in tokens]
|
| 199 |
+
).decode("utf-8")
|
| 200 |
+
return out_string
|
| 201 |
+
|
| 202 |
+
def save_vocabulary(
|
| 203 |
+
self, save_directory: str, filename_prefix: Optional[str] = None
|
| 204 |
+
) -> Tuple[str]:
|
| 205 |
+
index = 0
|
| 206 |
+
if os.path.isdir(save_directory):
|
| 207 |
+
vocab_file = os.path.join(
|
| 208 |
+
save_directory,
|
| 209 |
+
(filename_prefix + "-" if filename_prefix else "") + "vocab.txt",
|
| 210 |
+
)
|
| 211 |
+
else:
|
| 212 |
+
vocab_file = (
|
| 213 |
+
filename_prefix + "-" if filename_prefix else ""
|
| 214 |
+
) + save_directory
|
| 215 |
+
with open(vocab_file, "w", encoding="utf-8") as writer:
|
| 216 |
+
for token, token_index in sorted(
|
| 217 |
+
self.encoder.items(), key=lambda kv: kv[1]
|
| 218 |
+
):
|
| 219 |
+
if index != token_index:
|
| 220 |
+
logger.warning(
|
| 221 |
+
f"Saving vocabulary to {vocab_file}: vocabulary indices are not consecutive."
|
| 222 |
+
" Please check that the vocabulary is not corrupted!"
|
| 223 |
+
)
|
| 224 |
+
index = token_index
|
| 225 |
+
writer.write(str(token) + "\n")
|
| 226 |
+
index += 1
|
| 227 |
+
return (vocab_file,)
|
| 228 |
+
|
| 229 |
+
def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
|
| 230 |
+
if self.add_bos_token:
|
| 231 |
+
bos_token_ids = [self.bos_token_id]
|
| 232 |
+
else:
|
| 233 |
+
bos_token_ids = []
|
| 234 |
+
|
| 235 |
+
output = bos_token_ids + token_ids_0
|
| 236 |
+
|
| 237 |
+
if token_ids_1 is None:
|
| 238 |
+
return output
|
| 239 |
+
|
| 240 |
+
return output + bos_token_ids + token_ids_1
|
| 241 |
+
|
| 242 |
+
def get_special_tokens_mask(
|
| 243 |
+
self,
|
| 244 |
+
token_ids_0: List[int],
|
| 245 |
+
token_ids_1: Optional[List[int]] = None,
|
| 246 |
+
already_has_special_tokens: bool = False,
|
| 247 |
+
) -> List[int]:
|
| 248 |
+
"""
|
| 249 |
+
Retrieves sequence ids from a token list that has no special tokens added. This method is called when adding
|
| 250 |
+
special tokens using the tokenizer `prepare_for_model` or `encode_plus` methods.
|
| 251 |
+
|
| 252 |
+
Args:
|
| 253 |
+
token_ids_0 (`List[int]`):
|
| 254 |
+
List of IDs.
|
| 255 |
+
token_ids_1 (`List[int]`, *optional*):
|
| 256 |
+
Optional second list of IDs for sequence pairs.
|
| 257 |
+
already_has_special_tokens (`bool`, *optional*, defaults to `False`):
|
| 258 |
+
Whether or not the token list is already formatted with special tokens for the model.
|
| 259 |
+
|
| 260 |
+
Returns:
|
| 261 |
+
`List[int]`: A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.
|
| 262 |
+
"""
|
| 263 |
+
if already_has_special_tokens:
|
| 264 |
+
return super().get_special_tokens_mask(
|
| 265 |
+
token_ids_0=token_ids_0,
|
| 266 |
+
token_ids_1=token_ids_1,
|
| 267 |
+
already_has_special_tokens=True,
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
if not self.add_bos_token:
|
| 271 |
+
return super().get_special_tokens_mask(
|
| 272 |
+
token_ids_0=token_ids_0,
|
| 273 |
+
token_ids_1=token_ids_1,
|
| 274 |
+
already_has_special_tokens=False,
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
if token_ids_1 is None:
|
| 278 |
+
return [1] + ([0] * len(token_ids_0))
|
| 279 |
+
return [1] + ([0] * len(token_ids_0)) + [1] + ([0] * len(token_ids_1))
|
modeling_rwkv7.py
CHANGED
|
@@ -30,22 +30,9 @@ from transformers.initialization import zeros_
|
|
| 30 |
from transformers.modeling_layers import GradientCheckpointingLayer
|
| 31 |
from transformers.modeling_utils import PreTrainedModel
|
| 32 |
from transformers.utils import ModelOutput, auto_docstring, can_return_tuple, logging
|
| 33 |
-
from transformers.utils.import_utils import is_triton_available
|
| 34 |
from .configuration_rwkv7 import Rwkv7Config
|
| 35 |
|
| 36 |
|
| 37 |
-
# Imported at module scope, NOT lazily from the forward. A function containing an
|
| 38 |
-
# `import` is on dynamo's skip list whether or not the import actually runs, so
|
| 39 |
-
# calling one per layer breaks the graph at every layer -- which cost 138.9 tok/s at
|
| 40 |
-
# batch 1 down to 47.4, eager speed, while the kernel itself was fine. `sparse_channel_mix`
|
| 41 |
-
# avoids this by importing from `allocate_state`, outside anything traced; there is no
|
| 42 |
-
# equivalent hook here, so it happens once at import.
|
| 43 |
-
if is_triton_available():
|
| 44 |
-
from .fused_wkv import fused_wkv_one
|
| 45 |
-
else: # pragma: no cover - exercised only where triton is absent
|
| 46 |
-
fused_wkv_one = None
|
| 47 |
-
|
| 48 |
-
|
| 49 |
logger = logging.get_logger(__name__)
|
| 50 |
|
| 51 |
|
|
@@ -72,7 +59,7 @@ class Rwkv7TokenShift(nn.Module):
|
|
| 72 |
if cu_seq_lens is not None:
|
| 73 |
# In a packed row the token before a segment's first one belongs to the
|
| 74 |
# PREVIOUS sequence. Resetting the recurrent state per segment does not
|
| 75 |
-
# cover this
|
| 76 |
# each segment gets the zero shift a sequence start is supposed to see.
|
| 77 |
positions = torch.arange(x.shape[1], device=x.device)
|
| 78 |
starts = (positions[:, None] == cu_seq_lens[None, :-1]).any(dim=1)
|
|
@@ -97,7 +84,7 @@ class Rwkv7TokenShift(nn.Module):
|
|
| 97 |
|
| 98 |
# e^-0.5. The decay LoRA emits w_log = -INV_SQRT_E * sigmoid(...), so w_log lies in
|
| 99 |
# (-e^-0.5, 0) and the per-step decay exp(w_log) lies in (exp(-e^-0.5), 1), i.e.
|
| 100 |
-
# (0.5452, 1)
|
| 101 |
# comment said the latter, and `rwkv7_chunked` built its chunk_size bound on it.
|
| 102 |
_INV_SQRT_E = 0.6065306597126334
|
| 103 |
|
|
@@ -126,7 +113,7 @@ def rwkv7_recurrent(
|
|
| 126 |
out = r @ S # uses the POST-update state
|
| 127 |
|
| 128 |
The state axes are (key, value); `S[i, j]` accumulates key channel `i` against
|
| 129 |
-
value channel `j`. Accumulation is fp32 regardless of the activation dtype
|
| 130 |
the recurrence is unrolled over the whole sequence, so a lower-precision state
|
| 131 |
drifts. This is the portable path; a fused kernel may replace it as long as it
|
| 132 |
reproduces these values.
|
|
@@ -180,14 +167,14 @@ def rwkv7_chunked(
|
|
| 180 |
|
| 181 |
`chunk_size` is bounded by that division by `c`, but by OVERFLOW rather than by
|
| 182 |
precision, and the difference is worth a factor of four. The per-step decay is at
|
| 183 |
-
least `exp(-e^-0.5)` = 0.5452, so at the worst case
|
| 184 |
-
floor
|
| 185 |
fp32 tops out near 3.4e38, so there are twenty-odd decades of headroom left at the
|
| 186 |
default, and the ceiling is `ln(finfo.max) / e^-0.5` = 146 in fp32.
|
| 187 |
|
| 188 |
That derivation is worth stating carefully because the first version of it was
|
| 189 |
wrong in a way the numbers hid. It said the decay floor was `e^-0.5` rather than
|
| 190 |
-
`exp(-e^-0.5)`, hence growth like `e^(0.5 * chunk_size)`
|
| 191 |
177, and measured at the decay floor this function returns all-NaN from 147 up. The
|
| 192 |
quoted 7.2e16 came from the correct law all along (`e^(0.5*64)` is 7.9e13), so the
|
| 193 |
arithmetic had been done right and written up wrong, and following the prose rather
|
|
@@ -292,7 +279,7 @@ def rwkv7_eager(
|
|
| 292 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 293 |
"""Portable WKV: the sequential step for a single token, chunk-parallel otherwise.
|
| 294 |
|
| 295 |
-
With `cu_seq_lens` the row is a *packed* batch
|
| 296 |
laid end to end, the layout a varlen kernel consumes. Each one has to start
|
| 297 |
from a fresh state, so they are run in turn and their outputs concatenated;
|
| 298 |
the state returned is the last segment's, which is the one a continuation
|
|
@@ -326,55 +313,6 @@ def rwkv7_eager(
|
|
| 326 |
RWKV7_WKV_FUNCTIONS = {"eager": rwkv7_eager}
|
| 327 |
|
| 328 |
|
| 329 |
-
def rwkv7_fused(
|
| 330 |
-
r: torch.Tensor,
|
| 331 |
-
w_log: torch.Tensor,
|
| 332 |
-
k: torch.Tensor,
|
| 333 |
-
v: torch.Tensor,
|
| 334 |
-
kk: torch.Tensor,
|
| 335 |
-
a: torch.Tensor,
|
| 336 |
-
state: torch.Tensor,
|
| 337 |
-
cu_seq_lens: torch.Tensor | None = None,
|
| 338 |
-
**kwargs,
|
| 339 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 340 |
-
"""`"eager"`, with the single-token step replaced by a fused Triton kernel.
|
| 341 |
-
|
| 342 |
-
Only the decode step changes; prefill and packed batches fall through to the
|
| 343 |
-
portable path, which is also where this lands when Triton is missing or the tensors
|
| 344 |
-
are on CPU. A narrower state is NOT a fallback case -- the kernel stores back
|
| 345 |
-
through the state's own dtype, which is the point of letting `wkv_state_dtype`
|
| 346 |
-
select it, and this docstring claimed the opposite for several commits after that
|
| 347 |
-
fallback was deliberately removed. The state is handed back rather than mutated in
|
| 348 |
-
place; the portable path does not mutate either, and saying it did was the same
|
| 349 |
-
stale prose.
|
| 350 |
-
|
| 351 |
-
Worth it only with batch: the kernel's whole point is that the state is read once
|
| 352 |
-
instead of three times, and at batch 1 the state is not what the step is spending
|
| 353 |
-
its time on. Measured at 7.2B on one RTX 5090, against albatross faster3a: batched
|
| 354 |
-
decode 55% -> 85% at 256x1, 91% -> 100% at 32x1, and 1x1 unchanged.
|
| 355 |
-
"""
|
| 356 |
-
single_token = r.shape[1] == 1 and cu_seq_lens is None
|
| 357 |
-
# The kernel loads to fp32 and stores back in whatever the state's dtype is, so a
|
| 358 |
-
# narrower state is not a different code path -- and it is the only lever left at
|
| 359 |
-
# batch. The kernel already runs at ~86% of this card's bandwidth moving a 268 MB
|
| 360 |
-
# fp32 state per layer, so halving the state is worth more than any tuning of it.
|
| 361 |
-
# `head_dim` a power of two is part of the kernel's applicability domain, not a
|
| 362 |
-
# detail: `tl.arange` requires it, and without this term a width like 192 reached
|
| 363 |
-
# Triton and came back as a CompilationError raised from inside the kernel rather
|
| 364 |
-
# than as a fallback. The portable path has no such constraint, so falling through
|
| 365 |
-
# is both correct and what the docstring already promises for the other cases.
|
| 366 |
-
width = r.shape[-1]
|
| 367 |
-
if not (single_token and r.is_cuda and fused_wkv_one is not None and width & (width - 1) == 0):
|
| 368 |
-
return rwkv7_eager(r, w_log, k, v, kk, a, state, cu_seq_lens=cu_seq_lens, **kwargs)
|
| 369 |
-
return fused_wkv_one(r, w_log, k, v, kk, a, state), state
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
# Added after the definition rather than in the literal above: the modular converter
|
| 373 |
-
# is free to move the dict, and it emits it next to `rwkv7_eager` -- ahead of this
|
| 374 |
-
# function, which made the generated file raise NameError on import.
|
| 375 |
-
RWKV7_WKV_FUNCTIONS["fused"] = rwkv7_fused
|
| 376 |
-
|
| 377 |
-
|
| 378 |
class Rwkv7Attention(nn.Module):
|
| 379 |
"""RWKV-7 time-mixing block (the recurrent replacement for self-attention)."""
|
| 380 |
|
|
@@ -467,9 +405,9 @@ class Rwkv7Attention(nn.Module):
|
|
| 467 |
#
|
| 468 |
# The decay is held at w = exp(0) = 1, so the transition is the identity.
|
| 469 |
#
|
| 470 |
-
# `k` and `v` are zeroed EXPLICITLY
|
| 471 |
-
#
|
| 472 |
-
#
|
| 473 |
# A pad that FOLLOWS a real token still receives that token's hidden
|
| 474 |
# state through the token shift, so `delta = shifted - 0` is non-zero,
|
| 475 |
# and so are `k` and `v`. The update term `k v^T` then entered the state
|
|
@@ -503,7 +441,7 @@ class Rwkv7Attention(nn.Module):
|
|
| 503 |
def _heads(t):
|
| 504 |
return t.view(batch, seq_len, H, N)
|
| 505 |
|
| 506 |
-
# Named rather than indexed:
|
| 507 |
# bare `KeyError: 'chunked'` from inside the forward, several frames from the
|
| 508 |
# config field that caused it, and a caller looping over shapes would record it
|
| 509 |
# as "this shape did not run" instead of "this model was never built". The
|
|
@@ -562,51 +500,6 @@ class Rwkv7Attention(nn.Module):
|
|
| 562 |
return w_log, a, g, v_gate
|
| 563 |
|
| 564 |
|
| 565 |
-
_SPARSE_OP_READY = False
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
def _ensure_sparse_op() -> bool:
|
| 569 |
-
"""Import the Triton kernels once, outside anything that gets traced.
|
| 570 |
-
|
| 571 |
-
A lazy `import` inside the forward is itself on dynamo's skip list, so doing
|
| 572 |
-
this on the hot path breaks the graph at every layer even when the kernel is a
|
| 573 |
-
properly registered custom op.
|
| 574 |
-
"""
|
| 575 |
-
global _SPARSE_OP_READY
|
| 576 |
-
if not _SPARSE_OP_READY and is_triton_available():
|
| 577 |
-
from . import sparse_channel_mix # noqa: F401 (registers the op)
|
| 578 |
-
|
| 579 |
-
_SPARSE_OP_READY = True
|
| 580 |
-
return _SPARSE_OP_READY
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
def sparse_channel_mix_value(
|
| 584 |
-
activation: torch.Tensor,
|
| 585 |
-
weight_t: torch.Tensor,
|
| 586 |
-
accumulator: torch.Tensor,
|
| 587 |
-
index: torch.Tensor,
|
| 588 |
-
value: torch.Tensor,
|
| 589 |
-
counter: torch.Tensor,
|
| 590 |
-
) -> torch.Tensor:
|
| 591 |
-
"""`out = activation @ weight_t`, reading only the rows the input selects.
|
| 592 |
-
|
| 593 |
-
`activation` is `relu(key(x))**2`, so its zeros are EXACT and a zero channel
|
| 594 |
-
contributes exactly nothing — skipping its weight row is not an approximation.
|
| 595 |
-
On a 7.2B checkpoint ~90% of channels are zero at any step (10.07% density,
|
| 596 |
-
measured over 16 decode steps) while this projection is a third of the model's
|
| 597 |
-
bytes, which is why it is worth a kernel at all. `index`/`value`/`counter` are
|
| 598 |
-
the scratch the kernel compacts the surviving channels into.
|
| 599 |
-
|
| 600 |
-
Only pays when kernel launches are captured (`torch.compile` with CUDA graphs).
|
| 601 |
-
Eagerly it LOSES to one dense cuBLAS call, because the step is then bound by
|
| 602 |
-
launch overhead rather than by the weight stream — measured both ways.
|
| 603 |
-
Falls back to a dense matmul when Triton is unavailable.
|
| 604 |
-
"""
|
| 605 |
-
if _SPARSE_OP_READY:
|
| 606 |
-
return torch.ops.rwkv7.sparse_channel_mix_value(activation, weight_t, accumulator, index, value, counter)
|
| 607 |
-
return torch.nn.functional.linear(activation, weight_t.t())
|
| 608 |
-
|
| 609 |
-
|
| 610 |
class Rwkv7FeedForward(nn.Module):
|
| 611 |
"""RWKV-7 channel-mixing block: squared-ReLU over a single token shift."""
|
| 612 |
|
|
@@ -619,12 +512,6 @@ class Rwkv7FeedForward(nn.Module):
|
|
| 619 |
self.x_k = nn.Parameter(torch.zeros(1, 1, C))
|
| 620 |
self.key = nn.Linear(C, config.intermediate_size, bias=False)
|
| 621 |
self.value = nn.Linear(config.intermediate_size, C, bias=False)
|
| 622 |
-
self._value_t = None # [inter, hidden] copy for the sparse path, built lazily
|
| 623 |
-
self._value_fingerprint = None
|
| 624 |
-
self._accumulator = None
|
| 625 |
-
self._compact_index = None
|
| 626 |
-
self._compact_value = None
|
| 627 |
-
self._compact_counter = None
|
| 628 |
|
| 629 |
def forward(
|
| 630 |
self,
|
|
@@ -643,9 +530,8 @@ class Rwkv7FeedForward(nn.Module):
|
|
| 643 |
# because the design keeps the table in RAM/SSD and prefetches per token.
|
| 644 |
# Its width says which side it attaches to -- `intermediate_size` scales the
|
| 645 |
# projection's INPUT (the reference "4x" variant), `hidden_size` its OUTPUT
|
| 646 |
-
# ("1x"). Resolved once here
|
| 647 |
-
#
|
| 648 |
-
# silently dropped the 4x variant once already.
|
| 649 |
scale_output = None
|
| 650 |
if deep_embed is not None:
|
| 651 |
if deep_embed.shape[-1] == inner.shape[-1]:
|
|
@@ -659,93 +545,8 @@ class Rwkv7FeedForward(nn.Module):
|
|
| 659 |
return out, new_shift_state
|
| 660 |
|
| 661 |
def _project(self, inner: torch.Tensor) -> torch.Tensor:
|
| 662 |
-
"""`value(inner)`, sparsely when that is both enabled and worthwhile.
|
| 663 |
-
|
| 664 |
-
The sparse kernel decodes one token at a time on CUDA; anything else (a
|
| 665 |
-
batch, a prefill, CPU) takes the dense projection, which is also what makes
|
| 666 |
-
the two paths comparable in tests.
|
| 667 |
-
"""
|
| 668 |
-
single_token = inner.shape[0] * inner.shape[1] == 1
|
| 669 |
-
if self.config.sparse_channel_mix and single_token and inner.is_cuda:
|
| 670 |
-
return self._sparse_value(inner.reshape(-1)).view(1, 1, -1)
|
| 671 |
return self.value(inner)
|
| 672 |
|
| 673 |
-
def _sparse_value(self, activation: torch.Tensor) -> torch.Tensor:
|
| 674 |
-
"""Value projection over only the nonzero channels of `relu(key(x))**2`.
|
| 675 |
-
|
| 676 |
-
The transposed weight is built on first use rather than stored: the sparse
|
| 677 |
-
read needs one contiguous row per input channel, which the `nn.Linear`
|
| 678 |
-
layout does not give, and materialising it eagerly would cost every user
|
| 679 |
-
the memory whether or not they enable this.
|
| 680 |
-
"""
|
| 681 |
-
if not torch.compiler.is_compiling():
|
| 682 |
-
self.build_sparse_cache()
|
| 683 |
-
return sparse_channel_mix_value(
|
| 684 |
-
activation,
|
| 685 |
-
self._value_t,
|
| 686 |
-
self._accumulator,
|
| 687 |
-
self._compact_index,
|
| 688 |
-
self._compact_value,
|
| 689 |
-
self._compact_counter,
|
| 690 |
-
)
|
| 691 |
-
|
| 692 |
-
def build_sparse_cache(self) -> None:
|
| 693 |
-
"""Materialise the transposed weight and the accumulator the sparse path needs.
|
| 694 |
-
|
| 695 |
-
Called lazily from the projection, and eagerly by
|
| 696 |
-
[`Rwkv7Model.allocate_state`] — which is what makes it safe to compile. Built
|
| 697 |
-
for the first time *inside* a compiled region these buffers cannot have their
|
| 698 |
-
addresses pinned, inductor declines CUDA graphs for a region that mutates its
|
| 699 |
-
inputs, and the decode runs 2.7x slower while reporting nothing but a single
|
| 700 |
-
line of warning.
|
| 701 |
-
"""
|
| 702 |
-
weight = self.value.weight
|
| 703 |
-
_ensure_sparse_op()
|
| 704 |
-
# Tie the cache to the weight's identity AND its in-place version, so a
|
| 705 |
-
# `mul_`, a fresh load, or a replaced parameter all invalidate it. A cache
|
| 706 |
-
# that silently survives a weight change is worse than no cache.
|
| 707 |
-
#
|
| 708 |
-
# One mutation this does NOT see, stated because it is the one people write:
|
| 709 |
-
# `weight.data.copy_(...)`. Going through `.data` is what detaches from the
|
| 710 |
-
# autograd version counter, so the version is unchanged, the storage is
|
| 711 |
-
# unchanged, and the fingerprint matches a weight that no longer exists.
|
| 712 |
-
# Measured on torch 2.13: `mul_` steps the version, `.data.copy_` does not,
|
| 713 |
-
# `load_state_dict` does (it copies through the parameter). Quantisation and
|
| 714 |
-
# adapter-merging code is where `.data.copy_` shows up. Call
|
| 715 |
-
# [`invalidate_sparse_cache`] after doing that -- there is no cheap way to
|
| 716 |
-
# notice from here, and the expensive way is a device sync per decoded token.
|
| 717 |
-
fingerprint = (weight.data_ptr(), weight._version, weight.dtype)
|
| 718 |
-
if self._value_t is None or self._value_fingerprint != fingerprint:
|
| 719 |
-
inter, hidden = weight.shape[1], weight.shape[0]
|
| 720 |
-
self._value_t = weight.t().contiguous()
|
| 721 |
-
self._accumulator = torch.zeros(hidden, device=weight.device, dtype=torch.float32)
|
| 722 |
-
# The projection compacts the surviving channels into these before
|
| 723 |
-
# walking them. Allocated here with everything else rather than on
|
| 724 |
-
# first use: a buffer that first appears inside a compiled region
|
| 725 |
-
# cannot be pinned, and CUDA graphs are then declined for the whole
|
| 726 |
-
# region -- which costs far more than the projection saves.
|
| 727 |
-
self._compact_index = torch.zeros(inter, device=weight.device, dtype=torch.int32)
|
| 728 |
-
self._compact_value = torch.zeros(inter, device=weight.device, dtype=torch.float32)
|
| 729 |
-
self._compact_counter = torch.zeros(1, device=weight.device, dtype=torch.int32)
|
| 730 |
-
self._value_fingerprint = fingerprint
|
| 731 |
-
|
| 732 |
-
def invalidate_sparse_cache(self) -> None:
|
| 733 |
-
"""Drop the transposed weight, so the next call rebuilds it.
|
| 734 |
-
|
| 735 |
-
The escape hatch for a weight change the fingerprint cannot see -- see
|
| 736 |
-
[`build_sparse_cache`] for which those are. Rebuilding allocates, so do it
|
| 737 |
-
before compiling, not between decode steps.
|
| 738 |
-
"""
|
| 739 |
-
self._value_fingerprint = None
|
| 740 |
-
|
| 741 |
-
def _load_from_state_dict(self, *args, **kwargs):
|
| 742 |
-
# Loading replaces the projection under a cache that may already be warm.
|
| 743 |
-
# The version counter happens to catch this on current torch, but that is a
|
| 744 |
-
# property of how `load_state_dict` copies rather than a promise, and the
|
| 745 |
-
# cost of being wrong is a silently stale projection.
|
| 746 |
-
super()._load_from_state_dict(*args, **kwargs)
|
| 747 |
-
self.invalidate_sparse_cache()
|
| 748 |
-
|
| 749 |
|
| 750 |
class Rwkv7CacheLayer(LinearAttentionLayer):
|
| 751 |
"""One block's slice of the recurrent state: the WKV matrix and two token shifts.
|
|
@@ -1072,24 +873,18 @@ class Rwkv7Model(Rwkv7PreTrainedModel):
|
|
| 1072 |
def allocate_state(self, batch: int, device=None, dtype=None) -> Rwkv7Cache:
|
| 1073 |
"""A zeroed cache, plus everything else the decode path would build lazily.
|
| 1074 |
|
| 1075 |
-
Call this before compiling, and pass the result in as `state=`.
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
weight. Either way the decode runs several times slower and says so only in
|
| 1082 |
-
a line of warning, so both are handled here rather than left to the caller
|
| 1083 |
-
to remember.
|
| 1084 |
"""
|
| 1085 |
state = self._empty_state(
|
| 1086 |
batch,
|
| 1087 |
device if device is not None else self.emb.weight.device,
|
| 1088 |
dtype if dtype is not None else self.emb.weight.dtype,
|
| 1089 |
)
|
| 1090 |
-
if self.config.sparse_channel_mix:
|
| 1091 |
-
for block in self.blocks:
|
| 1092 |
-
block.ffn.build_sparse_cache()
|
| 1093 |
return state
|
| 1094 |
|
| 1095 |
def _empty_state(self, batch: int, device, dtype) -> Rwkv7Cache:
|
|
@@ -1125,11 +920,11 @@ class Rwkv7Model(Rwkv7PreTrainedModel):
|
|
| 1125 |
*history* when `cu_seq_lens` is given -- see there.
|
| 1126 |
deep_embeds (`torch.FloatTensor`, *optional*):
|
| 1127 |
RWKV-8 DeepEmbed vectors for this batch, shaped
|
| 1128 |
-
`[num_layers, batch, seq_len,
|
| 1129 |
Only meaningful when `config.use_deep_embed` is set; the table itself is
|
| 1130 |
external to the checkpoint by design.
|
| 1131 |
cu_seq_lens (`torch.LongTensor`, *optional*):
|
| 1132 |
-
Cumulative sequence lengths for a *packed* batch
|
| 1133 |
concatenated into one row instead of padded to a rectangle, starting at
|
| 1134 |
0 and ending at `seq_len`, and non-decreasing. Each segment then decodes
|
| 1135 |
from a fresh recurrent state, as if it had been run on its own. This is
|
|
@@ -1178,9 +973,9 @@ class Rwkv7Model(Rwkv7PreTrainedModel):
|
|
| 1178 |
# can hand over the whole conversation's mask. A prefix chunk must slice
|
| 1179 |
# its own mask to match, or it silently reads the wrong positions.
|
| 1180 |
#
|
| 1181 |
-
# Applied at seq_len == 1 as well.
|
| 1182 |
-
#
|
| 1183 |
-
#
|
| 1184 |
# of the model, and a fully-masked 1-token row was moving the state.
|
| 1185 |
keep = attention_mask[:, -inputs_embeds.shape[1] :, None].to(inputs_embeds.dtype)
|
| 1186 |
|
|
@@ -1217,8 +1012,8 @@ class Rwkv7Model(Rwkv7PreTrainedModel):
|
|
| 1217 |
if output_hidden_states:
|
| 1218 |
all_hidden_states = all_hidden_states + (hidden_states,)
|
| 1219 |
# Gated on the config flag as well as on the argument: passing
|
| 1220 |
-
# `deep_embeds` to a model configured without them
|
| 1221 |
-
# channel-mix anyway, which is a silently different model.
|
| 1222 |
layer_deep_embed = (
|
| 1223 |
deep_embeds[block.layer_id] if deep_embeds is not None and self.config.use_deep_embed else None
|
| 1224 |
)
|
|
@@ -1258,9 +1053,9 @@ class Rwkv7ForCausalLM(Rwkv7PreTrainedModel, GenerationMixin):
|
|
| 1258 |
)
|
| 1259 |
model_inputs["state"] = state
|
| 1260 |
# Everything else the caller passed goes through, minus what this model does
|
| 1261 |
-
# not take.
|
| 1262 |
-
# `generate(output_hidden_states=True)`
|
| 1263 |
-
#
|
| 1264 |
# nothing it got back. Any user kwarg met the same fate, silently. The two
|
| 1265 |
# excluded here are `labels`, which would make generate compute a loss it
|
| 1266 |
# never reads, and the KV-cache bookkeeping that belongs to models with a KV
|
|
|
|
| 30 |
from transformers.modeling_layers import GradientCheckpointingLayer
|
| 31 |
from transformers.modeling_utils import PreTrainedModel
|
| 32 |
from transformers.utils import ModelOutput, auto_docstring, can_return_tuple, logging
|
|
|
|
| 33 |
from .configuration_rwkv7 import Rwkv7Config
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
logger = logging.get_logger(__name__)
|
| 37 |
|
| 38 |
|
|
|
|
| 59 |
if cu_seq_lens is not None:
|
| 60 |
# In a packed row the token before a segment's first one belongs to the
|
| 61 |
# PREVIOUS sequence. Resetting the recurrent state per segment does not
|
| 62 |
+
# cover this (the shift reaches back through it), so the first token of
|
| 63 |
# each segment gets the zero shift a sequence start is supposed to see.
|
| 64 |
positions = torch.arange(x.shape[1], device=x.device)
|
| 65 |
starts = (positions[:, None] == cu_seq_lens[None, :-1]).any(dim=1)
|
|
|
|
| 84 |
|
| 85 |
# e^-0.5. The decay LoRA emits w_log = -INV_SQRT_E * sigmoid(...), so w_log lies in
|
| 86 |
# (-e^-0.5, 0) and the per-step decay exp(w_log) lies in (exp(-e^-0.5), 1), i.e.
|
| 87 |
+
# (0.5452, 1). See RWKV-7 reference. Note the floor is exp(-e^-0.5), not e^-0.5: this
|
| 88 |
# comment said the latter, and `rwkv7_chunked` built its chunk_size bound on it.
|
| 89 |
_INV_SQRT_E = 0.6065306597126334
|
| 90 |
|
|
|
|
| 113 |
out = r @ S # uses the POST-update state
|
| 114 |
|
| 115 |
The state axes are (key, value); `S[i, j]` accumulates key channel `i` against
|
| 116 |
+
value channel `j`. Accumulation is fp32 regardless of the activation dtype:
|
| 117 |
the recurrence is unrolled over the whole sequence, so a lower-precision state
|
| 118 |
drifts. This is the portable path; a fused kernel may replace it as long as it
|
| 119 |
reproduces these values.
|
|
|
|
| 167 |
|
| 168 |
`chunk_size` is bounded by that division by `c`, but by OVERFLOW rather than by
|
| 169 |
precision, and the difference is worth a factor of four. The per-step decay is at
|
| 170 |
+
least `exp(-e^-0.5)` = 0.5452, so at the worst case, every channel pinned at that
|
| 171 |
+
floor, `1/c` grows like `e^(e^-0.5 * chunk_size)`. That reaches 7.2e16 at 64 and
|
| 172 |
fp32 tops out near 3.4e38, so there are twenty-odd decades of headroom left at the
|
| 173 |
default, and the ceiling is `ln(finfo.max) / e^-0.5` = 146 in fp32.
|
| 174 |
|
| 175 |
That derivation is worth stating carefully because the first version of it was
|
| 176 |
wrong in a way the numbers hid. It said the decay floor was `e^-0.5` rather than
|
| 177 |
+
`exp(-e^-0.5)`, hence growth like `e^(0.5 * chunk_size)`, which puts the ceiling at
|
| 178 |
177, and measured at the decay floor this function returns all-NaN from 147 up. The
|
| 179 |
quoted 7.2e16 came from the correct law all along (`e^(0.5*64)` is 7.9e13), so the
|
| 180 |
arithmetic had been done right and written up wrong, and following the prose rather
|
|
|
|
| 279 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 280 |
"""Portable WKV: the sequential step for a single token, chunk-parallel otherwise.
|
| 281 |
|
| 282 |
+
With `cu_seq_lens` the row is a *packed* batch: several independent sequences
|
| 283 |
laid end to end, the layout a varlen kernel consumes. Each one has to start
|
| 284 |
from a fresh state, so they are run in turn and their outputs concatenated;
|
| 285 |
the state returned is the last segment's, which is the one a continuation
|
|
|
|
| 313 |
RWKV7_WKV_FUNCTIONS = {"eager": rwkv7_eager}
|
| 314 |
|
| 315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
class Rwkv7Attention(nn.Module):
|
| 317 |
"""RWKV-7 time-mixing block (the recurrent replacement for self-attention)."""
|
| 318 |
|
|
|
|
| 405 |
#
|
| 406 |
# The decay is held at w = exp(0) = 1, so the transition is the identity.
|
| 407 |
#
|
| 408 |
+
# `k` and `v` are zeroed EXPLICITLY rather than relying on the blanked
|
| 409 |
+
# hidden state to make them zero: the projections are bias-free, so that
|
| 410 |
+
# only holds for a pad with nothing before it.
|
| 411 |
# A pad that FOLLOWS a real token still receives that token's hidden
|
| 412 |
# state through the token shift, so `delta = shifted - 0` is non-zero,
|
| 413 |
# and so are `k` and `v`. The update term `k v^T` then entered the state
|
|
|
|
| 441 |
def _heads(t):
|
| 442 |
return t.view(batch, seq_len, H, N)
|
| 443 |
|
| 444 |
+
# Named rather than indexed: an unregistered key would otherwise surface as a
|
| 445 |
# bare `KeyError: 'chunked'` from inside the forward, several frames from the
|
| 446 |
# config field that caused it, and a caller looping over shapes would record it
|
| 447 |
# as "this shape did not run" instead of "this model was never built". The
|
|
|
|
| 500 |
return w_log, a, g, v_gate
|
| 501 |
|
| 502 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
class Rwkv7FeedForward(nn.Module):
|
| 504 |
"""RWKV-7 channel-mixing block: squared-ReLU over a single token shift."""
|
| 505 |
|
|
|
|
| 512 |
self.x_k = nn.Parameter(torch.zeros(1, 1, C))
|
| 513 |
self.key = nn.Linear(C, config.intermediate_size, bias=False)
|
| 514 |
self.value = nn.Linear(config.intermediate_size, C, bias=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
|
| 516 |
def forward(
|
| 517 |
self,
|
|
|
|
| 530 |
# because the design keeps the table in RAM/SSD and prefetches per token.
|
| 531 |
# Its width says which side it attaches to -- `intermediate_size` scales the
|
| 532 |
# projection's INPUT (the reference "4x" variant), `hidden_size` its OUTPUT
|
| 533 |
+
# ("1x"). Resolved once here rather than per branch, so a later branch cannot
|
| 534 |
+
# drop the 4x variant silently.
|
|
|
|
| 535 |
scale_output = None
|
| 536 |
if deep_embed is not None:
|
| 537 |
if deep_embed.shape[-1] == inner.shape[-1]:
|
|
|
|
| 545 |
return out, new_shift_state
|
| 546 |
|
| 547 |
def _project(self, inner: torch.Tensor) -> torch.Tensor:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
return self.value(inner)
|
| 549 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
class Rwkv7CacheLayer(LinearAttentionLayer):
|
| 552 |
"""One block's slice of the recurrent state: the WKV matrix and two token shifts.
|
|
|
|
| 873 |
def allocate_state(self, batch: int, device=None, dtype=None) -> Rwkv7Cache:
|
| 874 |
"""A zeroed cache, plus everything else the decode path would build lazily.
|
| 875 |
|
| 876 |
+
Call this before compiling, and pass the result in as `state=`.
|
| 877 |
+
`mark_static_address` cannot run during tracing, so anything first allocated
|
| 878 |
+
*inside* the compiled region stays unpinned, and inductor declines CUDA graphs
|
| 879 |
+
for a region that mutates its inputs. Starting from `state=None` loses the
|
| 880 |
+
state buffers that way, and the decode then runs several times slower while
|
| 881 |
+
saying so only in a line of warning.
|
|
|
|
|
|
|
|
|
|
| 882 |
"""
|
| 883 |
state = self._empty_state(
|
| 884 |
batch,
|
| 885 |
device if device is not None else self.emb.weight.device,
|
| 886 |
dtype if dtype is not None else self.emb.weight.dtype,
|
| 887 |
)
|
|
|
|
|
|
|
|
|
|
| 888 |
return state
|
| 889 |
|
| 890 |
def _empty_state(self, batch: int, device, dtype) -> Rwkv7Cache:
|
|
|
|
| 920 |
*history* when `cu_seq_lens` is given -- see there.
|
| 921 |
deep_embeds (`torch.FloatTensor`, *optional*):
|
| 922 |
RWKV-8 DeepEmbed vectors for this batch, shaped
|
| 923 |
+
`[num_layers, batch, seq_len, hidden_size or intermediate_size]` (or broadcastable).
|
| 924 |
Only meaningful when `config.use_deep_embed` is set; the table itself is
|
| 925 |
external to the checkpoint by design.
|
| 926 |
cu_seq_lens (`torch.LongTensor`, *optional*):
|
| 927 |
+
Cumulative sequence lengths for a *packed* batch: several sequences
|
| 928 |
concatenated into one row instead of padded to a rectangle, starting at
|
| 929 |
0 and ending at `seq_len`, and non-decreasing. Each segment then decodes
|
| 930 |
from a fresh recurrent state, as if it had been run on its own. This is
|
|
|
|
| 973 |
# can hand over the whole conversation's mask. A prefix chunk must slice
|
| 974 |
# its own mask to match, or it silently reads the wrong positions.
|
| 975 |
#
|
| 976 |
+
# Applied at seq_len == 1 as well. Skipping it there assumes a single
|
| 977 |
+
# decoded token is never padding -- true of `generate`, but an
|
| 978 |
+
# assumption about the caller rather than a property
|
| 979 |
# of the model, and a fully-masked 1-token row was moving the state.
|
| 980 |
keep = attention_mask[:, -inputs_embeds.shape[1] :, None].to(inputs_embeds.dtype)
|
| 981 |
|
|
|
|
| 1012 |
if output_hidden_states:
|
| 1013 |
all_hidden_states = all_hidden_states + (hidden_states,)
|
| 1014 |
# Gated on the config flag as well as on the argument: passing
|
| 1015 |
+
# `deep_embeds` to a model configured without them would otherwise modulate
|
| 1016 |
+
# the channel-mix anyway, which is a silently different model.
|
| 1017 |
layer_deep_embed = (
|
| 1018 |
deep_embeds[block.layer_id] if deep_embeds is not None and self.config.use_deep_embed else None
|
| 1019 |
)
|
|
|
|
| 1053 |
)
|
| 1054 |
model_inputs["state"] = state
|
| 1055 |
# Everything else the caller passed goes through, minus what this model does
|
| 1056 |
+
# not take. An allowlist of specific names instead would make
|
| 1057 |
+
# `generate(output_hidden_states=True)` return a tuple of `None`: the flag
|
| 1058 |
+
# would be dropped here, the forward would never see it, and generate collects the
|
| 1059 |
# nothing it got back. Any user kwarg met the same fate, silently. The two
|
| 1060 |
# excluded here are `labels`, which would make generate compute a loss it
|
| 1061 |
# never reads, and the KV-cache bookkeeping that belongs to models with a KV
|
rwkv_vocab_v20230424.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
sparse_channel_mix.py
DELETED
|
@@ -1,175 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 The RWKV team and The HuggingFace Inc. team. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
"""Triton sparse channel-mix value projection for RWKV-7 decode.
|
| 15 |
-
|
| 16 |
-
Kept out of `modeling_rwkv7.py` so the model file stays importable without Triton;
|
| 17 |
-
`sparse_channel_mix_value` there dispatches here and falls back to a dense matmul.
|
| 18 |
-
|
| 19 |
-
Registered as a custom op rather than called directly: a raw Triton launch is on
|
| 20 |
-
dynamo's skip list, so calling it from the model breaks the graph at every layer
|
| 21 |
-
(21 breaks in a 7.2B forward, measured) and costs about 8% end to end. As an opaque
|
| 22 |
-
op it stays inside the single captured graph.
|
| 23 |
-
|
| 24 |
-
The weight is indexed `[inter, hidden]` so one selected input channel reads one
|
| 25 |
-
contiguous row.
|
| 26 |
-
|
| 27 |
-
STRUCTURE. The surviving indices are compacted into a shared list first, and the
|
| 28 |
-
projection then walks that list with scalar loads and a runtime trip count. Five
|
| 29 |
-
structures were measured in situ on a 7.2B decode -- end to end, batch 1, under
|
| 30 |
-
`max-autotune` with CUDA graphs engaged:
|
| 31 |
-
|
| 32 |
-
compacted indices, SPLIT=128 133.9 tok/s <- shipped
|
| 33 |
-
tile-scan, (inter/16, hidden/1024) grid 127.8
|
| 34 |
-
scalar re-read instead of a reduction slower than tile-scan
|
| 35 |
-
input axis moved off the grid, 512 prog slower than tile-scan
|
| 36 |
-
coarse output tiles, few programs far worse
|
| 37 |
-
|
| 38 |
-
The compacted structure was tried once before and rejected, and why is worth
|
| 39 |
-
keeping, because the reason was true and has stopped being true. Measured with one
|
| 40 |
-
launch per stage and no CUDA graph, its extra launch and the barrier its
|
| 41 |
-
data-dependent trip count forces cost ~1.3 ms/step of pipeline bubbles against the
|
| 42 |
-
0.43 ms of kernel time it saved. Once the decode compiled into a single graph with
|
| 43 |
-
cudagraphs engaged, GPU-busy time equals wall time and there are no bubbles left to
|
| 44 |
-
pay -- so the arithmetic that killed it no longer applied to anything.
|
| 45 |
-
|
| 46 |
-
The first re-test still lost, 122.3 against 127.8, because it inherited SPLIT=8:
|
| 47 |
-
128 programs against the tile-scan's 4096. The split decides how much of the card
|
| 48 |
-
the walk occupies and had never been swept. It is the whole difference:
|
| 49 |
-
|
| 50 |
-
SPLIT 8 -> 122.3 SPLIT 64 -> 133.3
|
| 51 |
-
SPLIT 16 -> 131.1 SPLIT 128 -> 133.9 <- optimum, bracketed both ways
|
| 52 |
-
SPLIT 32 -> 133.3 SPLIT 256 -> 132.9
|
| 53 |
-
SPLIT 512 -> 132.4
|
| 54 |
-
|
| 55 |
-
The durable lesson is about the record rather than the kernel: a rejection is only
|
| 56 |
-
as good as the conditions it was measured under, and one that does not name them
|
| 57 |
-
gets re-read as a fact about the algorithm. 128 is measured at hidden=4096; other
|
| 58 |
-
widths have not been swept.
|
| 59 |
-
|
| 60 |
-
Measure any variant of this end to end, not as a standalone kernel. Three separate
|
| 61 |
-
standalone harnesses each favoured a configuration that lost in the model -- by
|
| 62 |
-
allowing L2 reuse, by letting independent iterations overlap, and by using random
|
| 63 |
-
weights whose `relu(x)**2` is ~50% dense instead of the ~10% a real checkpoint
|
| 64 |
-
gives at any decode step.
|
| 65 |
-
|
| 66 |
-
Cross-tile partials land in an fp32 accumulator through atomics, and the finalize
|
| 67 |
-
pass re-zeros both that accumulator and the counter as it casts, so no separate
|
| 68 |
-
clear is ever launched.
|
| 69 |
-
"""
|
| 70 |
-
|
| 71 |
-
import torch
|
| 72 |
-
from transformers.utils.import_utils import is_triton_available
|
| 73 |
-
|
| 74 |
-
# Guarded rather than bare, although the module itself is only ever imported
|
| 75 |
-
# under the same `is_triton_available()` condition: import scanners that parse
|
| 76 |
-
# this file in isolation (transformers' remote-code `check_imports` is one)
|
| 77 |
-
# exempt imports inside an availability-guarded block, and demand triton on
|
| 78 |
-
# every machine otherwise.
|
| 79 |
-
if is_triton_available():
|
| 80 |
-
import triton
|
| 81 |
-
import triton.language as tl
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
# How many programs the walk is split into. Measured optimum at hidden=4096; it sets
|
| 85 |
-
# how much of the card the matvec occupies, so it is the first thing to sweep on a
|
| 86 |
-
# model of a different width.
|
| 87 |
-
_SPLIT = 128
|
| 88 |
-
_BLOCK_H = 256
|
| 89 |
-
_BLOCK_C = 256
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
@triton.jit
|
| 93 |
-
def _compact_kernel(act_ptr, idx_ptr, val_ptr, cnt_ptr, inter, BLOCK: tl.constexpr):
|
| 94 |
-
"""Append this tile's surviving (index, value) pairs to one shared list."""
|
| 95 |
-
offs = tl.program_id(0) * BLOCK + tl.arange(0, BLOCK)
|
| 96 |
-
act = tl.load(act_ptr + offs, mask=offs < inter, other=0.0).to(tl.float32)
|
| 97 |
-
nonzero = act != 0.0
|
| 98 |
-
count = tl.sum(nonzero.to(tl.int32))
|
| 99 |
-
if count > 0:
|
| 100 |
-
# reserve a contiguous range, then place each survivor at its rank within it
|
| 101 |
-
base = tl.atomic_add(cnt_ptr, count)
|
| 102 |
-
rank = tl.cumsum(nonzero.to(tl.int32)) - nonzero.to(tl.int32)
|
| 103 |
-
tl.store(idx_ptr + base + rank, offs, mask=nonzero)
|
| 104 |
-
tl.store(val_ptr + base + rank, act, mask=nonzero)
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
@triton.jit
|
| 108 |
-
def _sparse_value_kernel(
|
| 109 |
-
idx_ptr,
|
| 110 |
-
val_ptr,
|
| 111 |
-
cnt_ptr,
|
| 112 |
-
w_ptr,
|
| 113 |
-
acc_ptr,
|
| 114 |
-
hidden,
|
| 115 |
-
SPLIT: tl.constexpr,
|
| 116 |
-
BLOCK_H: tl.constexpr,
|
| 117 |
-
):
|
| 118 |
-
"""Walk a slice of the compacted list, accumulating into one output tile."""
|
| 119 |
-
pid_h, pid_s = tl.program_id(0), tl.program_id(1)
|
| 120 |
-
total = tl.load(cnt_ptr)
|
| 121 |
-
per = (total + SPLIT - 1) // SPLIT
|
| 122 |
-
start = pid_s * per
|
| 123 |
-
stop = tl.minimum(start + per, total)
|
| 124 |
-
offs_h = pid_h * BLOCK_H + tl.arange(0, BLOCK_H)
|
| 125 |
-
mask_h = offs_h < hidden
|
| 126 |
-
acc = tl.zeros([BLOCK_H], dtype=tl.float32)
|
| 127 |
-
for i in range(start, stop):
|
| 128 |
-
idx = tl.load(idx_ptr + i)
|
| 129 |
-
a = tl.load(val_ptr + i)
|
| 130 |
-
acc += a * tl.load(w_ptr + idx * hidden + offs_h, mask=mask_h, other=0.0).to(tl.float32)
|
| 131 |
-
if stop > start:
|
| 132 |
-
tl.atomic_add(acc_ptr + offs_h, acc, mask=mask_h)
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
@triton.jit
|
| 136 |
-
def _sparse_finalize_kernel(acc_ptr, out_ptr, cnt_ptr, hidden, BLOCK: tl.constexpr):
|
| 137 |
-
offs = tl.program_id(0) * BLOCK + tl.arange(0, BLOCK)
|
| 138 |
-
mask = offs < hidden
|
| 139 |
-
value = tl.load(acc_ptr + offs, mask=mask, other=0.0)
|
| 140 |
-
tl.store(out_ptr + offs, value.to(out_ptr.dtype.element_ty), mask=mask)
|
| 141 |
-
tl.store(acc_ptr + offs, tl.zeros([BLOCK], dtype=tl.float32), mask=mask)
|
| 142 |
-
if tl.program_id(0) == 0:
|
| 143 |
-
tl.store(cnt_ptr, 0)
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
@torch.library.custom_op("rwkv7::sparse_channel_mix_value", mutates_args={"accumulator", "index", "value", "counter"})
|
| 147 |
-
def triton_sparse_value(
|
| 148 |
-
activation: torch.Tensor,
|
| 149 |
-
weight_t: torch.Tensor,
|
| 150 |
-
accumulator: torch.Tensor,
|
| 151 |
-
index: torch.Tensor,
|
| 152 |
-
value: torch.Tensor,
|
| 153 |
-
counter: torch.Tensor,
|
| 154 |
-
) -> torch.Tensor:
|
| 155 |
-
inter, hidden = weight_t.shape
|
| 156 |
-
out = torch.empty(hidden, device=activation.device, dtype=activation.dtype)
|
| 157 |
-
_compact_kernel[(triton.cdiv(inter, _BLOCK_C),)](activation, index, value, counter, inter, BLOCK=_BLOCK_C)
|
| 158 |
-
_sparse_value_kernel[(triton.cdiv(hidden, _BLOCK_H), _SPLIT)](
|
| 159 |
-
index,
|
| 160 |
-
value,
|
| 161 |
-
counter,
|
| 162 |
-
weight_t,
|
| 163 |
-
accumulator,
|
| 164 |
-
hidden,
|
| 165 |
-
SPLIT=_SPLIT,
|
| 166 |
-
BLOCK_H=_BLOCK_H,
|
| 167 |
-
num_warps=4,
|
| 168 |
-
)
|
| 169 |
-
_sparse_finalize_kernel[(triton.cdiv(hidden, 256),)](accumulator, out, counter, hidden, BLOCK=256)
|
| 170 |
-
return out
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
@triton_sparse_value.register_fake
|
| 174 |
-
def _(activation, weight_t, accumulator, index, value, counter):
|
| 175 |
-
return activation.new_empty(weight_t.shape[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token": "<|rwkv_tokenizer_end_of_text|>",
|
| 3 |
+
"eos_token": "\n\n",
|
| 4 |
+
"unk_token": "<|rwkv_tokenizer_end_of_text|>",
|
| 5 |
+
"pad_token": "<|rwkv_tokenizer_end_of_text|>"
|
| 6 |
+
}
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"added_tokens_decoder": {
|
| 4 |
+
"0": {
|
| 5 |
+
"content": "<|rwkv_tokenizer_end_of_text|>",
|
| 6 |
+
"lstrip": false,
|
| 7 |
+
"normalized": false,
|
| 8 |
+
"rstrip": false,
|
| 9 |
+
"single_word": false,
|
| 10 |
+
"special": true
|
| 11 |
+
}
|
| 12 |
+
},
|
| 13 |
+
"auto_map": {
|
| 14 |
+
"AutoTokenizer": [
|
| 15 |
+
"hf_rwkv_tokenizer.RwkvTokenizer",
|
| 16 |
+
null
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
"bos_token": "<|rwkv_tokenizer_end_of_text|>",
|
| 20 |
+
"pad_token": "<|rwkv_tokenizer_end_of_text|>",
|
| 21 |
+
"clean_up_tokenization_spaces": false,
|
| 22 |
+
"eos_token": "\n\n",
|
| 23 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 24 |
+
"tokenizer_class": "RwkvTokenizer",
|
| 25 |
+
"unk_token": "<|rwkv_tokenizer_end_of_text|>",
|
| 26 |
+
"use_fast": false,
|
| 27 |
+
"chat_template": "{{ '<|rwkv_tokenizer_end_of_text|>' }}{% for message in messages %}{% if message['role'] == 'user' %}{{'User: ' + message['content'] + '\n\n'}}{% elif message['role'] == 'system' %}{{'System: ' + message['content'] + '\n\n'}}{% elif message['role'] == 'assistant' %}{{'Assistant: ' + message['content'] + '\n\n'}}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ 'Assistant:' }}{% endif %}"
|
| 28 |
+
}
|