embeddings-are-overpriced / kernel /export_arm_c.py
QLNI's picture
Embeddings Are Overpriced? release: code, data, checkpoints and evaluation
f31f59b
Raw History Blame Contribute Delete
2.35 kB
"""Arm C checkpoint -> armC.bin for cgemma.c (CPU kernel for the Gemma-3-270M shape with a ternary BitNet body and the
frozen 512-bit table). Every ternary tensor is packed exactly as the trainer's BitLinear rounds it (round-trip checked).
python kernel/export_arm_c.py finals/C/step_07820.pt kernel/armC.bin
"""
import struct, sys, numpy as np, torch
sys.path.insert(0, ".")
import train
V, BITS, D, L, NH, NKV, HD, FFN = 262144, 512, 640, 18, 4, 1, 256, 2048
ckpt, out = sys.argv[1], sys.argv[2]
sd = torch.load(ckpt, map_location="cpu", weights_only=False)["model"]
f = open(out, "wb")
f.write(b"CGEM"); f.write(struct.pack("<8i", V, BITS, D, L, NH, NKV, HD, FFN))
def f32(t): np.ascontiguousarray(t.detach().float().numpy(), dtype=np.float32).tofile(f)
def tern(w):
"""BitLinear: sw = 1/mean|w|; code = round(w*sw).clamp(-1,1); kernel value = code / sw. Packed 2 bits, code+1, row-major."""
w = w.detach().float(); sw = 1.0 / w.abs().mean().clamp_min(1e-5)
code = (w * sw).round().clamp(-1, 1)
assert torch.allclose(code / sw, train.ste(w, (w * sw).round().clamp(-1, 1) / sw), atol=1e-6, rtol=0), "round-trip" # trainer value = w + (q - w) in fp32
q = (code + 1).to(torch.uint8).numpy().reshape(-1)
packed = (q[0::4] | (q[1::4] << 2) | (q[2::4] << 4) | (q[3::4] << 6)).astype(np.uint8)
packed.tofile(f); np.full(w.shape[0], float(1.0 / sw), np.float32).tofile(f)
bits = ((sd["table"] > 0).to(torch.uint8).numpy()) # +1 -> bit 1
np.packbits(bits, axis=1, bitorder="little").tofile(f) # [V, 64] bytes; byte b bit j <-> dim 8b+j
f32(sd["seal"]); f32(sd["inp.weight"]); f32(sd["out.weight"]) # seal [V]; inp [640,512]; out [512,640]
f32(1.0 + sd["body.norm.weight"])
for l in range(L):
p = f"body.layers.{l}."
for n in ("input_layernorm", "post_attention_layernorm", "pre_feedforward_layernorm", "post_feedforward_layernorm"):
f32(1.0 + sd[p + n + ".weight"])
f32(1.0 + sd[p + "self_attn.q_norm.weight"]); f32(1.0 + sd[p + "self_attn.k_norm.weight"])
for n in ("self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj", "self_attn.o_proj", "mlp.gate_proj", "mlp.up_proj", "mlp.down_proj"):
tern(sd[p + n + ".weight"])
f.close()
import os; print("wrote", out, f"{os.path.getsize(out)/1e6:.1f} MB")