jon1012 commited on
Commit
aaee68b
·
verified ·
1 Parent(s): f502798

Final card and build scripts: measured numbers, Engram caveat, pruning analysis

Browse files
Files changed (1) hide show
  1. dsv41_fp4_stream.py +129 -2
dsv41_fp4_stream.py CHANGED
@@ -50,7 +50,7 @@ Source tensor naming (flat, no "model." prefix, "ffn" not "mlp"):
50
  layers.{L}.engram.embed.scale e8m0 [rows, 8]
51
  mtp.{M}.ffn.experts.{E}... same as backbone
52
  """
53
- import argparse, json, os, re, shutil, sys, time
54
  import torch
55
  from safetensors import safe_open
56
  from safetensors.torch import save_file
@@ -97,7 +97,8 @@ def quantize_e2m1_codes(x: torch.Tensor) -> torch.Tensor:
97
  code, matching what the FP4 hardware conversion does.
98
  """
99
  mag = x.abs().clamp(max=FP4_MAX)
100
- grid = FP4_TABLE[:8] # 8 magnitudes, ascending
 
101
  # midpoints between consecutive grid points: .25 .75 1.25 1.75 2.5 3.5 5.0
102
  mid = (grid[1:] + grid[:-1]) / 2
103
  code = torch.bucketize(mag, mid) # 0..7
