Looped-Qwen-pre / scripts /test_looped_gdn.py
summer
Looped GDN: weights + custom code + scripts
51ad11c verified
Raw History Blame Contribute Delete
2.92 kB
import sys, tempfile, torch
from transformers.models.qwen3_5.configuration_qwen3_5 import Qwen3_5TextConfig
from transformers.models.qwen3_5.modeling_qwen3_5 import Qwen3_5ForCausalLM
from looped_gdn import build_from_source
torch.manual_seed(0)
L = 8
cfg = Qwen3_5TextConfig(
vocab_size=500, hidden_size=128, intermediate_size=256, num_hidden_layers=L,
layer_types=["linear_attention"] * L, linear_num_key_heads=4, linear_num_value_heads=4,
linear_key_head_dim=32, linear_value_head_dim=32, num_attention_heads=2, num_key_value_heads=1,
head_dim=64, tie_word_embeddings=True,
rope_parameters={"rope_type": "default", "rope_theta": 1e7, "partial_rotary_factor": 0.25,
"mrope_section": [3, 3, 2], "mrope_interleaved": True},
)
src = Qwen3_5ForCausalLM(cfg).eval()
with torch.no_grad(): # make per-layer small params distinct so merging is non-trivial
for l in src.model.layers:
l.input_layernorm.weight.normal_(0, 0.1); l.post_attention_layernorm.weight.normal_(0, 0.1)
d = tempfile.mkdtemp(); src.save_pretrained(d)
ids = torch.randint(0, 500, (2, 12))
ref = src(ids, use_cache=False).logits
ok = True
# 1) r = 1 (G = L): must reproduce the source exactly (gate ~1, RoPE-injection alpha=0)
m1 = build_from_source(d, num_groups=L, dtype=torch.float32, gate_init_bias=30.0).eval()
out = m1(ids)["logits"]
err = (out - ref).abs().max().item(); print("r=1 vs source max|diff| =", err); ok &= err < 1e-4
# 2) spatial merge G=2, r=4 ; 3) cached decode == full forward
m = build_from_source(d, num_groups=2, dtype=torch.float32).eval()
print("G=2 r=4 params:", sum(p.numel() for p in m.parameters()), "vs source", sum(p.numel() for p in src.parameters()))
full = m(ids)["logits"]
o = m(ids[:, :8], use_cache=True); c = o["past_key_values"]; steps = [o["logits"]]
for t in range(8, 12):
o = m(ids[:, t:t + 1], past_key_values=c, use_cache=True); steps.append(o["logits"])
inc = torch.cat(steps, 1)
err2 = (inc - full).abs().max().item(); print("cached decode vs full max|diff| =", err2); ok &= err2 < 1e-3
# 4) gradients reach gates / RoPE-injection / per-step params; non-trivial RoPE alters output
m.train(); m(ids)["logits"].float().pow(2).mean().backward()
for n in ["rope_alpha", "step_embed", "groups.0.gate_w", "groups.0.gate_b", "groups.0.ln1", "groups.0.A_log"]:
g = dict(m.named_parameters())[n].grad; print(f"grad {n:18s}", None if g is None else f"{g.norm():.3e}"); ok &= g is not None
with torch.no_grad(): m.rope_alpha.fill_(0.5)
m.eval(); print("RoPE active changes output:", (m(ids)["logits"] - full).abs().max().item() > 1e-3)
# 5) early exit (no cache)
m.halt_threshold = 0.5
with torch.no_grad(): m.groups[1].gate_b[2:] = -10
r = m(ids, return_gates=True); print("executed inner steps:", len(r["gates"]), "/", m.G * m.r)
print("generate:", m.generate(ids[:1, :5], 5).tolist())
print("ALL OK" if ok else "FAIL"); sys.exit(0 if ok else 1)