hybrid_loader.py 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. """
  2. hybrid_loader.py — Path A: TP4-native NVFP4/NF3 hybrid loader for vLLM (modelopt path).
  3. Deploy via .pth (`import hybrid_loader`). Fires in every worker.
  4. GLM-5.2 hybrid checkpoint: routed experts are per-layer mixed — top-K (=64) NVFP4 (crisp) +
  5. the rest NF3 (3-bit, group-32). Non-experts bf16 (excluded). Stock modelopt would allocate
  6. uniform NVFP4 for all 256 experts (~420 GiB → OOM on 384). This loader allocates COMPACT
  7. two-group storage (~326 GiB) and does a reference two-pass forward.
  8. Interceptions (armed by an import hook on vllm...modelopt):
  9. 1. ModelOptNvFp4Config.from_config -> stash `hybrid_bit_map` (stock strips unknown keys).
  10. 2. FusedMoEMethodCls -> HybridNvFp4MoE.
  11. NO mapping patch: stock RoutedExperts.make_expert_params_mapping is PREFIX-based, so
  12. `...gate_proj.weight_packed` -> `...routed_experts.w13_weight_packed`, `...weight_scale` ->
  13. `...w13_weight_scale`, etc. We register compact params under those exact names; the per-layer
  14. weight_loader demuxes NVFP4 vs NF3 by the expert's group (this layer's remap) and TP-shards.
  15. Facts baked in (verified against the eldritch image):
  16. * apply(layer, x, topk_weights, topk_ids, shared_experts, shared_experts_input): routing is
  17. upstream (sigmoid/noaux_tc/norm/2.5x already applied); shared experts run by the runner
  18. separately -> apply returns ROUTED-ONLY output.
  19. * moe_kernel stays None for hybrid layers -> is_monolithic False (via experts_cls) -> apply
  20. dispatched; maybe_make_prepare_finalize overridden to None so MK-init doesn't raise.
  21. * on-disk NVFP4 block scales are LINEAR (2D) -> dequantize_to_dtype(..., swizzle=False).
  22. * TP=4, moe_intermediate=2048 -> clean chunk, no padding. gate/up shard dim0, down shard dim1.
  23. """
  24. import os, sys, importlib.abc, importlib.util
  25. _NF3_VALS = [-1.0, -0.6047, -0.3563, -0.1275, 0.1275, 0.3563, 0.6047, 1.0]
  26. _HYBRID_DEBUG = os.environ.get("HYBRID_DEBUG") not in (None, "", "0") # eager-only: per-tier norms
  27. # triton | b12x | b12x_nf3 | ref. b12x_nf3 = BOTH tiers through the b12x W4A16
  28. # CuteDSL kernel: kept-64 NVFP4 as weight_layout="packed", NF3-192 as the new
  29. # weight_layout="nf3_2p1" (e4m3_k32 scales). HYBRID_NF3 is ignored in that mode.
  30. _HYBRID_KEPT = os.environ.get("HYBRID_KEPT", "triton")
  31. _HYBRID_TIER = os.environ.get("HYBRID_TIER", "both") # both|a|b (b12x_nf3 isolation)
  32. _HYBRID_ACT_CAPTURE = os.environ.get("HYBRID_ACT_CAPTURE", "") # dir: save MoE-layer inputs for GPTQ Hessians
  33. _act_store = {"n": 0, "buf": {}, "flushed": {}}
  34. _HYBRID_NF3 = os.environ.get("HYBRID_NF3", "fast") # fast | ref (NF3 3-bit tier)
  35. _HYBRID_PROFILE = os.environ.get("HYBRID_PROFILE", "0") == "1" # per-tier CUDA-event timing at prefill M>64
  36. # TC-decode fast path for m<=8 (2026-07-05): compile a SECOND launch per tier at
  37. # size_m=max_m (small-size_m compiles are the broken family) with block-8 direct
  38. # top-k + fused sum. Measured standalone: 0.27ms vs prod 0.36-0.38ms/launch at
  39. # decode m, numerics rel<0.012. Gated OFF by default; flag-off = today's bytes.
  40. _HYBRID_TC_DECODE = os.environ.get("HYBRID_TC_DECODE", "0") == "1"
  41. _prof_store = {"n": 0, "a_ms": 0.0, "b_ms": 0.0, "rows": 0, "pend": []}
  42. _dbg_first_build = [True] # keep kept-tier originals for ONLY the first b12x layer (VRAM)
  43. _HBM = None # cached hybrid_bit_map (per-process); read once from the checkpoint config.json
  44. # ---- b12x_nf3 backend constants / shared runtime ----
  45. # Pinned CTA tiles (fc1_tile_k, fc1_tile_n, fc2_tile_k, fc2_tile_n): the NF3
  46. # flat-span weight layout is packed for a SPECIFIC tile_n, but the kernel's
  47. # auto tile selection is m-dependent (fc1_tile_n flips 128<->256 across m).
  48. # (64,256,64,256) validated for our shapes (fc1 N=1024 K=6144 / fc2 N=6144
  49. # K=512) at BOTH moe_block_size 8 (decode) and 64 (prefill), both scale
  50. # formats: smem fits (45-76KB <= 100.9KB) and the SM121 reg-count table has
  51. # entries for (256,{1,4},16,4). It is also exactly what auto-selection picks
  52. # for the max-m prefill, so prefill throughput is the natural one.
  53. _B12X_NF3_TILES = (64, 256, 64, 256)
  54. _B12X_NF3_DECODE_M = 8 # <=8 -> preplanned TC-decode launch (fused topk sum)
  55. _B12X_NF3_MAX_TOKENS = int(os.environ.get("HYBRID_B12X_MAX_TOKENS", "8192"))
  56. _b12x_nf3_rt = { # module-level, shared across ALL layers (one scratch set)
  57. "max_m": None, # fixed at first apply: max(env, first-call m)
  58. "topk": None,
  59. "launches": {}, # (E, layout, scale_fmt, topk, max_m) -> (dec, pre)
  60. "buffers": None, # W4A16PackedBuffers planned at max_m/route_E=256
  61. "out_a": None, # [max_m, H] bf16 per-tier outputs (fully overwritten
  62. "out_b": None, # by every run_w4a16_moe call that uses them)
  63. }
  64. def _load_hbm(quant_config=None):
  65. """Reliable in-worker hybrid_bit_map: config-object attr, else read config.json off disk.
  66. (from_config stash on the config object does NOT survive pickling to TP workers.)"""
  67. global _HBM
  68. if _HBM is not None:
  69. return _HBM
  70. hbm = getattr(quant_config, "hybrid_bit_map", None) if quant_config is not None else None
  71. if hbm is None:
  72. try:
  73. import json, os
  74. from vllm.config import get_current_vllm_config
  75. mp = get_current_vllm_config().model_config.model
  76. cfgp = os.path.join(mp, "config.json")
  77. qc = json.load(open(cfgp)).get("quantization_config", {})
  78. hbm = qc.get("hybrid_bit_map")
  79. print(f"[hybrid_loader] hbm read from {cfgp}: {len(hbm) if hbm else 0} layers", flush=True)
  80. except Exception as e:
  81. print("[hybrid_loader] hbm config.json read failed:", e, flush=True)
  82. _HBM = hbm
  83. return hbm
  84. def _unpack_nf3(packed, scale, out_cols, blk=32):
  85. """packed:[R, out_cols//8*3] uint8, scale:[R, out_cols//blk] fp8 -> [R, out_cols] bf16."""
  86. import torch
  87. nf = torch.tensor(_NF3_VALS, device=packed.device, dtype=torch.float32)
  88. R = packed.shape[0]
  89. p = packed.reshape(R, out_cols // 8, 3).to(torch.int32)
  90. w24 = p[..., 0] | (p[..., 1] << 8) | (p[..., 2] << 16)
  91. codes = torch.stack([(w24 >> (3 * i)) & 0x7 for i in range(8)], -1).reshape(R, out_cols)
  92. return (nf[codes.long()] * scale.float().repeat_interleave(blk, 1)).to(torch.bfloat16)
  93. def _fp8_ne_transform(weights):
  94. """Dequant fp8 non-expert weights -> bf16 on the fly. Speaks BOTH scale
  95. dialects: `.weight_scale_fp8` (rev-2, bf16 per-channel) and `.weight_scale`
  96. U8 (rev-3 MXFP8, e8m0 per-32 groups, [out, in//32]). Expert tensors never
  97. match (uint8-packed weights / F8 scales) and pass through untouched."""
  98. import torch
  99. scales, pend = {}, {}
  100. def _deq(w, s):
  101. kind, st = s
  102. if kind == "chan":
  103. return (w.to(torch.float32) * st.to(torch.float32).unsqueeze(1)).to(torch.bfloat16)
  104. sc = torch.pow(2.0, st.to(torch.float32) - 127.0)
  105. return (w.to(torch.float32) * sc.repeat_interleave(32, 1)).to(torch.bfloat16)
  106. def _stage(t):
  107. # fastsafetensors yields CUDA tensors; holding tier pairs in VRAM
  108. # mid-shard OOMs the load. CPU-stage ONLY the F8 tier (16GB of 327) —
  109. # experts keep streaming GPU-direct.
  110. return t.cpu() if getattr(t, "is_cuda", False) else t
  111. for name, t in weights:
  112. if name.endswith(".weight_scale_fp8"):
  113. wn = name[:-len(".weight_scale_fp8")] + ".weight"
  114. w = pend.pop(wn, None)
  115. if w is not None:
  116. yield wn, _deq(w, ("chan", _stage(t)))
  117. else:
  118. scales[wn] = ("chan", _stage(t))
  119. elif (name.endswith(".weight_scale")
  120. and getattr(t, "dtype", None) == torch.uint8 and t.dim() == 2):
  121. wn = name[:-len(".weight_scale")] + ".weight"
  122. w = pend.pop(wn, None)
  123. if w is not None:
  124. yield wn, _deq(w, ("mx", _stage(t)))
  125. else:
  126. scales[wn] = ("mx", _stage(t))
  127. elif name.endswith(".weight") and getattr(t, "dtype", None) == torch.float8_e4m3fn:
  128. s = scales.pop(name, None)
  129. if s is not None:
  130. yield name, _deq(_stage(t), s)
  131. else:
  132. pend[name] = _stage(t)
  133. else:
  134. yield name, t
  135. for wn, t in pend.items(): # unmatched fp8 weight (shouldn't happen) -> best-effort
  136. yield wn, t.to(torch.bfloat16)
  137. def _deq_nv(w, s, g, gs, deq):
  138. """NVFP4 dequant (linear on-disk scales). g is [2] for fused w13 or scalar for w2."""
  139. import torch
  140. if g.numel() == 2: # fused w13: gate rows use g[0], up rows use g[1]
  141. half = w.shape[0] // 2
  142. a = deq(w[:half], s[:half], g[0], torch.bfloat16, gs, swizzle=False)
  143. b = deq(w[half:], s[half:], g[1], torch.bfloat16, gs, swizzle=False)
  144. return torch.cat([a, b], 0)
  145. return deq(w, s, g.reshape(()), torch.bfloat16, gs, swizzle=False)
  146. def _patch(mod):
  147. import re, torch
  148. import torch.nn.functional as F
  149. from vllm.model_executor.utils import set_weight_attrs
  150. from vllm.model_executor.layers.quantization.utils.nvfp4_emulation_utils import dequantize_to_dtype
  151. from vllm.distributed import (get_tensor_model_parallel_rank,
  152. get_tensor_model_parallel_world_size)
  153. Base = mod.ModelOptNvFp4FusedMoE
  154. Cfg = mod.ModelOptNvFp4Config
  155. # ---- 1. keep hybrid_bit_map alive through from_config ----
  156. _orig_fc = getattr(Cfg.from_config, "__func__", Cfg.from_config)
  157. def _from_config(cls, config):
  158. obj = _orig_fc(cls, config)
  159. hbm = None
  160. if isinstance(config, dict):
  161. hbm = config.get("hybrid_bit_map")
  162. if hbm is None and isinstance(config.get("quantization"), dict):
  163. hbm = config["quantization"].get("hybrid_bit_map")
  164. try:
  165. obj.hybrid_bit_map = hbm
  166. print(f"[hybrid_loader] hybrid_bit_map stashed: "
  167. f"{len(hbm) if hbm else 0} layers", flush=True)
  168. except Exception as e:
  169. print("[hybrid_loader] stash failed:", e, flush=True)
  170. return obj
  171. Cfg.from_config = classmethod(_from_config)
  172. class HybridNvFp4MoE(Base):
  173. def maybe_make_prepare_finalize(self, *a, **k):
  174. return None # we own the forward via apply(); no external prepare/finalize
  175. def _bits(self, layer):
  176. hbm = _load_hbm(self.quant_config)
  177. pfx = getattr(layer, "prefix", None)
  178. if pfx is None:
  179. pfx = getattr(layer, "layer_name", "") or ""
  180. m = re.search(r"layers\.(\d+)\b", str(pfx))
  181. b = hbm.get(str(int(m.group(1)))) if (hbm and m) else None
  182. if b is None:
  183. print(f"[hybrid][MISS] pfx={pfx!r} hbm_len={len(hbm) if hbm else 0} "
  184. f"idx={m.group(1) if m else None}", flush=True)
  185. return b
  186. def create_weights(self, layer, num_experts, hidden_size,
  187. intermediate_size_per_partition, params_dtype, **extra):
  188. bits = self._bits(layer)
  189. if bits is None:
  190. # Non-hybrid MoE (the MTP/nextn layer) — its experts are uniform NVFP4.
  191. # Route it through OUR path as all-kept (bits=4) so it uses our Triton NVFP4
  192. # kernel + our weight_loader. (Falling to super() uses the stock MoE loader,
  193. # whose weight-scale quant_method check at routed_experts.py:906 rejects it
  194. # under our method -> the MTP boot crash.)
  195. bits = [4] * num_experts
  196. H, I = hidden_size, intermediate_size_per_partition
  197. gs = self.quant_config.group_size
  198. tp_rank = get_tensor_model_parallel_rank()
  199. tp_size = get_tensor_model_parallel_world_size()
  200. kept = [e for e, b in enumerate(bits) if b == 4]
  201. dem = [e for e, b in enumerate(bits) if b == 3]
  202. Kn, Km = len(kept), len(dem)
  203. layer.hyb = {"remap": {**{e: (0, i) for i, e in enumerate(kept)},
  204. **{e: (1, i) for i, e in enumerate(dem)}},
  205. "H": H, "I": I, "gs": gs, "E": num_experts}
  206. _pfx = getattr(layer, "prefix", None) or getattr(layer, "layer_name", "?")
  207. layer.hyb["lname"] = str(_pfx)
  208. print(f"[hybrid] {_pfx}: {Kn}NVFP4 + {Km}NF3 (tp{tp_rank}/{tp_size} I={I})", flush=True)
  209. def wl(param, loaded, name_mapped=None, *, shard_id=None,
  210. expert_id=None, return_success=False, **kw):
  211. nm = name_mapped or ""
  212. if "input_scale" in nm: # W4A16 reference -> unused
  213. return True
  214. grp, li = layer.hyb["remap"][int(expert_id)]
  215. fam = "w13" if "w13_" in nm else "w2"
  216. sh = shard_id
  217. if "weight_scale_2" in nm: # NVFP4 per-tensor global (kept only)
  218. tgt = getattr(layer, f"{fam}_weight_scale_2")
  219. if fam == "w13":
  220. tgt.data[li, 0 if sh == "w1" else 1] = loaded.reshape(()).to(tgt.dtype)
  221. else:
  222. tgt.data[li] = loaded.reshape(()).to(tgt.dtype)
  223. return True
  224. # TP shard the block-quantized 2D tensor (gate/up -> dim0, down -> dim1)
  225. if tp_size > 1 and loaded.ndim >= 2:
  226. if sh in ("w1", "w3"):
  227. loaded = loaded.chunk(tp_size, 0)[tp_rank]
  228. elif sh == "w2":
  229. loaded = loaded.chunk(tp_size, 1)[tp_rank]
  230. if "weight_scale" in nm: # block scale -> real storage, demux by group
  231. tgt = getattr(layer, f"{fam}_nv_s" if grp == 0 else f"{fam}_nf_s")
  232. elif "weight_packed" in nm: # NF3 packed weight
  233. tgt = getattr(layer, f"{fam}_weight_packed")
  234. else: # plain NVFP4 weight
  235. tgt = getattr(layer, f"{fam}_weight")
  236. d = tgt.data[li]
  237. if fam == "w13" and sh in ("w1", "w3"): # gate->top half, up->bottom half
  238. half = d.shape[0] // 2
  239. d = d[:half] if sh == "w1" else d[half:]
  240. d.copy_(loaded.reshape(d.shape).to(d.dtype))
  241. return True
  242. def P(name, shape, dt=torch.uint8):
  243. p = torch.nn.Parameter(torch.zeros(shape, dtype=dt,
  244. device=torch.cuda.current_device()), requires_grad=False)
  245. set_weight_attrs(p, {"weight_loader": wl})
  246. layer.register_parameter(name, p)
  247. mk = lambda n: max(n, 1)
  248. # --- names the stock (prefix-based) mapping produces (routing needs no patch) ---
  249. P("w13_weight", (mk(Kn), 2 * I, H // 2)) # NVFP4 weight (kept)
  250. P("w13_weight_packed", (mk(Km), 2 * I, H // 8 * 3)) # NF3 packed weight (demoted)
  251. P("w13_weight_scale", (1,)) # dispatcher (routes to *_s)
  252. P("w13_weight_scale_2", (mk(Kn), 2), torch.float32) # NVFP4 global (kept)
  253. P("w13_input_scale", (1,), torch.float32) # dispatcher (ignored)
  254. P("w2_weight", (mk(Kn), H, I // 2))
  255. P("w2_weight_packed", (mk(Km), H, I // 8 * 3))
  256. P("w2_weight_scale", (1,))
  257. P("w2_weight_scale_2", (mk(Kn),), torch.float32)
  258. P("w2_input_scale", (1,), torch.float32)
  259. # --- real block-scale storage (filled by dispatcher; not mapping-routed) ---
  260. for nm, sh in [("w13_nv_s", (mk(Kn), 2 * I, H // gs)),
  261. ("w13_nf_s", (mk(Km), 2 * I, H // 32)),
  262. ("w2_nv_s", (mk(Kn), H, I // gs)),
  263. ("w2_nf_s", (mk(Km), H, I // 32))]:
  264. layer.register_parameter(nm, torch.nn.Parameter(
  265. torch.zeros(sh, dtype=torch.float8_e4m3fn,
  266. device=torch.cuda.current_device()), requires_grad=False))
  267. def _build_kept_b12x(self, layer):
  268. """Build a REAL b12x NVFP4 fused-MoE kernel over just the Kn kept experts.
  269. Reuses the production kernel (graph-safe, sm120) via a cloned num_experts=Kn
  270. FusedMoEConfig. Validated standalone: correct + skips out-of-range ids + cudagraph-safe.
  271. apply() remaps topk so kept->[0,Kn), non-kept->Kn (the kernel drops the sentinel)."""
  272. import dataclasses
  273. import torch.nn as _nn
  274. from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import (
  275. select_nvfp4_moe_backend, convert_to_nvfp4_moe_kernel_format,
  276. make_nvfp4_moe_quant_config, make_nvfp4_moe_kernel)
  277. from vllm.model_executor.layers.fused_moe.config import FusedMoEParallelConfig
  278. from vllm.model_executor.layers.quantization.utils.quant_utils import kNvfp4Static
  279. from vllm.model_executor.layers.fused_moe.activation import MoEActivation
  280. E = layer.hyb["E"]
  281. dev = layer.w13_weight.device
  282. remap = layer.hyb["remap"]
  283. Kn = sum(1 for (grp, li) in remap.values() if grp == 0)
  284. # Build the kept kernel as a NON-PARALLEL (tp=1) MoE over the PER-RANK intermediate:
  285. # the weights are already TP-sharded by the weight_loader (ispp per rank), so the
  286. # kernel must see intermediate_size == ispp and tp=1 (else it mis-indexes full vs
  287. # sharded -> per-element garbage). The layer's post-apply all-reduce handles TP.
  288. ispp = self.moe.intermediate_size_per_partition
  289. kept_moe = dataclasses.replace(
  290. self.moe, num_experts=Kn, num_local_experts=Kn, num_logical_experts=Kn,
  291. intermediate_size=ispp,
  292. moe_parallel_config=FusedMoEParallelConfig.make_no_parallel())
  293. backend, experts_cls = select_nvfp4_moe_backend(
  294. config=kept_moe, weight_key=kNvfp4Static, activation_key=None)
  295. KL = _nn.Module()
  296. KL.activation = getattr(layer, "activation", MoEActivation.SILU)
  297. KL.moe_config = kept_moe
  298. KL.local_num_experts = Kn
  299. a13 = torch.ones(Kn, 2, device=dev, dtype=torch.float32)
  300. a2 = torch.ones(Kn, device=dev, dtype=torch.float32)
  301. w13ks2 = layer.w13_weight_scale_2[:, 0].contiguous()
  302. conv = convert_to_nvfp4_moe_kernel_format(
  303. nvfp4_backend=backend, layer=KL,
  304. w13=layer.w13_weight, w13_scale=layer.w13_nv_s, w13_scale_2=w13ks2, a13_scale=a13,
  305. w2=layer.w2_weight, w2_scale=layer.w2_nv_s, w2_scale_2=layer.w2_weight_scale_2,
  306. a2_scale=a2, is_act_and_mul=True, use_a16=True)
  307. (cw13, cw13s, cw13s2, ca13, cw2, cw2s, cw2s2, ca2) = conv
  308. for k, v in [("w13_weight", cw13), ("w13_weight_scale", cw13s),
  309. ("w13_weight_scale_2", cw13s2), ("w13_input_scale", ca13),
  310. ("w2_weight", cw2), ("w2_weight_scale", cw2s),
  311. ("w2_weight_scale_2", cw2s2), ("w2_input_scale", ca2)]:
  312. setattr(KL, k, v)
  313. qconf = make_nvfp4_moe_quant_config(backend, cw13s, cw2s, cw13s2, cw2s2,
  314. ca13, ca2, use_a16=True)
  315. kk = make_nvfp4_moe_kernel(qconf, kept_moe, experts_cls, routing_tables=None)
  316. kk.fused_experts.process_weights_after_loading(KL)
  317. # Mark this method as owning its modular kernel (supports_internal_mk -> True) so
  318. # vLLM's post-load maybe_init_modular_kernel() returns early instead of rebuilding
  319. # the parent kernel from the (freed) standard weight attrs.
  320. self.moe_kernel = kk
  321. # remap: global -> kept-local, else sentinel Kn (b12x skips ids >= num_experts)
  322. nvfp4_remap = torch.full((E,), Kn, dtype=torch.int32, device=dev)
  323. for g, (grp, li) in remap.items():
  324. if grp == 0:
  325. nvfp4_remap[g] = li
  326. if _HYBRID_DEBUG and not _b12x_nf3_rt.get("_selftested"):
  327. _b12x_nf3_rt["_selftested"] = True
  328. try:
  329. from vllm.distributed import get_tensor_model_parallel_rank as _gr
  330. _rk = _gr()
  331. except Exception:
  332. _rk = -1
  333. xt = torch.randn(4, layer.hyb["H"], device=dev, dtype=torch.bfloat16) * 0.03
  334. ids = (torch.arange(32, device=dev, dtype=torch.int32) % Kn).reshape(4, 8)
  335. tww = torch.full((4, 8), 0.125, device=dev, dtype=torch.float32)
  336. o = kk.apply(xt, KL.w13_weight, KL.w2_weight, tww, ids,
  337. activation=KL.activation, global_num_experts=Kn,
  338. expert_map=None, apply_router_weight_on_input=False,
  339. shared_experts=None, shared_experts_input=None)
  340. torch.cuda.synchronize()
  341. print(f"[selftest] r{_rk} {getattr(layer, 'prefix', '?')}: "
  342. f"out={o.float().norm():.4f} inf={bool(torch.isinf(o).any())} "
  343. f"nan={bool(torch.isnan(o).any())} "
  344. f"cw13={KL.w13_weight.view(torch.uint8).float().mean():.3f} "
  345. f"cw13s2_max={KL.w13_weight_scale_2.float().abs().max():.3e} "
  346. f"cw2s2_max={KL.w2_weight_scale_2.float().abs().max():.3e}", flush=True)
  347. layer.hyb["kept_kernel"] = kk
  348. layer.hyb["kept_KL"] = KL
  349. layer.hyb["Kn"] = Kn
  350. layer.hyb["nvfp4_remap"] = nvfp4_remap
  351. # CRITICAL: keep the converted tensors alive past this function. b12x's
  352. # process_weights compacts the source weight to (0,) but its prepared weights
  353. # VIEW the converted `cw13`/`cw2`; if those locals are freed on return the views
  354. # dangle -> per-layer garbage (works in a script where they stay in scope).
  355. layer.hyb["_keepalive"] = conv
  356. # free the BIG compact kept originals (KL holds the converted copies) -> flat VRAM.
  357. # keep the tiny *_weight_scale* params (get_fused_moe_quant_config may still read them).
  358. keep_dbg = _HYBRID_DEBUG and _dbg_first_build[0] # keep originals for ONE layer only
  359. if keep_dbg:
  360. _dbg_first_build[0] = False
  361. for nm in ("w13_weight", "w2_weight", "w13_nv_s", "w2_nv_s"):
  362. if not keep_dbg and hasattr(layer, nm):
  363. try:
  364. delattr(layer, nm)
  365. except Exception:
  366. setattr(layer, nm, None)
  367. print(f"[hybrid] {getattr(layer, 'prefix', '?')}: kept b12x kernel built "
  368. f"(Kn={Kn}, backend={backend}, kept_ispp={kept_moe.intermediate_size_per_partition}, "
  369. f"kept_tp={kept_moe.moe_parallel_config.tp_size}, "
  370. f"w13={tuple(KL.w13_weight.shape)})", flush=True)
  371. def _build_b12x_nf3(self, layer):
  372. """HYBRID_KEPT=b12x_nf3: drive BOTH tiers through the b12x W4A16
  373. CuteDSL MoE kernel.
  374. Object A (kept NVFP4) -> the PRODUCTION vLLM chain via
  375. _build_kept_b12x (select_nvfp4_moe_backend -> convert_to_nvfp4_
  376. moe_kernel_format -> make_nvfp4_moe_kernel, no-parallel clone,
  377. keepalive). Manual composition through the prepare building
  378. blocks is numerically WRONG for varying real-range scales
  379. (harness-proven 2026-07-01: only the production convert chain
  380. passes vs the dequant reference; probe test_b12x.py TESTS A-D).
  381. Object B (NF3 192) -> weight_layout="nf3_2p1"/e4m3_k32 packed
  382. from the 837 checkpoint planes (chunked over experts) through
  383. our NF3 kernel format (GPU unit test + in-model norms PASS).
  384. Launches/buffers are built lazily at first apply() (topk + real max
  385. m known there; the first forward is vLLM's EAGER profile run, so
  386. nothing compiles inside CUDA-graph capture)."""
  387. import torch
  388. from b12x.moe.fused.w4a16.prepare import (
  389. PreparedNF3MoeWeights,
  390. W4A16PackedWeights,
  391. _make_workspace,
  392. _nf3_pack_code_experts,
  393. _nf3_pack_scale_experts,
  394. _permute_nvfp4_scales,
  395. _repack_weight,
  396. )
  397. import nf3_replan
  398. hyb = layer.hyb
  399. E, H, I = hyb["E"], hyb["H"], hyb["I"]
  400. remap = hyb["remap"]
  401. dev = layer.w13_weight.device
  402. Kn = sum(1 for (grp, _li) in remap.values() if grp == 0)
  403. Km = sum(1 for (grp, _li) in remap.values() if grp == 1)
  404. emap_a = torch.full((E,), -1, dtype=torch.int32, device=dev)
  405. emap_b = torch.full((E,), -1, dtype=torch.int32, device=dev)
  406. for g, (grp, li) in remap.items():
  407. (emap_a if grp == 0 else emap_b)[g] = li
  408. fc1_tn, fc2_tn = _B12X_NF3_TILES[1], _B12X_NF3_TILES[3]
  409. # ---- object B: NF3 -> "nf3_2p1" (chunked over experts) ----
  410. # (built FIRST: object A's production build frees the kept originals)
  411. prep_b = None
  412. if Km > 0:
  413. chunk = 16 # bound transient VRAM (codes int32 = ~400MB/16 w13 experts)
  414. w13_planes, w2_planes = [], []
  415. for e0 in range(0, Km, chunk):
  416. codes = nf3_replan.unpack_837_codes(
  417. layer.w13_weight_packed[e0:e0 + chunk], H)
  418. w13_planes.append(_nf3_pack_code_experts(
  419. codes, size_k=H, size_n=2 * I, tile_n=fc1_tn))
  420. del codes
  421. for e0 in range(0, Km, chunk):
  422. codes = nf3_replan.unpack_837_codes(
  423. layer.w2_weight_packed[e0:e0 + chunk], I)
  424. w2_planes.append(_nf3_pack_code_experts(
  425. codes, size_k=I, size_n=H, tile_n=fc2_tn))
  426. del codes
  427. w13_nf3 = torch.cat(w13_planes, 0).contiguous(); del w13_planes
  428. w2_nf3 = torch.cat(w2_planes, 0).contiguous(); del w2_planes
  429. w13_ns = _nf3_pack_scale_experts(
  430. layer.w13_nf_s.float(), size_k=H, size_n=2 * I)
  431. w2_ns = _nf3_pack_scale_experts(
  432. layer.w2_nf_s.float(), size_k=I, size_n=H)
  433. nf3_global = torch.full(
  434. (Km,), 2.0 ** 116, dtype=torch.float32, device=dev)
  435. prep_b = PreparedNF3MoeWeights(
  436. w13=w13_nf3, w13_scale=w13_ns, w13_global_scale=nf3_global,
  437. w2=w2_nf3, w2_scale=w2_ns,
  438. w2_global_scale=nf3_global.clone(),
  439. workspace=_make_workspace(dev),
  440. hidden_size=H, intermediate_size=I, num_experts=Km,
  441. is_gated=True, params_dtype=torch.bfloat16,
  442. fc1_tile_n=fc1_tn, fc2_tile_n=fc2_tn)
  443. hyb["prepB"] = prep_b
  444. hyb["prepA"] = None # filled by the kept build below
  445. hyb["emap_a"], hyb["emap_b"] = emap_a, emap_b
  446. hyb["Kn"], hyb["Km"] = Kn, Km
  447. keep_dbg = _HYBRID_DEBUG and _dbg_first_build[0]
  448. if keep_dbg:
  449. _dbg_first_build[0] = False
  450. # ---- object A: kept NVFP4 -> manual "packed" composition.
  451. # REHABILITATED 2026-07-01: byte-identical to the stock prepare entry
  452. # (verified) and PASSES the FIXED (swizzle=False) dequant reference on
  453. # ranks 0/1/2 at rel 0.006. The earlier condemnation used a broken
  454. # reference. Produces weight_layout="packed" -> TC-decode launches
  455. # compile; NO modular kernel / workspace manager in the forward.
  456. if Kn > 0:
  457. g13 = layer.w13_weight_scale_2[:Kn, 0].contiguous()
  458. g2 = layer.w2_weight_scale_2[:Kn].contiguous()
  459. w13_packed = _repack_weight(
  460. layer.w13_weight.contiguous(), size_k=H, size_n=2 * I)
  461. w2_packed = _repack_weight(
  462. layer.w2_weight.contiguous(), size_k=I, size_n=H)
  463. w13_ps, w13_pg = _permute_nvfp4_scales(
  464. layer.w13_nv_s, g13, size_k=H, size_n=2 * I,
  465. a_dtype=torch.bfloat16)
  466. w2_ps, w2_pg = _permute_nvfp4_scales(
  467. layer.w2_nv_s, g2, size_k=I, size_n=H,
  468. a_dtype=torch.bfloat16)
  469. hyb["prepA"] = W4A16PackedWeights(
  470. w13=w13_packed, w13_scale=w13_ps, w13_global_scale=w13_pg,
  471. w2=w2_packed, w2_scale=w2_ps, w2_global_scale=w2_pg,
  472. workspace=_make_workspace(dev),
  473. hidden_size=H, intermediate_size=I, num_experts=Kn,
  474. is_gated=True, params_dtype=torch.bfloat16,
  475. source_format="modelopt_nvfp4", w13_layout="w13",
  476. weight_layout="packed", scale_format="e4m3_k16")
  477. if not keep_dbg:
  478. for nm in ("w13_weight", "w2_weight", "w13_nv_s", "w2_nv_s"):
  479. p_ = getattr(layer, nm, None)
  480. if p_ is not None and getattr(p_, "data", None) is not None:
  481. p_.data = p_.data.new_empty((0,))
  482. if not keep_dbg:
  483. for nm in ("w13_weight_packed", "w2_weight_packed",
  484. "w13_nf_s", "w2_nf_s"):
  485. p = getattr(layer, nm, None)
  486. if p is not None and getattr(p, "data", None) is not None:
  487. p.data = p.data.new_empty((0,))
  488. print(f"[hybrid] {getattr(layer, 'prefix', '?')}: b12x_nf3 built "
  489. f"(Kn={Kn} via production chain + Km={Km} nf3_2p1, "
  490. f"tiles={_B12X_NF3_TILES})", flush=True)
  491. def _ensure_b12x_nf3_runtime(self, layer, m, topk):
  492. """First-apply init: pinned-tile preplanned launches (per object) +
  493. ONE module-level scratch/buffer set. First apply = vLLM's eager
  494. profile run at max_num_batched_tokens, so max_m sizes itself to the
  495. real serving ceiling and nothing compiles during graph capture."""
  496. import dataclasses
  497. import torch
  498. from b12x.moe.fused.w4a16.kernel import compile_w4a16_fused_moe
  499. from b12x.moe.fused.w4a16.host import (
  500. make_w4a16_packed_buffers, max_packed_route_slots)
  501. hyb = layer.hyb
  502. st = _b12x_nf3_rt
  503. E, H, I = hyb["E"], hyb["H"], hyb["I"]
  504. dev = hyb["emap_a"].device
  505. if st["max_m"] is None:
  506. st["max_m"] = max(_B12X_NF3_MAX_TOKENS, int(m))
  507. st["topk"] = int(topk)
  508. if int(topk) != st["topk"]:
  509. raise RuntimeError(
  510. f"b12x_nf3: topk changed {st['topk']} -> {topk}")
  511. props = torch.cuda.get_device_properties(dev)
  512. sms = int(props.multi_processor_count)
  513. max_shared_mem = int(getattr(
  514. props, "shared_memory_per_block_optin", 101_376))
  515. def launches(prepared):
  516. key = (prepared.num_experts, prepared.weight_layout,
  517. prepared.scale_format, st["topk"], st["max_m"], H, I)
  518. got = st["launches"].get(key)
  519. if got is not None:
  520. return got
  521. common = dict(
  522. hidden_size=H, intermediate_size=I,
  523. num_experts=prepared.num_experts, top_k=st["topk"],
  524. activation="silu", apply_router_weight_on_input=False,
  525. element_dtype="bf16", fast_math=True, sms=sms,
  526. max_shared_mem=max_shared_mem,
  527. weight_layout=prepared.weight_layout,
  528. scale_format=prepared.scale_format,
  529. force_tile_config=_B12X_NF3_TILES)
  530. # ONE launch per object for ALL m (1..max_m): spec-0 (size_m>=2)
  531. # block-64 packed-route + expert_map + zero_fc2_output=True.
  532. # VALIDATED on real ckpt data at m=1/8/33 (rel 0.02-0.05).
  533. # TC-decode/block-8 and the size_m=1 compile are BROKEN for these
  534. # shapes+tiles (rel 1.4 / 197) — never use them. Decode padding
  535. # cost ~0: blocks exist only for ACTIVE experts; pad slots waste
  536. # ALU, not HBM bytes, and decode is bandwidth-bound.
  537. cap_slots = max_packed_route_slots(st["max_m"] * st["topk"], 64, E)
  538. pre = compile_w4a16_fused_moe(
  539. size_m=st["max_m"], zero_fc2_output=True,
  540. moe_block_size=64, max_m_blocks=(cap_slots + 63) // 64,
  541. direct_topk_routes=False, tc_decode_fused_sum=False,
  542. **common)
  543. assert (int(pre.fc1_tile_n), int(pre.fc2_tile_n)) == (
  544. _B12X_NF3_TILES[1], _B12X_NF3_TILES[3]), "tile pin failed"
  545. dec = pre
  546. if _HYBRID_TC_DECODE:
  547. # dec launch: SMALL size_m (8) direct-topk + fused-sum at FORCED
  548. # PIN tiles. Needs the op-boundary tile-config passthrough fix
  549. # (b12x_nf3_*_fix/kernel.py): the stock op re-resolves its kernel
  550. # WITHOUT force_tile_config, silently swapping PIN->auto so the
  551. # 256-pack was read with wrong geometry (garbage rel 1.3-1.5 at
  552. # m=1,6,8). Fixed kernel honors the forced PIN -> reads the SAME
  553. # production 256-pack (NO 2nd pack, NO pool cost). Opus-validated
  554. # rel 0.006-0.011 @ m=1..8, single-compile reuse worst 0.0099.
  555. # 0.277ms vs packed 0.365ms = ~14ms/step decode bounty.
  556. # Run side: LUT-mapped local ids, NO expert_map (direct-topk).
  557. try:
  558. cand = compile_w4a16_fused_moe(
  559. size_m=_B12X_NF3_DECODE_M, zero_fc2_output=False,
  560. moe_block_size=8,
  561. max_m_blocks=_B12X_NF3_DECODE_M * st["topk"],
  562. direct_topk_routes=True, tc_decode_fused_sum=True,
  563. **common) # common carries force_tile_config=_B12X_NF3_TILES
  564. assert (int(cand.fc1_tile_n), int(cand.fc2_tile_n)) == (
  565. _B12X_NF3_TILES[1], _B12X_NF3_TILES[3]), "tc tile pin failed"
  566. dec = cand
  567. print(f"[hybrid] tc-decode launch armed (force PIN) "
  568. f"E={prepared.num_experts} tiles="
  569. f"{int(cand.fc1_tile_n)},{int(cand.fc2_tile_n)}",
  570. flush=True)
  571. except Exception as _te:
  572. print(f"[hybrid] tc-decode compile failed, packed fallback: "
  573. f"{_te}", flush=True)
  574. st["launches"][key] = (dec, pre)
  575. return st["launches"][key]
  576. if hyb.get("prepA") is not None:
  577. hyb["launchA"] = launches(hyb["prepA"])
  578. if hyb.get("prepB") is not None:
  579. hyb["launchB"] = launches(hyb["prepB"])
  580. if st["buffers"] is None:
  581. prep_any = hyb.get("prepA") or hyb.get("prepB")
  582. if prep_any is None:
  583. # uniform-NVFP4 layer (MTP) first: no NF3 object anywhere yet;
  584. # the kept kernel manages its own workspace -> no buffers needed.
  585. hyb["_rt_ready"] = True
  586. return
  587. buf = make_w4a16_packed_buffers(
  588. prep_any, m=st["max_m"], topk=st["topk"],
  589. dtype=torch.bfloat16, device=dev, route_num_experts=E)
  590. # the preplanned prefill launch validates route capacity at
  591. # moe_block_size=64; the plan's own block choice can be smaller
  592. # for small max_m -> upsize the route buffers if needed.
  593. need_slots = max_packed_route_slots(st["max_m"] * st["topk"], 64, E)
  594. need_blocks = (need_slots + 63) // 64
  595. if (buf.packed_route_indices.numel() < need_slots
  596. or buf.block_expert_ids.numel() < need_blocks):
  597. buf = dataclasses.replace(
  598. buf,
  599. packed_route_indices=torch.empty(
  600. (need_slots,), dtype=torch.int32, device=dev),
  601. block_expert_ids=torch.empty(
  602. (need_blocks,), dtype=torch.int32, device=dev))
  603. st["buffers"] = buf
  604. st["out_a"] = buf.output # [max_m, H], fully overwritten per call
  605. st["out_b"] = torch.empty_like(buf.output)
  606. print(f"[hybrid] b12x_nf3 runtime ready: max_m={st['max_m']} "
  607. f"topk={st['topk']} sms={sms}", flush=True)
  608. hyb["_rt_ready"] = True
  609. def _apply_b12x_nf3(self, layer, x, topk_weights, topk_ids):
  610. import torch
  611. from b12x.moe.fused.w4a16.kernel import run_w4a16_moe
  612. hyb = layer.hyb
  613. st = _b12x_nf3_rt
  614. m = int(x.shape[0])
  615. topk = int(topk_ids.shape[1])
  616. if not hyb.get("_rt_ready"):
  617. self._ensure_b12x_nf3_runtime(layer, m, topk)
  618. if m > st["max_m"]:
  619. raise RuntimeError(
  620. f"b12x_nf3: m={m} exceeds planned capacity {st['max_m']}; "
  621. "set HYBRID_B12X_MAX_TOKENS >= max_num_batched_tokens")
  622. decode = m <= _B12X_NF3_DECODE_M
  623. if _HYBRID_ACT_CAPTURE and m > 8:
  624. try:
  625. from vllm.distributed import get_tensor_model_parallel_rank
  626. if get_tensor_model_parallel_rank() == 0:
  627. pfx = str(hyb.get("lname") or getattr(layer, "prefix", None)
  628. or id(layer)).replace("/", "_").replace(".", "_")
  629. b = _act_store["buf"].setdefault(pfx, {"x": [], "ids": []})
  630. if _act_store["flushed"].get(pfx, 0) < 32768: # cap tokens/layer
  631. b["x"].append(x.detach().to(torch.float16).cpu())
  632. b["ids"].append(topk_ids.detach().to(torch.int32).cpu())
  633. if sum(t.shape[0] for t in b["x"]) >= 8192:
  634. import os as _os
  635. _os.makedirs(_HYBRID_ACT_CAPTURE, exist_ok=True)
  636. fn = f"{_HYBRID_ACT_CAPTURE}/{pfx}.pt"
  637. prev = torch.load(fn) if _os.path.exists(fn) else {"x": [], "ids": []}
  638. prev["x"].append(torch.cat(b["x"]))
  639. prev["ids"].append(torch.cat(b["ids"]))
  640. torch.save(prev, fn)
  641. _act_store["flushed"][pfx] = _act_store["flushed"].get(pfx, 0) + sum(t.shape[0] for t in b["x"])
  642. b["x"], b["ids"] = [], []
  643. except Exception as _ce:
  644. if not _act_store.get("_warned"):
  645. _act_store["_warned"] = True
  646. print("[hybrid][act-capture] failed:", repr(_ce), flush=True)
  647. tw = (topk_weights if topk_weights.dtype == torch.float32
  648. else topk_weights.float())
  649. if not tw.is_contiguous():
  650. tw = tw.contiguous()
  651. buf = st["buffers"]
  652. def run(prepared, launch_pair, emap, out):
  653. use_dec = (_HYBRID_TC_DECODE and decode
  654. and launch_pair[0] is not launch_pair[1])
  655. launch = launch_pair[0] if use_dec else launch_pair[1]
  656. ids = (topk_ids if topk_ids.dtype == torch.int32
  657. else topk_ids.to(torch.int32))
  658. if not ids.is_contiguous():
  659. ids = ids.contiguous()
  660. if use_dec:
  661. # direct-topk path: kernel reads FLAT local ids and skips
  662. # negatives itself; expert_map must NOT be used. emap doubles
  663. # as the global->local LUT (graph-safe gather).
  664. ids = emap[ids.long()].to(torch.int32).contiguous()
  665. em = None
  666. else:
  667. em = emap # kernel translates global->local + drops -1 (zero=True pairs)
  668. return run_w4a16_moe(
  669. x, prepared, tw, ids,
  670. activation="silu",
  671. intermediate_cache13=buf.intermediate_cache13,
  672. intermediate_cache2=buf.intermediate_cache2,
  673. output=out,
  674. fc1_c_tmp=buf.fc1_c_tmp, fc2_c_tmp=buf.fc2_c_tmp,
  675. packed_route_indices=buf.packed_route_indices,
  676. block_expert_ids=buf.block_expert_ids,
  677. packed_route_count=buf.packed_route_count,
  678. expert_offsets=buf.expert_offsets,
  679. expert_map=em,
  680. fused_launch=launch)
  681. if hyb["Km"] == 0:
  682. # uniform-NVFP4 layer (MTP/nextn): single tier through OUR launcher
  683. out = torch.empty((m, hyb["H"]), dtype=x.dtype, device=x.device)
  684. out = run(hyb["prepA"], hyb["launchA"], hyb["emap_a"], out)
  685. if _HYBRID_DEBUG and not hyb.get("_dbg"):
  686. hyb["_dbg"] = True
  687. print(f"[dbg-nf3] {getattr(layer, 'prefix', '?')} m={m} "
  688. f"A-only={out.float().norm():.1f}", flush=True)
  689. return out
  690. out_a = out_b = None
  691. if _HYBRID_TIER == "a":
  692. out = run(hyb["prepA"], hyb["launchA"], hyb["emap_a"],
  693. st["out_a"][:m]).clone()
  694. elif _HYBRID_TIER == "b":
  695. out = run(hyb["prepB"], hyb["launchB"], hyb["emap_b"],
  696. st["out_b"][:m]).clone()
  697. else:
  698. prof = _HYBRID_PROFILE and m > 64
  699. if prof:
  700. import time as _time
  701. _w0 = _time.perf_counter()
  702. ev = [torch.cuda.Event(enable_timing=True) for _ in range(3)]
  703. ev[0].record()
  704. out_a = run(hyb["prepA"], hyb["launchA"], hyb["emap_a"],
  705. st["out_a"][:m])
  706. if prof:
  707. ev[1].record()
  708. out_b = run(hyb["prepB"], hyb["launchB"], hyb["emap_b"],
  709. st["out_b"][:m])
  710. if prof:
  711. ev[2].record()
  712. _prof_store["wall_ms"] = _prof_store.get("wall_ms", 0.0) + \
  713. (_time.perf_counter() - _w0) * 1000.0
  714. _prof_store["pend"].append((m, ev))
  715. _prof_store["n"] += 1
  716. if _prof_store["n"] % 624 == 0: # ~8 full 78-layer prefill passes
  717. try:
  718. torch.cuda.synchronize()
  719. for pm, pe in _prof_store["pend"]:
  720. _prof_store["a_ms"] += pe[0].elapsed_time(pe[1])
  721. _prof_store["b_ms"] += pe[1].elapsed_time(pe[2])
  722. _prof_store["rows"] += pm
  723. _prof_store["pend"] = []
  724. from vllm.distributed import get_tensor_model_parallel_rank
  725. if get_tensor_model_parallel_rank() == 0:
  726. n = _prof_store["n"]
  727. a = _prof_store["a_ms"] / n
  728. b = _prof_store["b_ms"] / n
  729. mavg = _prof_store["rows"] / n
  730. w = _prof_store.get("wall_ms", 0.0) / n
  731. print(f"[hybrid-prof] n={n} avg_m={mavg:.0f} "
  732. f"A(nvfp4)={a:.3f}ms B(nf3)={b:.3f}ms "
  733. f"wall={w:.3f}ms /layer-chunk "
  734. f"-> MoE-gpu {78*(a+b):.0f}ms MoE-wall {78*w:.0f}ms "
  735. f"per {mavg:.0f}-tok chunk",
  736. flush=True)
  737. except Exception as _pe:
  738. print("[hybrid-prof] harvest failed:", repr(_pe), flush=True)
  739. out = out_a + out_b
  740. if _HYBRID_DEBUG and not hyb.get("_dbg"):
  741. hyb["_dbg"] = True
  742. na = "-" if out_a is None else f"{out_a.float().norm():.1f}"
  743. nb = "-" if out_b is None else f"{out_b.float().norm():.1f}"
  744. print(f"[dbg-nf3] {getattr(layer, 'prefix', '?')} m={m} "
  745. f"decode={decode} tier={_HYBRID_TIER} x={x.float().norm():.1f} "
  746. f"A={na} B={nb} out={out.float().norm():.1f} "
  747. f"nan={bool(torch.isnan(out).any())}", flush=True)
  748. if getattr(layer, "w13_weight", None) is not None and \
  749. layer.w13_weight.numel() > 0:
  750. try:
  751. ra = self._apply_ref(layer, x, topk_weights, topk_ids, 0)
  752. rb = self._apply_ref(layer, x, topk_weights, topk_ids, 1)
  753. ea = float((out_a.float() - ra.float()).abs().sum()
  754. / (ra.float().abs().sum() + 1e-9))
  755. eb = float((out_b.float() - rb.float()).abs().sum()
  756. / (rb.float().abs().sum() + 1e-9))
  757. print(f"[dbgcmp-nf3] A_rel={ea:.4f} B_rel={eb:.4f}",
  758. flush=True)
  759. except Exception as _e:
  760. print("[dbgcmp-nf3] ref failed:", repr(_e), flush=True)
  761. return out
  762. def process_weights_after_loading(self, layer):
  763. if not hasattr(layer, "hyb"):
  764. return super().process_weights_after_loading(layer)
  765. if _HYBRID_KEPT == "b12x_nf3":
  766. try:
  767. self._build_b12x_nf3(layer)
  768. layer.hyb["_b12x_nf3"] = True
  769. layer.hyb["_b12x"] = False
  770. layer.hyb["_kept_triton"] = False
  771. layer.hyb["_fast"] = False
  772. return
  773. except Exception as e:
  774. import traceback
  775. print("[hybrid] b12x_nf3 build FAILED -> triton/ref "
  776. "fallback:", e, flush=True)
  777. traceback.print_exc()
  778. layer.hyb["_b12x_nf3"] = False
  779. # originals are freed only on success -> the stock flow
  780. # below still has everything it needs.
  781. E = layer.hyb["E"]
  782. dev = layer.w13_weight.device
  783. # --- NF3 tier: expert map (demoted global id -> local NF3 index, else -1) ---
  784. emap = torch.full((E,), -1, dtype=torch.int32, device=dev)
  785. for g, (grp, li) in layer.hyb["remap"].items():
  786. if grp == 1:
  787. emap[g] = li
  788. layer.hyb["emap_nf3"] = emap
  789. if _HYBRID_NF3 == "fast":
  790. try:
  791. import nf3_kernel # noqa: F401 (mounted alongside loader; warm import)
  792. layer.hyb["_fast"] = True
  793. except Exception as e:
  794. print("[hybrid] nf3_kernel import failed -> reference:", e, flush=True)
  795. layer.hyb["_fast"] = False
  796. else:
  797. layer.hyb["_fast"] = False
  798. # --- NVFP4 kept tier ---
  799. layer.hyb["_b12x"] = False
  800. layer.hyb["_kept_triton"] = False
  801. if _HYBRID_KEPT in ("triton", "b12x_nf3"):
  802. # Custom Triton NVFP4 grouped-GEMM: reads the checkpoint weights DIRECTLY
  803. # (no convert/prepare/copy), graph-safe. emap: kept global -> local, else -1.
  804. # ("b12x_nf3" reaches here ONLY after a failed b12x_nf3 build ->
  805. # fall back to the proven triton kept tier; originals intact.)
  806. kmap = torch.full((E,), -1, dtype=torch.int32, device=dev)
  807. for gid, (grp, li) in layer.hyb["remap"].items():
  808. if grp == 0:
  809. kmap[gid] = li
  810. layer.hyb["emap_nvfp4"] = kmap
  811. layer.hyb["g13"] = layer.w13_weight_scale_2[:, 0].contiguous()
  812. layer.hyb["g2"] = layer.w2_weight_scale_2.contiguous()
  813. try:
  814. import nvfp4_kernel # noqa: F401
  815. layer.hyb["_kept_triton"] = True
  816. except Exception as e:
  817. print("[hybrid] nvfp4_kernel import failed -> reference:", e, flush=True)
  818. elif _HYBRID_KEPT == "b12x":
  819. try:
  820. self._build_kept_b12x(layer)
  821. layer.hyb["_b12x"] = True
  822. except Exception as e:
  823. import traceback
  824. print("[hybrid] kept b12x build FAILED -> reference NVFP4:", e, flush=True)
  825. traceback.print_exc()
  826. def _apply_ref(self, layer, x, topk_weights, topk_ids, only_grp):
  827. H, I, gs = layer.hyb["H"], layer.hyb["I"], layer.hyb["gs"]
  828. remap = layer.hyb["remap"]
  829. out = torch.zeros_like(x)
  830. for e in torch.unique(topk_ids).tolist():
  831. if e < 0 or e not in remap:
  832. continue
  833. grp, li = remap[e]
  834. if grp != only_grp:
  835. continue
  836. if grp == 0:
  837. w13 = _deq_nv(layer.w13_weight[li], layer.w13_nv_s[li], layer.w13_weight_scale_2[li], gs, dequantize_to_dtype)
  838. w2 = _deq_nv(layer.w2_weight[li], layer.w2_nv_s[li], layer.w2_weight_scale_2[li], gs, dequantize_to_dtype)
  839. else:
  840. w13 = _unpack_nf3(layer.w13_weight_packed[li], layer.w13_nf_s[li], H)
  841. w2 = _unpack_nf3(layer.w2_weight_packed[li], layer.w2_nf_s[li], I)
  842. sel = (topk_ids == e)
  843. tok = sel.any(-1)
  844. if not tok.any():
  845. continue
  846. g, u = (x[tok] @ w13.t()).chunk(2, -1)
  847. y = (F.silu(g) * u) @ w2.t()
  848. wgt = (topk_weights * sel).sum(-1)[tok].unsqueeze(-1).to(y.dtype)
  849. out[tok] += wgt * y
  850. return out
  851. def apply(self, layer, x, topk_weights, topk_ids,
  852. shared_experts=None, shared_experts_input=None):
  853. # routed experts only; shared experts + routing handled by the runner.
  854. if not hasattr(layer, "hyb"):
  855. return super().apply(layer, x, topk_weights, topk_ids,
  856. shared_experts, shared_experts_input)
  857. if layer.hyb.get("_b12x_nf3"):
  858. return self._apply_b12x_nf3(layer, x, topk_weights, topk_ids)
  859. H, I, E = layer.hyb["H"], layer.hyb["I"], layer.hyb["E"]
  860. # --- NVFP4 kept tier ---
  861. if layer.hyb.get("_kept_triton"):
  862. import nvfp4_kernel
  863. kept_out = nvfp4_kernel.nvfp4_moe_layer(
  864. x, layer.w13_weight, layer.w13_nv_s, layer.hyb["g13"],
  865. layer.w2_weight, layer.w2_nv_s, layer.hyb["g2"],
  866. topk_ids, topk_weights, layer.hyb["emap_nvfp4"], E, H, I, 16)
  867. elif layer.hyb.get("_b12x"):
  868. kk = layer.hyb["kept_kernel"]
  869. KL = layer.hyb["kept_KL"]
  870. Kn = layer.hyb["Kn"]
  871. kept_ids = layer.hyb["nvfp4_remap"][topk_ids] # kept->[0,Kn), else Kn (b12x skips)
  872. kept_out = kk.apply(
  873. x, KL.w13_weight, KL.w2_weight, topk_weights, kept_ids,
  874. activation=KL.activation, global_num_experts=Kn, expert_map=None,
  875. apply_router_weight_on_input=False,
  876. shared_experts=None, shared_experts_input=None)
  877. else:
  878. kept_out = self._apply_ref(layer, x, topk_weights, topk_ids, 0)
  879. # --- NF3 tier (192 experts) ---
  880. if layer.hyb.get("_fast"):
  881. import nf3_kernel
  882. nf3_out = nf3_kernel.nf3_moe_layer(
  883. x, layer.w13_weight_packed, layer.w13_nf_s,
  884. layer.w2_weight_packed, layer.w2_nf_s,
  885. topk_ids, topk_weights, layer.hyb["emap_nf3"], E, 32, H, I)
  886. else:
  887. nf3_out = self._apply_ref(layer, x, topk_weights, topk_ids, 1)
  888. out = kept_out + nf3_out
  889. if _HYBRID_DEBUG and not layer.hyb.get("_dbg"):
  890. layer.hyb["_dbg"] = True
  891. print(f"[dbg] {getattr(layer, 'prefix', '?')} "
  892. f"x={x.float().norm():.1f} kept={kept_out.float().norm():.1f} "
  893. f"nf3={nf3_out.float().norm():.1f} out={out.float().norm():.1f} "
  894. f"nan={bool(torch.isnan(out).any())}", flush=True)
  895. if layer.hyb.get("_b12x") and hasattr(layer, "w13_weight"):
  896. try:
  897. rk = self._apply_ref(layer, x, topk_weights, topk_ids, 0)
  898. ratio = float(kept_out.float().norm() / (rk.float().norm() + 1e-9))
  899. erel = float((kept_out.float() - rk.float()).abs().sum()
  900. / (rk.float().abs().sum() + 1e-9))
  901. print(f"[dbgcmp] {getattr(layer, 'prefix', '?')} "
  902. f"b12x={kept_out.float().norm():.3f} ref={rk.float().norm():.3f} "
  903. f"ratio={ratio:.3f} elem_rel={erel:.3f}", flush=True)
  904. except Exception as _e:
  905. print("[dbgcmp] ref failed:", repr(_e), flush=True)
  906. return out
  907. mod.ModelOptNvFp4Config.FusedMoEMethodCls = HybridNvFp4MoE
  908. print("[hybrid_loader] HybridNvFp4MoE installed", flush=True)
  909. # ---- fp8 non-expert dequant-on-load (weight_scale_fp8 modules are in `ignore` -> bf16) ----
  910. try:
  911. from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
  912. if not getattr(DefaultModelLoader, "_hyb_wrapped", False):
  913. _oga = DefaultModelLoader.get_all_weights
  914. def get_all_weights(self, model_config, model):
  915. return _fp8_ne_transform(_oga(self, model_config, model))
  916. DefaultModelLoader.get_all_weights = get_all_weights
  917. DefaultModelLoader._hyb_wrapped = True
  918. print("[hybrid_loader] fp8 non-expert dequant-on-load installed", flush=True)
  919. except Exception as e:
  920. print("[hybrid_loader] wrap get_all_weights failed:", e, flush=True)
  921. # ---- 3. HYBRID_MXFP8_NATIVE: serve the F8 ne-tier via online-mxfp8 ----
  922. # Instead of dequant-to-bf16 (2 B/param resident), excluded LinearBase
  923. # modules whose disk tensors are F8 get Mxfp8OnlineLinearMethod: loader
  924. # still feeds them our dequanted bf16, the method re-quantizes e8m0/32 at
  925. # load (bit-exact round trip vs rev-3 disk) and serves through the B12X
  926. # fp8 GEMM (VLLM_USE_B12X_FP8_GEMM=1 forces B12xMxfp8LinearKernel).
  927. # Net: ~-3.65 GiB/GPU weights. Activations on these linears become
  928. # dynamic-A8 (Festr serving convention) — KLD re-gate before publishing.
  929. if os.environ.get("HYBRID_MXFP8_NATIVE", "0") == "1":
  930. try:
  931. import json as _json
  932. from vllm.model_executor.layers.quantization.online.mxfp8 import (
  933. Mxfp8OnlineLinearMethod)
  934. from vllm.model_executor.layers.linear import (
  935. LinearBase, UnquantizedLinearMethod)
  936. from vllm.model_executor.layers.vocab_parallel_embedding import (
  937. ParallelLMHead)
  938. _tier_path = os.environ.get(
  939. "HYBRID_MXFP8_TIER_JSON",
  940. "/opt/venv/lib/python3.12/site-packages/mxfp8_tier.json")
  941. _allow = set(_json.load(open(_tier_path))["module_prefixes"])
  942. def _mx_norm(p):
  943. i = p.find("layers.")
  944. return p[i:] if i >= 0 else p
  945. _base = mod.ModelOptQuantConfigBase
  946. if not getattr(_base, "_hyb_mxfp8_overlay", False):
  947. _orig_gqm = _base.get_quant_method
  948. _n_over = [0]
  949. def get_quant_method(self, layer, prefix):
  950. m = _orig_gqm(self, layer, prefix)
  951. if (type(m) is UnquantizedLinearMethod
  952. and isinstance(layer, LinearBase)
  953. and not isinstance(layer, ParallelLMHead)
  954. and _mx_norm(prefix) in _allow):
  955. _n_over[0] += 1
  956. if _n_over[0] <= 4 or _n_over[0] % 128 == 0:
  957. print(f"[hybrid_loader] mxfp8 overlay #{_n_over[0]}: "
  958. f"{prefix}", flush=True)
  959. return Mxfp8OnlineLinearMethod()
  960. return m
  961. _base.get_quant_method = get_quant_method
  962. _base._hyb_mxfp8_overlay = True
  963. print(f"[hybrid_loader] mxfp8 ne-tier overlay armed "
  964. f"({len(_allow)} module prefixes)", flush=True)
  965. except Exception as e:
  966. import traceback
  967. print("[hybrid_loader] mxfp8 overlay FAILED:", e, flush=True)
  968. traceback.print_exc()
  969. class _Hook(importlib.abc.MetaPathFinder):
  970. T = "vllm.model_executor.layers.quantization.modelopt"
  971. def find_spec(self, name, path, target=None):
  972. if name == self.T and not getattr(self, "_d", False):
  973. self._d = True
  974. spec = importlib.util.find_spec(name)
  975. if spec:
  976. real_exec = spec.loader.exec_module
  977. def ex(module):
  978. real_exec(module)
  979. try:
  980. _patch(module)
  981. except Exception as e:
  982. import traceback
  983. print("[hybrid_loader] patch FAILED:", e, flush=True)
  984. traceback.print_exc()
  985. spec.loader.exec_module = ex
  986. return spec
  987. return None
  988. if not any(isinstance(f, _Hook) for f in sys.meta_path):
  989. sys.meta_path.insert(0, _Hook())
  990. print("[hybrid_loader] import hook armed", flush=True)