Source: Official BlinkDL/RWKV-LM repository (https://github.com/BlinkDL/RWKV-LM)
Retrieved: 2026-01-12
Purpose: Explain observed softmax saturation in Task 0.0.1 and document correct training practices
Our Task 0.0.1 baseline showed:
- Logits range: [-55, +83] (should be much smaller)
- Max probability: 1.0 (saturating)
- Entropy: 1.70 (random = 9.68)
Root Cause: Initialization differs significantly from BlinkDL's recommendations.
Sub-task 0.0.1.a tested BlinkDL init vs our original init:
| Metric | Original | BlinkDL | Change |
|---|---|---|---|
| Final loss | 34.3 | 7.9 | 4.3x better |
| Max logit | 83 | 6.4 | 13x smaller |
| Max prob | 1.0 | 0.082 | No saturation |
| Entropy | 1.7 | 9.2 | Near random |
| Saturation | 15.6% | 0% | Fixed |
Conclusion: Adopt BlinkDL initialization for all future RWKV work.
From generate_init_weight() in src/model.py:
# Embedding - VERY SMALL
emb.weight => nn.init.uniform_(a=-1e-4, b=1e-4)
# Note: ln0 of block0 is the layernorm for emb.weight
# Head (output projection)
head.weight => nn.init.orthogonal_(gain=0.5*sqrt(n_vocab / n_embd))
# Attention weights
att.receptance.weight => nn.init.orthogonal_(gain=1)
att.key.weight => nn.init.orthogonal_(gain=0.1) # LOW GAIN
att.value.weight => nn.init.orthogonal_(gain=1)
att.gate.weight => nn.init.orthogonal_(gain=0.1) # LOW GAIN
att.output.weight => zero # ZERO INIT
# GroupNorm layer scaling (ln_x)
att.ln_x.weight => ((1 + layer_id) / total_layers) ** 0.7
# FFN weights
ffn.key.weight => nn.init.orthogonal_(gain=1)
ffn.value.weight => zero # ZERO INIT
ffn.receptance.weight => zero # ZERO INIT| Parameter | Our Current | BlinkDL Official | Impact |
|---|---|---|---|
emb.weight |
Default (~N(0,1)) | uniform(-1e-4, 1e-4) | 10,000x too large |
head.weight |
Tied to embed | orthogonal(0.5*sqrt(V/D)) | Tied is fine, but embed too large |
ffn.value.weight |
xavier(0.5) | ZERO | Residual starts non-zero |
att.output.weight |
Default | ZERO | Residual starts non-zero |
att.key.weight |
Default | orthogonal(0.1) | Key scaling matters |
| Weight decay | 0.1 to all params | 0.1 only to projections | LN/bias shouldn't decay |
| Normalization | RMSNorm | PreLN LayerNorm | Minor difference |
From BlinkDL's README:
- Upgrade to RWKV-7 (most stable, spike-free)
- K-clamping: Add
k = k * torch.clamp(w, max=0).exp()before WKV kernel - Adam eps: Use
--adam_eps 1e-18 - Beta2: Use
--beta2 0.95if seeing spikes - Warmup:
lr = lr * (0.01 + 0.99 * step / warmup_steps)with--warmup_steps 20 - Weight decay: 0.1 with
lr_final = lr_init / 100
Direct quote from BlinkDL:
"When I train RWKV music models, I use deep & narrow (such as L29-D512) dimensions, and apply wd and dropout (such as wd=2 dropout=0.02). Note RWKV-LM dropout is very effective - use 1/4 of your usual value."
Implications for our 8L×144D model:
- Consider deeper architecture (more layers, smaller hidden)
- Apply dropout (0.02) with
x = x + dropout(att(x)) - Weight decay can be higher (up to 2.0)
Direct quote from BlinkDL:
"Only apply weight decay to large matrix parameters (basically projections) in your model instead of all parameters. THIS IS VERY IMPORTANT."
Correct implementation:
def get_parameter_groups(model, base_lr=3e-4, wd=0.1):
decay_params = []
no_decay_params = []
for name, p in model.named_parameters():
if p.dim() >= 2: # Matrix weights
decay_params.append(p)
else: # Biases, LayerNorm, small params
no_decay_params.append(p)
return [
{'params': decay_params, 'weight_decay': wd},
{'params': no_decay_params, 'weight_decay': 0.0},
]Direct quote from BlinkDL:
"Use PreLN LayerNorm (instead of RMSNorm) for RWKV. I think it's related to better initial state, because I am not using trainable initial state (found it useless when using LayerNorm)."
- Add initialization ablation cell to notebook
- Test tiny embedding init:
uniform(-1e-4, 1e-4) - Test zero-init for FFN output and att.output
- Apply weight decay only to projections
- Implement proper BlinkDL initialization in production model
- Consider PreLN LayerNorm instead of RMSNorm
- Add K-clamping for RWKV-6 stability
- Test deeper/narrower architectures for small models
- BlinkDL/RWKV-LM - Official repository
- RWKV-5/6 Paper - Eagle/Finch paper
- rwkv7_train_simplified.py - Reference implementation
Observed:
- Logits: [-55, +83] (exploding)
- Max prob: 1.0 (saturating)
- Entropy: 1.70 vs random 9.68
Root Cause Analysis:
- Embedding init too large (default ~N(0,1) vs required uniform(-1e-4, 1e-4))
- FFN output not zero-initialized (residual stream starts non-zero)
- Weight decay applied to LayerNorm (causes drift)
Conclusion: Softmax saturation is NOT an RWKV-6 architectural flaw. It's an initialization/training configuration issue that BlinkDL has solved. Our AMPLIFIER characterization (1.28x/layer) is valid, but the saturation can be mitigated with proper initialization.
Next Steps: Create ablation cell to verify these hypotheses before proceeding to Task 0.0.2.