| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- """The DSpark draft's LM head from an fp8 copy of the (shared) target LM head. Default OFF.
- TP4 variant of adapter/draft_head_fp8.py, selected with DSV41_DRAFT_HEAD_FP8=1 plus
- DSV41_DRAFT_HEAD_FP8_IMPL=tp4 (the TP4 production profile). Same fp8 copy, row tiles sized to the
- batch (see _TILES below); without the IMPL switch adapter/draft_head_fp8.py is used unchanged.
- DSV41_DRAFT_HEAD_FP8=1. The draft borrows the target's vocab-parallel lm_head (bf16, 32320 x 5120
- per rank at TP4) and reads it every step for its 5 block rows. Only the DRAFT gets the fp8 copy
- (e4m3 + one power-of-two scale per 32x32 block): the target's logits, and therefore every accepted
- token, are untouched. Offline the draft's acceptance moved 3.0882 -> 3.0875 tokens/step.
- """
- import os
- import torch
- import triton
- import triton.language as tl
- ENABLED = os.environ.get("DSV41_DRAFT_HEAD_FP8", "0").strip() not in ("0", "", "off", "false")
- # Draft head rows = DSPARK_BLOCK_SIZE (5) x decode bs: 5..80 on the bs <= 16 graph list. Row tiles cover
- # the whole batch so the fp8 head is streamed once per call (16-row tiles re-read it per tile, which
- # made M >= 40 slower than the bf16 head). Measured on Spark_04 (real head shard 32320 x 5120, us per
- # call, CUDA graph; diagnostics/dsv41-moe-plan-table): M=5 721 (bf16 1374); M=40 16-row 2087 -> 794
- # (bf16 1627); M=60 2824 -> 797 (bf16 1559); M=80 16-row 3593 -> 1138 (bf16 1566, today's fallback).
- # Every tiling gives bit-identical fp32 logits per row (same K order, one tl.dot chain per row).
- # Rows above 64 (bs 14 and 16) stay on the bf16 head as before: moving them to the fp8 copy changes the
- # draft proposals there, and a fleet A/B showed no gain at c16 (the gain is at c4-c12, where the retiled
- # kernel is bit-identical to the old one). DSV41_DRAFT_HEAD_FP8_MAX_M raises it (<= 128).
- MAX_M = min(128, int(os.environ.get("DSV41_DRAFT_HEAD_FP8_MAX_M", "64")))
- _TILES = ((16, 16, 256, 4, 3), (64, 64, 128, 4, 3), (128, 128, 64, 8, 3)) # max M, BM, BK, warps, stages
- _TWIN = {} # lm_head weight data_ptr -> (e4m3, exponent)
- @triton.jit
- def _head_fp8_kernel(X, W8, S, Y, M, N, K: tl.constexpr, BM: tl.constexpr, BN: tl.constexpr, BK: tl.constexpr):
- pid = tl.program_id(0)
- m = tl.program_id(1) * BM + tl.arange(0, BM)
- n = pid * BN + tl.arange(0, BN)
- nmask = n < N
- acc = tl.zeros((BM, BN), tl.float32)
- for k0 in range(0, K, BK):
- k = k0 + tl.arange(0, BK)
- x = tl.load(X + m[:, None] * K + k[None, :], m[:, None] < M, 0.0)
- w8 = tl.load(W8 + n[None, :] * K + k[:, None], nmask[None, :], 0.0)
- e = tl.load(S + (n[None, :] // 32) * (K // 32) + k[:, None] // 32, nmask[None, :], 127)
- w = (w8.to(tl.float32) * tl.exp2(e.to(tl.float32) - 127.0)).to(tl.bfloat16)
- acc += tl.dot(x, w)
- tl.store(Y + m[:, None] * N + n[None, :], acc, (m[:, None] < M) & nmask[None, :])
- def make_twin(w: torch.Tensor):
- """bf16 [N, K] -> (e4m3 [N, K], exponent uint8 [ceil(N/32), K/32]); lossy, draft-only."""
- n, k = w.shape
- pad = (-n) % 32
- wf = torch.nn.functional.pad(w.float(), (0, 0, 0, pad)).view((n + pad) // 32, 32, k // 32, 32)
- amax = wf.abs().amax(dim=(1, 3)).clamp_min(2.0 ** -126)
- e = torch.ceil(torch.log2(amax / 448.0)).clamp(-127, 127)
- q = (wf / torch.exp2(e)[:, None, :, None]).to(torch.float8_e4m3fn)
- return q.view(n + pad, k)[:n].contiguous(), (e + 127).to(torch.uint8).contiguous()
- def head_fp8(x: torch.Tensor, twin) -> torch.Tensor:
- """x [M, K] bf16 (M <= MAX_M) -> fp32 logits [M, N]."""
- w8, s = twin
- m, k = x.shape
- n = w8.shape[0]
- y = torch.empty((m, n), dtype=torch.float32, device=x.device)
- # BN 32; M <= 16 keeps the M=5 tuning (BK 256, 4 warps, 3 stages = 230 GB/s of fp8)
- _, bm, bk, warps, stages = next(t for t in _TILES if m <= t[0])
- _head_fp8_kernel[(triton.cdiv(n, 32), triton.cdiv(m, bm))](x.contiguous(), w8, s, y, m, n, k, bm, 32, bk,
- num_warps=warps, num_stages=stages)
- return y
- def install(dspark_module):
- """sglang.srt.models.deepseek_v4_dspark: fp8 twin built when the target's lm_head is attached,
- used by _logits_from_x_post_hc for up to MAX_M rows; everything else is the stock path."""
- if not ENABLED:
- return
- cls = dspark_module.DeepseekV4ForCausalLMDSpark
- for name in ("attach_shared_modules", "_logits_from_x_post_hc"):
- if not hasattr(cls, name):
- raise RuntimeError(f"DSV41_DRAFT_HEAD_FP8: DeepseekV4ForCausalLMDSpark.{name} is gone; engine drifted")
- if getattr(cls, "_dsv41_draft_head_fp8", False):
- return
- cls._dsv41_draft_head_fp8 = True
- orig_attach = cls.attach_shared_modules
- orig_logits = cls._logits_from_x_post_hc
- gather = getattr(dspark_module, "gather_and_crop_vocab")
- def attach_shared_modules(self, *a, **kw):
- out = orig_attach(self, *a, **kw)
- w = getattr(self.lm_head, "weight", None)
- if w is not None and w.dtype == torch.bfloat16 and w.dim() == 2 and w.shape[1] % 32 == 0:
- _TWIN[w.data_ptr()] = make_twin(w.data)
- torch.cuda.empty_cache() # the fp32 temporaries must not shrink the KV pool
- print(f"[draft_head_fp8] fp8 twin of the draft LM head {tuple(w.shape)}", flush=True)
- return out
- def _logits_from_x_post_hc(self, x_post_hc):
- w = getattr(self.lm_head, "weight", None)
- twin = None if w is None else _TWIN.get(w.data_ptr())
- if twin is None or x_post_hc.shape[0] > MAX_M or x_post_hc.shape[0] == 0:
- return orig_logits(self, x_post_hc)
- x = self.stages[-1].norm(x_post_hc)
- local = head_fp8(x.to(torch.bfloat16), twin).to(w.dtype)
- if self._opt_markov_w2_tp_shard:
- return local
- return gather(local, self.lm_head)
- cls.attach_shared_modules = attach_shared_modules
- cls._logits_from_x_post_hc = _logits_from_x_post_hc
- print("[draft_head_fp8] armed", flush=True)
|