flashmap 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flashmap/__init__.py +14 -0
- flashmap/configuration_flashmap.py +105 -0
- flashmap/modeling_flashmap.py +556 -0
- flashmap/sglang/__init__.py +78 -0
- flashmap/sglang/jump_bank.py +203 -0
- flashmap/sglang/kernels.py +457 -0
- flashmap/sglang/model.py +1076 -0
- flashmap/sglang/sampler.py +188 -0
- flashmap/sglang/tree.py +78 -0
- flashmap/sglang/tree_worker.py +396 -0
- flashmap/sglang/verify_attention.py +474 -0
- flashmap/vllm/__init__.py +268 -0
- flashmap/vllm/fold.py +128 -0
- flashmap/vllm/kernels.py +443 -0
- flashmap/vllm/model.py +881 -0
- flashmap/vllm/speculator.py +235 -0
- flashmap-0.1.0.dist-info/METADATA +152 -0
- flashmap-0.1.0.dist-info/RECORD +22 -0
- flashmap-0.1.0.dist-info/WHEEL +5 -0
- flashmap-0.1.0.dist-info/entry_points.txt +5 -0
- flashmap-0.1.0.dist-info/licenses/LICENSE +21 -0
- flashmap-0.1.0.dist-info/top_level.txt +1 -0
flashmap/vllm/model.py
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
"""FlashMap draft model for vLLM.
|
|
2
|
+
|
|
3
|
+
The model is driven by ``FlashMapSpeculator`` (speculator.py), which reuses
|
|
4
|
+
vLLM's DFlash machinery: the target's aux hidden states are fused by ``fc``
|
|
5
|
+
(``combine_hidden_states``) and every committed token's cross-attention keys and
|
|
6
|
+
values go to the draft's paged KV cache; each draft step then runs ``forward`` on
|
|
7
|
+
``block_size`` query rows per request, laid out as in DSpark: row 0 carries the
|
|
8
|
+
anchor token at the anchor position ``p`` and row ``k`` sits at ``p + k``.
|
|
9
|
+
|
|
10
|
+
A FlashMap draft step, for each request:
|
|
11
|
+
|
|
12
|
+
* writes the keys/values of the context committed since the last step and of the
|
|
13
|
+
anchor (its embedding through ``anchor_proj`` and ``anchor_norm``, at ``p``,
|
|
14
|
+
where the target's own entry for that token lands once it is verified), all
|
|
15
|
+
projected by one GEMM over every layer's weights;
|
|
16
|
+
* evaluates the DiT on ``block_size`` slots at positions ``p + 1 ..`` from noise,
|
|
17
|
+
their cross-attention reading positions ``0 .. p`` straight from the paged KV
|
|
18
|
+
cache with FlashAttention;
|
|
19
|
+
* returns the final hidden states, which the speculator maps to logits through
|
|
20
|
+
the DiT head folded into the target's LM head.
|
|
21
|
+
|
|
22
|
+
One jump (the default) needs no vocabulary-sized tensor: the state at ``t = 0``
|
|
23
|
+
embeds to a constant (its posterior is uniform) plus the noise's embedding,
|
|
24
|
+
drawn in the embedding's eigenbasis (fold.py). Everything that depends only on
|
|
25
|
+
the jump times (every AdaLN modulation) is computed once at load. More jumps
|
|
26
|
+
run the reference algorithm exactly, with a vocabulary-sized state.
|
|
27
|
+
|
|
28
|
+
Requirements: the V2 model runner, tensor parallel size 1, the FlashAttention
|
|
29
|
+
backend and a bf16 KV cache (checked at load).
|
|
30
|
+
|
|
31
|
+
The draft noise is keyed by each request's seed and the slot's position, so a
|
|
32
|
+
seeded request drafts the same blocks on every run. ``FLASHMAP_NUM_STEPS``
|
|
33
|
+
overrides the checkpoint's number of jumps.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
from collections.abc import Iterable
|
|
38
|
+
|
|
39
|
+
import torch
|
|
40
|
+
import torch.nn.functional as F
|
|
41
|
+
from torch import nn
|
|
42
|
+
from vllm import _custom_ops as ops
|
|
43
|
+
from vllm.config import VllmConfig
|
|
44
|
+
from vllm.distributed import get_tensor_model_parallel_world_size
|
|
45
|
+
from vllm.forward_context import get_forward_context
|
|
46
|
+
from vllm.logger import init_logger
|
|
47
|
+
from vllm.model_executor.layers.attention import Attention
|
|
48
|
+
from vllm.platforms import current_platform
|
|
49
|
+
from vllm.v1.attention.backends.fa_utils import flash_attn_varlen_func
|
|
50
|
+
|
|
51
|
+
from flashmap.vllm.fold import (
|
|
52
|
+
fold_head,
|
|
53
|
+
fold_noise,
|
|
54
|
+
quantize_fp8_per_channel,
|
|
55
|
+
timestep_embedding,
|
|
56
|
+
)
|
|
57
|
+
from flashmap.vllm.kernels import (
|
|
58
|
+
block_self_attention,
|
|
59
|
+
rms_norm,
|
|
60
|
+
rms_norm_rope,
|
|
61
|
+
rope_table,
|
|
62
|
+
seeded_normal,
|
|
63
|
+
write_context_kv,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
from vllm.vllm_flash_attn import get_scheduler_metadata
|
|
68
|
+
except ImportError: # no FlashAttention 3 build
|
|
69
|
+
get_scheduler_metadata = None
|
|
70
|
+
|
|
71
|
+
logger = init_logger(__name__)
|
|
72
|
+
|
|
73
|
+
__all__ = ["FlashMapDraftModel", "FlashMapModel"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@torch.library.custom_op("flashmap_vllm::paged_cross_attention", mutates_args=())
|
|
77
|
+
def paged_cross_attention(
|
|
78
|
+
query: torch.Tensor,
|
|
79
|
+
kv_cache: torch.Tensor,
|
|
80
|
+
cu_seqlens_q: torch.Tensor,
|
|
81
|
+
seqused_k: torch.Tensor,
|
|
82
|
+
block_table: torch.Tensor,
|
|
83
|
+
scheduler_metadata: torch.Tensor | None,
|
|
84
|
+
descales: torch.Tensor | None,
|
|
85
|
+
max_seqlen_q: int,
|
|
86
|
+
max_seqlen_k: int,
|
|
87
|
+
softmax_scale: float,
|
|
88
|
+
fa_version: int,
|
|
89
|
+
) -> torch.Tensor:
|
|
90
|
+
"""Non-causal FlashAttention of ``query (tokens, heads, head_dim)`` onto the
|
|
91
|
+
first ``seqused_k`` cached positions of each request. An fp8 cache takes an
|
|
92
|
+
fp8 ``query`` and ``descales (3, requests, heads)`` for the query, keys and
|
|
93
|
+
values."""
|
|
94
|
+
head_dim = query.shape[-1]
|
|
95
|
+
key_cache, value_cache = kv_cache.transpose(1, 2).split(head_dim, dim=-1)
|
|
96
|
+
q_descale = k_descale = v_descale = None
|
|
97
|
+
if descales is not None:
|
|
98
|
+
key_cache = key_cache.view(query.dtype)
|
|
99
|
+
value_cache = value_cache.view(query.dtype)
|
|
100
|
+
q_descale, k_descale, v_descale = descales.unbind(0)
|
|
101
|
+
return flash_attn_varlen_func(
|
|
102
|
+
q=query,
|
|
103
|
+
k=key_cache,
|
|
104
|
+
v=value_cache,
|
|
105
|
+
cu_seqlens_q=cu_seqlens_q,
|
|
106
|
+
max_seqlen_q=max_seqlen_q,
|
|
107
|
+
seqused_k=seqused_k,
|
|
108
|
+
max_seqlen_k=max_seqlen_k,
|
|
109
|
+
softmax_scale=softmax_scale,
|
|
110
|
+
causal=False,
|
|
111
|
+
block_table=block_table,
|
|
112
|
+
scheduler_metadata=scheduler_metadata,
|
|
113
|
+
q_descale=q_descale,
|
|
114
|
+
k_descale=k_descale,
|
|
115
|
+
v_descale=v_descale,
|
|
116
|
+
fa_version=fa_version,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@paged_cross_attention.register_fake
|
|
121
|
+
def _(query, kv_cache, cu_seqlens_q, seqused_k, block_table, *args):
|
|
122
|
+
return torch.empty(query.shape, dtype=torch.bfloat16, device=query.device)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class RMSNorm(nn.Module):
|
|
126
|
+
"""FlashMap's RMSNorm: ``x * bf16(weight * rsqrt(mean(x^2) + eps))``."""
|
|
127
|
+
|
|
128
|
+
def __init__(self, dim: int, eps: float):
|
|
129
|
+
super().__init__()
|
|
130
|
+
self.eps = eps
|
|
131
|
+
self.weight = nn.Parameter(torch.ones(dim), requires_grad=False)
|
|
132
|
+
|
|
133
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
134
|
+
return rms_norm(x, self.weight, self.eps)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class TimestepEmbedding(nn.Module):
|
|
138
|
+
def __init__(self, dim: int, frequency_dim: int | None = None):
|
|
139
|
+
super().__init__()
|
|
140
|
+
self.frequency_dim = frequency_dim or dim
|
|
141
|
+
self.linear_1 = nn.Linear(self.frequency_dim, dim)
|
|
142
|
+
self.linear_2 = nn.Linear(dim, dim)
|
|
143
|
+
|
|
144
|
+
def forward(self, t: torch.Tensor) -> torch.Tensor:
|
|
145
|
+
emb = timestep_embedding(t, self.frequency_dim)
|
|
146
|
+
emb = emb.to(self.linear_1.weight.dtype)
|
|
147
|
+
return self.linear_2(F.silu(self.linear_1(emb)))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class AdaLN(nn.Module):
|
|
151
|
+
"""AdaLN-zero's condition projection; returns ``(scale, shift, gate)``."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, cond_dim: int, dim: int):
|
|
154
|
+
super().__init__()
|
|
155
|
+
self.linear = nn.Linear(cond_dim, 3 * dim)
|
|
156
|
+
|
|
157
|
+
def forward(self, cond: torch.Tensor) -> tuple[torch.Tensor, ...]:
|
|
158
|
+
return tuple(self.linear(F.silu(cond)).chunk(3, dim=-1))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class FlashMapAttention(nn.Module):
|
|
162
|
+
def __init__(self, dim: int, num_heads: int, head_dim: int, eps: float):
|
|
163
|
+
super().__init__()
|
|
164
|
+
self.num_heads = num_heads
|
|
165
|
+
self.head_dim = head_dim
|
|
166
|
+
self.q_proj = nn.Linear(dim, num_heads * head_dim, bias=False)
|
|
167
|
+
self.k_proj = nn.Linear(dim, num_heads * head_dim, bias=False)
|
|
168
|
+
self.v_proj = nn.Linear(dim, num_heads * head_dim, bias=False)
|
|
169
|
+
self.o_proj = nn.Linear(num_heads * head_dim, dim, bias=False)
|
|
170
|
+
self.q_norm = RMSNorm(head_dim, eps)
|
|
171
|
+
self.k_norm = RMSNorm(head_dim, eps)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class FlashMapMLP(nn.Module):
|
|
175
|
+
def __init__(self, dim: int, hidden: int):
|
|
176
|
+
super().__init__()
|
|
177
|
+
self.fc1 = nn.Linear(dim, hidden)
|
|
178
|
+
self.fc2 = nn.Linear(hidden, dim)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class FlashMapLayer(nn.Module):
|
|
182
|
+
def __init__(self, config, layer_prefix: str, vllm_config: VllmConfig):
|
|
183
|
+
super().__init__()
|
|
184
|
+
dim, cond_dim = config.hidden_size, config.timestep_embed_dim
|
|
185
|
+
heads = config.num_attention_heads
|
|
186
|
+
head_dim = getattr(config, "head_dim", None) or dim // heads
|
|
187
|
+
self.self_attn_adaln = AdaLN(cond_dim, dim)
|
|
188
|
+
self.self_attn = FlashMapAttention(dim, heads, head_dim, config.qk_norm_eps)
|
|
189
|
+
self.cross_attn_adaln = AdaLN(cond_dim, dim)
|
|
190
|
+
self.cross_attn = FlashMapAttention(dim, heads, head_dim, config.qk_norm_eps)
|
|
191
|
+
self.mlp_adaln = AdaLN(cond_dim, dim)
|
|
192
|
+
self.mlp = FlashMapMLP(dim, config.intermediate_size)
|
|
193
|
+
# The cross-attention context lives in vLLM's paged KV cache; this layer
|
|
194
|
+
# only owns the cache (the draft step reads it with its own kernels).
|
|
195
|
+
self.cross_attn.attn = Attention(
|
|
196
|
+
heads,
|
|
197
|
+
head_dim,
|
|
198
|
+
head_dim**-0.5,
|
|
199
|
+
num_kv_heads=heads,
|
|
200
|
+
cache_config=vllm_config.cache_config,
|
|
201
|
+
prefix=f"{layer_prefix}.cross_attn.attn",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class FlashMapModel(nn.Module):
|
|
206
|
+
"""The FlashMap weights under their canonical names. ``embed_tokens`` is the
|
|
207
|
+
target's, attached at load (``load_dflash_model``)."""
|
|
208
|
+
|
|
209
|
+
def __init__(self, config, vllm_config: VllmConfig, prefix: str, start: int):
|
|
210
|
+
super().__init__()
|
|
211
|
+
dim, target_hidden = config.hidden_size, config.target_hidden_size
|
|
212
|
+
eps = config.rms_norm_eps
|
|
213
|
+
num_aux = len(config.eagle_aux_hidden_state_layer_ids)
|
|
214
|
+
self.fc = nn.Linear(num_aux * target_hidden, dim, bias=False)
|
|
215
|
+
self.hidden_norm = RMSNorm(dim, eps)
|
|
216
|
+
self.anchor_proj = nn.Linear(target_hidden, dim)
|
|
217
|
+
self.anchor_norm = RMSNorm(dim, eps)
|
|
218
|
+
self.state_norm = RMSNorm(target_hidden, eps)
|
|
219
|
+
self.noise_norm = RMSNorm(target_hidden, eps)
|
|
220
|
+
self.noise_gate = nn.Parameter(torch.ones(1), requires_grad=False)
|
|
221
|
+
self.x_embed = nn.Linear(target_hidden, dim)
|
|
222
|
+
frequency_dim = getattr(config, "timestep_frequency_dim", None)
|
|
223
|
+
self.time_embed = TimestepEmbedding(config.timestep_embed_dim, frequency_dim)
|
|
224
|
+
self.jump_embed = TimestepEmbedding(config.timestep_embed_dim, frequency_dim)
|
|
225
|
+
self.layers = nn.ModuleList(
|
|
226
|
+
[
|
|
227
|
+
FlashMapLayer(config, f"{prefix}.layers.{start + i}", vllm_config)
|
|
228
|
+
for i in range(config.num_hidden_layers)
|
|
229
|
+
]
|
|
230
|
+
)
|
|
231
|
+
self.final_adaln = AdaLN(config.timestep_embed_dim, dim)
|
|
232
|
+
self.head = nn.Linear(dim, target_hidden)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class FlashMapDraftModel(nn.Module):
|
|
236
|
+
# Scales the starting noise; tests set 0 to compare deterministic drafts.
|
|
237
|
+
noise_scale = 1.0
|
|
238
|
+
# torch.compile the draft step's prologue and DiT (fuses elementwise chains).
|
|
239
|
+
compile = True
|
|
240
|
+
|
|
241
|
+
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
|
|
242
|
+
super().__init__()
|
|
243
|
+
if get_tensor_model_parallel_world_size() != 1:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
"FlashMap drafting supports tensor parallel size 1 only (its "
|
|
246
|
+
"load-time folds read the whole target embedding and LM head)."
|
|
247
|
+
)
|
|
248
|
+
if not vllm_config.use_v2_model_runner:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
"FlashMap drafting needs vLLM's V2 model runner; unset "
|
|
251
|
+
"VLLM_USE_V2_MODEL_RUNNER=0."
|
|
252
|
+
)
|
|
253
|
+
speculative_config = vllm_config.speculative_config
|
|
254
|
+
assert speculative_config is not None
|
|
255
|
+
self.config = config = speculative_config.draft_model_config.hf_config
|
|
256
|
+
self.dtype = vllm_config.model_config.dtype
|
|
257
|
+
if self.dtype != torch.bfloat16:
|
|
258
|
+
raise ValueError(f"FlashMap drafting runs in bfloat16, not {self.dtype}")
|
|
259
|
+
self.max_model_len = vllm_config.model_config.max_model_len
|
|
260
|
+
self.block_size = int(config.block_size)
|
|
261
|
+
# FLASHMAP_NUM_STEPS overrides the checkpoint's number of jumps.
|
|
262
|
+
self.num_steps = int(os.environ.get("FLASHMAP_NUM_STEPS", config.num_steps))
|
|
263
|
+
self.time_grid = [float(t) for t in config.time_grids[self.num_steps - 1]]
|
|
264
|
+
start = vllm_config.model_config.get_total_num_hidden_layers()
|
|
265
|
+
self.model = FlashMapModel(
|
|
266
|
+
config, vllm_config, f"{prefix}.model" if prefix else "model", start
|
|
267
|
+
)
|
|
268
|
+
# Draft vocabulary: a restricted head drafts over d2t's tokens only.
|
|
269
|
+
self.vocab_size = int(config.vocab_size)
|
|
270
|
+
draft_vocab_size = int(
|
|
271
|
+
getattr(config, "draft_vocab_size", None) or self.vocab_size
|
|
272
|
+
)
|
|
273
|
+
self.d2t: nn.Parameter | None = None
|
|
274
|
+
if draft_vocab_size != self.vocab_size:
|
|
275
|
+
self.d2t = nn.Parameter(
|
|
276
|
+
torch.zeros(draft_vocab_size, dtype=torch.long), requires_grad=False
|
|
277
|
+
)
|
|
278
|
+
self._pending_context: tuple[torch.Tensor, ...] | None = None
|
|
279
|
+
self._cache_args = None
|
|
280
|
+
self._noise_keys: tuple[torch.Tensor, torch.Tensor] | None = None
|
|
281
|
+
# FLASHMAP_FP8=1: e4m3 copies of the large GEMM weights (per output
|
|
282
|
+
# channel), activations quantized per token (sm89+).
|
|
283
|
+
self.fp8_weights = os.environ.get("FLASHMAP_FP8", "0") == "1"
|
|
284
|
+
self.fp8_linears: dict[str, tuple[torch.Tensor, torch.Tensor]] = {}
|
|
285
|
+
self.kv_fp8 = False
|
|
286
|
+
self.kv_scales: torch.Tensor | None = None
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def layers(self) -> nn.ModuleList:
|
|
290
|
+
return self.model.layers
|
|
291
|
+
|
|
292
|
+
# ------------------------------------------------------------------ loading
|
|
293
|
+
|
|
294
|
+
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
|
295
|
+
params = dict(self.named_parameters())
|
|
296
|
+
loaded = set()
|
|
297
|
+
for checkpoint_name, tensor in weights:
|
|
298
|
+
if checkpoint_name in ("embed_tokens.weight", "lm_head.weight", "t2d"):
|
|
299
|
+
continue # the target's own, shared at load
|
|
300
|
+
if checkpoint_name in ("ids", "counts"):
|
|
301
|
+
# A release folder's draft_vocab.safetensors: the target's
|
|
302
|
+
# tokens, most frequent first; the draft head keeps the first
|
|
303
|
+
# draft_vocab_size of them.
|
|
304
|
+
if checkpoint_name == "ids" and self.d2t is not None:
|
|
305
|
+
size = self.d2t.shape[0]
|
|
306
|
+
hot = tensor[:size].to(torch.int64).sort().values
|
|
307
|
+
self.d2t.data.copy_(hot - torch.arange(size, device=hot.device))
|
|
308
|
+
loaded.add("d2t")
|
|
309
|
+
continue
|
|
310
|
+
name = (
|
|
311
|
+
checkpoint_name
|
|
312
|
+
if checkpoint_name == "d2t"
|
|
313
|
+
else "model." + (checkpoint_name)
|
|
314
|
+
)
|
|
315
|
+
param = params.get(name)
|
|
316
|
+
if param is None:
|
|
317
|
+
raise ValueError(f"unexpected FlashMap weight {checkpoint_name!r}")
|
|
318
|
+
if param.shape != tensor.shape:
|
|
319
|
+
raise ValueError(
|
|
320
|
+
f"FlashMap weight {checkpoint_name!r} has shape "
|
|
321
|
+
f"{tuple(tensor.shape)}, expected {tuple(param.shape)}"
|
|
322
|
+
)
|
|
323
|
+
param.data.copy_(tensor)
|
|
324
|
+
loaded.add(name)
|
|
325
|
+
# The attention layers' own (KV-cache scale) parameters are not weights.
|
|
326
|
+
missing = {name for name in params if ".attn." not in name} - loaded
|
|
327
|
+
if missing:
|
|
328
|
+
raise ValueError(f"FlashMap checkpoint is missing {sorted(missing)}")
|
|
329
|
+
return set(params)
|
|
330
|
+
|
|
331
|
+
@torch.no_grad()
|
|
332
|
+
def prepare(self) -> None: # noqa: C901
|
|
333
|
+
"""Precompute what the draft step needs, once the target's embedding
|
|
334
|
+
(``model.embed_tokens``) and LM head (``lm_head``) are attached."""
|
|
335
|
+
model = self.model
|
|
336
|
+
embed_tokens = model.embed_tokens.weight[: self.vocab_size]
|
|
337
|
+
lm_head = self.lm_head.weight[: self.vocab_size]
|
|
338
|
+
device, dtype = model.fc.weight.device, self.dtype
|
|
339
|
+
attn = self.layers[0].cross_attn
|
|
340
|
+
self.num_heads, self.head_dim = attn.num_heads, attn.head_dim
|
|
341
|
+
self.dim = self.num_heads * self.head_dim
|
|
342
|
+
self.softmax_scale = self.head_dim**-0.5
|
|
343
|
+
self._check_attention()
|
|
344
|
+
|
|
345
|
+
# AdaLN modulations for each jump (s, t) of the grid.
|
|
346
|
+
self.modulations, self.final_modulations = [], []
|
|
347
|
+
grid = torch.tensor(self.time_grid, device=device, dtype=torch.float32)
|
|
348
|
+
scale = float(self.config.time_scale)
|
|
349
|
+
for step in range(self.num_steps):
|
|
350
|
+
cond = model.time_embed(grid[step : step + 1] * scale) + model.jump_embed(
|
|
351
|
+
grid[step + 1 : step + 2] * scale
|
|
352
|
+
)
|
|
353
|
+
per_layer = []
|
|
354
|
+
for layer in self.layers:
|
|
355
|
+
for adaln in (
|
|
356
|
+
layer.self_attn_adaln,
|
|
357
|
+
layer.cross_attn_adaln,
|
|
358
|
+
layer.mlp_adaln,
|
|
359
|
+
):
|
|
360
|
+
mod_scale, shift, gate = adaln(cond)
|
|
361
|
+
per_layer += [(1 + mod_scale), shift, gate]
|
|
362
|
+
# (layers, 9, dim): (1 + scale, shift, gate) for self, cross, mlp.
|
|
363
|
+
self.modulations.append(
|
|
364
|
+
torch.cat(per_layer).view(len(self.layers), 9, -1).contiguous()
|
|
365
|
+
)
|
|
366
|
+
f_scale, f_shift, _ = model.final_adaln(cond)
|
|
367
|
+
self.final_modulations.append(
|
|
368
|
+
torch.cat([1 + f_scale, f_shift]).contiguous()
|
|
369
|
+
)
|
|
370
|
+
gate = model.noise_gate.float()
|
|
371
|
+
if self.config.noise_gate == "jump":
|
|
372
|
+
self.noise_gates = [
|
|
373
|
+
float(gate * (grid[s + 1] - grid[s])) for s in range(self.num_steps)
|
|
374
|
+
]
|
|
375
|
+
else:
|
|
376
|
+
self.noise_gates = [float(gate)] * self.num_steps
|
|
377
|
+
|
|
378
|
+
# embed_state at t = 0: the uniform posterior's embedding (bf16, as the
|
|
379
|
+
# reference forms softmax(0) @ E) through state_norm.
|
|
380
|
+
vocab = embed_tokens.shape[0]
|
|
381
|
+
uniform = torch.full((1, vocab), 1.0 / vocab, device=device, dtype=dtype)
|
|
382
|
+
self.first_hidden = model.x_embed(model.state_norm(uniform @ embed_tokens))[0]
|
|
383
|
+
self.noise_embed, self.noise_eigvals = fold_noise(
|
|
384
|
+
embed_tokens, model.noise_norm.weight, model.x_embed.weight
|
|
385
|
+
)
|
|
386
|
+
self.embed_weight = embed_tokens
|
|
387
|
+
|
|
388
|
+
# Context keys/values of every layer in one GEMM; self-attention
|
|
389
|
+
# queries, keys and values in one GEMM per layer.
|
|
390
|
+
self.context_kv_weight = torch.cat(
|
|
391
|
+
[
|
|
392
|
+
torch.cat(
|
|
393
|
+
[layer.cross_attn.k_proj.weight, layer.cross_attn.v_proj.weight]
|
|
394
|
+
)
|
|
395
|
+
for layer in self.layers
|
|
396
|
+
]
|
|
397
|
+
)
|
|
398
|
+
self.context_k_norm = torch.stack(
|
|
399
|
+
[layer.cross_attn.k_norm.weight.float() for layer in self.layers]
|
|
400
|
+
).contiguous()
|
|
401
|
+
self.self_qkv_weights = [
|
|
402
|
+
torch.cat(
|
|
403
|
+
[
|
|
404
|
+
layer.self_attn.q_proj.weight,
|
|
405
|
+
layer.self_attn.k_proj.weight,
|
|
406
|
+
layer.self_attn.v_proj.weight,
|
|
407
|
+
]
|
|
408
|
+
)
|
|
409
|
+
for layer in self.layers
|
|
410
|
+
]
|
|
411
|
+
for layer in self.layers: # drop the now-duplicated projections
|
|
412
|
+
for proj in (
|
|
413
|
+
layer.cross_attn.k_proj,
|
|
414
|
+
layer.cross_attn.v_proj,
|
|
415
|
+
layer.self_attn.q_proj,
|
|
416
|
+
layer.self_attn.k_proj,
|
|
417
|
+
layer.self_attn.v_proj,
|
|
418
|
+
):
|
|
419
|
+
proj.weight = nn.Parameter(
|
|
420
|
+
proj.weight.new_empty(0), requires_grad=False
|
|
421
|
+
)
|
|
422
|
+
rope = getattr(self.config, "rope_parameters", None) or {}
|
|
423
|
+
self.cos_sin = rope_table(
|
|
424
|
+
self.max_model_len + self.block_size + 1,
|
|
425
|
+
self.head_dim,
|
|
426
|
+
float(rope.get("rope_theta", getattr(self.config, "rope_theta", 1e4))),
|
|
427
|
+
device,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# The DiT head folded into the LM head (no nonlinearity between them).
|
|
431
|
+
self.hot_ids = None
|
|
432
|
+
if self.d2t is not None:
|
|
433
|
+
self.hot_ids = torch.arange(self.d2t.shape[0], device=device) + self.d2t
|
|
434
|
+
head = model.head
|
|
435
|
+
self.head_weight, self.head_bias = fold_head(
|
|
436
|
+
lm_head, head.weight, head.bias, self.hot_ids
|
|
437
|
+
)
|
|
438
|
+
self.jump_head = None
|
|
439
|
+
if self.num_steps > 1:
|
|
440
|
+
# Between jumps the state mixes in a full-vocabulary distribution.
|
|
441
|
+
self.jump_head = (
|
|
442
|
+
(self.head_weight, self.head_bias)
|
|
443
|
+
if self.hot_ids is None
|
|
444
|
+
else fold_head(lm_head, head.weight, head.bias)
|
|
445
|
+
)
|
|
446
|
+
self._prologue_fn = self._prologue
|
|
447
|
+
self._one_jump_fn = self._one_jump
|
|
448
|
+
self._dit_fn = self._dit
|
|
449
|
+
if self.compile:
|
|
450
|
+
self._prologue_fn = torch.compile(
|
|
451
|
+
self._prologue, dynamic=True, fullgraph=True
|
|
452
|
+
)
|
|
453
|
+
self._one_jump_fn = torch.compile(
|
|
454
|
+
self._one_jump, dynamic=True, fullgraph=True
|
|
455
|
+
)
|
|
456
|
+
self._dit_fn = torch.compile(self._dit, dynamic=True, fullgraph=True)
|
|
457
|
+
if self.fp8_weights:
|
|
458
|
+
self._quantize_weights()
|
|
459
|
+
torch.cuda.empty_cache()
|
|
460
|
+
logger.info(
|
|
461
|
+
"FlashMap draft: %d jump(s) on grid %s, draft vocabulary %d",
|
|
462
|
+
self.num_steps,
|
|
463
|
+
self.time_grid,
|
|
464
|
+
self.head_weight.shape[0],
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
def _quantize_weights(self) -> None:
|
|
468
|
+
"""e4m3 copies (per output channel) of the draft step's large GEMM
|
|
469
|
+
weights, for ``_lin``."""
|
|
470
|
+
if not current_platform.has_device_capability(89):
|
|
471
|
+
raise ValueError("FLASHMAP_FP8=1 needs fp8 GEMMs (Ada or Hopper GPUs)")
|
|
472
|
+
weights = {
|
|
473
|
+
"fc": self.model.fc.weight,
|
|
474
|
+
"context_kv": self.context_kv_weight,
|
|
475
|
+
"noise": self.noise_embed,
|
|
476
|
+
"head": self.head_weight,
|
|
477
|
+
}
|
|
478
|
+
for i, layer in enumerate(self.layers):
|
|
479
|
+
weights[f"self_qkv.{i}"] = self.self_qkv_weights[i]
|
|
480
|
+
weights[f"self_o.{i}"] = layer.self_attn.o_proj.weight
|
|
481
|
+
weights[f"cross_q.{i}"] = layer.cross_attn.q_proj.weight
|
|
482
|
+
weights[f"cross_o.{i}"] = layer.cross_attn.o_proj.weight
|
|
483
|
+
weights[f"fc1.{i}"] = layer.mlp.fc1.weight
|
|
484
|
+
weights[f"fc2.{i}"] = layer.mlp.fc2.weight
|
|
485
|
+
fp8 = current_platform.fp8_dtype()
|
|
486
|
+
self.fp8_linears = {
|
|
487
|
+
name: quantize_fp8_per_channel(weight, fp8)
|
|
488
|
+
for name, weight in weights.items()
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
def _check_attention(self) -> None:
|
|
492
|
+
for layer in self.layers:
|
|
493
|
+
attn = layer.cross_attn.attn
|
|
494
|
+
version = getattr(attn.impl, "vllm_flash_attn_version", None)
|
|
495
|
+
if version is None:
|
|
496
|
+
raise ValueError(
|
|
497
|
+
"FlashMap drafting needs the FlashAttention backend, got "
|
|
498
|
+
f"{type(attn.impl).__name__}"
|
|
499
|
+
)
|
|
500
|
+
kv_dtype = str(getattr(attn, "kv_cache_dtype", "auto"))
|
|
501
|
+
if kv_dtype in ("fp8", "fp8_e4m3"):
|
|
502
|
+
if version < 3: # noqa: PLR2004
|
|
503
|
+
raise ValueError(
|
|
504
|
+
"An fp8 FlashMap draft KV cache needs FlashAttention 3 "
|
|
505
|
+
"(Hopper)."
|
|
506
|
+
)
|
|
507
|
+
self.kv_fp8 = True
|
|
508
|
+
elif kv_dtype not in ("auto", "bfloat16"):
|
|
509
|
+
raise ValueError(
|
|
510
|
+
"FlashMap drafting supports a bf16 or fp8_e4m3 draft KV cache, "
|
|
511
|
+
f"got kv_cache_dtype={kv_dtype!r}"
|
|
512
|
+
)
|
|
513
|
+
self.fa_version = version
|
|
514
|
+
if self.kv_fp8:
|
|
515
|
+
# Per-tensor scales of vLLM's fp8 KV caches (1 unless calibrated).
|
|
516
|
+
self.kv_scales = torch.stack(
|
|
517
|
+
[
|
|
518
|
+
torch.stack(
|
|
519
|
+
[
|
|
520
|
+
layer.cross_attn.attn._k_scale.float().reshape(()), # noqa: SLF001
|
|
521
|
+
layer.cross_attn.attn._v_scale.float().reshape(()), # noqa: SLF001
|
|
522
|
+
]
|
|
523
|
+
)
|
|
524
|
+
for layer in self.layers
|
|
525
|
+
]
|
|
526
|
+
).to(self.model.fc.weight.device)
|
|
527
|
+
self.q_scales = [
|
|
528
|
+
float(layer.cross_attn.attn._q_scale.float()) # noqa: SLF001
|
|
529
|
+
for layer in self.layers
|
|
530
|
+
]
|
|
531
|
+
|
|
532
|
+
def set_noise_keys(self, seeds: torch.Tensor, rows: torch.Tensor) -> None:
|
|
533
|
+
"""Key each query row's noise by ``seeds[rows[i]]`` and its position."""
|
|
534
|
+
self._noise_keys = (seeds, rows)
|
|
535
|
+
|
|
536
|
+
# ------------------------------------------------------------------ context
|
|
537
|
+
|
|
538
|
+
def combine_hidden_states(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
539
|
+
return self._lin(hidden_states, "fc", self.model.fc.weight)
|
|
540
|
+
|
|
541
|
+
def _lin(
|
|
542
|
+
self,
|
|
543
|
+
x: torch.Tensor,
|
|
544
|
+
name: str,
|
|
545
|
+
weight: torch.Tensor,
|
|
546
|
+
bias: torch.Tensor | None = None,
|
|
547
|
+
) -> torch.Tensor:
|
|
548
|
+
"""``F.linear``, or vLLM's fp8 scaled mm for a weight quantized at load
|
|
549
|
+
(per output channel, activations per token)."""
|
|
550
|
+
quantized = self.fp8_linears.get(name)
|
|
551
|
+
if quantized is None:
|
|
552
|
+
return F.linear(x, weight, bias)
|
|
553
|
+
w_q, w_scale = quantized
|
|
554
|
+
rows = x.reshape(-1, x.shape[-1]).contiguous()
|
|
555
|
+
x_q, x_scale = ops.scaled_fp8_quant(rows, use_per_token_if_dynamic=True)
|
|
556
|
+
out = ops.cutlass_scaled_mm(
|
|
557
|
+
x_q, w_q, scale_a=x_scale, scale_b=w_scale, out_dtype=x.dtype, bias=bias
|
|
558
|
+
)
|
|
559
|
+
return out.view(*x.shape[:-1], w_q.shape[1])
|
|
560
|
+
|
|
561
|
+
def cache_layout(self):
|
|
562
|
+
"""Data pointers and strides of every layer's KV cache, or None before
|
|
563
|
+
the caches are allocated."""
|
|
564
|
+
caches = [layer.cross_attn.attn.kv_cache for layer in self.layers]
|
|
565
|
+
if not all(isinstance(c, torch.Tensor) and c.numel() for c in caches):
|
|
566
|
+
return None
|
|
567
|
+
ptrs = [c.data_ptr() for c in caches]
|
|
568
|
+
if self._cache_args is None or self._cache_args[0] != ptrs:
|
|
569
|
+
expected = (
|
|
570
|
+
(torch.uint8, torch.float8_e4m3fn) if self.kv_fp8 else (torch.bfloat16,)
|
|
571
|
+
)
|
|
572
|
+
if caches[0].dtype not in expected:
|
|
573
|
+
raise ValueError(
|
|
574
|
+
f"unexpected FlashMap draft KV cache dtype {caches[0].dtype}"
|
|
575
|
+
)
|
|
576
|
+
key, value = caches[0].transpose(1, 2).split(self.head_dim, dim=-1)
|
|
577
|
+
assert key.stride(-1) == 1
|
|
578
|
+
assert all(c.stride() == caches[0].stride() for c in caches)
|
|
579
|
+
strides = (
|
|
580
|
+
key.stride(0),
|
|
581
|
+
key.stride(2),
|
|
582
|
+
key.stride(1),
|
|
583
|
+
(value.data_ptr() - key.data_ptr()) // key.element_size(),
|
|
584
|
+
)
|
|
585
|
+
ptr_tensor = torch.tensor(ptrs, dtype=torch.int64, device=caches[0].device)
|
|
586
|
+
self._cache_args = (ptrs, ptr_tensor, strides, key.shape[1])
|
|
587
|
+
return self._cache_args
|
|
588
|
+
|
|
589
|
+
def _write(self, kv: torch.Tensor, positions, slots) -> None:
|
|
590
|
+
layout = self.cache_layout()
|
|
591
|
+
if layout is None:
|
|
592
|
+
return
|
|
593
|
+
_, ptrs, strides, block_size = layout
|
|
594
|
+
write_context_kv(
|
|
595
|
+
kv.view(-1, len(self.layers), 2, self.num_heads, self.head_dim),
|
|
596
|
+
positions,
|
|
597
|
+
slots,
|
|
598
|
+
self.context_k_norm,
|
|
599
|
+
self.cos_sin,
|
|
600
|
+
ptrs,
|
|
601
|
+
strides,
|
|
602
|
+
block_size,
|
|
603
|
+
self.layers[0].cross_attn.k_norm.eps,
|
|
604
|
+
self.kv_scales,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
def precompute_and_store_context_kv(
|
|
608
|
+
self,
|
|
609
|
+
context_states: torch.Tensor,
|
|
610
|
+
context_positions: torch.Tensor,
|
|
611
|
+
context_slot_mapping: torch.Tensor | None = None,
|
|
612
|
+
) -> None:
|
|
613
|
+
context = self.model.hidden_norm(context_states)
|
|
614
|
+
kv = self._lin(context, "context_kv", self.context_kv_weight)
|
|
615
|
+
if context_slot_mapping is not None:
|
|
616
|
+
self._write(kv, context_positions, context_slot_mapping)
|
|
617
|
+
|
|
618
|
+
def defer_context_kv(
|
|
619
|
+
self,
|
|
620
|
+
context_states: torch.Tensor,
|
|
621
|
+
context_positions: torch.Tensor,
|
|
622
|
+
context_slot_mapping: torch.Tensor,
|
|
623
|
+
) -> None:
|
|
624
|
+
"""``precompute_and_store_context_kv``, done by the next ``forward``
|
|
625
|
+
together with its anchors (one GEMM over the context K/V weight)."""
|
|
626
|
+
self._pending_context = (
|
|
627
|
+
context_states,
|
|
628
|
+
context_positions,
|
|
629
|
+
context_slot_mapping,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
def _prologue(
|
|
633
|
+
self,
|
|
634
|
+
input_ids: torch.Tensor,
|
|
635
|
+
positions: torch.Tensor,
|
|
636
|
+
seq_lens: torch.Tensor,
|
|
637
|
+
slot_mapping: torch.Tensor,
|
|
638
|
+
context: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None,
|
|
639
|
+
):
|
|
640
|
+
"""Keys/values, positions and slots to write (the pending context, then
|
|
641
|
+
each request's anchor) and each request's context length ``p + 1``."""
|
|
642
|
+
model = self.model
|
|
643
|
+
blocks = input_ids.shape[0] // self.block_size
|
|
644
|
+
anchors = input_ids.view(blocks, self.block_size)[:, 0]
|
|
645
|
+
anchor_positions = positions.view(blocks, self.block_size)[:, 0]
|
|
646
|
+
slots = slot_mapping.view(blocks, self.block_size)[:, 0]
|
|
647
|
+
rows = model.anchor_norm(
|
|
648
|
+
model.anchor_proj(F.embedding(anchors, self.embed_weight))
|
|
649
|
+
)
|
|
650
|
+
write_positions = anchor_positions
|
|
651
|
+
if context is not None:
|
|
652
|
+
states, context_positions, context_slots = context
|
|
653
|
+
rows = torch.cat([model.hidden_norm(states), rows])
|
|
654
|
+
write_positions = torch.cat([context_positions, anchor_positions])
|
|
655
|
+
slots = torch.cat([context_slots, slots])
|
|
656
|
+
kv = self._lin(rows, "context_kv", self.context_kv_weight)
|
|
657
|
+
# The context is positions 0..p. (The DFlash metadata's seq_lens count
|
|
658
|
+
# the block's other rows too, clamped to the model length.)
|
|
659
|
+
seqused_k = torch.where(
|
|
660
|
+
seq_lens > 0, anchor_positions.to(seq_lens.dtype) + 1, 0
|
|
661
|
+
)
|
|
662
|
+
return kv, write_positions, slots, seqused_k
|
|
663
|
+
|
|
664
|
+
# ------------------------------------------------------------------ drafting
|
|
665
|
+
|
|
666
|
+
def _dit(
|
|
667
|
+
self,
|
|
668
|
+
hidden: torch.Tensor,
|
|
669
|
+
positions: torch.Tensor,
|
|
670
|
+
modulations: torch.Tensor,
|
|
671
|
+
final_modulations: torch.Tensor,
|
|
672
|
+
kv_caches: list[torch.Tensor],
|
|
673
|
+
attention: tuple | None,
|
|
674
|
+
) -> torch.Tensor:
|
|
675
|
+
"""The DiT on ``hidden (tokens, dim)`` for query rows at ``positions``
|
|
676
|
+
(slots at ``positions + 1``) with one jump's modulations; returns the
|
|
677
|
+
final AdaLN output."""
|
|
678
|
+
tokens = hidden.shape[0]
|
|
679
|
+
blocks = tokens // self.block_size
|
|
680
|
+
heads, head_dim, dim = self.num_heads, self.head_dim, self.dim
|
|
681
|
+
eps = self.config.adaln_eps
|
|
682
|
+
slot_positions = positions + 1
|
|
683
|
+
for layer_id, layer in enumerate(self.layers):
|
|
684
|
+
mods = modulations[layer_id]
|
|
685
|
+
self_attn, cross_attn = layer.self_attn, layer.cross_attn
|
|
686
|
+
normed = F.layer_norm(hidden, (dim,), eps=eps) * mods[0] + mods[1]
|
|
687
|
+
qkv = self._lin(
|
|
688
|
+
normed, f"self_qkv.{layer_id}", self.self_qkv_weights[layer_id]
|
|
689
|
+
)
|
|
690
|
+
attn = block_self_attention(
|
|
691
|
+
qkv.view(blocks, self.block_size, 3, heads, head_dim),
|
|
692
|
+
self_attn.q_norm.weight,
|
|
693
|
+
self_attn.k_norm.weight,
|
|
694
|
+
self.cos_sin,
|
|
695
|
+
slot_positions,
|
|
696
|
+
self_attn.q_norm.eps,
|
|
697
|
+
)
|
|
698
|
+
hidden = hidden + mods[2] * self._lin(
|
|
699
|
+
attn, f"self_o.{layer_id}", self_attn.o_proj.weight
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
normed = F.layer_norm(hidden, (dim,), eps=eps) * mods[3] + mods[4]
|
|
703
|
+
query = rms_norm_rope(
|
|
704
|
+
self._lin(normed, f"cross_q.{layer_id}", cross_attn.q_proj.weight).view(
|
|
705
|
+
tokens, heads, head_dim
|
|
706
|
+
),
|
|
707
|
+
cross_attn.q_norm.weight,
|
|
708
|
+
self.cos_sin,
|
|
709
|
+
slot_positions,
|
|
710
|
+
cross_attn.q_norm.eps,
|
|
711
|
+
)
|
|
712
|
+
if attention is None: # profiling run: no KV cache yet
|
|
713
|
+
context = torch.zeros_like(query)
|
|
714
|
+
else:
|
|
715
|
+
cu_seqlens_q, seqused_k, block_table, schedule, queries = attention
|
|
716
|
+
descales = None
|
|
717
|
+
if self.kv_fp8:
|
|
718
|
+
q_scale = self.q_scales[layer_id]
|
|
719
|
+
query = (query.float() / q_scale).to(torch.float8_e4m3fn)
|
|
720
|
+
descales = torch.stack(
|
|
721
|
+
[
|
|
722
|
+
self.kv_scales.new_full((), q_scale),
|
|
723
|
+
self.kv_scales[layer_id, 0],
|
|
724
|
+
self.kv_scales[layer_id, 1],
|
|
725
|
+
]
|
|
726
|
+
)[:, None, None].expand(3, seqused_k.shape[0], heads)
|
|
727
|
+
context = paged_cross_attention(
|
|
728
|
+
query,
|
|
729
|
+
kv_caches[layer_id],
|
|
730
|
+
cu_seqlens_q,
|
|
731
|
+
seqused_k,
|
|
732
|
+
block_table,
|
|
733
|
+
schedule,
|
|
734
|
+
descales,
|
|
735
|
+
queries,
|
|
736
|
+
self.max_model_len,
|
|
737
|
+
self.softmax_scale,
|
|
738
|
+
self.fa_version,
|
|
739
|
+
)
|
|
740
|
+
hidden = hidden + mods[5] * self._lin(
|
|
741
|
+
context.view(tokens, dim),
|
|
742
|
+
f"cross_o.{layer_id}",
|
|
743
|
+
cross_attn.o_proj.weight,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
normed = F.layer_norm(hidden, (dim,), eps=eps) * mods[6] + mods[7]
|
|
747
|
+
mlp = layer.mlp
|
|
748
|
+
hidden = hidden + mods[8] * self._lin(
|
|
749
|
+
F.gelu(
|
|
750
|
+
self._lin(normed, f"fc1.{layer_id}", mlp.fc1.weight, mlp.fc1.bias)
|
|
751
|
+
),
|
|
752
|
+
f"fc2.{layer_id}",
|
|
753
|
+
mlp.fc2.weight,
|
|
754
|
+
mlp.fc2.bias,
|
|
755
|
+
)
|
|
756
|
+
normed = F.layer_norm(hidden, (dim,), eps=eps)
|
|
757
|
+
return normed * final_modulations[0] + final_modulations[1]
|
|
758
|
+
|
|
759
|
+
def _one_jump(
|
|
760
|
+
self,
|
|
761
|
+
z: torch.Tensor,
|
|
762
|
+
positions: torch.Tensor,
|
|
763
|
+
kv_caches: list[torch.Tensor],
|
|
764
|
+
attention: tuple | None,
|
|
765
|
+
) -> torch.Tensor:
|
|
766
|
+
"""One jump from noise ``z (tokens, hidden)``: its embedded state (the
|
|
767
|
+
uniform posterior's constant plus the folded noise read-out), then the
|
|
768
|
+
DiT."""
|
|
769
|
+
rms = torch.rsqrt((z * z) @ self.noise_eigvals + self.model.noise_norm.eps)
|
|
770
|
+
noise = self._lin(z.to(self.dtype), "noise", self.noise_embed)
|
|
771
|
+
hidden = (
|
|
772
|
+
self.first_hidden
|
|
773
|
+
+ (self.noise_gates[0] * rms)[:, None].to(self.dtype) * noise
|
|
774
|
+
)
|
|
775
|
+
return self._dit(
|
|
776
|
+
hidden,
|
|
777
|
+
positions,
|
|
778
|
+
self.modulations[0],
|
|
779
|
+
self.final_modulations[0],
|
|
780
|
+
kv_caches,
|
|
781
|
+
attention,
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
def _embed_state(self, state: torch.Tensor, step: int) -> torch.Tensor:
|
|
785
|
+
"""``x_embed(embed_state(state, s, t))`` of a vocabulary-sized float32
|
|
786
|
+
``state`` at jump ``step``, as the reference computes it."""
|
|
787
|
+
model, weight = self.model, self.embed_weight
|
|
788
|
+
start = self.time_grid[step]
|
|
789
|
+
evidence = start / max(1.0 - start, 1e-3) ** 2
|
|
790
|
+
posterior = (state * evidence).softmax(dim=-1)
|
|
791
|
+
both = torch.cat([posterior, state]).to(weight.dtype) @ weight
|
|
792
|
+
posterior_emb, state_emb = both.split(state.shape[0])
|
|
793
|
+
embedded = model.state_norm(posterior_emb) + (
|
|
794
|
+
self.noise_gates[step] * model.noise_norm(state_emb)
|
|
795
|
+
).to(weight.dtype)
|
|
796
|
+
return model.x_embed(embedded)
|
|
797
|
+
|
|
798
|
+
def _noise(self, positions: torch.Tensor, n_cols: int) -> torch.Tensor:
|
|
799
|
+
tokens = positions.shape[0]
|
|
800
|
+
if self._noise_keys is None:
|
|
801
|
+
noise = torch.randn(
|
|
802
|
+
tokens, n_cols, device=positions.device, dtype=torch.float32
|
|
803
|
+
)
|
|
804
|
+
return noise if self.noise_scale == 1.0 else noise * self.noise_scale
|
|
805
|
+
seeds, rows = self._noise_keys
|
|
806
|
+
return seeded_normal(seeds, rows[:tokens], positions, n_cols, self.noise_scale)
|
|
807
|
+
|
|
808
|
+
def _cross_attention_schedule(self, metadata, seqused_k):
|
|
809
|
+
"""FlashAttention 3's tile schedule for the step's cross-attention,
|
|
810
|
+
shared by every layer (it depends only on shapes and lengths)."""
|
|
811
|
+
if self.fa_version != 3 or get_scheduler_metadata is None: # noqa: PLR2004
|
|
812
|
+
return None
|
|
813
|
+
caches = self.layers[0].cross_attn.attn.kv_cache
|
|
814
|
+
return get_scheduler_metadata(
|
|
815
|
+
batch_size=seqused_k.shape[0],
|
|
816
|
+
max_seqlen_q=self.block_size,
|
|
817
|
+
max_seqlen_k=self.max_model_len,
|
|
818
|
+
num_heads_q=self.num_heads,
|
|
819
|
+
num_heads_kv=self.num_heads,
|
|
820
|
+
headdim=self.head_dim,
|
|
821
|
+
cache_seqlens=seqused_k,
|
|
822
|
+
qkv_dtype=current_platform.fp8_dtype() if self.kv_fp8 else self.dtype,
|
|
823
|
+
cu_seqlens_q=metadata.query_start_loc,
|
|
824
|
+
page_size=caches.shape[2],
|
|
825
|
+
causal=False,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
def forward(
|
|
829
|
+
self,
|
|
830
|
+
input_ids: torch.Tensor,
|
|
831
|
+
positions: torch.Tensor,
|
|
832
|
+
inputs_embeds: torch.Tensor | None = None, # noqa: ARG002
|
|
833
|
+
) -> torch.Tensor:
|
|
834
|
+
tokens = input_ids.shape[0]
|
|
835
|
+
kv_caches = [layer.cross_attn.attn.kv_cache for layer in self.layers]
|
|
836
|
+
metadata = get_forward_context().attn_metadata
|
|
837
|
+
pending, self._pending_context = self._pending_context, None
|
|
838
|
+
attention = None
|
|
839
|
+
if metadata is not None:
|
|
840
|
+
metadata = metadata[self.layers[0].cross_attn.attn.layer_name]
|
|
841
|
+
kv, write_positions, write_slots, seqused_k = self._prologue_fn(
|
|
842
|
+
input_ids,
|
|
843
|
+
positions,
|
|
844
|
+
metadata.seq_lens,
|
|
845
|
+
metadata.slot_mapping[:tokens],
|
|
846
|
+
pending,
|
|
847
|
+
)
|
|
848
|
+
self._write(kv, write_positions, write_slots)
|
|
849
|
+
attention = (
|
|
850
|
+
metadata.query_start_loc,
|
|
851
|
+
seqused_k,
|
|
852
|
+
metadata.block_table,
|
|
853
|
+
self._cross_attention_schedule(metadata, seqused_k),
|
|
854
|
+
self.block_size,
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
if self.num_steps == 1:
|
|
858
|
+
z = self._noise(positions, self.noise_embed.shape[1])
|
|
859
|
+
return self._one_jump_fn(z, positions, kv_caches, attention)
|
|
860
|
+
|
|
861
|
+
# Several jumps (Algorithm 8): x <- X_{t_n, t_n+1}(x) between jumps.
|
|
862
|
+
state = self._noise(positions, self.vocab_size)
|
|
863
|
+
for step in range(self.num_steps):
|
|
864
|
+
hidden = self._dit_fn(
|
|
865
|
+
self._embed_state(state, step),
|
|
866
|
+
positions,
|
|
867
|
+
self.modulations[step],
|
|
868
|
+
self.final_modulations[step],
|
|
869
|
+
kv_caches,
|
|
870
|
+
attention,
|
|
871
|
+
)
|
|
872
|
+
if step + 1 < self.num_steps:
|
|
873
|
+
weight, bias = self.jump_head
|
|
874
|
+
delta = F.linear(hidden, weight, bias).float().softmax(dim=-1)
|
|
875
|
+
start, end = self.time_grid[step], self.time_grid[step + 1]
|
|
876
|
+
state = ((1.0 - end) * state + (end - start) * delta) / (1.0 - start)
|
|
877
|
+
return hidden
|
|
878
|
+
|
|
879
|
+
def compute_draft_logits(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
880
|
+
"""Logits over the draft vocabulary (``hot_ids`` when restricted)."""
|
|
881
|
+
return self._lin(hidden_states, "head", self.head_weight, self.head_bias)
|