Coverage for transformer_lens/model_bridge/supported_architectures/rwkv.py: 94%
32 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-08-11 18:50 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-08-11 18:50 +0000
1"""RWKV architecture adapter.
3BlinkDL's RWKV-4 (``RwkvForCausalLM``, native in transformers): the
4canonical WKV linear-attention RNN, trained on the Pile in a
5Pythia-parallel suite. Blocks pair a time-mix module (token-shift
6interpolation into key/value/receptance projections, recurrent WKV
7kernel, gated output) with a channel-mix module (token-shift key/
8receptance, squared-relu value) under pre-LNs, plus an extra pre_ln on
9layer 0 before anything else. Both mixers delegate to HF (the WKV
10recurrence has no attention-shaped reconstruction); their projections
11are wrapped for hooks.
13HF rescales attention.output/feed_forward.value weights by 2^(layer //
14rescale_every) at eval (the forward divides hidden states to compensate,
15so the function is unchanged); bridge and reference both keep the default
16so their weights match exactly. use_cache is forced off: the recurrent
17state buffers are written in-place per layer, which breaks autograd under
18backward hooks — and generation (the only state consumer) runs on a
19bespoke ``state`` kwarg the bridge's loop doesn't speak, so generation
20phases are excluded.
21"""
23from typing import Any
25from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
26from transformer_lens.model_bridge.generalized_components import (
27 BlockBridge,
28 EmbeddingBridge,
29 LinearBridge,
30 MLPBridge,
31 NormalizationBridge,
32 UnembeddingBridge,
33)
34from transformer_lens.model_bridge.generalized_components.base import (
35 GeneralizedComponent,
36)
39class _RwkvBlockBridge(BlockBridge):
40 """RWKV blocks have no attention: replace the attention-flavored alias set
41 with time-mix/channel-mix names (resid_mid is ln2's input, pre-mix)."""
43 hook_aliases = {
44 "hook_resid_pre": "hook_in",
45 "hook_resid_mid": "ln2.hook_in",
46 "hook_resid_post": "hook_out",
47 "hook_time_mix_in": "time_mix.hook_in",
48 "hook_time_mix_out": "time_mix.hook_out",
49 "hook_channel_mix_in": "channel_mix.hook_in",
50 "hook_channel_mix_out": "channel_mix.hook_out",
51 }
54class RwkvArchitectureAdapter(ArchitectureAdapter):
55 """Architecture adapter for RwkvForCausalLM models."""
57 applicable_phases: list[int] = [1, 2, 3, 4]
58 supports_generation: bool = True
59 # HF threads recurrence through a bespoke `state` kwarg, not past_key_values;
60 # generation recomputes the full prefix per step (exact, O(n^2)).
61 supports_kv_cache: bool = False
62 # RwkvModel ignores attention_mask entirely, so left-padding would silently
63 # poison the recurrent state instead of being masked out.
64 supports_batched_generation: bool = False
65 # Pre-LNs feed the mixers, but the recurrent WKV kernel consumes raw and
66 # time-shifted inputs — no fold target exists.
67 supports_fold_ln = False
69 def __init__(self, cfg: Any) -> None:
70 """Initialize the RWKV architecture adapter."""
71 super().__init__(cfg)
73 self.cfg.normalization_type = "LN"
74 self.cfg.uses_rms_norm = False
75 self.cfg.positional_embedding_type = "none"
76 self.cfg.gated_mlp = False
77 self.cfg.attn_only = False
78 self.cfg.final_rms = False
80 self.weight_processing_conversions = {}
82 self.component_mapping = {
83 "embed": EmbeddingBridge(name="rwkv.embeddings"),
84 "blocks": _RwkvBlockBridge(
85 name="rwkv.blocks",
86 config=self.cfg,
87 submodules={
88 # Layer 0 only: an extra LN before the block body.
89 "pre_ln": NormalizationBridge(
90 name="pre_ln",
91 config=self.cfg,
92 use_native_layernorm_autograd=True,
93 optional=True,
94 ),
95 "ln1": NormalizationBridge(
96 name="ln1", config=self.cfg, use_native_layernorm_autograd=True
97 ),
98 "time_mix": GeneralizedComponent(
99 name="attention",
100 submodules={
101 "key": LinearBridge(name="key"),
102 "value": LinearBridge(name="value"),
103 "receptance": LinearBridge(name="receptance"),
104 "output": LinearBridge(name="output"),
105 },
106 ),
107 "ln2": NormalizationBridge(
108 name="ln2", config=self.cfg, use_native_layernorm_autograd=True
109 ),
110 # MLPBridge so the component harness sizes inputs by the
111 # true in/out dims (key: d_model->4d, value: 4d->d_model).
112 "channel_mix": MLPBridge(
113 name="feed_forward",
114 config=self.cfg,
115 submodules={
116 "in": LinearBridge(name="key"),
117 "receptance": LinearBridge(name="receptance"),
118 "out": LinearBridge(name="value"),
119 },
120 ),
121 },
122 ),
123 "ln_final": NormalizationBridge(
124 name="rwkv.ln_out", config=self.cfg, use_native_layernorm_autograd=True
125 ),
126 "unembed": UnembeddingBridge(name="head"),
127 }
129 def prepare_loading(self, model_name: str, model_kwargs: dict) -> None:
130 """Force use_cache off: per-layer in-place state writes break autograd
131 under backward hooks, and only recurrent generation consumes them."""
132 config = model_kwargs.get("config")
133 if config is not None: 133 ↛ 135line 133 didn't jump to line 135 because the condition on line 133 was always true
134 config.use_cache = False
135 super().prepare_loading(model_name, model_kwargs)
137 def prepare_model(self, hf_model: Any) -> None:
138 """Re-assert use_cache=False -- prepare_loading only fires on the boot path, so
139 directly-wrapped modules keep the default and leak state tuples into tensor-only hooks."""
140 super().prepare_model(hf_model)
141 config = getattr(hf_model, "config", None)
142 if config is not None: 142 ↛ exitline 142 didn't return from function 'prepare_model' because the condition on line 142 was always true
143 config.use_cache = False