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") |