aether-rwkv-25m / model.py
konpep's picture
Upload 12 files
4122ecb verified
Raw
History Blame Contribute Delete
10.5 kB
"""
RWKV v4 — Correct implementation with time-mix WKV formula
Changes from previous version:
1. Correct time-mix WKV with time_decay + time_first (bonus)
2. state_num = w * state_num + exp(k) * v (only keeps the past)
3. wkv_t = (state_num + exp(u) * exp(k_t) * v_t) / (state_den + exp(u) * exp(k_t))
4. ~25M params (hidden=544, layers=10)
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class RWKV_TimeMix(nn.Module):
"""
Time-Mix with correct WKV formula
wkv_t = (sum_{past} w * exp(k_i) * v_i + exp(u) * exp(k_t) * v_t) /
(sum_{past} w * exp(k_i) + exp(u) * exp(k_t))
where w = exp(-exp(time_decay)), u = time_first
"""
def __init__(self, hidden_size):
super().__init__()
self.hidden_size = hidden_size
self.key = nn.Linear(hidden_size, hidden_size, bias=False)
self.value = nn.Linear(hidden_size, hidden_size, bias=False)
self.receptance = nn.Linear(hidden_size, hidden_size, bias=False)
# time_decay: how fast the past is forgotten (per channel)
# initialized at log(1) ≈ 0, so exp(-exp(0)) ≈ 0.37
self.time_decay = nn.Parameter(torch.empty(hidden_size))
# time_first: bonus for the current token (per channel)
self.time_first = nn.Parameter(torch.empty(hidden_size))
self.init_weights()
def init_weights(self):
nn.init.orthogonal_(self.key.weight, gain=0.1)
nn.init.orthogonal_(self.value.weight, gain=1.0)
nn.init.orthogonal_(self.receptance.weight, gain=1.0)
nn.init.constant_(self.time_decay, 0.0)
nn.init.constant_(self.time_first, 5.0)
def forward(self, x, state=None):
"""
Time-mix for training (parallel processing)
Computes correct WKV:
- state_num = w * state_num + exp(k) * v (past only)
- num = w * state_num + exp(u) * exp(k_t) * v_t (past + current bonus)
"""
B, T, C = x.shape
k = self.key(x)
v = self.value(x)
r = self.receptance(x)
decay = torch.exp(-torch.exp(self.time_decay)).view(1, 1, C)
exp_bonus = torch.exp(self.time_first).view(1, 1, C)
exp_k = torch.exp(k)
num = torch.zeros(B, T, C, device=x.device)
den = torch.zeros(B, T, C, device=x.device)
if state is not None:
s_num, s_den = state
else:
s_num = torch.zeros(B, 1, C, device=x.device)
s_den = torch.zeros(B, 1, C, device=x.device)
for t in range(T):
ek = exp_k[:, t:t+1, :]
v_t = v[:, t:t+1, :]
num_t = s_num * decay + exp_bonus * ek * v_t
den_t = s_den * decay + exp_bonus * ek
num[:, t:t+1, :] = num_t
den[:, t:t+1, :] = den_t
s_num = s_num * decay + ek * v_t
s_den = s_den * decay + ek
wkv = num / (den + 1e-8)
output = torch.sigmoid(r) * wkv
return output, (s_num, s_den)
def forward_step(self, x, state):
k = self.key(x)
v = self.value(x)
r = self.receptance(x)
decay = torch.exp(-torch.exp(self.time_decay)).view(1, -1, 1)
exp_bonus = torch.exp(self.time_first).view(1, -1, 1)
s_num, s_den = state
ek = torch.exp(k)
num = s_num * decay + exp_bonus * ek * v
den = s_den * decay + exp_bonus * ek
wkv = num / (den + 1e-8)
s_num = s_num * decay + ek * v
s_den = s_den * decay + ek
output = torch.sigmoid(r) * wkv
return output, (s_num, s_den)
class RWKV_ChannelMix(nn.Module):
"""
Channel-Mix: MLP with ReLU² + Sigmoid gate
Structure: Linear(×2) → ReLU² → Linear(÷2) × Sigmoid(receptance)
"""
def __init__(self, hidden_size):
super().__init__()
ff_size = hidden_size * 2
self.key = nn.Linear(hidden_size, ff_size, bias=False)
self.value = nn.Linear(ff_size, hidden_size, bias=False)
self.receptance = nn.Linear(hidden_size, hidden_size, bias=False)
self.init_weights()
def init_weights(self):
nn.init.orthogonal_(self.key.weight, gain=1.0)
nn.init.zeros_(self.value.weight)
nn.init.zeros_(self.receptance.weight)
def forward(self, x):
k = F.relu(self.key(x)) ** 2
v = self.value(k)
r = torch.sigmoid(self.receptance(x))
return r * v
class RWKV_Block(nn.Module):
"""
Block: LayerNorm → TimeMix → Residual → LayerNorm → ChannelMix → Residual
"""
def __init__(self, hidden_size, dropout=0.0):
super().__init__()
self.ln1 = nn.LayerNorm(hidden_size)
self.ln2 = nn.LayerNorm(hidden_size)
self.time_mix = RWKV_TimeMix(hidden_size)
self.channel_mix = RWKV_ChannelMix(hidden_size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, state=None):
tm_out, new_state = self.time_mix(self.ln1(x), state)
x = x + self.dropout(tm_out)
cm_out = self.channel_mix(self.ln2(x))
x = x + self.dropout(cm_out)
return x, new_state
def forward_step(self, x, state):
tm_out, tm_state = self.time_mix.forward_step(self.ln1(x), state[0])
x = x + tm_out
cm_out = self.channel_mix(self.ln2(x))
x = x + cm_out
return x, (tm_state,)
class RWKV(nn.Module):
"""
Full RWKV v4 model
Embedding → 10× RWKV_Block → LayerNorm → Linear(output)
~25M params (hidden=544)
"""
def __init__(self, vocab_size, hidden_size=480, num_layers=12, ff_size_mult=2, dropout=0.0):
super().__init__()
self.vocab_size = vocab_size
self.hidden_size = hidden_size
self.num_layers = num_layers
self.dropout = dropout
# Change ff_size if needed
RWKV_ChannelMix._ff_mult = ff_size_mult
self.embed = nn.Embedding(vocab_size, hidden_size)
self.embed_drop = nn.Dropout(dropout)
self.blocks = nn.ModuleList([
RWKV_Block(hidden_size, dropout) for _ in range(num_layers)
])
self.ln_out = nn.LayerNorm(hidden_size)
self.head = nn.Linear(hidden_size, vocab_size, bias=False)
self.embed.weight = self.head.weight
self._block_outputs = None
# Init head specifically: orthogonal gain = 0.5 * sqrt(vocab / hidden)
head_gain = 0.5 * (vocab_size / hidden_size) ** 0.5
nn.init.orthogonal_(self.head.weight, gain=head_gain)
# Init remaining layers (LayerNorm)
nn.init.ones_(self.ln_out.weight)
nn.init.zeros_(self.ln_out.bias)
total = sum(p.numel() for p in self.parameters())
print(f"RWKV (v4 WKV) created: {total:,} total parameters")
def forward(self, x, state=None):
x = self.embed_drop(self.embed(x))
new_states = []
for i, block in enumerate(self.blocks):
block_state = state[i] if state is not None else None
x, new_state = block(x, block_state)
new_states.append(new_state)
x = self.ln_out(x)
logits = self.head(x)
return logits, new_states
def forward_step(self, x, state):
x = self.embed(x)
new_states = []
for i, block in enumerate(self.blocks):
x, new_state = block.forward_step(x, state[i])
new_states.append(new_state)
if self._block_outputs is not None:
self._block_outputs.append(x.abs().mean().item())
x = self.ln_out(x)
logits = self.head(x)
return logits, new_states
def init_state(self, batch_size=1, device='cpu'):
state = []
for block in self.blocks:
n = torch.zeros(batch_size, 1, self.hidden_size, device=device)
d = torch.zeros(batch_size, 1, self.hidden_size, device=device)
state.append(((n, d),))
return state
def generate(self, prompt_ids, max_new=50, temperature=0.7, top_k=20, repetition_penalty=1.1, progress_cb=None):
self.eval()
device = next(self.parameters()).device
generated = list(prompt_ids)
state = self.init_state(device=device)
with torch.no_grad():
for token_id in prompt_ids[:-1]:
x = torch.tensor([[token_id]], device=device)
_, state = self.forward_step(x, state)
x = torch.tensor([[prompt_ids[-1]]], device=device)
for step in range(max_new):
if progress_cb:
self._block_outputs = []
logits, state = self.forward_step(x, state)
logits = logits[0, 0] # squeeze from (1,1,vocab) -> (vocab)
if progress_cb:
progress_cb(step + 1, max_new, list(self._block_outputs), generated)
self._block_outputs = None
if repetition_penalty > 1.0:
for tid in set(generated[-20:]):
if logits[tid] > 0:
logits[tid] /= repetition_penalty
else:
logits[tid] *= repetition_penalty
logits = logits / temperature
if top_k > 0:
vals, _ = torch.topk(logits, min(top_k, logits.size(-1)))
logits[logits < vals[-1]] = -float('Inf')
probs = F.softmax(logits, dim=-1)
next_id = torch.multinomial(probs, num_samples=1).item()
generated.append(next_id)
x = torch.tensor([[next_id]], device=device)
if next_id == 2:
break
self._block_outputs = None
return generated
if __name__ == "__main__":
model = RWKV(vocab_size=1024, hidden_size=128, num_layers=2)
x = torch.randint(0, 1024, (2, 10))
logits, state = model(x)
print(f"Output shape: {logits.shape}")
print(f"Params: {sum(p.numel() for p in model.parameters()):,}")