File size: 12,656 Bytes
e1acc15 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | """Small ops of the DSpark drafter forward, written to the engine's semantics (NOTES.md).
Everything here is plain torch except `markov_bias_rows`, a copy of the serve's Triton kernel
(vllm/models/deepseek_v41/nvidia/ops/markov_cache.py, Apache-2.0) so cached and computed Markov rows have the same
bits as on the Spark's code path. Float formats follow the engine: the hyper-connection stream and every activation
handed between modules are bf16; norms, mixes, RoPE, attention scores and routing are computed in fp32.
"""
from __future__ import annotations
import math
import torch
import triton
import triton.language as tl
# --------------------------------------------------------------------------------------------- RoPE
def rope_cos_sin(max_pos: int, rope_dim: int = 64, base: float = 10000.0, beta_fast: int = 32, beta_slow: int = 1,
orig_max_pos: int = 1048576) -> torch.Tensor:
"""fp32 [max_pos, rope_dim] = cat(cos, sin) exactly as vLLM's DeepseekV4ScalingRotaryEmbedding builds it for the
pure sliding-window layers (common/rope.py: rope_type deepseek_yarn, factor 1.0, mscale 1, original max position =
max_position_embeddings): the YaRN blend of two equal frequency sets, then an fp32 outer product with the
positions and cos / sin of each row. Built on the CPU like vLLM's (at module init, before .to(device))."""
pos_freqs = base ** (torch.arange(0, rope_dim, 2, dtype=torch.float) / rope_dim)
inv_freq_extrapolation = 1.0 / pos_freqs
inv_freq_interpolation = 1.0 / (1.0 * pos_freqs)
def corr_dim(num_rot: float) -> float:
return (rope_dim * math.log(orig_max_pos / (num_rot * 2 * math.pi))) / (2 * math.log(base))
low = max(math.floor(corr_dim(beta_fast)), 0)
high = min(math.ceil(corr_dim(beta_slow)), rope_dim - 1)
if low == high:
high += 0.001
ramp = torch.clamp((torch.arange(rope_dim // 2, dtype=torch.float) - low) / (high - low), 0, 1)
mask = 1 - ramp
inv_freq = inv_freq_interpolation * (1 - mask) + inv_freq_extrapolation * mask
t = torch.arange(max_pos, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
return torch.cat((freqs.cos() * 1.0, freqs.sin() * 1.0), dim=-1)
def apply_rope(x: torch.Tensor, pos: torch.Tensor, cos_sin: torch.Tensor, inverse: bool = False) -> torch.Tensor:
"""Rotate the last rope_dim channels of x (interleaved pairs, as is_neox_style=False), in fp32, at positions pos.
x: [N, ..., D] (any float dtype), pos: [N] int64. Returns a new tensor in x's dtype; the NoPE channels are copied."""
rd = cos_sin.shape[-1]
cs = cos_sin[pos] # [N, rd]
cos, sin = cs[:, : rd // 2], cs[:, rd // 2:]
shape = [x.shape[0]] + [1] * (x.dim() - 2) + [rd // 2]
cos, sin = cos.view(shape), sin.view(shape)
if inverse:
sin = -sin
r = x[..., -rd:].float().unflatten(-1, (rd // 2, 2))
x0, x1 = r[..., 0], r[..., 1]
y = torch.stack((x0 * cos - x1 * sin, x1 * cos + x0 * sin), dim=-1).flatten(-2)
out = x.clone()
out[..., -rd:] = y.to(x.dtype)
return out
# --------------------------------------------------------------------------------------------- KV record
def kv_record_qdq(kv: torch.Tensor, nope: int = 448, group: int = 64) -> torch.Tensor:
"""Values of the V4 fp8_ds_mla record (SM12x, 584 B): the NoPE channels as e4m3 with one power-of-two (UE8M0) scale
per `group` channels, scale = 2^ceil(log2(amax / 448)); the RoPE channels kept bf16. Returns bf16 [.., 512] holding
exactly the values the engine's attention reads back (e4m3 x 2^k is exact in bf16).
The ceil(log2) is taken from the IEEE bits of amax * (1/448) (DeepSeek's fast_round_scale, the plugin's
_qdq_ue8m0 kernel): exact at powers of two, where log2f-then-ceil can round the wrong way."""
n = kv[..., :nope].float().unflatten(-1, (nope // group, group))
a = (n.abs().amax(-1, keepdim=True) * (1.0 / 448.0)).clamp_min(2.0 ** -126)
bits = a.view(torch.int32)
e = ((bits >> 23) & 0xFF) - 127 + ((bits & 0x7FFFFF) != 0).to(torch.int32)
scale = ((e + 127) << 23).view(torch.float32)
q = (n / scale).clamp(-448.0, 448.0).to(torch.float8_e4m3fn).float() * scale
out = kv.to(torch.bfloat16).clone()
out[..., :nope] = q.flatten(-2).to(torch.bfloat16)
return out
# --------------------------------------------------------------------------------------------- norms / mHC
def rms_norm(x: torch.Tensor, w: torch.Tensor, eps: float) -> torch.Tensor:
"""DeepSeek reference RMSNorm: fp32 math, weight applied in fp32, one rounding to x's dtype (bf16)."""
xf = x.float()
xf = xf * torch.rsqrt(xf.square().mean(-1, keepdim=True) + eps)
return (w.float() * xf).to(x.dtype)
def hc_mixes(stream: torch.Tensor, fn: torch.Tensor, scale: torch.Tensor, base: torch.Tensor, rms_eps: float,
hc_eps: float, iters: int) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""pre [T, hc], post [T, hc], comb [T, hc, hc] (fp32) from the bf16 stream [T, hc, H] (mhc_pre_delayed_torch)."""
hc = stream.shape[1]
x = stream.flatten(1).float()
mixes = (x @ fn.t()) * torch.rsqrt(x.square().mean(-1, keepdim=True) + rms_eps)
pre = torch.sigmoid(mixes[:, :hc] * scale[0] + base[:hc]) + hc_eps
post = torch.sigmoid(mixes[:, hc:2 * hc] * scale[1] + base[hc:2 * hc]) * 2.0
comb = mixes[:, 2 * hc:].view(-1, hc, hc) * scale[2] + base[2 * hc:].view(1, hc, hc)
comb = torch.softmax(comb, dim=-1) + hc_eps
comb = comb / (comb.sum(dim=-2, keepdim=True) + hc_eps)
for _ in range(iters - 1):
comb = comb / (comb.sum(dim=-1, keepdim=True) + hc_eps)
comb = comb / (comb.sum(dim=-2, keepdim=True) + hc_eps)
return pre, post, comb
def hc_collapse(stream: torch.Tensor, pre_mix: torch.Tensor | None) -> torch.Tensor:
"""[T, hc, H] -> [T, H]: copy 0 when pre_mix is None (the first stage), else sum_i pre_i * stream_i in fp32 -> bf16."""
if pre_mix is None:
return stream[:, 0].contiguous()
return (pre_mix.unsqueeze(-1) * stream.float()).sum(dim=1).to(stream.dtype)
def hc_post(y: torch.Tensor, stream: torch.Tensor, post: torch.Tensor, comb: torch.Tensor) -> torch.Tensor:
"""new stream_j = post_j * y + sum_i comb[i, j] * stream_i, fp32 -> bf16 (mhc_post_torch)."""
mixed = torch.einsum("tij,tih->tjh", comb, stream.float())
return (mixed + post.unsqueeze(-1) * y.float().unsqueeze(1)).to(stream.dtype)
# --------------------------------------------------------------------------------------------- routing
def route(x: torch.Tensor, gate_w: torch.Tensor, bias: torch.Tensor, top_k: int, route_scale: float
) -> tuple[torch.Tensor, torch.Tensor]:
"""sqrtsoftplus / noaux_tc: experts by top-k of score + bias, weights = the unbiased scores renormalized x scale.
gate_w fp32 [E, H]. Returns (ids int64 [T, k], weights fp32 [T, k])."""
logits = x.float() @ gate_w.t()
scores = torch.nn.functional.softplus(logits).sqrt()
ids = (scores + bias).topk(top_k, dim=-1)[1]
w = scores.gather(1, ids)
w = w / (w.sum(dim=-1, keepdim=True) + 1e-20) * route_scale
return ids, w
# --------------------------------------------------------------------------------------------- attention
def block_attention(q: torch.Tensor, ctx_kv: torch.Tensor, ctx_valid: torch.Tensor, blk_kv: torch.Tensor,
sink: torch.Tensor, scale: float) -> torch.Tensor:
"""The DSpark block attention: every block row sees the request's valid context window and the whole block.
q [B, k, Hh, D] bf16; ctx_kv [B, W, D] bf16 (keys = values, MQA); ctx_valid [B, W] bool; blk_kv [B, k, D] bf16;
sink [Hh] fp32 (joins the softmax denominator only). fp32 scores; the probabilities are rounded to bf16 for the
weighted sum (as DeepSeek's sparse_attn kernel and FlashInfer do), the denominator stays fp32; bf16 out."""
B, k, Hh, D = q.shape
keys = torch.cat((ctx_kv, blk_kv), dim=1).float() # [B, W + k, D] (exact bf16 values)
s = torch.bmm(q.reshape(B, k * Hh, D).float(), keys.transpose(1, 2)) * scale # [B, k*Hh, W+k]
valid = torch.cat((ctx_valid, torch.ones(B, k, dtype=torch.bool, device=q.device)), dim=1)
s = s.masked_fill(~valid.unsqueeze(1), float("-inf"))
sk = sink.view(1, 1, Hh).expand(B, k, Hh).reshape(B, k * Hh, 1)
m = torch.maximum(s.amax(-1, keepdim=True), sk)
p = torch.exp(s - m)
denom = p.sum(-1, keepdim=True) + torch.exp(sk - m)
o = torch.bmm(p.to(torch.bfloat16).float(), keys) / denom
return o.view(B, k, Hh, D).to(torch.bfloat16)
# --------------------------------------------------------------------------------------------- Markov bias
# Copied from vllm/models/deepseek_v41/nvidia/ops/markov_cache.py (the running image, Apache-2.0): one program per
# (row, 64 vocab columns); cached rows are read back, others computed as an fp32 dot over the rank, stored in the
# output dtype. The cache is filled by this same kernel, so a token's bias has the same bits either way.
_BLOCK_V = 64
@triton.jit
def _markov_bias_kernel(ids_ptr, slot_ptr, cache_ptr, emb_ptr, w2_ptr, out_ptr, V, cache_stride, w2_stride,
emb_stride, out_stride, R: tl.constexpr, BLOCK_V: tl.constexpr):
b = tl.program_id(0)
cols = tl.program_id(1) * BLOCK_V + tl.arange(0, BLOCK_V)
mask = cols < V
tok = tl.load(ids_ptr + b).to(tl.int64)
slot = tl.load(slot_ptr + tok)
if slot >= 0:
row = tl.load(cache_ptr + slot.to(tl.int64) * cache_stride + cols, mask=mask)
tl.store(out_ptr + b * out_stride + cols, row, mask=mask)
else:
r = tl.arange(0, R)
e = tl.load(emb_ptr + b * emb_stride + r).to(tl.float32)
w = tl.load(w2_ptr + cols[:, None].to(tl.int64) * w2_stride + r[None, :], mask=mask[:, None], other=0.0)
acc = tl.sum(w.to(tl.float32) * e[None, :], axis=1)
tl.store(out_ptr + b * out_stride + cols, acc.to(out_ptr.dtype.element_ty), mask=mask)
def markov_bias_rows(token_ids: torch.Tensor, slot_of_token: torch.Tensor, cache: torch.Tensor,
markov_embed: torch.Tensor, w2: torch.Tensor, out: torch.Tensor) -> torch.Tensor:
"""out[b] = cache[slot_of_token[t_b]] if cached, else w2 @ markov_embed[b] (fp32 dot, one rounding)."""
num_rows, rank = markov_embed.shape
vocab = w2.shape[0]
assert w2.stride(1) == 1 and markov_embed.stride(1) == 1 and out.stride(1) == 1
if num_rows == 0:
return out
grid = (num_rows, triton.cdiv(vocab, _BLOCK_V))
_markov_bias_kernel[grid](token_ids, slot_of_token, cache, markov_embed, w2, out, vocab,
cache.stride(0) if cache.numel() else 0, w2.stride(0), markov_embed.stride(0),
out.stride(0), R=rank, BLOCK_V=_BLOCK_V, num_warps=4)
return out
class MarkovBias:
"""The Markov bias with cached rows for the serve's most frequent tokens (VLLM_DSPARK_MARKOV_CACHE list, first
`rows` distinct ids, default 4096 like VLLM_DSPARK_MARKOV_CACHE_ROWS)."""
def __init__(self, w1: torch.Tensor, w2: torch.Tensor, token_list: list[int] | None, rows: int = 4096):
self.w1, self.w2 = w1, w2
vocab, dev = w1.shape[0], w2.device
ids: list[int] = []
seen: set[int] = set()
for t in token_list or []:
t = int(t)
if 0 <= t < vocab and t not in seen:
seen.add(t)
ids.append(t)
ids = ids[:rows]
self.slot_of_token = torch.full((vocab,), -1, dtype=torch.int32, device=dev)
self.cache = torch.empty((max(len(ids), 1), w2.shape[0]), dtype=torch.bfloat16, device=dev)
if ids:
tok = torch.tensor(ids, dtype=torch.int64, device=dev)
for s in range(0, len(ids), 256):
t = tok[s: s + 256]
markov_bias_rows(t, self.slot_of_token, self.cache, w1[t].contiguous(), w2, self.cache[s: s + 256])
self.slot_of_token[tok] = torch.arange(len(ids), dtype=torch.int32, device=dev)
self.cached_rows = len(ids)
def embed(self, token_ids: torch.Tensor) -> torch.Tensor:
return self.w1[token_ids]
def __call__(self, token_ids: torch.Tensor, markov_embed: torch.Tensor, out: torch.Tensor | None = None
) -> torch.Tensor:
if out is None:
out = torch.empty((markov_embed.shape[0], self.w2.shape[0]), dtype=torch.bfloat16,
device=markov_embed.device)
return markov_bias_rows(token_ids, self.slot_of_token, self.cache, markov_embed.contiguous(), self.w2, out)
|