fast_load.py 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346
  1. """Faster checkpoint loading without changing the bytes that reach the model.
  2. Measured on the 4x Spark TP4/EP2 fleet (2026-09-17): the target load_weight took 230-290 s while
  3. every rank copied only ~130 GB of the 510 GB checkpoint. The stock loader hands the model
  4. mmap-backed safetensors tensors, and the 24-thread copy pool page-faults them in 4-128 KB pieces
  5. (0.5 GB/s on the ranks with moe_tp_rank 0, ~2 GB/s on the others) while the same NVMe delivers
  6. 5-6 GB/s to threaded ``pread``. Warming the page cache ahead of the copies does not survive on
  7. GB10: GPU memory is system memory, so the cache is squeezed while the weights land and the
  8. warmed pages are evicted before the copy touches them. ``posix_fadvise(WILLNEED)`` is capped at
  9. the 128 KB readahead window anyway.
  10. What this does, gated by ``DSV41_FAST_LOAD=1``:
  11. 1. When the loader opens a shard, the tensors *this rank* will copy are read eagerly into
  12. pinned host memory (torch's caching host allocator; anonymous mmaps without CUDA) by a
  13. 16-thread ``pread`` pool (every tensor except the routed experts
  14. another EP rank owns and the Engram tables, which the row store serves straight from NVMe).
  15. ``get_tensor`` for those names returns the resident tensor; everything else stays the stock
  16. mmap tensor. Same file offsets, same dtype, same shape: the values are identical by
  17. construction and a wrong ownership guess only costs I/O.
  18. 2. The model's ``load_weights`` submits every copy to a thread pool and never waits, so the
  19. shard enumeration would run far ahead and keep whole shards resident. ``maybe_executor_submit``
  20. is wrapped with a byte budget (``DSV41_FAST_LOAD_INFLIGHT_GB``, default 6): the enumeration,
  21. and with it the loader's window and the eager reads, stays just ahead of the copies. A copy
  22. whose source is a view into a slab charges the whole slab, once, until the last queued copy
  23. from that slab finishes (one slow small copy keeps its whole slab alive); other sources charge
  24. their own bytes. Host memory in flight is bounded by that budget plus one slab plus the loader
  25. window (``--model-loader-extra-config {"num_threads":1}`` = 2 shards, ~3.4 GB each on TP4/EP2).
  26. 3. The DSpark draft is loaded from the same 48 shards but consumes only ``mtp.*`` tensors (see
  27. ``_remap_dspark_weight_name``). During that load, shards without any ``mtp.*`` key are handed
  28. back empty so the loader does not open and enumerate 45 files for nothing.
  29. 4. Expert tensor parallelism (``moe_ep_size == 1``, ``moe_tp_size > 1``; knob
  30. ``DSV41_FAST_LOAD_TP_SLICE``, default ``auto``): every rank needs a 1/moe_tp slice of every
  31. routed expert, so a routed ``w1``/``w3`` (weight and scale) is read as the rank's contiguous row
  32. block and a ``w2`` as whole rows (every 4 KB page holds needed bytes, so the device reads it all
  33. anyway) from which a per-thread bounce buffer keeps the rank's columns. ``get_tensor`` returns a
  34. full-shape stand-in (``_tp_slice_cls``) whose ``narrow`` along the sliced dim returns the resident
  35. slice; any other access raises. EP2 is untouched unless ``DSV41_FAST_LOAD_TP_SLICE=1``.
  36. 5. Slabs (``DSV41_FAST_LOAD_SLAB_MB``, default 256; 0 = one pinned buffer per tensor as before):
  37. a shard's eager tensors are packed, in the order the loader yields them (sorted names), into
  38. power-of-two pinned slabs, and every tensor is a view into its slab. Tensors larger than a
  39. slab get their own buffer. Full slabs share one size bin of torch's caching host allocator, so
  40. a freed slab is reused by the next shard: a load makes tens of ``cudaHostAlloc`` calls instead
  41. of thousands (EP1 used to request 92k small pinned blocks, each rounded up to a power of two).
  42. A slab goes back to the allocator when the last view into it dies; ``_release_all`` checks with
  43. storage weak references that none is still alive (an escaped view would pin a whole slab) and
  44. names the tensors if one is. After the target load an escaped slab is an error (it would sit in
  45. the KV pool's budget); after the draft load it is a warning. ``DSV41_FAST_LOAD_MEMLOG=1`` logs
  46. the host-memory counters, the GPU processes and the pinned-allocation counts at each phase end.
  47. Everything is released before the KV pool is sized, so the head's memory budget is unchanged.
  48. """
  49. import concurrent.futures
  50. import json
  51. import logging
  52. import os
  53. import struct
  54. import threading
  55. logger = logging.getLogger(__name__)
  56. _DRAFT_PREFIX = "mtp."
  57. _TARGET_SKIP_PREFIXES = (_DRAFT_PREFIX,)
  58. _ENGRAM_TABLE = ".engram.embed."
  59. _CHUNK = 8 << 20
  60. _state = {"phase": "target", "armed": False, "ep_warned": False, "layout_logged": False,
  61. "bytes": 0, "files": 0, "skipped": 0, "resident": 0, "sliced": 0, "tp_logged": False,
  62. "open_failures": 0, "read_failures": 0,
  63. # host buffers requested by this module in the current phase (slabs + standalone tensors)
  64. "host_allocs": 0, "host_alloc_bytes": 0, "slabs": 0, "slab_peak_live": 0, "slab_peak_bytes": 0}
  65. _SLAB_ALIGN = 4096 # every tensor starts on a page boundary inside its slab
  66. _slab_refs: list = [] # [StorageWeakRef, nbytes, path, members, base ptr] of every slab of this phase
  67. _slab_size: dict = {} # storage base ptr -> (nbytes, StorageWeakRef) of this phase's slabs (pacing)
  68. _load_start: dict = {} # MemAvailable / cuda_reserved when the target load began (memlog)
  69. _lock = threading.Lock()
  70. _header_cache: dict = {}
  71. _pool = None
  72. # Routed-expert projections the FusedMoE loader narrows by moe_tp_size, and along which dim
  73. # (FusedMoE._weight_loader_impl: SHARD_ID_TO_SHARDED_DIM = {"w1": 0, "w2": 1, "w3": 0}; the block
  74. # scales take the same dim through _load_model_weight_or_group_weight_scale).
  75. _TP_SLICE_DIM = {"w1": 0, "w3": 0, "w2": 1}
  76. _TP_SLICE_KINDS = ("weight", "scale")
  77. _tls = threading.local()
  78. _bounces: list = []
  79. _DTYPES = {
  80. "F8_E4M3": "float8_e4m3fn", "F8_E5M2": "float8_e5m2", "F8_E8M0": "float8_e8m0fnu",
  81. "BF16": "bfloat16", "F16": "float16", "F32": "float32", "F64": "float64",
  82. "I8": "int8", "U8": "uint8", "I16": "int16", "I32": "int32", "I64": "int64", "BOOL": "bool",
  83. }
  84. def enabled() -> bool:
  85. return os.environ.get("DSV41_FAST_LOAD", "0").strip() in ("1", "on", "true")
  86. def observing() -> bool:
  87. """``DSV41_FAST_LOAD=observe``: stock loader, but the same memory snapshot after each load."""
  88. return enabled() or os.environ.get("DSV41_FAST_LOAD", "0").strip() == "observe"
  89. def _parse_header(path):
  90. with _lock:
  91. hit = _header_cache.get(path)
  92. if hit is not None:
  93. return hit
  94. with open(path, "rb") as f:
  95. n = struct.unpack("<Q", f.read(8))[0]
  96. header = json.loads(f.read(n))
  97. header.pop("__metadata__", None)
  98. entry = (8 + n, header)
  99. with _lock:
  100. _header_cache[path] = entry
  101. return entry
  102. def _ep_info():
  103. """(ep_rank, ep_size) of this process, or None when unknown."""
  104. try:
  105. from sglang.srt.runtime_context import get_parallel
  106. par = get_parallel()
  107. return int(par.moe_ep_rank), int(par.moe_ep_size)
  108. except Exception as exc:
  109. try:
  110. from sglang.srt.distributed import parallel_state as ps
  111. return ps.get_moe_expert_parallel_rank(), ps.get_moe_expert_parallel_world_size()
  112. except Exception as exc2:
  113. # Last resort: SGLang assigns moe_ep_rank = tp_rank // (tp_size // ep_size).
  114. try:
  115. ep_size = int(os.environ["DSV41_FAST_LOAD_EP_SIZE"])
  116. from sglang.srt.distributed import parallel_state as ps
  117. tp_rank, tp_size = ps.get_tensor_model_parallel_rank(), ps.get_tensor_model_parallel_world_size()
  118. return tp_rank // (tp_size // ep_size), ep_size
  119. except Exception as exc3:
  120. if not _state["ep_warned"]:
  121. _state["ep_warned"] = True
  122. logger.warning("DSV41 fast load: EP layout unknown (%r / %r / %r); eager reads for non-expert tensors only",
  123. exc, exc2, exc3)
  124. return None
  125. def _moe_tp_info():
  126. """(moe_tp_rank, moe_tp_size) as FusedMoE reads them (``get_parallel()``), or None when unknown."""
  127. try:
  128. from sglang.srt.runtime_context import get_parallel
  129. par = get_parallel()
  130. return int(par.moe_tp_rank), int(par.moe_tp_size)
  131. except Exception:
  132. try:
  133. from sglang.srt.distributed import parallel_state as ps
  134. return ps.get_moe_tensor_parallel_rank(), ps.get_moe_tensor_parallel_world_size()
  135. except Exception:
  136. # Last resort: the MoE-TP groups are contiguous TP ranks (initialize_model_parallel),
  137. # moe_tp_size = tp_size // ep_size and moe_tp_rank = tp_rank % moe_tp_size.
  138. try:
  139. ep_size = int(os.environ["DSV41_FAST_LOAD_EP_SIZE"])
  140. from sglang.srt.distributed import parallel_state as ps
  141. tp_rank, tp_size = ps.get_tensor_model_parallel_rank(), ps.get_tensor_model_parallel_world_size()
  142. moe_tp = tp_size // ep_size
  143. return tp_rank % moe_tp, moe_tp
  144. except Exception:
  145. return None
  146. def _tp_slice_mode():
  147. v = os.environ.get("DSV41_FAST_LOAD_TP_SLICE", "auto").strip().lower()
  148. if v in ("0", "off", "false"):
  149. return "off"
  150. if v in ("1", "on", "true", "force"):
  151. return "on"
  152. return "auto"
  153. def tp_slicing(ep, tp, mode=None):
  154. """The (moe_tp_rank, moe_tp_size) to slice routed experts by, or None to read them whole.
  155. ``auto`` slices only at moe_ep_size == 1 (every rank holds a slice of every expert), so EP>1
  156. keeps today's whole-expert reads; ``on`` also slices the owned experts under EP>1.
  157. """
  158. mode = mode or _tp_slice_mode()
  159. if mode == "off" or tp is None or tp[1] <= 1:
  160. return None
  161. if mode == "auto" and (ep is None or ep[1] != 1):
  162. return None
  163. return tp
  164. def _find_key(obj, key):
  165. if isinstance(obj, dict):
  166. if key in obj:
  167. return obj[key]
  168. for v in obj.values():
  169. hit = _find_key(v, key)
  170. if hit is not None:
  171. return hit
  172. return None
  173. def _n_routed_experts(path):
  174. """Routed expert count from config.json next to the shards (nested under text_config for V4.1)."""
  175. cfg = os.path.join(os.path.dirname(path), "config.json")
  176. try:
  177. with open(cfg) as f:
  178. hit = _find_key(json.load(f), "n_routed_experts")
  179. if hit is not None:
  180. return int(hit)
  181. except Exception:
  182. pass
  183. env = os.environ.get("DSV41_FAST_LOAD_N_EXPERTS")
  184. return int(env) if env else None
  185. def _expert_id(name):
  186. if ".experts." not in name:
  187. return None
  188. try:
  189. return int(name.split(".experts.", 1)[1].split(".", 1)[0])
  190. except ValueError:
  191. return None
  192. def needed_names(path, phase, ep=None, n_routed=None):
  193. """Tensor names this rank will copy out of ``path`` in ``phase``."""
  194. _, header = _parse_header(path)
  195. if phase == "draft":
  196. return [k for k in header if k.startswith(_DRAFT_PREFIX)]
  197. if ep is None or n_routed is None or n_routed % ep[1] != 0:
  198. return [k for k in header if not k.startswith(_TARGET_SKIP_PREFIXES) and _ENGRAM_TABLE not in k
  199. and _expert_id(k) is None]
  200. per_rank = n_routed // ep[1]
  201. lo, hi = ep[0] * per_rank, (ep[0] + 1) * per_rank
  202. out = []
  203. for k in header:
  204. if k.startswith(_TARGET_SKIP_PREFIXES) or _ENGRAM_TABLE in k:
  205. continue
  206. eid = _expert_id(k)
  207. if eid is None or lo <= eid < hi:
  208. out.append(k)
  209. return out
  210. def needed_ranges(path, phase, ep=None, n_routed=None):
  211. """Merged absolute byte ranges of ``needed_names`` (for tests and accounting)."""
  212. base, header = _parse_header(path)
  213. ranges = sorted((base + a, base + b) for k in needed_names(path, phase, ep, n_routed)
  214. for a, b in [header[k]["data_offsets"]] if b > a)
  215. merged = []
  216. for a, b in ranges:
  217. if merged and a <= merged[-1][1]:
  218. merged[-1][1] = max(merged[-1][1], b)
  219. else:
  220. merged.append([a, b])
  221. return [(a, b) for a, b in merged]
  222. def slice_spec(name, info, tp):
  223. """(dim, start, length) of this rank's slice of a routed expert tensor, or None to read it whole."""
  224. if tp is None or _expert_id(name) is None:
  225. return None
  226. parts = name.rsplit(".", 2)
  227. if len(parts) != 3 or parts[1] not in _TP_SLICE_DIM or parts[2] not in _TP_SLICE_KINDS:
  228. return None
  229. dim, shape = _TP_SLICE_DIM[parts[1]], info["shape"]
  230. if len(shape) <= dim or shape[dim] % tp[1] != 0:
  231. return None
  232. length = shape[dim] // tp[1]
  233. return dim, tp[0] * length, length
  234. def read_plan(path, phase, ep=None, n_routed=None, tp=None, experts=True):
  235. """{name: slice spec or None} of what this rank reads eagerly out of ``path``.
  236. ``tp`` is ``tp_slicing(...)``'s result; ``experts=False`` leaves the routed experts to the stock
  237. mmap tensors (used at moe_ep_size == 1 when they cannot be sliced).
  238. """
  239. _, header = _parse_header(path)
  240. names = needed_names(path, phase, ep, n_routed)
  241. if not experts:
  242. names = [k for k in names if _expert_id(k) is None]
  243. return {k: slice_spec(k, header[k], tp) for k in names}
  244. def _itemsize(dtype_name):
  245. return {"F64": 8, "I64": 8, "F32": 4, "I32": 4, "BF16": 2, "F16": 2, "I16": 2}.get(dtype_name, 1)
  246. def _slice_geometry(info, spec):
  247. """(outer, row_bytes, inner_bytes): the tensor as [outer, shape[dim], inner] with byte rows."""
  248. dim = spec[0]
  249. shape = info["shape"]
  250. outer = 1
  251. for s in shape[:dim]:
  252. outer *= s
  253. inner = _itemsize(info["dtype"])
  254. for s in shape[dim + 1:]:
  255. inner *= s
  256. return outer, shape[dim] * inner, inner
  257. def spec_bytes(info, spec):
  258. """(bytes read from the file, bytes kept resident) for one planned tensor."""
  259. a, b = info["data_offsets"]
  260. if spec is None:
  261. return b - a, b - a
  262. outer, row, inner = _slice_geometry(info, spec)
  263. kept = outer * spec[2] * inner
  264. return (kept if outer == 1 else outer * row), kept
  265. def plan_ranges(path, plan):
  266. """Merged absolute byte ranges a read plan touches (sliced w1/w3 are row blocks, w2 whole)."""
  267. base, header = _parse_header(path)
  268. ranges = []
  269. for k, spec in plan.items():
  270. a, b = header[k]["data_offsets"]
  271. if b <= a:
  272. continue
  273. if spec is not None:
  274. outer, _, inner = _slice_geometry(header[k], spec)
  275. if outer == 1:
  276. a, b = a + spec[1] * inner, a + (spec[1] + spec[2]) * inner
  277. ranges.append((base + a, base + b))
  278. ranges.sort()
  279. merged = []
  280. for a, b in ranges:
  281. if merged and a <= merged[-1][1]:
  282. merged[-1][1] = max(merged[-1][1], b)
  283. else:
  284. merged.append([a, b])
  285. return [(a, b) for a, b in merged]
  286. def slab_bytes():
  287. """Slab size from ``DSV41_FAST_LOAD_SLAB_MB`` (default 256), rounded down to a power of two
  288. (torch's caching host allocator rounds every block up to one); 0 = one buffer per tensor."""
  289. try:
  290. mb = float(os.environ.get("DSV41_FAST_LOAD_SLAB_MB", "256"))
  291. except ValueError:
  292. mb = 256.0
  293. if mb <= 0:
  294. return 0
  295. n = max(1 << 20, int(mb * 2**20))
  296. return 1 << (n.bit_length() - 1)
  297. def _align(n, a=_SLAB_ALIGN):
  298. return (n + a - 1) // a * a
  299. def slab_layout(header, plan, slab):
  300. """Pack a shard's eager tensors into slabs, in the order the loader consumes them.
  301. Returns ``[(nbytes, [(name, offset, kept_bytes), ...]), ...]``. Names go in sorted order (the
  302. order ``buffered_multi_thread_safetensors_weights_iterator`` yields them), each at a 4 KiB
  303. aligned offset, so slabs free progressively as the copies drain. A slab holds at most ``slab``
  304. bytes; a tensor larger than that is a slab of its own. Empty tensors are left out.
  305. """
  306. out = []
  307. cur, used = [], 0
  308. for name in sorted(plan):
  309. kept = spec_bytes(header[name], plan[name])[1]
  310. if kept <= 0:
  311. continue
  312. if kept > slab:
  313. out.append((kept, [(name, 0, kept)]))
  314. continue
  315. if cur and used + kept > slab:
  316. out.append((used, cur))
  317. cur, used = [], 0
  318. cur.append((name, used, kept))
  319. used = _align(used + kept)
  320. if cur:
  321. out.append((min(used, slab), cur))
  322. return out
  323. _TP_SLICE_CLS = []
  324. def _tp_slice_cls():
  325. """Full-shape stand-in for a checkpoint tensor of which only this rank's slice is resident.
  326. The FusedMoE loader only reads ``shape``/``size``/``dim``/``device`` and calls
  327. ``narrow(shard_dim, moe_tp_rank * n, n)``; that returns the resident slice (a narrow inside it
  328. returns the matching sub-slice). Every other operation, and any narrow reaching outside the
  329. slice or along another dim, raises instead of returning bytes that were never read.
  330. """
  331. if _TP_SLICE_CLS:
  332. return _TP_SLICE_CLS[0]
  333. import torch
  334. T = torch.Tensor
  335. meta = (T.shape.__get__, T.dtype.__get__, T.device.__get__, T.ndim.__get__, T.layout.__get__,
  336. T.is_cuda.__get__, T.is_cpu.__get__, T.requires_grad.__get__, T.dim, T.size, T.numel,
  337. T.nelement, T.element_size, T.__len__, T.__hash__)
  338. class TpSliceTensor(T):
  339. @staticmethod
  340. def __new__(cls, part, full_shape, dim, start, name):
  341. r = T._make_wrapper_subclass(cls, tuple(full_shape), dtype=part.dtype, device=part.device)
  342. r._dsv41_part = part
  343. r._dsv41_dim = dim
  344. r._dsv41_start = start
  345. r._dsv41_name = name
  346. r._dsv41_resident_nbytes = part.numel() * part.element_size()
  347. return r
  348. def __repr__(self):
  349. return (f"TpSliceTensor({self._dsv41_name}, shape={tuple(self.shape)}, dtype={self.dtype}, "
  350. f"resident dim {self._dsv41_dim} [{self._dsv41_start}, "
  351. f"{self._dsv41_start + self._dsv41_part.shape[self._dsv41_dim]}))")
  352. def _narrow(self, dim, start, length):
  353. dim, start, length = int(dim), int(start), int(length)
  354. if dim < 0:
  355. dim += self.dim()
  356. lo = self._dsv41_start
  357. hi = lo + self._dsv41_part.shape[self._dsv41_dim]
  358. if dim != self._dsv41_dim or start < lo or start + length > hi or length < 0:
  359. raise RuntimeError(
  360. f"DSV41 fast load: {self._dsv41_name}: narrow(dim={dim}, start={start}, length={length}) "
  361. f"reaches outside the resident slice (dim {self._dsv41_dim} [{lo}, {hi})); the engine's "
  362. f"moe_tp layout differs from the one fast_load read for. Boot with "
  363. f"DSV41_FAST_LOAD_TP_SLICE=0 (or DSV41_FAST_LOAD=0) and report this.")
  364. part = self._dsv41_part
  365. if start == lo and length == part.shape[dim]:
  366. return part
  367. return part.narrow(dim, start - lo, length)
  368. @classmethod
  369. def __torch_function__(cls, func, types, args=(), kwargs=None):
  370. kwargs = kwargs or {}
  371. if func is T.narrow or func is torch.narrow:
  372. self = args[0]
  373. rest = list(args[1:])
  374. for key in ("dim", "start", "length"):
  375. if key in kwargs:
  376. rest.append(kwargs[key])
  377. return self._narrow(*rest)
  378. if func in meta:
  379. with torch._C.DisableTorchFunctionSubclass():
  380. return func(*args, **kwargs)
  381. name = next((a._dsv41_name for a in args if isinstance(a, cls)), "?")
  382. raise RuntimeError(f"DSV41 fast load: {name} holds only this rank's moe_tp slice; "
  383. f"{getattr(func, '__name__', func)} is not supported on it (only narrow "
  384. f"to the slice). Boot with DSV41_FAST_LOAD_TP_SLICE=0 and report this.")
  385. @classmethod
  386. def __torch_dispatch__(cls, func, types, args=(), kwargs=None):
  387. raise RuntimeError(f"DSV41 fast load: {func} reached a TP-sliced stand-in tensor")
  388. with _lock:
  389. if not _TP_SLICE_CLS:
  390. _TP_SLICE_CLS.append(TpSliceTensor)
  391. return _TP_SLICE_CLS[0]
  392. def _executor():
  393. global _pool
  394. with _lock:
  395. if _pool is None:
  396. _pool = concurrent.futures.ThreadPoolExecutor(
  397. max_workers=int(os.environ.get("DSV41_FAST_LOAD_THREADS", "16")), thread_name_prefix="dsv41-read")
  398. return _pool
  399. def _read_into(fd, mv, off):
  400. got = 0
  401. n = len(mv)
  402. while got < n:
  403. r = os.preadv(fd, [mv[got:]], off + got)
  404. if r <= 0:
  405. raise IOError(f"short read at {off + got}")
  406. got += r
  407. def _alloc_host(n):
  408. """A 1-D uint8 host buffer of ``n`` bytes: pinned, or an anonymous mmap without CUDA.
  409. Pinned source: the H2D copy is a plain DMA. Pageable sources cost more than the copy: the
  410. driver keeps a staging pool behind after bursts of concurrent pageable copies (measured ~0.4 GB
  411. per 3 GB burst on a worker, ~1.5 GB after a full load on the head), and that memory is gone
  412. from MemAvailable when the KV pool is sized. Pinned blocks come from torch's caching host
  413. allocator and are returned to the driver by _release_all. The fallback is an anonymous private
  414. mmap (like safetensors' own tensors), not a malloc allocation: glibc keeps freed multi-MB chunks
  415. in its arenas once the dynamic mmap threshold has grown, and a first version of this loader left
  416. ~11 GB resident on the head after the load, which cost 5x of the KV pool. Either way the memory
  417. goes back when the last tensor viewing it is released.
  418. """
  419. import mmap
  420. import torch
  421. if _pinned_ok():
  422. flat = torch.empty(n, dtype=torch.uint8, pin_memory=True)
  423. else:
  424. flat = torch.frombuffer(mmap.mmap(-1, n, flags=mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS), dtype=torch.uint8)
  425. with _lock:
  426. _state["host_allocs"] += 1
  427. _state["host_alloc_bytes"] += n
  428. return flat
  429. def _new_slab(n, path, members):
  430. """Allocate one slab and register a storage weak reference to it (expired = all views gone).
  431. ``members``: the ``slab_layout`` entries ``(name, offset, kept)`` placed in it.
  432. """
  433. flat = _alloc_host(n)
  434. try:
  435. from torch.multiprocessing.reductions import StorageWeakRef
  436. ref = StorageWeakRef(flat.untyped_storage())
  437. except Exception as exc:
  438. ref = None
  439. logger.warning("DSV41 fast load: no weak reference to a slab of %s (%r); its release is not checked",
  440. os.path.basename(path), exc)
  441. with _lock:
  442. _slab_refs.append([ref, n, path, list(members), flat.data_ptr()])
  443. _slab_size[flat.data_ptr()] = (n, ref)
  444. _state["slabs"] += 1
  445. live = [r for r in _slab_refs if r[0] is not None and not r[0].expired()]
  446. _state["slab_peak_live"] = max(_state["slab_peak_live"], len(live))
  447. _state["slab_peak_bytes"] = max(_state["slab_peak_bytes"], sum(r[1] for r in live))
  448. return flat
  449. def _read_tensor(fd, base, info, dst=None):
  450. """Read one safetensors entry into a contiguous torch tensor.
  451. ``dst``: the tensor's region of a slab (uint8, exactly its bytes); None allocates a buffer of
  452. its own (``_alloc_host``). The returned tensor is a view of that buffer.
  453. """
  454. import torch
  455. dtype = getattr(torch, _DTYPES[info["dtype"]])
  456. a, b = info["data_offsets"]
  457. n = b - a
  458. if n == 0:
  459. return torch.empty(info["shape"], dtype=dtype)
  460. flat = _alloc_host(n) if dst is None else dst
  461. assert flat.numel() == n, (flat.numel(), n)
  462. mv = memoryview(flat.numpy())
  463. # Sequential in this thread (a nested pool submit would starve the pool); the pool's
  464. # parallelism comes from the ~1200 tensors of a shard being read concurrently.
  465. try:
  466. for c in range(0, n, _CHUNK):
  467. _read_into(fd, mv[c:c + _CHUNK], base + a + c)
  468. finally:
  469. mv.release()
  470. return flat.view(dtype).reshape(info["shape"])
  471. def _bounce(n):
  472. """This reader thread's staging buffer (anonymous mmap, closed by _release_all)."""
  473. import mmap
  474. buf = getattr(_tls, "buf", None)
  475. if buf is None or len(buf) < n:
  476. buf = mmap.mmap(-1, max(n, _CHUNK), flags=mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS)
  477. _tls.buf = buf
  478. with _lock:
  479. _bounces.append(buf)
  480. return buf
  481. def _read_slice(fd, base, info, spec, dst=None):
  482. """Read this rank's slice (dim, start, length) of one safetensors entry into a compact tensor.
  483. ``dst`` as in ``_read_tensor`` (the slice's kept bytes).
  484. Leading-dim slices (w1/w3 rows) are one contiguous pread straight into the buffer. Other dims
  485. (w2 columns: 288 of every 1152 bytes per row at TP4) read whole rows through a per-thread bounce
  486. buffer and keep the slice: a 4 KB page holds ~3.5 such rows, so the device delivers every byte
  487. either way, and one pread plus a strided copy costs ~0.35 ms per w2 against ~2.6 ms for 5120
  488. per-row preads (GIL-bound Python loop).
  489. """
  490. import torch
  491. dtype = getattr(torch, _DTYPES[info["dtype"]])
  492. dim, start, length = spec
  493. shape = list(info["shape"])
  494. part_shape = shape[:dim] + [length] + shape[dim + 1:]
  495. outer, row, inner = _slice_geometry(info, spec)
  496. n = outer * length * inner
  497. if n == 0:
  498. return torch.empty(part_shape, dtype=dtype)
  499. a = info["data_offsets"][0]
  500. flat = _alloc_host(n) if dst is None else dst
  501. assert flat.numel() == n, (flat.numel(), n)
  502. if outer == 1:
  503. mv = memoryview(flat.numpy())
  504. off = base + a + start * inner
  505. for c in range(0, n, _CHUNK):
  506. _read_into(fd, mv[c:c + _CHUNK], off + c)
  507. mv.release()
  508. else:
  509. rows = max(1, _CHUNK // row)
  510. buf = _bounce(min(outer, rows) * row)
  511. dst = flat.view(outer, length, inner)
  512. mv = memoryview(buf)
  513. src_all = torch.frombuffer(buf, dtype=torch.uint8)
  514. try:
  515. for r0 in range(0, outer, rows):
  516. k = min(rows, outer - r0)
  517. _read_into(fd, mv[:k * row], base + a + r0 * row)
  518. src = src_all[:k * row].view(k, shape[dim], inner)
  519. dst[r0:r0 + k].copy_(src[:, start:start + length, :])
  520. finally:
  521. del src_all
  522. mv.release()
  523. return flat.view(dtype).reshape(part_shape)
  524. def _pinned_ok():
  525. hit = _state.get("pinned")
  526. if hit is None:
  527. try:
  528. import torch
  529. hit = bool(torch.cuda.is_available()) and os.environ.get("DSV41_FAST_LOAD_PINNED", "1") == "1"
  530. if hit:
  531. torch.empty(1, dtype=torch.uint8, pin_memory=True)
  532. except Exception:
  533. hit = False
  534. _state["pinned"] = hit
  535. logger.warning("DSV41 fast load: eager buffers are %s", "pinned host memory" if hit else "anonymous mmaps")
  536. return hit
  537. class _EmptyShard:
  538. """What the loader sees for a shard the current load cannot use."""
  539. def __enter__(self):
  540. return self
  541. def __exit__(self, *exc):
  542. return False
  543. def keys(self):
  544. return []
  545. def offset_keys(self):
  546. return []
  547. def metadata(self):
  548. return {}
  549. def get_tensor(self, name):
  550. raise KeyError(name)
  551. class _EagerShard:
  552. """Stock safe_open handle whose needed tensors are read eagerly by the thread pool."""
  553. def __init__(self, inner, path, names):
  554. """``names``: a list (every tensor read whole) or a ``read_plan`` dict {name: slice spec}."""
  555. self._inner = inner
  556. self._path = path
  557. base, header = _parse_header(path)
  558. plan = names if isinstance(names, dict) else dict.fromkeys(names)
  559. self._specs = {k: s for k, s in plan.items() if s is not None}
  560. # Destination of every tensor: its region of a slab (the job holds the region, and so the
  561. # slab, until the read is done; afterwards only the returned view does).
  562. dst = {}
  563. slab = slab_bytes()
  564. if slab:
  565. for nbytes, members in slab_layout(header, plan, slab):
  566. flat = _new_slab(nbytes, path, members)
  567. for name, off, kept in members:
  568. dst[name] = flat.narrow(0, off, kept)
  569. del flat
  570. self._fd = os.open(path, os.O_RDONLY)
  571. ex = _executor()
  572. self._futures = {k: (ex.submit(_read_tensor, self._fd, base, header[k], dst.pop(k, None)) if s is None
  573. else ex.submit(_read_slice, self._fd, base, header[k], s, dst.pop(k, None)))
  574. for k, s in plan.items()}
  575. total = kept = 0
  576. for k, s in plan.items():
  577. r, h = spec_bytes(header[k], s)
  578. total += r
  579. kept += h
  580. with _lock:
  581. _state["bytes"] += total
  582. _state["resident"] += kept
  583. _state["sliced"] += len(self._specs)
  584. _state["files"] += 1
  585. def __enter__(self):
  586. self._inner.__enter__()
  587. return self
  588. def __exit__(self, *exc):
  589. try:
  590. # Futures nobody asked for would keep their tensor, and with it the whole slab, alive for
  591. # as long as this handle is referenced: cancel the queued ones, let the running ones
  592. # finish (they read through self._fd) and drop every reference.
  593. pending = list(self._futures.values())
  594. self._futures.clear()
  595. running = [f for f in pending if not f.cancel()]
  596. if running:
  597. concurrent.futures.wait(running)
  598. del pending, running
  599. try:
  600. os.posix_fadvise(self._fd, 0, 0, os.POSIX_FADV_DONTNEED)
  601. except OSError:
  602. pass
  603. os.close(self._fd)
  604. finally:
  605. ret = self._inner.__exit__(*exc)
  606. return ret
  607. def keys(self):
  608. return self._inner.keys()
  609. def offset_keys(self):
  610. return self._inner.offset_keys()
  611. def metadata(self):
  612. return self._inner.metadata()
  613. def get_slice(self, name):
  614. return self._inner.get_slice(name)
  615. def get_tensor(self, name):
  616. fut = self._futures.pop(name, None)
  617. if fut is None:
  618. return self._inner.get_tensor(name)
  619. try:
  620. t = fut.result()
  621. spec = self._specs.get(name)
  622. if spec is None:
  623. return t
  624. _, header = _parse_header(self._path)
  625. return _tp_slice_cls()(t, header[name]["shape"], spec[0], spec[1], name)
  626. except Exception as exc:
  627. with _lock:
  628. _state["read_failures"] += 1
  629. n = _state["read_failures"]
  630. if n <= 8 or n & (n - 1) == 0:
  631. logger.warning("DSV41 fast load: eager read of %s failed (%r); falling back to mmap "
  632. "(%d eager-read failures so far)", name, exc, n)
  633. return self._inner.get_tensor(name)
  634. def _wrap_open(orig, filename, args, kwargs):
  635. phase = _state["phase"]
  636. if not (isinstance(filename, (str, os.PathLike)) and str(filename).endswith(".safetensors")):
  637. return orig(filename, *args, **kwargs)
  638. path = os.fspath(filename)
  639. ep = _ep_info() if phase == "target" else None
  640. n_routed = _n_routed_experts(path) if ep else None
  641. mode = _tp_slice_mode()
  642. # Expert TP layout, for both phases (the draft's FusedMoE layers use the same moe_tp groups).
  643. lay_ep = ep if phase == "target" else (_ep_info() if mode != "off" else None)
  644. moe_tp = _moe_tp_info() if (lay_ep is not None or mode == "on") else None
  645. tp = tp_slicing(lay_ep, moe_tp, mode)
  646. # moe_ep_size == 1 without slicing would read every expert whole on every rank (~2x the EP2
  647. # bytes, and a pinned window twice EP2's): leave the experts to the stock mmap tensors then.
  648. experts = not (tp is None and lay_ep is not None and lay_ep[1] == 1 and (moe_tp is None or moe_tp[1] > 1))
  649. if phase == "target" and not _state["layout_logged"]:
  650. _state["layout_logged"] = True
  651. logger.warning("DSV41 fast load: target layout ep=%s n_routed_experts=%s moe_tp=%s tp_slice=%s "
  652. "(%s)%s first shard %s", ep, n_routed, moe_tp, tp, mode,
  653. "" if experts else "; routed experts left to mmap", path)
  654. names = needed_names(path, phase, ep, n_routed)
  655. if phase == "draft" and not names:
  656. with _lock:
  657. _state["skipped"] += 1
  658. return _EmptyShard()
  659. inner = orig(filename, *args, **kwargs)
  660. plan = read_plan(path, phase, ep, n_routed, tp, experts) if (tp is not None or not experts) else names
  661. if not plan or kwargs.get("device", "cpu") not in ("cpu", None) or (len(args) > 1 and args[1] != "cpu"):
  662. return inner
  663. return _EagerShard(inner, path, plan)
  664. def install_weight_utils(module):
  665. """Wrap ``safetensors.safe_open`` as used by ``weight_utils``."""
  666. if not enabled() or _state["armed"]:
  667. return
  668. import safetensors
  669. orig = safetensors.safe_open
  670. def safe_open(filename, *args, **kwargs):
  671. try:
  672. return _wrap_open(orig, filename, args, kwargs)
  673. except Exception as exc: # never let the fast path break the load, but say so for every shard
  674. with _lock:
  675. _state["open_failures"] += 1
  676. logger.warning("DSV41 fast load: shard %s falls back to the stock loader after error %r "
  677. "(%d shards so far this phase)", filename, exc, _state["open_failures"])
  678. return orig(filename, *args, **kwargs)
  679. safetensors.safe_open = safe_open
  680. if getattr(module, "safetensors", None) is not None:
  681. module.safetensors.safe_open = safe_open
  682. _state["armed"] = True
  683. logger.warning("DSV41 fast load ARMED: eager threaded reads of this rank's tensors; draft load opens only mtp shards")
  684. def _malloc_trim():
  685. try:
  686. import ctypes
  687. ctypes.CDLL("libc.so.6").malloc_trim(0)
  688. except Exception:
  689. pass
  690. def _rss_mb():
  691. try:
  692. for line in open("/proc/self/status"):
  693. if line.startswith("VmRSS"):
  694. return int(line.split()[1]) // 1024
  695. except Exception:
  696. pass
  697. return -1
  698. def _release_all():
  699. """After a load: drop the reader pool, header cache and malloc arenas; report what is still mapped."""
  700. global _pool
  701. import gc
  702. import mmap
  703. import warnings
  704. if _pool is not None:
  705. _pool.shutdown(wait=True)
  706. _pool = None
  707. _tls.__dict__.pop("buf", None)
  708. with _lock:
  709. bounces = list(_bounces)
  710. _bounces.clear()
  711. for buf in bounces:
  712. try:
  713. buf.close()
  714. except BufferError: # still exported by a live tensor; unmapped when that goes
  715. pass
  716. with _lock:
  717. paths = list(_header_cache)
  718. _header_cache.clear()
  719. gc.collect()
  720. with warnings.catch_warnings(): # isinstance() over every object trips deprecation shims
  721. warnings.simplefilter("ignore")
  722. live = [o for o in gc.get_objects() if isinstance(o, mmap.mmap) and not o.closed]
  723. escaped = _escaped_slabs()
  724. _malloc_trim()
  725. try:
  726. import torch
  727. torch.cuda.synchronize()
  728. torch.cuda.empty_cache()
  729. if hasattr(torch._C, "_host_emptyCache"):
  730. torch._C._host_emptyCache()
  731. except Exception:
  732. pass
  733. # The KV pool is sized from what CUDA reports free, and on GB10 that is the kernel's
  734. # MemFree: page cache counts as used. Drop every shard's cached pages now (the stock
  735. # handles of the last window are closed by this point).
  736. for d in {os.path.dirname(p) for p in paths}:
  737. try:
  738. for name in os.listdir(d):
  739. if name.endswith(".safetensors"):
  740. fd = os.open(os.path.join(d, name), os.O_RDONLY)
  741. try:
  742. os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_DONTNEED)
  743. finally:
  744. os.close(fd)
  745. except OSError:
  746. pass
  747. return len(live), sum(len(o) for o in live), _rss_mb(), escaped
  748. def _escaped_slabs():
  749. """Slabs of this phase still alive after the load (call after gc.collect()), then forget them.
  750. Returns ``[(nbytes, path, [names of the tensors still viewing it])]``. A slab can only stay
  751. alive through a view of one of its tensors that the model kept (the loader copies into its
  752. parameters and drops the source); that would hold the whole slab out of the KV pool budget,
  753. so it is reported by name. Found by the data pointers of live tensors (``gc`` tracks them).
  754. """
  755. import bisect
  756. import gc
  757. with _lock:
  758. refs = list(_slab_refs)
  759. _slab_refs.clear()
  760. _slab_size.clear()
  761. alive = [r for r in refs if r[0] is not None and not r[0].expired()]
  762. if not alive:
  763. return []
  764. found = {id(r): set() for r in alive}
  765. try:
  766. import torch
  767. spans = sorted((r[4], r[4] + r[1], r) for r in alive)
  768. starts = [s[0] for s in spans]
  769. for o in gc.get_objects():
  770. if not isinstance(o, torch.Tensor):
  771. continue
  772. try:
  773. parts = [getattr(o, "_dsv41_part", None), o]
  774. p = next(x for x in parts if x is not None and x.device.type == "cpu" and x.numel())
  775. ptr = p.data_ptr()
  776. except Exception:
  777. continue
  778. i = bisect.bisect_right(starts, ptr) - 1
  779. if i < 0 or ptr >= spans[i][1]:
  780. continue
  781. r = spans[i][2]
  782. offs = [m[1] for m in r[3]]
  783. j = bisect.bisect_right(offs, ptr - r[4]) - 1
  784. found[id(r)].add(r[3][j][0] if j >= 0 else f"+{ptr - r[4]}")
  785. except Exception as exc: # diagnostics only
  786. return [(r[1], r[2], [f"lookup failed: {exc!r}"]) for r in alive]
  787. return [(r[1], r[2], sorted(found[id(r)]) or ["no live tensor object found (C++-held view)"]) for r in alive]
  788. def _meminfo_gb(key):
  789. try:
  790. for line in open("/proc/meminfo"):
  791. if line.startswith(key):
  792. return int(line.split()[1]) / 2**20
  793. except Exception:
  794. pass
  795. return -1.0
  796. def _snapshot():
  797. out = {k: round(_meminfo_gb(k), 2) for k in ("MemFree", "MemAvailable", "Cached", "Shmem", "AnonPages", "Mapped",
  798. "Unevictable", "Mlocked", "Slab", "SUnreclaim", "KReclaimable",
  799. "PageTables", "VmallocUsed", "Percpu", "HugePages_Total")}
  800. try:
  801. for line in open("/proc/self/smaps_rollup"):
  802. if line.startswith(("Rss", "Anonymous", "Shared_Clean", "Private_Clean")):
  803. out["self_" + line.split(":")[0]] = round(int(line.split()[1]) / 2**20, 2)
  804. except Exception:
  805. pass
  806. try:
  807. import torch
  808. out["cuda_free"] = round(torch.cuda.mem_get_info()[0] / 2**30, 2)
  809. out["cuda_reserved"] = round(torch.cuda.memory_reserved() / 2**30, 2)
  810. out["cuda_allocated"] = round(torch.cuda.memory_allocated() / 2**30, 2)
  811. st = torch.cuda.memory_stats()
  812. out["cuda_inactive_split"] = round(st.get("inactive_split_bytes.all.current", 0) / 2**30, 2)
  813. except Exception:
  814. pass
  815. try: # biggest mappings of this process (file or anonymous), MB
  816. big = []
  817. cur = None
  818. for line in open("/proc/self/smaps"):
  819. if line[0] in "0123456789abcdef" and "-" in line.split()[0]:
  820. parts = line.split()
  821. cur = parts[5] if len(parts) > 5 else "[anon]"
  822. elif line.startswith("Rss:"):
  823. kb = int(line.split()[1])
  824. if kb >= 64 * 1024:
  825. big.append((kb // 1024, cur))
  826. big.sort(reverse=True)
  827. out["big_maps_mb"] = big[:8]
  828. except Exception:
  829. pass
  830. return out
  831. def _log_phase(phase):
  832. """Release and report the phase; returns the escaped slabs (see ``_escaped_slabs``)."""
  833. with _lock:
  834. b, f, s = _state["bytes"], _state["files"], _state["skipped"]
  835. kept, sliced = _state["resident"], _state["sliced"]
  836. fails = _state["open_failures"], _state["read_failures"]
  837. allocs = {k: _state[k] for k in ("host_allocs", "host_alloc_bytes", "slabs", "slab_peak_live", "slab_peak_bytes")}
  838. for k in ("bytes", "files", "skipped", "resident", "sliced", "open_failures", "read_failures") + tuple(allocs):
  839. _state[k] = 0
  840. before = _snapshot()
  841. escaped = []
  842. if enabled():
  843. live, live_bytes, rss, escaped = _release_all()
  844. extra = f" ({sliced} expert tensors TP-sliced, {kept / 1e9:.1f} GB kept)" if sliced else ""
  845. logger.warning("DSV41 fast load: phase=%s read %.1f GB eagerly over %d shards, %d shards skipped%s; "
  846. "host buffers: %d requested (%.1f GB), %d slabs of %d MB, peak %d live (%.2f GB), "
  847. "%d alive after release; after release: %d anonymous maps alive (%.2f GB), RSS %d MB",
  848. phase, b / 1e9, f, s, extra, allocs["host_allocs"], allocs["host_alloc_bytes"] / 1e9,
  849. allocs["slabs"], slab_bytes() >> 20, allocs["slab_peak_live"], allocs["slab_peak_bytes"] / 1e9,
  850. len(escaped), live, live_bytes / 1e9, rss)
  851. if any(fails):
  852. logger.warning("DSV41 fast load: phase=%s %d shards fell back to the stock loader, %d eager reads fell "
  853. "back to mmap (see the warnings above)", phase, *fails)
  854. for nbytes, path, names in escaped:
  855. logger.warning("DSV41 fast load: phase=%s slab of %.1f MB from %s still alive after the load, held by "
  856. "a view the model kept: %s. It stays pinned and out of the KV pool budget; report this "
  857. "(DSV41_FAST_LOAD_SLAB_MB=0 restores one buffer per tensor).",
  858. phase, nbytes / 2**20, os.path.basename(path), names[:8])
  859. logger.warning("DSV41 fast load: phase=%s memory before release %s | after %s", phase, before, _snapshot())
  860. if memlog_on():
  861. logger.warning("DSV41 fast load memlog: phase=%s %s", phase, _memlog(allocs, len(escaped)))
  862. return escaped
  863. def memlog_on() -> bool:
  864. return os.environ.get("DSV41_FAST_LOAD_MEMLOG", "0").strip() in ("1", "on", "true")
  865. def _meminfo_all():
  866. """/proc/meminfo in GiB, one read."""
  867. out = {}
  868. try:
  869. for line in open("/proc/meminfo"):
  870. k, v = line.split(":", 1)
  871. parts = v.split()
  872. out[k] = int(parts[0]) / 2**20 if len(parts) > 1 else int(parts[0])
  873. except Exception:
  874. pass
  875. return out
  876. def _cuda_mem():
  877. try:
  878. import torch
  879. if torch.cuda.is_available() and torch.cuda.is_initialized():
  880. return torch.cuda.memory_reserved() / 2**30, torch.cuda.memory_allocated() / 2**30
  881. except Exception:
  882. pass
  883. return None, None
  884. def _gpu_procs():
  885. """NVML: [(pid, MiB)] of the processes on the GPU and the device's used GiB (None if not readable).
  886. PIDs are the host's (they differ from os.getpid() inside a container); GB10 may report N/A."""
  887. try:
  888. import pynvml
  889. except Exception:
  890. return None, None
  891. procs, used = [], None
  892. try:
  893. pynvml.nvmlInit()
  894. except Exception:
  895. return None, None
  896. try:
  897. for i in range(pynvml.nvmlDeviceGetCount()):
  898. h = pynvml.nvmlDeviceGetHandleByIndex(i)
  899. for fn in ("nvmlDeviceGetComputeRunningProcesses", "nvmlDeviceGetGraphicsRunningProcesses"):
  900. try:
  901. for p in getattr(pynvml, fn)(h):
  902. mb = getattr(p, "usedGpuMemory", None)
  903. procs.append((int(p.pid), None if mb is None else round(mb / 2**20)))
  904. except Exception:
  905. pass
  906. try:
  907. used = round(pynvml.nvmlDeviceGetMemoryInfo(h).used / 2**30, 2)
  908. except Exception:
  909. pass
  910. except Exception:
  911. pass
  912. finally:
  913. try:
  914. pynvml.nvmlShutdown()
  915. except Exception:
  916. pass
  917. return sorted(set(procs)), used
  918. def _torch_host_stats():
  919. """Process-wide counters of torch's pinned (caching host) allocator, when it has any."""
  920. try:
  921. import torch
  922. st = torch.cuda.host_memory_stats()
  923. except Exception:
  924. return None
  925. keep = ("num_host_alloc", "num_host_free", "allocations.current", "allocations.peak",
  926. "allocated_bytes.current", "allocated_bytes.peak", "host_alloc_time.total", "host_free_time.total")
  927. return {k: (round(st[k] / 2**30, 2) if "bytes" in k else st[k]) for k in keep if k in st} or None
  928. def _memlog(allocs, n_escaped):
  929. """One line for the phase end: host counters, GPU processes, pinned allocations, and two derived
  930. numbers. ``unaccounted_gib`` = MemTotal - (MemFree + Buffers + Cached + AnonPages + Slab +
  931. KernelStack + PageTables + SecPageTables + Percpu) - cuda_reserved: memory no counter shows.
  932. ``nontorch_since_load_start_gib`` = MemAvailable lost since the target's load_weights began,
  933. minus what the CUDA caching allocator reserved in that time."""
  934. m = _meminfo_all()
  935. reserved, allocated = _cuda_mem()
  936. out = {k: round(m[k], 2) for k in ("MemTotal", "MemFree", "MemAvailable", "Buffers", "Cached", "Shmem",
  937. "AnonPages", "Mapped", "Slab", "KReclaimable", "KernelStack",
  938. "PageTables", "SecPageTables", "Percpu", "VmallocUsed", "Unevictable")
  939. if k in m}
  940. out["cuda_reserved_gib"] = None if reserved is None else round(reserved, 2)
  941. out["cuda_allocated_gib"] = None if allocated is None else round(allocated, 2)
  942. if "MemTotal" in m and reserved is not None:
  943. counted = sum(m.get(k, 0.0) for k in ("MemFree", "Buffers", "Cached", "AnonPages", "Slab", "KernelStack",
  944. "PageTables", "SecPageTables", "Percpu"))
  945. out["unaccounted_gib"] = round(m["MemTotal"] - counted - reserved, 2)
  946. if _load_start and "MemAvailable" in m and reserved is not None:
  947. out["nontorch_since_load_start_gib"] = round(
  948. (_load_start["avail"] - m["MemAvailable"]) - (reserved - _load_start["reserved"]), 2)
  949. out["load_start"] = dict(_load_start)
  950. procs, used = _gpu_procs()
  951. out["gpu_procs_mib"] = procs
  952. out["nvml_used_gib"] = used
  953. out["self_pid"] = os.getpid()
  954. out["self_rss_mb"] = _rss_mb()
  955. out["pinned_requests"] = allocs["host_allocs"]
  956. out["pinned_request_gb"] = round(allocs["host_alloc_bytes"] / 1e9, 2)
  957. out["slabs"] = allocs["slabs"]
  958. out["slab_mb"] = slab_bytes() >> 20
  959. out["slab_peak_live"] = allocs["slab_peak_live"]
  960. out["slab_peak_gb"] = round(allocs["slab_peak_bytes"] / 1e9, 2)
  961. out["slabs_alive_after_release"] = n_escaped
  962. out["torch_host_alloc"] = _torch_host_stats()
  963. out["alloc_conf"] = os.environ.get("PYTORCH_CUDA_ALLOC_CONF") or os.environ.get("PYTORCH_ALLOC_CONF")
  964. return out
  965. def _mark_load_start():
  966. """Memlog: MemAvailable and CUDA reserved when the target's load_weights begins (the model's
  967. parameters are allocated by then, the checkpoint not yet read)."""
  968. if not memlog_on():
  969. return
  970. m = _meminfo_all()
  971. reserved, _ = _cuda_mem()
  972. if "MemAvailable" in m:
  973. _load_start.clear()
  974. _load_start.update(avail=round(m["MemAvailable"], 2), reserved=round(reserved or 0.0, 2))
  975. def _tensor_bytes(func_args):
  976. for a in func_args:
  977. try:
  978. import torch
  979. if isinstance(a, torch.Tensor) and not isinstance(a, torch.nn.Parameter):
  980. kept = getattr(a, "_dsv41_resident_nbytes", None) # TP-sliced stand-in: its slice
  981. return kept if kept is not None else a.numel() * a.element_size()
  982. except Exception:
  983. pass
  984. return 0
  985. def _charge(func_args):
  986. """(key, bytes) a copy charges against the pacing budget.
  987. The source tensor (first non-Parameter tensor argument; a TP-sliced stand-in's resident slice)
  988. is looked up by its storage: a view into a slab of this phase charges the whole slab under the
  989. key ``("slab", base ptr)``, once for all its copies in flight, because any one of them keeps
  990. the whole slab alive. Anything else charges its own bytes under a key of its own (None).
  991. """
  992. try:
  993. import torch
  994. for a in func_args:
  995. if isinstance(a, torch.Tensor) and not isinstance(a, torch.nn.Parameter):
  996. part = getattr(a, "_dsv41_part", None)
  997. src = part if part is not None else a
  998. with torch._C.DisableTorchFunctionSubclass():
  999. ptr = src.untyped_storage().data_ptr()
  1000. with _lock:
  1001. hit = _slab_size.get(ptr)
  1002. # an expired entry is a freed slab whose address now backs another buffer
  1003. if hit is not None and (hit[1] is None or not hit[1].expired()):
  1004. return ("slab", ptr), hit[0]
  1005. break
  1006. except Exception:
  1007. pass
  1008. return None, _tensor_bytes(func_args)
  1009. def install_deepseek_v4(module):
  1010. """Pace the model's async weight copies so the eager reads stay just ahead of consumption."""
  1011. if not enabled():
  1012. return
  1013. orig = module.maybe_executor_submit
  1014. budget = int(float(os.environ.get("DSV41_FAST_LOAD_INFLIGHT_GB", "6")) * 2**30)
  1015. cv = threading.Condition()
  1016. inflight = [0]
  1017. held = {} # slab key -> [copies in flight, bytes charged]
  1018. def paced_submit(*, executor, futures, use_async, func, func_args=(), func_kwargs=None):
  1019. if not use_async:
  1020. return orig(executor=executor, futures=futures, use_async=use_async, func=func,
  1021. func_args=func_args, func_kwargs=func_kwargs)
  1022. key, nbytes = _charge(func_args)
  1023. size = max(1, nbytes)
  1024. with cv:
  1025. while not (key is not None and key in held) and inflight[0] > 0 and inflight[0] + size > budget:
  1026. cv.wait(timeout=1.0)
  1027. if key is not None and key in held:
  1028. held[key][0] += 1 # the slab is already charged (and alive)
  1029. else:
  1030. inflight[0] += size
  1031. if key is not None:
  1032. held[key] = [1, size]
  1033. before = len(futures)
  1034. def release(_f=None):
  1035. with cv:
  1036. if key is None:
  1037. inflight[0] -= size
  1038. else:
  1039. h = held[key]
  1040. h[0] -= 1
  1041. if h[0] == 0:
  1042. inflight[0] -= h[1]
  1043. del held[key]
  1044. cv.notify_all()
  1045. try:
  1046. orig(executor=executor, futures=futures, use_async=use_async, func=func,
  1047. func_args=func_args, func_kwargs=func_kwargs)
  1048. except BaseException:
  1049. release()
  1050. raise
  1051. if len(futures) > before:
  1052. futures[-1].add_done_callback(release)
  1053. else:
  1054. release()
  1055. module.maybe_executor_submit = paced_submit
  1056. logger.warning("DSV41 fast load: weight copies paced to %.1f GB in flight", budget / 2**30)
  1057. _wrap_target_load(module)
  1058. def _wrap_target_load(module):
  1059. """Release everything the moment the target's copies are done, before the engine measures
  1060. its memory: SGLang derives the KV pool from what is free after the weights landed."""
  1061. cls = getattr(module, "DeepseekV4ForCausalLM", None)
  1062. if cls is None or getattr(cls.load_weights, "_dsv41_fast_load", False):
  1063. return
  1064. orig = cls.load_weights
  1065. def load_weights(self, weights, *args, **kwargs):
  1066. target = _state["phase"] == "target"
  1067. if target:
  1068. _mark_load_start()
  1069. escaped = []
  1070. try:
  1071. out = orig(self, weights, *args, **kwargs)
  1072. finally:
  1073. if target:
  1074. escaped = _log_phase("target")
  1075. if escaped:
  1076. # an escaped slab stays pinned outside the KV pool budget the engine sizes next
  1077. raise RuntimeError(
  1078. f"DSV41 fast load: {len(escaped)} slab(s) ({sum(e[0] for e in escaped) / 2**20:.0f} MiB) still "
  1079. f"alive after the target load, held by {sorted({n for e in escaped for n in e[2]})[:8]}; boot with "
  1080. f"DSV41_FAST_LOAD_SLAB_MB=0 (one buffer per tensor) and report this")
  1081. return out
  1082. load_weights._dsv41_fast_load = True
  1083. cls.load_weights = load_weights
  1084. def _ps_top(n=6):
  1085. try:
  1086. rows = []
  1087. for pid in os.listdir("/proc"):
  1088. if not pid.isdigit():
  1089. continue
  1090. try:
  1091. rss = 0
  1092. for line in open(f"/proc/{pid}/status"):
  1093. if line.startswith("VmRSS"):
  1094. rss = int(line.split()[1]) // 1024
  1095. break
  1096. comm = open(f"/proc/{pid}/comm").read().strip()
  1097. rows.append((rss, pid, comm))
  1098. except Exception:
  1099. pass
  1100. rows.sort(reverse=True)
  1101. return rows[:n]
  1102. except Exception:
  1103. return []
  1104. def _schedule_late_snapshots():
  1105. """Diagnostics: how the host memory settles after the loads (other processes still starting)."""
  1106. import time
  1107. def run():
  1108. for delay in (30, 60, 120, 240):
  1109. time.sleep(delay if delay == 30 else delay - prev[0])
  1110. prev[0] = delay
  1111. snap = _snapshot()
  1112. snap.pop("big_maps_mb", None)
  1113. logger.warning("DSV41 fast load: +%ds after draft load: %s | top rss MB %s", delay, snap, _ps_top())
  1114. prev = [0]
  1115. threading.Thread(target=run, name="dsv41-late-snap", daemon=True).start()
  1116. def install_dspark(module):
  1117. """Mark the DSpark draft load so shard filtering and mtp-only eager reads apply."""
  1118. if not observing():
  1119. return
  1120. cls = module.DeepseekV4ForCausalLMDSpark
  1121. orig = cls.load_weights
  1122. def load_weights(self, weights, *args, **kwargs):
  1123. _state["phase"] = "draft"
  1124. try:
  1125. return orig(self, weights, *args, **kwargs)
  1126. finally:
  1127. _log_phase("draft")
  1128. _state["phase"] = "target"
  1129. if os.environ.get("DSV41_FAST_LOAD_DEBUG") == "1":
  1130. _schedule_late_snapshots()
  1131. cls.load_weights = load_weights
  1132. if not enabled():
  1133. # observe mode: the target hook is otherwise installed by install_deepseek_v4
  1134. import sys
  1135. mod = sys.modules.get("sglang.srt.models.deepseek_v4")
  1136. if mod is not None:
  1137. _wrap_target_load(mod)
  1138. if __name__ == "__main__": # self-test: python3 fast_load.py <shard> <ep_rank> <ep_size> [<moe_tp_rank> <moe_tp_size>]
  1139. import sys, time
  1140. path, ep = sys.argv[1], (int(sys.argv[2]), int(sys.argv[3]))
  1141. tp = tp_slicing(ep, (int(sys.argv[4]), int(sys.argv[5])), "on") if len(sys.argv) > 5 else None
  1142. plan = read_plan(path, "target", ep, _n_routed_experts(path) or 384, tp)
  1143. base, header = _parse_header(path)
  1144. total = sum(spec_bytes(header[k], sp)[0] for k, sp in plan.items())
  1145. kept = sum(spec_bytes(header[k], sp)[1] for k, sp in plan.items())
  1146. fd = os.open(path, os.O_RDONLY)
  1147. os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_DONTNEED)
  1148. t = time.time()
  1149. futs = [_executor().submit(_read_tensor, fd, base, header[k]) if sp is None
  1150. else _executor().submit(_read_slice, fd, base, header[k], sp) for k, sp in plan.items()]
  1151. tensors = [f.result() for f in futs]
  1152. dt = time.time() - t
  1153. print(f"{os.path.basename(path)}: read {total/1e9:.2f} GB ({kept/1e9:.2f} GB kept) in {len(plan)} tensors "
  1154. f"in {dt:.1f}s = {total/1e9/dt:.2f} GB/s")