wo_a_w8.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. """wo_a from its fp8 checkpoint bytes instead of the bf16 copy, same Triton tiling. Gated on DSV41_WO_A_W8.
  2. The checkpoint ships wo_a as e4m3 with 32x32 ue8m0 block scales. The engine dequantizes it to bf16
  3. at load (its fp8 absorb path needs 128x128 blocks) and the verify/draft small-batch kernel
  4. `_wo_a_partial` then streams 16.8 MB per layer and rank. This adapter keeps an fp8 twin of every
  5. wo_a (e4m3 values plus one exponent per 32x32 block), built after load and kept only when it
  6. reconstructs the bf16 weight exactly, and runs a copy of `_wo_a_partial` that loads the e4m3 tile
  7. and its exponent and rebuilds the same bf16 tile in registers before the same `tl.dot`.
  8. Half the weight bytes for the same operand values. Measured on GB10 (TP4, G=2, 43 layers, M=6):
  9. 3.43 -> 2.20 ms; the MXFP8-epilogue path (what production runs) is bitwise identical to the stock
  10. kernel; the plain bf16 path differs only in the MMA's fp32 accumulation order (1 bf16 ulp on
  11. 0.016 % of outputs). Only 2 <= M <= 8 rows take the new kernel (verify at bs=1, the draft block);
  12. every other shape and any layer whose twin is not exact stays on the stock path.
  13. The group count is the local o_groups shard: 2 at TP4, 4 at padded TP3 (o_groups 8→12). dev-dsv41
  14. has no wo_a_bf16 small-batch module; there the same kernels are dispatched from
  15. _apply_wo_a_bf16_matmul, and rows 9..192 run only while a decode or verify forward is on the stack
  16. so a short prefill stays on einsum.
  17. DSV41_WO_A_W8_MID=1 (with DSV41_WO_A_W8=1): verify/draft calls with 9..192 rows (c2..c32) also read
  18. the twin, in a second Triton kernel tuned on GB10 with DRAM-resident weights (bf16 bmm 84-134 us per
  19. layer at 12-96 rows, the twin 49-74 us); prefill keeps the stock kernel. Split-K partials are summed
  20. in a fixed order, so the result is deterministic.
  21. DSV41_WO_A_W8_DROP=1: after the twins exist and a dequantized copy was checked bit-identical, the
  22. bf16 storage of every twinned wo_a is released (16.8 MB per layer: 722 MB per rank incl. the draft,
  23. which the head node returns to the KV pool); every row count is then served from the twin, prefill
  24. through a per-call dequantized bf16 buffer, so its numerics are unchanged.
  25. """
  26. import os
  27. import torch
  28. import triton
  29. import triton.language as tl
  30. ENABLED = os.environ.get("DSV41_WO_A_W8", "0").strip() not in ("0", "", "off", "false")
  31. _TWINS = {} # bf16 weight data_ptr -> (e4m3 [G,1024,4096], exponent uint8 [G,32,128])
  32. _CTX = {"spec": False} # True during decode / target-verify / draft-extend forwards
  33. def _wo_a_view(w):
  34. """Local wo_a as [G, 1024, 4096]. G is 2 at TP4 and 4 when TP3 pads o_groups 8→12."""
  35. if w is None or w.dtype != torch.bfloat16:
  36. return None
  37. if w.dim() == 3 and tuple(w.shape[1:]) == (1024, 4096) and 1 <= w.shape[0] <= 8:
  38. return w if w.is_contiguous() else w.contiguous()
  39. row = 1024 * 4096
  40. if w.numel() % row == 0:
  41. g = w.numel() // row
  42. if 1 <= g <= 8:
  43. return w.data.view(g, 1024, 4096)
  44. return None
  45. @triton.jit
  46. def _wo_a_partial_w8(X, W8, S, P, M: tl.constexpr, SX: tl.constexpr, G: tl.constexpr):
  47. tile, group, split = tl.program_id(0), tl.program_id(1), tl.program_id(2)
  48. m = tl.arange(0, 16)
  49. n = tile * 64 + tl.arange(0, 64)
  50. k = split * 512 + tl.arange(0, 128)
  51. acc = tl.zeros((16, 64), tl.float32)
  52. for i in range(4):
  53. offsets = k + i * 128
  54. x = tl.load(
  55. X + m[:, None] * SX + group * 4096 + offsets[None, :], m[:, None] < M, 0
  56. )
  57. w8 = tl.load(W8 + (group * 1024 + n[None, :]) * 4096 + offsets[:, None])
  58. e = tl.load(S + (group * 32 + n[None, :] // 32) * 128 + offsets[:, None] // 32)
  59. w = (w8.to(tl.float32) * tl.exp2(e.to(tl.float32) - 127.0)).to(tl.bfloat16)
  60. acc += tl.dot(x, w)
  61. tl.store(
  62. P + ((split * M + m[:, None]) * G + group) * 1024 + n[None, :],
  63. acc,
  64. m[:, None] < M,
  65. )
  66. def make_twin(w: torch.Tensor):
  67. """bf16 [2, 1024, 4096] -> (e4m3, exponent) or None if the twin does not reproduce w exactly."""
  68. g, r, d = w.shape
  69. wf = w.float().view(g, r // 32, 32, d // 32, 32)
  70. amax = wf.abs().amax(dim=(2, 4)).clamp_min(2.0 ** -126)
  71. e = torch.ceil(torch.log2(amax / 448.0)).clamp(-127, 127)
  72. scale = torch.exp2(e)[:, :, None, :, None]
  73. q = (wf / scale).to(torch.float8_e4m3fn)
  74. back = (q.float() * scale).to(torch.bfloat16).view(g, r, d)
  75. if not torch.equal(back, w):
  76. return None
  77. return q.view(g, r, d).contiguous(), (e + 127).to(torch.uint8).contiguous()
  78. def _partial_w8(x, twin, m):
  79. w8, s = twin
  80. g = w8.shape[0]
  81. partial = torch.empty((8, m, g, 1024), dtype=torch.float32, device=x.device)
  82. _wo_a_partial_w8[(16, g, 8)](x, w8, s, partial, m, x.stride(0), g, num_warps=4, num_stages=3)
  83. return partial
  84. def _fp8_small(x, twin):
  85. return _partial_w8(x, twin, x.shape[0]).sum(dim=0).to(torch.bfloat16)
  86. MID = os.environ.get("DSV41_WO_A_W8_MID", "0").strip() not in ("0", "", "off", "false")
  87. # tuned on GB10 with 43 distinct layers (DRAM-bound, not L2): rows -> (split_k, BM, BN, warps)
  88. _MID_CFG = ((16, (4, 16, 64, 8)), (32, (2, 32, 32, 4)), (64, (1, 64, 32, 4)), (128, (1, 32, 64, 4)),
  89. (192, (1, 64, 32, 4)))
  90. @triton.jit
  91. def _wo_a_mid_kernel(X, W8, S, Y, M, SXM, SPLIT_K: tl.constexpr, BM: tl.constexpr, BN: tl.constexpr,
  92. BK: tl.constexpr, G: tl.constexpr):
  93. pid_n = tl.program_id(0)
  94. g = tl.program_id(1)
  95. pid_mk = tl.program_id(2)
  96. pid_m = pid_mk // SPLIT_K
  97. pid_k = pid_mk % SPLIT_K
  98. m = pid_m * BM + tl.arange(0, BM)
  99. n = pid_n * BN + tl.arange(0, BN)
  100. KC: tl.constexpr = 4096 // SPLIT_K
  101. acc = tl.zeros((BM, BN), tl.float32)
  102. for k0 in range(pid_k * KC, (pid_k + 1) * KC, BK):
  103. k = k0 + tl.arange(0, BK)
  104. x = tl.load(X + m[:, None] * SXM + g * 4096 + k[None, :], m[:, None] < M, 0.0)
  105. w8 = tl.load(W8 + g * 1024 * 4096 + n[None, :] * 4096 + k[:, None])
  106. e = tl.load(S + g * 32 * 128 + (n[None, :] // 32) * 128 + k[:, None] // 32)
  107. w = (w8.to(tl.float32) * tl.exp2(e.to(tl.float32) - 127.0)).to(tl.bfloat16)
  108. acc += tl.dot(x, w)
  109. row: tl.constexpr = G * 1024
  110. if SPLIT_K == 1:
  111. tl.store(Y + m[:, None] * row + g * 1024 + n[None, :], acc.to(tl.bfloat16), m[:, None] < M)
  112. else:
  113. # per-split partials, summed in a fixed order afterwards (atomics made the order random)
  114. tl.store(Y + pid_k * M * row + m[:, None] * row + g * 1024 + n[None, :], acc, m[:, None] < M)
  115. def wo_a_mid(o, twin):
  116. """o [M, G, 4096] (stride (>=G*4096, 4096, 1)) bf16, 9 <= M <= 192 -> bf16 [M, G, 1024]."""
  117. w8, s = twin
  118. m = o.shape[0]
  119. g = w8.shape[0]
  120. sk, bm, bn, warps = next(cfg for lim, cfg in _MID_CFG if m <= lim)
  121. grid = (1024 // bn, g, triton.cdiv(m, bm) * sk)
  122. if sk == 1:
  123. y = torch.empty((m, g, 1024), dtype=torch.bfloat16, device=o.device)
  124. _wo_a_mid_kernel[grid](o, w8, s, y, m, o.stride(0), 1, bm, bn, 128, g, num_warps=warps, num_stages=3)
  125. return y
  126. y = torch.empty((sk, m, g, 1024), dtype=torch.float32, device=o.device)
  127. _wo_a_mid_kernel[grid](o, w8, s, y, m, o.stride(0), sk, bm, bn, 128, g, num_warps=warps, num_stages=3)
  128. return y.sum(dim=0).to(torch.bfloat16)
  129. DROP = os.environ.get("DSV41_WO_A_W8_DROP", "0").strip() not in ("0", "", "off", "false")
  130. _REFS = {}
  131. _DROPPED = set() # tag data_ptrs of wo_a weights whose bf16 storage was released
  132. @triton.jit
  133. def _dequant_kernel(W8, S, OUT):
  134. g = tl.program_id(0)
  135. rb = tl.program_id(1)
  136. cb = tl.program_id(2)
  137. r = rb * 32 + tl.arange(0, 32)
  138. c = cb * 32 + tl.arange(0, 32)
  139. off = g * 1024 * 4096 + r[:, None] * 4096 + c[None, :]
  140. e = tl.load(S + g * 32 * 128 + rb * 128 + cb)
  141. w = tl.load(W8 + off).to(tl.float32) * tl.exp2(e.to(tl.float32) - 127.0)
  142. tl.store(OUT + off, w.to(tl.bfloat16))
  143. def dequant_into_scratch(twin, device):
  144. # a fresh buffer per call: the caching allocator orders reuse per stream, so target and draft
  145. # work on different streams never share it (one shared scratch raced)
  146. w8, s = twin
  147. g, r, d = w8.shape
  148. buf = torch.empty((g, r, d), dtype=torch.bfloat16, device=device)
  149. _dequant_kernel[(g, r // 32, d // 32)](w8, s, buf)
  150. return buf
  151. def drop_bf16(model):
  152. """After the twins exist: release the bf16 storage of every exactly-twinned wo_a. The weight
  153. becomes a zero-stride view of a 1-element tag (unique data_ptr, correct shape), and every call
  154. is served from the twin by the wrapper in install_mid."""
  155. freed = mismatched = 0
  156. freed_bytes = 0
  157. for mod in model.modules():
  158. wo_a = getattr(mod, "wo_a", None)
  159. w = getattr(wo_a, "weight", None)
  160. w3 = _wo_a_view(w)
  161. if w3 is None:
  162. continue
  163. old_ptr = w3.data_ptr()
  164. twin = _TWINS.get(old_ptr)
  165. if twin is None:
  166. continue
  167. if not torch.equal(dequant_into_scratch(twin, w.device), w3):
  168. mismatched += 1
  169. continue # the stock path must see the identical bf16 weight
  170. del _TWINS[old_ptr] # the freed address may be reused by another tensor
  171. tag = torch.zeros(1, dtype=torch.bfloat16, device=w.device)
  172. w.data = tag.as_strided(tuple(w.shape), (0,) * w.dim())
  173. _TWINS[tag.data_ptr()] = twin
  174. _DROPPED.add(tag.data_ptr())
  175. freed += 1
  176. freed_bytes += w3.numel() * w3.element_size()
  177. torch.cuda.empty_cache()
  178. print(f"[wo_a_w8] released the bf16 copy of {freed} wo_a weights ({freed_bytes / 2**20:.0f} MB); "
  179. f"{mismatched} kept (dequantized copy not bit-identical)", flush=True)
  180. def install_mid(dsv4_module):
  181. """deepseek_v4._apply_wo_a_bf16_matmul: 9..192 rows (verify/draft at c2..c32) from the fp8 twin."""
  182. if not (ENABLED and (MID or DROP)) or getattr(dsv4_module, "_dsv41_wo_a_mid", False):
  183. return
  184. dsv4_module._dsv41_wo_a_mid = True
  185. orig = dsv4_module._apply_wo_a_bf16_matmul
  186. swizzled = getattr(dsv4_module, "Mxfp8SwizzledInput", None)
  187. def _shape_ok(o, twin):
  188. g = twin[0].shape[0]
  189. return (o.dtype == torch.bfloat16 and o.ndim == 3 and tuple(o.shape[1:]) == (g, 4096)
  190. and o.stride(2) == 1 and o.stride(1) == 4096 and o.stride(0) >= g * 4096)
  191. def _dropped_path(o, wo_a, twin, m, a, kw):
  192. std = _shape_ok(o, twin)
  193. if std and 2 <= m <= 8:
  194. p = _partial_w8(o, twin, m)
  195. if kw.get("fuse_mxfp8_quant", False) and swizzled is not None:
  196. return swizzled(*_REFS["quantize_partial"](p))
  197. result = torch.empty((m, p.shape[2], 1024), dtype=o.dtype, device=o.device)
  198. _REFS["reduce_k"][(triton.cdiv(m * p.shape[2] * 1024, 256),)](p, result, m * p.shape[2] * 1024, num_warps=4)
  199. return result
  200. if std and 9 <= m <= 192 and kw.get("is_target_verify", False) and not kw.get("is_prefill", False):
  201. return wo_a_mid(o, twin)
  202. return orig(o, dequant_into_scratch(twin, o.device), *a, **kw) # bit-identical bf16 weight
  203. def _apply_wo_a_bf16_matmul(o, wo_a, *a, **kw):
  204. m = o.shape[0]
  205. if _DROPPED and wo_a.data_ptr() in _DROPPED:
  206. return _dropped_path(o, wo_a, _TWINS[wo_a.data_ptr()], m, a, kw)
  207. if not MID:
  208. return orig(o, wo_a, *a, **kw)
  209. # verify/draft graphs only: prefill keeps the stock kernel (identical prefill numerics)
  210. if 9 <= m <= 192 and kw.get("is_target_verify", False) and not kw.get("is_prefill", False):
  211. twin = _TWINS.get(wo_a.data_ptr())
  212. if twin is not None and _shape_ok(o, twin):
  213. return wo_a_mid(o, twin)
  214. return orig(o, wo_a, *a, **kw)
  215. dsv4_module._apply_wo_a_bf16_matmul = _apply_wo_a_bf16_matmul
  216. print("[wo_a_w8] mid path armed: 9..192 rows from the fp8 twin", flush=True)
  217. def _patch_kernels(dsv4_module, kernel_module):
  218. for name in ("wo_a_bf16_small_batch", "wo_a_bf16_small_batch_mxfp8", "_wo_a_reduce", "_quantize_partial"):
  219. if not hasattr(kernel_module, name):
  220. raise RuntimeError(f"DSV41_WO_A_W8: {kernel_module.__name__}.{name} is gone; engine drifted")
  221. orig_small = kernel_module.wo_a_bf16_small_batch
  222. orig_mx = kernel_module.wo_a_bf16_small_batch_mxfp8
  223. reduce_k = kernel_module._wo_a_reduce
  224. quantize_partial = kernel_module._quantize_partial
  225. _REFS["reduce_k"], _REFS["quantize_partial"] = reduce_k, quantize_partial
  226. def small(x, weight):
  227. twin = _TWINS.get(weight.data_ptr())
  228. if twin is None:
  229. return orig_small(x, weight)
  230. m = x.shape[0]
  231. partial = _partial_w8(x, twin, m)
  232. g = partial.shape[2]
  233. result = torch.empty((m, g, 1024), dtype=x.dtype, device=x.device)
  234. reduce_k[(triton.cdiv(m * g * 1024, 256),)](partial, result, m * g * 1024, num_warps=4)
  235. return result
  236. def small_mx(x, weight):
  237. twin = _TWINS.get(weight.data_ptr())
  238. if twin is None:
  239. return orig_mx(x, weight)
  240. return quantize_partial(_partial_w8(x, twin, x.shape[0]))
  241. kernel_module.wo_a_bf16_small_batch = small
  242. kernel_module.wo_a_bf16_small_batch_mxfp8 = small_mx
  243. # deepseek_v4 imported both names into its own namespace
  244. if getattr(dsv4_module, "wo_a_bf16_small_batch", None) is not orig_small or \
  245. getattr(dsv4_module, "wo_a_bf16_small_batch_mxfp8", None) is not orig_mx:
  246. raise RuntimeError("DSV41_WO_A_W8: deepseek_v4 no longer imports the wo_a small-batch kernels by name")
  247. dsv4_module.wo_a_bf16_small_batch = small
  248. dsv4_module.wo_a_bf16_small_batch_mxfp8 = small_mx
  249. def build_twins(model: torch.nn.Module) -> tuple[int, int]:
  250. made = kept = 0
  251. for mod in model.modules():
  252. w = getattr(getattr(mod, "wo_a", None), "weight", None)
  253. w3 = _wo_a_view(w)
  254. if w3 is None:
  255. continue
  256. twin = make_twin(w3)
  257. if twin is None:
  258. kept += 1
  259. continue
  260. _TWINS[w3.data_ptr()] = twin
  261. made += 1
  262. print(f"[wo_a_w8] fp8 twins for {made} wo_a weights, {kept} left on bf16", flush=True)
  263. return made, kept
  264. def _wrap_load(cls, drop=None):
  265. if getattr(cls, "_dsv41_wo_a_w8", False):
  266. return
  267. cls._dsv41_wo_a_w8 = True
  268. orig = cls.load_weights
  269. do_drop = DROP if drop is None else drop
  270. def load_weights(self, *a, **kw):
  271. out = orig(self, *a, **kw)
  272. build_twins(self)
  273. if do_drop:
  274. drop_bf16(self)
  275. return out
  276. cls.load_weights = load_weights
  277. def _spec_forward(orig):
  278. def forward(self, input_ids, positions, forward_batch, *a, **kw):
  279. mode = forward_batch.forward_mode
  280. prev = _CTX["spec"]
  281. _CTX["spec"] = bool(
  282. mode.is_target_verify() or mode.is_decode() or mode.is_draft_extend_v2()
  283. )
  284. try:
  285. return orig(self, input_ids, positions, forward_batch, *a, **kw)
  286. finally:
  287. _CTX["spec"] = prev
  288. return forward
  289. def _std_activation(o, twin):
  290. g = twin[0].shape[0]
  291. return (o.dtype == torch.bfloat16 and o.ndim == 3 and tuple(o.shape[1:]) == (g, 4096)
  292. and tuple(twin[0].shape) == (g, 1024, 4096)
  293. and o.stride(2) == 1 and o.stride(1) == 4096 and o.stride(0) >= g * 4096)
  294. def _install_einsum_bridge(dsv4_module):
  295. """dev-dsv41 dispatches wo_a from _apply_wo_a_bf16_matmul (gemv at M=1, einsum otherwise)."""
  296. if getattr(dsv4_module, "_dsv41_wo_a_bridge", False):
  297. return
  298. dsv4_module._dsv41_wo_a_bridge = True
  299. orig = dsv4_module._apply_wo_a_bf16_matmul
  300. def _apply(o, wo_a, is_decode=False):
  301. ptr = wo_a.data_ptr()
  302. twin = _TWINS.get(ptr)
  303. m = o.shape[0] if o.ndim == 3 else 0
  304. if twin is not None and _std_activation(o, twin) and 2 <= m <= 8:
  305. return _fp8_small(o, twin)
  306. if twin is not None and MID and _CTX["spec"] and _std_activation(o, twin) and 9 <= m <= 192:
  307. return wo_a_mid(o, twin)
  308. if ptr in _DROPPED and twin is not None:
  309. return orig(o, dequant_into_scratch(twin, o.device), is_decode)
  310. return orig(o, wo_a, is_decode)
  311. dsv4_module._apply_wo_a_bf16_matmul = _apply
  312. cls = dsv4_module.DeepseekV4ForCausalLM
  313. if not getattr(cls, "_dsv41_wo_a_spec", False) and hasattr(cls, "forward"):
  314. cls._dsv41_wo_a_spec = True
  315. cls.forward = _spec_forward(cls.forward)
  316. print("[wo_a_w8] einsum bridge armed: fp8 twin for verify/draft rows, gemv left at M=1", flush=True)
  317. # DSV41_WO_A_W8_DRAFT=1 (bridge mode only): the draft's own wo_a einsum reads an fp8 twin as well
  318. # (its bf16 weight is kept; +16 MB per draft layer).
  319. DRAFT = os.environ.get("DSV41_WO_A_W8_DRAFT", "0").strip() not in ("0", "", "off", "false")
  320. def _install_draft_einsum(dspark_module):
  321. """Route the draft attention's inline torch.einsum("bgd,grd->bgr", o, wo_a) through the twin.
  322. The draft calls einsum through its module's global `torch`; that name is replaced by a proxy
  323. module that forwards every attribute to torch and intercepts only this equation when the weight
  324. has a twin and the activation has the standard layout (2..8 rows small kernel, 9..192 MID).
  325. Anything else, including the fp32 fallback path, goes to torch.einsum unchanged."""
  326. import types
  327. real = dspark_module.torch
  328. if getattr(real, "_dsv41_wo_a_proxy", False):
  329. return
  330. class _TorchProxy(types.ModuleType):
  331. _dsv41_wo_a_proxy = True
  332. def __getattr__(self, name):
  333. return getattr(real, name)
  334. proxy = _TorchProxy("torch")
  335. def einsum(eq, *ops, **kw):
  336. if eq == "bgd,grd->bgr" and len(ops) == 2 and not kw:
  337. o, w = ops
  338. twin = _TWINS.get(w.data_ptr()) if w.dtype == torch.bfloat16 else None
  339. if twin is not None and _std_activation(o, twin):
  340. m = o.shape[0]
  341. if 2 <= m <= 8:
  342. return _fp8_small(o, twin)
  343. if MID and 9 <= m <= 192:
  344. return wo_a_mid(o, twin)
  345. return real.einsum(eq, *ops, **kw)
  346. proxy.einsum = einsum
  347. dspark_module.torch = proxy
  348. print("[wo_a_w8] draft einsum routed to the fp8 twin (bf16 weight kept)", flush=True)
  349. def install_model(dsv4_module):
  350. """sglang.srt.models.deepseek_v4 (target model; also owns the wo_a dispatch the draft uses)."""
  351. if not ENABLED:
  352. return
  353. import importlib
  354. try:
  355. kern = importlib.import_module("sglang.kernels.ops.attention.dsv4.wo_a_bf16")
  356. except ModuleNotFoundError:
  357. kern = None
  358. if kern is not None and hasattr(kern, "wo_a_bf16_small_batch"):
  359. _patch_kernels(dsv4_module, kern)
  360. install_mid(dsv4_module)
  361. else:
  362. _install_einsum_bridge(dsv4_module)
  363. _CTX["bridge"] = True
  364. _wrap_load(dsv4_module.DeepseekV4ForCausalLM)
  365. def install_dspark(dspark_module):
  366. """sglang.srt.models.deepseek_v4_dspark: twins for the draft's three wo_a as well."""
  367. if not ENABLED:
  368. return
  369. if _CTX.get("bridge"):
  370. # dev-dsv41's draft attention runs its own einsum on self.wo_a.weight and never reaches
  371. # _apply_wo_a_bf16_matmul: DROP would feed it the 1-element tag (every draft garbage,
  372. # acceptance 1.0 -- measured 2026-09-24 on TP3), so the draft is never dropped here.
  373. if DRAFT:
  374. _wrap_load(dspark_module.DeepseekV4ForCausalLMDSpark, drop=False)
  375. _install_draft_einsum(dspark_module)
  376. else:
  377. print("[wo_a_w8] draft wo_a left on bf16 (this engine's draft does not use the shared dispatch)",
  378. flush=True)
  379. return
  380. cls = dspark_module.DeepseekV4ForCausalLMDSpark
  381. _wrap_load(cls)
  382. if not getattr(cls, "_dsv41_wo_a_spec", False) and hasattr(cls, "forward"):
  383. cls._dsv41_wo_a_spec = True
  384. cls.forward = _spec_forward(cls.forward)