| """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}") |
| |
| 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)") |
| |
| |
| |
| SPLIT = 4 |
| 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) |
| 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) |
| 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" |
| |
| 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])) |
|
|
| |
| 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") |