The DSpark drafter's BF16 linears on the decode GEMM engines.
The draft step multiplies R * k rows (R * (k + 1) for the context
projections) by 0.7 GB (TP8 rank) to 7 GB (DEP rank) of BF16 weights: a decode
GEMM whose time is the weight stream. cuBLAS runs these skinny shapes through
split-K kernels at 2.4-3.6 TB/s (Phase-1 drafter breakdown); the warp split-K of
cutedsl/gemm/warp_splitk.py (many small co-resident CTAs, one cp.async weight
ring each, mma.sync, FP32 split-K partials reduced in shared memory, one BF16
rounding) keeps the stream near HBM speed up to 32 rows.
DraftGemm picks the engine per row count (WARP_ROWS), compiles it the
first time an eager call sees that count (the CUDA-graph warmup runs every
captured size eagerly before its capture) and runs cuBLAS for anything else.
install swaps the drafter linears' unquantized apply for it. Precision:
BF16 operands, FP32 accumulation, one BF16 rounding, as cuBLAS; only the
summation order differs.
Classes:
-
DraftGemm
–
out[M, N] = x[M, K] @ weight[N, K]^T with an engine per prepared M.
-
DraftLinearMethod
–
An unquantized linear's apply on DraftGemm (2-D BF16 inputs without
Functions:
-
draft_logits
–
model.compute_draft_logits: the lm_head shard (the target's lm_head weight,
-
install
–
Route every unquantized BF16 linear under module through DraftGemm
-
warp_eligible
–
Shapes the warp split-K takes: 16-row output tiles and at most 64 K tiles
DraftGemm
out[M, N] = x[M, K] @ weight[N, K]^T with an engine per prepared M.
Methods:
-
prepare
–
Compile the engine of m rows (host only; never under capture).
Source code in vllm/models/kimi_k3/nvidia/specialized_decode/drafter/linears.py
| class DraftGemm:
"""``out[M, N] = x[M, K] @ weight[N, K]^T`` with an engine per prepared M."""
def __init__(self, weight):
self.weight = weight
n, k = weight.shape
self.n, self.k = n, k
self.sms = torch.cuda.get_device_properties(weight.device).multi_processor_count
self.eligible = (
weight.dtype == torch.bfloat16
and weight.stride() == (k, 1)
and weight.data_ptr() % 32 == 0
and warp_eligible(n, k)
)
self.engines = {}
def kind(self, m):
return "warp" if m in self.engines else "cublas"
def prepare(self, m):
"""Compile the engine of ``m`` rows (host only; never under capture)."""
if m in self.engines or not self.eligible or not 1 <= m <= WARP_ROWS:
return
from ..cutedsl.gemm.decode import warp_tactic
from ..cutedsl.gemm.warp_splitk import PreparedWarpSplitK
try:
tactic = warp_tactic(m, self.n, self.k, self.sms)
self.engines[m] = PreparedWarpSplitK(self.weight, {m: tactic})
except Exception as exc: # capability fallback: cuBLAS
logger.warning_once(
"DSpark drafter GEMM %dx%d (M %d) on cuBLAS: %s", self.n, self.k, m, exc
)
self.eligible = False
def run(self, x, out):
m = x.shape[0]
engine = self.engines.get(m)
if engine is None and not torch.cuda.is_current_stream_capturing():
self.prepare(m)
engine = self.engines.get(m)
if (
engine is None
or x.stride() != (self.k, 1)
or x.data_ptr() % 32
or out.stride() != (self.n, 1)
or out.data_ptr() % 32
):
return torch.mm(x, self.weight.t(), out=out)
return engine.run(x, out)
|
prepare(m)
Compile the engine of m rows (host only; never under capture).
Source code in vllm/models/kimi_k3/nvidia/specialized_decode/drafter/linears.py
| def prepare(self, m):
"""Compile the engine of ``m`` rows (host only; never under capture)."""
if m in self.engines or not self.eligible or not 1 <= m <= WARP_ROWS:
return
from ..cutedsl.gemm.decode import warp_tactic
from ..cutedsl.gemm.warp_splitk import PreparedWarpSplitK
try:
tactic = warp_tactic(m, self.n, self.k, self.sms)
self.engines[m] = PreparedWarpSplitK(self.weight, {m: tactic})
except Exception as exc: # capability fallback: cuBLAS
logger.warning_once(
"DSpark drafter GEMM %dx%d (M %d) on cuBLAS: %s", self.n, self.k, m, exc
)
self.eligible = False
|
DraftLinearMethod
Bases: _linear_method_base()
An unquantized linear's apply on DraftGemm (2-D BF16 inputs without
bias; anything else goes to the original method). A subclass of
UnquantizedLinearMethod so type checks on the layer still hold.
Source code in vllm/models/kimi_k3/nvidia/specialized_decode/drafter/linears.py
| class DraftLinearMethod(_linear_method_base()): # type: ignore[misc]
"""An unquantized linear's ``apply`` on ``DraftGemm`` (2-D BF16 inputs without
bias; anything else goes to the original method). A subclass of
``UnquantizedLinearMethod`` so type checks on the layer still hold."""
def __init__(self, layer, inner): # super().__init__ not needed
self.inner = inner
self.gemm = DraftGemm(layer.weight)
def __getattr__(self, name):
return getattr(self.__dict__["inner"], name)
def apply(self, layer, x, bias=None):
if (
bias is not None
or x.dim() != 2
or x.dtype != torch.bfloat16
or layer.weight is not self.gemm.weight
):
return self.inner.apply(layer, x, bias)
out = torch.empty(x.shape[0], self.gemm.n, dtype=x.dtype, device=x.device)
return self.gemm.run(x.contiguous(), out)
|
draft_logits(model, hidden_states)
model.compute_draft_logits: the lm_head shard (the target's lm_head weight,
shared; on DraftGemm for a plain BF16 head, else its own method), then the
vocab gather on the drafter's mailbox (comm.DrafterComm.gather_logits; vLLM's
all-gather for a shape it does not serve). BF16 logits only (an FP32 head keeps
compute_logits).
Source code in vllm/models/kimi_k3/nvidia/specialized_decode/drafter/linears.py
| def draft_logits(model, hidden_states):
"""``model.compute_draft_logits``: the lm_head shard (the target's lm_head weight,
shared; on ``DraftGemm`` for a plain BF16 head, else its own method), then the
vocab gather on the drafter's mailbox (``comm.DrafterComm.gather_logits``; vLLM's
all-gather for a shape it does not serve). BF16 logits only (an FP32 head keeps
``compute_logits``)."""
lp, head = model.logits_processor, model.lm_head
cached = getattr(model, "_draft_lm_head_gemm", None)
if cached is None or cached[0] is not head.weight:
gemm = None
if (
type(head.quant_method).__name__ == "UnquantizedEmbeddingMethod"
and head.weight.dtype == torch.bfloat16
and head.weight.dim() == 2
and lp.head_dtype in (None, torch.bfloat16)
and not lp.logits_as_input
):
gemm = DraftGemm(head.weight)
cached = (head.weight, gemm)
model._draft_lm_head_gemm = cached
gemm, comm = cached[1], getattr(model, "_drafter_comm", None)
if hidden_states.dim() != 2 or lp.logits_as_input:
return model.compute_logits(hidden_states)
if gemm is None and (
comm is None
or head.weight.dtype != torch.bfloat16
or lp.head_dtype not in (None, torch.bfloat16)
):
return model.compute_logits(hidden_states)
x = hidden_states.contiguous()
if gemm is not None:
logits = torch.empty(x.shape[0], gemm.n, dtype=x.dtype, device=x.device)
gemm.run(x, logits)
else:
logits = lp._apply_head(head, x, None)
if head.tp_size > 1:
gathered = None if comm is None else comm.gather_logits(logits)
logits = lp._gather_logits(logits) if gathered is None else gathered
logits = logits[..., : lp.org_vocab_size]
if lp.soft_cap is not None:
logits = torch.tanh(logits / lp.soft_cap) * lp.soft_cap
if lp.scale != 1.0:
logits *= lp.scale
return logits
|
install(module)
Route every unquantized BF16 linear under module through DraftGemm
(not kv_b_proj: the decode runs its absorbed W_UK / W_UV instead).
Source code in vllm/models/kimi_k3/nvidia/specialized_decode/drafter/linears.py
| def install(module) -> int:
"""Route every unquantized BF16 linear under ``module`` through DraftGemm
(not ``kv_b_proj``: the decode runs its absorbed W_UK / W_UV instead)."""
from vllm.model_executor.layers.linear import LinearBase, UnquantizedLinearMethod
count = 0
for name, sub in module.named_modules():
if not isinstance(sub, LinearBase) or name.endswith("kv_b_proj"):
continue
method = getattr(sub, "quant_method", None)
if type(method) is not UnquantizedLinearMethod:
continue
weight = getattr(sub, "weight", None)
if weight is None or weight.dtype != torch.bfloat16 or weight.dim() != 2:
continue
sub.quant_method = DraftLinearMethod(sub, method)
count += 1
return count
|
warp_eligible(n, k)
Shapes the warp split-K takes: 16-row output tiles and at most 64 K tiles
(128 wide, or 256 wide when K is a multiple of 256: the TP1 down projection's
K = 14336 is 56 tiles of 256).
Source code in vllm/models/kimi_k3/nvidia/specialized_decode/drafter/linears.py
| def warp_eligible(n, k):
"""Shapes the warp split-K takes: 16-row output tiles and at most 64 K tiles
(128 wide, or 256 wide when K is a multiple of 256: the TP1 down projection's
K = 14336 is 56 tiles of 256)."""
if n % 16 or k % 128:
return False
return k // 128 <= 64 or (k % 256 == 0 and k // 256 <= 64)
|