test_fast_load_checkpoint.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. """In-image check of adapter/fast_load.py against the real checkpoint (not part of the build).
  2. Run on a node with the weights mounted, CPU only:
  3. docker run --rm -v <model dir>:/models/DeepSeek-V4.1-Flash:ro \
  4. -v $PWD/tests/test_fast_load_checkpoint.py:/t.py:ro \
  5. --entrypoint /opt/sglang/bin/python3 dsv41-4x-spark:canary-roce /t.py
  6. Checks: draft filtering yields exactly the mtp keys with 45 shards skipped; eager tensors are
  7. bitwise equal to the stock safe_open tensors for every dtype; the target set covers ~155 GB per
  8. EP rank, and at EP1 (moe_tp 4) the same ~155 GB read / ~83 GB kept with every TP slice bitwise equal
  9. to the stock tensor's narrow; RSS returns to baseline after a shard's tensors are released.
  10. """
  11. import os, sys, json, time, torch
  12. os.environ["DSV41_FAST_LOAD"] = "1"
  13. sys.path.insert(0, "/opt/dsv41/adapter")
  14. import fast_load, safetensors
  15. from safetensors import safe_open as raw_open
  16. M = "/models/DeepSeek-V4.1-Flash"
  17. files = sorted(f"{M}/{f}" for f in os.listdir(M) if f.endswith(".safetensors"))
  18. idx = json.load(open(f"{M}/model.safetensors.index.json"))["weight_map"]
  19. mtp = {k for k in idx if k.startswith("mtp.")}
  20. n = fast_load._n_routed_experts(files[0]); assert n == 384, n
  21. for ep in ((0, 2), (1, 2)):
  22. tot = sum(b - a for f in files for a, b in fast_load.needed_ranges(f, "target", ep, n))
  23. print(f"target ep={ep}: {tot/1e9:.1f} GB eager"); assert 150e9 < tot < 160e9
  24. # EP1 (moe_tp 4): each rank reads its w1/w3 row block and whole w2 rows of all 384 experts
  25. for tp in ((0, 4), (3, 4)):
  26. plans = {f: fast_load.read_plan(f, "target", (0, 1), n, tp) for f in files}
  27. tot = sum(b - a for f in files for a, b in fast_load.plan_ranges(f, plans[f]))
  28. kept = sum(fast_load.spec_bytes(fast_load._parse_header(f)[1][k], sp)[1] for f in files for k, sp in plans[f].items())
  29. print(f"target ep1 moe_tp={tp}: {tot/1e9:.1f} GB read, {kept/1e9:.1f} GB kept"); assert 150e9 < tot < 160e9 and 80e9 < kept < 86e9
  30. from sglang.srt.model_loader import weight_utils
  31. fast_load.install_weight_utils(weight_utils)
  32. # draft phase through the real loader
  33. fast_load._state["phase"] = "draft"
  34. # Compare tensor by tensor and drop them: never hold a whole draft (7.9 GB) or shard in memory,
  35. # the buffers may be pinned and the node may be serving.
  36. t = time.time(); seen = set(); dtypes = {}; raw = {}
  37. def raw_tensor(name):
  38. f = raw.get(idx[name])
  39. if f is None:
  40. f = raw[idx[name]] = raw_open(f"{M}/{idx[name]}", framework="pt", device="cpu")
  41. return f.get_tensor(name)
  42. for name, e in weight_utils.buffered_multi_thread_safetensors_weights_iterator(files, max_workers=1):
  43. r = raw_tensor(name)
  44. assert e.dtype == r.dtype and e.shape == r.shape, name
  45. assert torch.equal(e.view(-1).view(torch.uint8), r.view(-1).view(torch.uint8)), name
  46. dtypes[str(e.dtype)] = dtypes.get(str(e.dtype), 0) + 1
  47. seen.add(name); del e, r
  48. print(f"draft iteration: {len(seen)} tensors in {time.time()-t:.1f}s; skipped={fast_load._state['skipped']}")
  49. assert seen == mtp, (len(seen), len(mtp))
  50. print("draft tensors bitwise equal:", dtypes)
  51. # target phase on one shard for EP rank 1 with a fake layout
  52. fast_load._state["phase"] = "target"
  53. fast_load._ep_info = lambda: (1, 2)
  54. with safetensors.safe_open(files[7], framework="pt", device="cpu") as f, raw_open(files[7], framework="pt", device="cpu") as g:
  55. keys = list(f.keys()); need = set(fast_load.needed_names(files[7], "target", (1, 2), n))
  56. ok = 0
  57. for k in keys:
  58. e = f.get_tensor(k); r = g.get_tensor(k)
  59. assert e.dtype == r.dtype and e.shape == r.shape and torch.equal(e.view(-1).view(torch.uint8), r.view(-1).view(torch.uint8)), k
  60. ok += 1; del e, r
  61. print(f"target shard: {ok} tensors ({len(need)} eager) bitwise equal to mmap")
  62. # EP1 TP slicing on the same shard for moe_tp rank 1 of 4: the narrow FusedMoE takes
  63. # (shape[dim] // 4 at rank 1) returns bytes equal to the stock tensor's narrow
  64. fast_load._ep_info = lambda: (0, 1)
  65. fast_load._moe_tp_info = lambda: (1, 4)
  66. Slice = fast_load._tp_slice_cls(); sl = 0
  67. with safetensors.safe_open(files[7], framework="pt", device="cpu") as f, raw_open(files[7], framework="pt", device="cpu") as g:
  68. for k in f.keys():
  69. e = f.get_tensor(k); r = g.get_tensor(k)
  70. if isinstance(e, Slice):
  71. d = fast_load._TP_SLICE_DIM[k.rsplit(".", 2)[1]]; m = r.shape[d] // 4
  72. a, b = e.narrow(d, m, m), r.narrow(d, m, m)
  73. assert a.dtype == b.dtype and a.shape == b.shape and torch.equal(
  74. a.contiguous().view(-1).view(torch.uint8), b.contiguous().view(-1).view(torch.uint8)), k
  75. try:
  76. e.narrow(d, 0, m); raise AssertionError(f"{k}: out-of-slice narrow did not raise")
  77. except RuntimeError:
  78. pass
  79. sl += 1
  80. else:
  81. assert torch.equal(e.view(-1).view(torch.uint8), r.view(-1).view(torch.uint8)), k
  82. del e, r
  83. print(f"target shard at EP1 moe_tp 1/4: {sl} expert tensors TP-sliced, slices bitwise equal"); assert sl > 0
  84. fast_load._ep_info = lambda: (1, 2)
  85. print("OK")
  86. # memory: eager tensors of a whole shard must be released, not retained by malloc arenas
  87. import gc
  88. def rss():
  89. for l in open("/proc/self/status"):
  90. if l.startswith("VmRSS"): return int(l.split()[1]) // 1024
  91. r0 = rss()
  92. with safetensors.safe_open(files[9], framework="pt", device="cpu") as f:
  93. held = [f.get_tensor(k) for k in fast_load.needed_names(files[9], "target", (1, 2), n)[:300]]
  94. r1 = rss(); del held; gc.collect(); fast_load._release_all(); r2 = rss()
  95. print(f"RSS MB: before {r0}, holding 300 tensors {r1}, after release {r2}")
  96. assert r1 - r0 > 500 and r2 - r0 < 300, (r0, r1, r2)
  97. print("OK3")