@@ -200,6 +201,124 @@ def quantize_engram_to_fp4(f, name, sname, rows, cols, chunk_rows=1 << 20, devic
200
 
201
  # ------------------------------------------------------------------ shard loop
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  DEVICE = "cpu"
204
 
205
 
@@ -280,6 +399,14 @@ def main():
280
  sp = os.path.join(args.src, s)
281
  if not os.path.exists(sp):
282
  print(f" SKIP (absent) {s}", flush=True); continue
 
 
 
 
 
 
 
 
283
  wmap, tb = process_shard(sp, os.path.join(args.out, s), do_e, do_g, stats)
284
  full_map.update(wmap); total += tb
285
  cos = (f" engram_cos={stats['engram_cos_sum']/stats['engram_cos_n']:.6f}"
 
50
  layers.{L}.engram.embed.scale e8m0 [rows, 8]
51
  mtp.{M}.ffn.experts.{E}... same as backbone
52
  """
53
+ import argparse, json, os, re, shutil, struct, sys, time
54
  import torch
55
  from safetensors import safe_open
56
  from safetensors.torch import save_file
 
97
  code, matching what the FP4 hardware conversion does.
98
  """
99
  mag = x.abs().clamp(max=FP4_MAX)
100
+ # boundaries must live on x's device -- torch.bucketize will not cross devices
101
+ grid = FP4_TABLE[:8].to(x.device) # 8 magnitudes, ascending
102
  # midpoints between consecutive grid points: .25 .75 1.25 1.75 2.5 3.5 5.0
103
  mid = (grid[1:] + grid[:-1]) / 2
104
  code = torch.bucketize(mag, mid) # 0..7
 
201
 
202
  # ------------------------------------------------------------------ shard loop
203
 
204
+
205
+ # ------------------------------------------- engram shard: stream straight to disk
206
+
207
+ ST_DTYPE = {torch.uint8: ("U8", 1), torch.int8: ("I8", 1),
208
+ torch.float8_e4m3fn: ("F8_E4M3", 1), torch.float8_e8m0fnu: ("F8_E8M0", 1),
209
+ torch.bfloat16: ("BF16", 2), torch.float16: ("F16", 2),
210
+ torch.float32: ("F32", 4), torch.int32: ("I32", 4), torch.int64: ("I64", 8)}
211
+
212
+
213
+ def write_engram_shard(src_path, dst_path, device="cpu", chunk_rows=1 << 20):
214
+ """Quantize and write an Engram shard without ever holding the table in RAM.
215
+
216
+ The obvious implementation -- build the packed tensor, hand the dict to save_file --
217
+ needs ~47 GiB resident for the weights alone, and on a GB10 the GPU allocator draws on
218
+ that same pool. It got OOM-killed at 83%. So we emit the safetensors container by hand:
219
+ the format is an 8-byte little-endian header length, that many bytes of JSON naming
220
+ each tensor's dtype/shape/byte-range, then the raw buffers back to back. Every offset
221
+ is known before a single value is computed, so the packed table can be streamed to the
222
+ file a chunk at a time.
223
+
224
+ Scales are the one thing still buffered (~3 GB for 384M rows x 8) because they are
225
+ produced by the same pass that produces the packed rows but live elsewhere in the file.
226
+
227
+ Peak memory is one chunk plus the scale buffer.
228
+ """
229
+ stats = {"engram": 0, "pass": 0, "cos_sum": 0.0, "cos_n": 0}
230
+ with safe_open(src_path, framework="pt") as f:
231
+ keys = list(f.keys())
232
+ emb = [k for k in keys if ENGRAM_RE.match(k)]
233
+ consumed = set()
234
+ plan = [] # (name, dtype_str, shape, kind, src)
235
+ for name in emb:
236
+ base = name[:-len(".weight")]
237
+ sname = base + ".scale"
238
+ rows, cols = f.get_slice(name).get_shape()
239
+ nblk = cols // SRC_BLOCK
240
+ plan.append((base + ".weight", "U8", [rows, cols // 2], "stream", (name, sname)))
241
+ plan.append((base + ".scale", "F8_E8M0", [rows, nblk], "scale", base))
242
+ consumed.add(name); consumed.add(sname)
243
+ for name in sorted(keys):
244
+ if name in consumed:
245
+ continue
246
+ plan.append((name, None, list(f.get_slice(name).get_shape()), "copy", name))
247
+
248
+ # resolve dtypes for the copied tensors (cheap: they are all small here)
249
+ resolved = []
250
+ for name, dts, shape, kind, srcref in plan:
251
+ if kind == "copy":
252
+ dts = ST_DTYPE[f.get_tensor(name).dtype][0]
253
+ resolved.append((name, dts, shape, kind, srcref))
254
+
255
+ header, off = {}, 0
256
+ for name, dts, shape, kind, srcref in resolved:
257
+ n = 1
258
+ for d in shape:
259
+ n *= d
260
+ nbytes = n * {"U8": 1, "I8": 1, "F8_E4M3": 1, "F8_E8M0": 1,
261
+ "BF16": 2, "F16": 2, "F32": 4, "I32": 4, "I64": 8}[dts]
262
+ header[name] = {"dtype": dts, "shape": shape, "data_offsets": [off, off + nbytes]}
263
+ off += nbytes
264
+ header["__metadata__"] = {"format": "pt"}
265
+ blob = json.dumps(header).encode("utf-8")
266
+ pad = (-len(blob)) % 8 # keep the data 8-byte aligned
267
+ blob += b" " * pad
268
+
269
+ scale_buf = {}
270
+ with open(dst_path, "wb", buffering=1 << 22) as out:
271
+ out.write(struct.pack("<Q", len(blob)))
272
+ out.write(blob)
273
+ for name, dts, shape, kind, srcref in resolved:
274
+ if kind == "stream":
275
+ wname, sname = srcref
276
+ base = name[:-len(".weight")]
277
+ rows, cols = f.get_slice(wname).get_shape()
278
+ nblk = cols // SRC_BLOCK
279
+ sb = torch.empty((rows, nblk), dtype=torch.float8_e8m0fnu)
280
+ wsl, ssl = f.get_slice(wname), f.get_slice(sname)
281
+ t0 = time.time()
282
+ for lo in range(0, rows, chunk_rows):
283
+ hi = min(lo + chunk_rows, rows)
284
+ n = hi - lo
285
+ v = (wsl[lo:hi].to(device).float().view(n, nblk, SRC_BLOCK)
286
+ * ssl[lo:hi].to(device).float().unsqueeze(-1))
287
+ amax = v.abs().amax(dim=-1, keepdim=True)
288
+ exp = torch.ceil(torch.log2((amax / FP4_MAX).clamp(min=1e-38))).clamp(-127, 127)
289
+ ns = torch.pow(torch.tensor(2.0, device=v.device), exp)
290
+ codes = quantize_e2m1_codes(v / ns)
291
+ out.write(pack_e2m1(codes.view(n, cols)).to("cpu")
292
+ .flatten().view(torch.uint8).numpy().tobytes())
293
+ sb[lo:hi] = ns.squeeze(-1).to(torch.float8_e8m0fnu).to("cpu")
294
+
295
+ k = min(4096, n)
296
+ a = v[:k].flatten(1)
297
+ b = (FP4_TABLE.to(v.device)[codes[:k].long()].view(k, nblk, SRC_BLOCK)
298
+ * ns[:k]).flatten(1)
299
+ num = (a * b).sum(1); den = a.norm(dim=1) * b.norm(dim=1)
300
+ ok = den > 0
301
+ if ok.any():
302
+ stats["cos_sum"] += float((num[ok] / den[ok]).sum())
303
+ stats["cos_n"] += int(ok.sum())
304
+ del v, codes, ns, amax, exp, a, b, num, den, ok
305
+ print(f" {base}: {hi}/{rows} ({100*hi/rows:.1f}%) "
306
+ f"cos={stats['cos_sum']/max(stats['cos_n'],1):.6f} "
307
+ f"[{time.time()-t0:.0f}s]", flush=True)
308
+ scale_buf[base + ".scale"] = sb
309
+ stats["engram"] += 1
310
+ elif kind == "scale":
311
+ out.write(scale_buf.pop(srcref + ".scale")
312
+ .flatten().view(torch.uint8).numpy().tobytes())
313
+ else:
314
+ t = f.get_tensor(srcref).contiguous()
315
+ # reinterpret as raw bytes for every dtype: numpy has no bfloat16 or
316
+ # float8, so .numpy() is not an option on most of what passes through
317
+ out.write(t.flatten().view(torch.uint8).numpy().tobytes())
318
+ stats["pass"] += 1
319
+ return stats
320
+
321
+
322
  DEVICE = "cpu"
323
 
324
 
 
399
  sp = os.path.join(args.src, s)
400
  if not os.path.exists(sp):
401
  print(f" SKIP (absent) {s}", flush=True); continue
402
+ has_engram = any(ENGRAM_RE.match(k) for k, v in index["weight_map"].items() if v == s)
403
+ if has_engram and do_g:
404
+ st = write_engram_shard(sp, os.path.join(args.out, s), device=DEVICE)
405
+ stats["engram"] += st["engram"]; stats["pass"] += st["pass"]
406
+ stats["engram_cos_sum"] += st["cos_sum"]; stats["engram_cos_n"] += st["cos_n"]
407
+ print(f" {s} [streamed engram shard] engram={st['engram']} pass={st['pass']} "
408
+ f"cos={st['cos_sum']/max(st['cos_n'],1):.6f} [{time.time()-t0:.0f}s]", flush=True)
409
+ continue
410
  wmap, tb = process_shard(sp, os.path.join(args.out, s), do_e, do_g, stats)
411
  full_map.update(wmap); total += tb
412
  cos = (f" engram_cos={stats['engram_cos_sum']/stats['engram_cos_n']:.6f}"