File size: 4,922 Bytes
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ef0d8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8ca8bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ef0d8f
e8ca8bd
 
0ef0d8f
e8ca8bd
 
 
 
0ef0d8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8ca8bd
0ef0d8f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""End-to-end verification of the converted RWKV-7 HF model."""
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

PATH = "/workspace/rwkv7-g1d-olmo"

print("=" * 70)
print("1) Loading with AutoModelForCausalLM(trust_remote_code=True)")
tok = AutoTokenizer.from_pretrained(PATH, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    PATH, trust_remote_code=True, dtype=torch.bfloat16
).cuda()
model.eval()
print("   model class:", type(model).__name__)
print("   emb:", tuple(model.get_input_embeddings().weight.shape),
      "head:", tuple(model.get_output_embeddings().weight.shape))
nparams = sum(p.numel() for p in model.parameters())
print(f"   params: {nparams/1e6:.1f}M")

ids = tok("The Eiffel tower is in the city of", return_tensors="pt").input_ids.cuda()
print("   input ids:", ids.tolist())

print("=" * 70)
print("2) Forward with CUDA kernel")
with torch.no_grad():
    out_k = model(ids).logits
print("   logits:", tuple(out_k.shape), out_k.dtype)

print("=" * 70)
print("3) Forward with PyTorch fallback + kernel-vs-fallback parity")
model.config.use_cuda_kernel = False
with torch.no_grad():
    out_f = model(ids).logits
model.config.use_cuda_kernel = True
diff = (out_k.float() - out_f.float()).abs()
print(f"   max abs diff kernel vs fallback: {diff.max().item():.4e}")
print(f"   mean abs diff:                   {diff.mean().item():.4e}")
# argmax agreement on last token
print("   kernel top-1 next id:", out_k[0, -1].argmax().item(),
      "| fallback:", out_f[0, -1].argmax().item())

print("=" * 70)
print("4) Cached vs uncached parity (prefill + stateful decode vs full recompute)")
# The stateful path always runs on the PyTorch implementation; compare against a
# full PyTorch-fallback forward so both sides share the same numerics (fp32
# internal, sequential over time) -> should match (near-)exactly.
SPLIT = 4  # decode the last SPLIT tokens one at a time
assert ids.shape[1] > SPLIT
model.config.use_cuda_kernel = False
with torch.no_grad():
    full = model(ids).logits
    pre = model(ids[:, :-SPLIT], use_cache=True)      # prefill -> state
    st, chunks = pre.state, [pre.logits]
    for t in range(ids.shape[1] - SPLIT, ids.shape[1]):
        o = model(ids[:, t:t + 1], state=st)          # passing state implies use_cache
        st, chunks = o.state, chunks + [o.logits]
    inc = torch.cat(chunks, dim=1)
model.config.use_cuda_kernel = True
diff_c = (inc.float() - full.float()).abs()
print(f"   max abs diff cached vs uncached: {diff_c.max().item():.4e}")
print(f"   mean abs diff:                   {diff_c.mean().item():.4e}")
assert torch.allclose(inc.float(), full.float(), atol=1e-3, rtol=0), \
    "cached (prefill+decode) logits diverge from full recompute"
# also check against the kernel forward with a looser bf16 tolerance
diff_ck = (inc.float() - out_k.float()).abs()
print(f"   max abs diff cached vs kernel:   {diff_ck.max().item():.4e}")
ax, wkv, fx = st[0]
print("   state[0] shapes:",
      "att_x_prev", tuple(ax.shape), ax.dtype, "|",
      "wkv", tuple(wkv.shape), wkv.dtype, "|",
      "ffn_x_prev", tuple(fx.shape), fx.dtype)

print("=" * 70)
print("5) Backward pass (gradients flow through kernel)")
model.train()
ids2 = tok("Backward test sentence for gradient check.", return_tensors="pt").input_ids.cuda()
out = model(ids2, labels=ids2)
loss = out.loss
loss.backward()
gnorm_emb = model.get_input_embeddings().weight.grad
g_att = model.rwkv.blocks[0].att.receptance.weight.grad
g_w1 = model.rwkv.blocks[6].att.w1.grad
print(f"   loss: {loss.item():.4f}")
print(f"   emb.grad is not None: {gnorm_emb is not None}, norm={gnorm_emb.float().norm().item():.4e}")
print(f"   blocks.0.att.receptance.grad norm: {g_att.float().norm().item():.4e}")
print(f"   blocks.6.att.w1 (decay-lora) grad norm: {g_w1.float().norm().item():.4e}")
n_with_grad = sum(1 for p in model.parameters() if p.grad is not None and p.grad.abs().sum() > 0)
n_total = sum(1 for _ in model.parameters())
print(f"   params with non-zero grad: {n_with_grad}/{n_total}")
model.zero_grad(set_to_none=True)

print("=" * 70)
print("6) generate() through the state cache")
model.eval()
with torch.no_grad():
    gen = model.generate(ids, max_new_tokens=10, do_sample=False)
print("   generated:", tok.decode(gen[0]))

# generate() must equal a manual RNN-mode greedy loop (same state-cache path)
with torch.no_grad():
    o = model(ids, use_cache=True)
    cur = o.logits[:, -1].argmax(-1, keepdim=True)
    st, toks = o.state, [cur]
    for _ in range(9):
        o = model(cur, state=st)
        st = o.state
        cur = o.logits[:, -1].argmax(-1, keepdim=True)
        toks.append(cur)
manual = torch.cat(toks, dim=1)
match = bool((gen[:, ids.shape[1]:] == manual).all())
print("   generate == manual stateful greedy:", match)
assert match, "generate() output diverges from manual stateful greedy decode"

print("=" * 70)
print("ALL CHECKS DONE")