| |
| """BF16 teacher streamer for GLM-5.2 (phase-2 full-AQLM teacher). |
| |
| Backs `get_expert(layer, expert, proj) -> torch.bfloat16` with HTTP range |
| reads of zai-org/GLM-5.2 (the original BF16 weights). The safetensors index |
| is fetched once; per-shard headers are cached on first touch under |
| /data/bf16-cache/headers/; fetched tensor byte ranges live in an on-disk LRU |
| blob cache under /data/bf16-cache/blobs/, capped at 400 GB with mtime-based |
| eviction. |
| |
| Range-fetch pattern reused from tools/gguf_remote.py and |
| tools/make_hot_manifest.py (safetensors: 8-byte little-endian header length, |
| JSON header of {name: {dtype, shape, data_offsets:[b,e]}}, tensor bytes at |
| 8 + header_len + b .. 8 + header_len + e). |
| |
| Run directly to VERIFY against the NVFP4 teacher: |
| python tools/bf16_stream.py [--n 10] |
| prints cosine similarity for N random (layer, expert, proj) triples between |
| the streamed BF16 tensor and the NVFP4-dequantized teacher (regions in |
| /tmp/glm52-hot-dl2); all must exceed 0.98. |
| """ |
| import argparse |
| import hashlib |
| import json |
| import os |
| import struct |
| import time |
| import urllib.request |
|
|
| BASE = "https://huggingface.co/zai-org/GLM-5.2/resolve/main" |
| CACHE = "/data/bf16-cache" |
| CAP_BYTES = 400 * (1 << 30) |
| LOCAL_TEACHER_LAYERS = {3, 4, 5, 8, 74, 75, 76, 77} |
|
|
| |
| _PROJ = { |
| "gate_proj": "gate_proj", "up_proj": "up_proj", "down_proj": "down_proj", |
| "gate": "gate_proj", "up": "up_proj", "down": "down_proj", |
| "w1": "gate_proj", "w3": "up_proj", "w2": "down_proj", |
| } |
|
|
| |
| _ST_DT = {"BF16": "bfloat16", "F16": "float16", "F32": "float32"} |
|
|
|
|
| def _fetch(url, start=None, length=None, retries=6, timeout=120): |
| """HTTP GET (optionally a byte range) with exponential backoff.""" |
| last = None |
| for attempt in range(retries): |
| try: |
| req = urllib.request.Request(url) |
| if start is not None: |
| req.add_header("Range", f"bytes={start}-{start + length - 1}") |
| with urllib.request.urlopen(req, timeout=timeout) as r: |
| return r.read() |
| except Exception as e: |
| last = e |
| if attempt == retries - 1: |
| break |
| time.sleep(min(30, 2 ** attempt)) |
| raise RuntimeError(f"fetch failed {url} [{start}:{length}]: {last}") |
|
|
|
|
| class Bf16Teacher: |
| def __init__(self, base=BASE, cache=CACHE, cap_bytes=CAP_BYTES): |
| self.base = base.rstrip("/") |
| self.cache = cache |
| self.cap = cap_bytes |
| self.hdr_dir = os.path.join(cache, "headers") |
| self.blob_dir = os.path.join(cache, "blobs") |
| os.makedirs(self.hdr_dir, exist_ok=True) |
| os.makedirs(self.blob_dir, exist_ok=True) |
| self._headers = {} |
| self._hdr_len = {} |
| self._wm = None |
|
|
| |
| def _index(self): |
| if self._wm is None: |
| p = os.path.join(self.cache, "index.json") |
| if os.path.exists(p): |
| idx = json.load(open(p)) |
| else: |
| idx = json.loads(_fetch(f"{self.base}/model.safetensors.index.json")) |
| json.dump(idx, open(p, "w")) |
| self._wm = idx["weight_map"] |
| return self._wm |
|
|
| def _header(self, shard): |
| if shard in self._headers: |
| return self._headers[shard], self._hdr_len[shard] |
| hp = os.path.join(self.hdr_dir, shard + ".json") |
| if os.path.exists(hp): |
| d = json.load(open(hp)) |
| hdr, hlen = d["header"], d["header_len"] |
| else: |
| url = f"{self.base}/{shard}" |
| hlen = struct.unpack("<Q", _fetch(url, 0, 8))[0] |
| hdr = json.loads(_fetch(url, 8, hlen)) |
| hdr.pop("__metadata__", None) |
| json.dump({"header_len": hlen, "header": hdr}, open(hp, "w")) |
| self._headers[shard] = hdr |
| self._hdr_len[shard] = hlen |
| return hdr, hlen |
|
|
| |
| def _blob_path(self, key): |
| h = hashlib.sha1(key.encode()).hexdigest() |
| return os.path.join(self.blob_dir, h + ".bin") |
|
|
| def _evict_if_needed(self, incoming): |
| files = [] |
| total = 0 |
| with os.scandir(self.blob_dir) as it: |
| for e in it: |
| if e.name.endswith(".bin"): |
| st = e.stat() |
| files.append((st.st_mtime, e.path, st.st_size)) |
| total += st.st_size |
| if total + incoming <= self.cap: |
| return |
| files.sort() |
| for _, path, size in files: |
| if total + incoming <= self.cap: |
| break |
| try: |
| os.remove(path) |
| total -= size |
| except OSError: |
| pass |
|
|
| def _get_bytes(self, name): |
| wm = self._index() |
| if name not in wm: |
| raise KeyError(name) |
| shard = wm[name] |
| hdr, hlen = self._header(shard) |
| info = hdr[name] |
| b, e = info["data_offsets"] |
| key = f"{shard}:{name}" |
| bp = self._blob_path(key) |
| if os.path.exists(bp): |
| os.utime(bp, None) |
| return open(bp, "rb").read(), info |
| length = e - b |
| buf = _fetch(f"{self.base}/{shard}", 8 + hlen + b, length) |
| self._evict_if_needed(len(buf)) |
| tmp = bp + f".tmp{os.getpid()}" |
| with open(tmp, "wb") as f: |
| f.write(buf) |
| os.replace(tmp, bp) |
| return buf, info |
|
|
| |
| def get_expert(self, layer: int, expert: int, proj: str): |
| import torch |
|
|
| pj = _PROJ.get(proj, proj) |
| name = f"model.layers.{layer}.mlp.experts.{expert}.{pj}.weight" |
| buf, info = self._get_bytes(name) |
| dt = _ST_DT.get(info["dtype"]) |
| if dt is None: |
| raise ValueError(f"unexpected dtype {info['dtype']} for {name}") |
| t = torch.frombuffer(bytearray(buf), dtype=getattr(torch, dt)) |
| return t.reshape(info["shape"]).to(torch.bfloat16) |
|
|
|
|
| |
| def _verify(n): |
| import random |
|
|
| import torch |
|
|
| import sys |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| from aqlm_converge import TeacherReader, dequant_teacher, FP4_LUT |
|
|
| teacher = Bf16Teacher() |
| tr = TeacherReader() |
| lut = torch.tensor(FP4_LUT, dtype=torch.float32) |
|
|
| |
| avail = {} |
| for shard, h in tr.headers.items(): |
| for nm in h["header"]: |
| if ".mlp.experts." in nm and nm.endswith("gate_proj.weight"): |
| p = nm.split(".") |
| li, e = int(p[2]), int(p[5]) |
| if li not in LOCAL_TEACHER_LAYERS: |
| avail.setdefault(li, set()).add(e) |
| pool = [(li, e) for li, es in avail.items() for e in es] |
| rng = random.Random(1234) |
| rng.shuffle(pool) |
|
|
| cosines = [] |
| tried = 0 |
| for li, e in pool: |
| if len(cosines) >= n: |
| break |
| proj = rng.choice(["gate_proj", "up_proj", "down_proj"]) |
| tried += 1 |
| try: |
| nv = dequant_teacher(tr, li, e, proj, "cpu", lut) |
| except KeyError: |
| continue |
| bf = teacher.get_expert(li, e, proj).float() |
| a = nv.reshape(-1) |
| b = bf.reshape(-1) |
| cos = torch.dot(a, b) / (a.norm() * b.norm() + 1e-12) |
| cosines.append(cos.item()) |
| print(f" L{li:>2} e{e:<3} {proj:<10} cos={cos.item():.5f}") |
| print(f"\n{len(cosines)} cosines (from {tried} tries); " |
| f"min={min(cosines):.5f} mean={sum(cosines)/len(cosines):.5f}") |
| assert all(c > 0.98 for c in cosines), "cosine <= 0.98 for some triple" |
| print("VERIFY PASS: all cosines > 0.98") |
|
|
|
|
| if __name__ == "__main__": |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--n", type=int, default=10) |
| _verify(ap.parse_args().n) |
|
|