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/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from transformers import AutoConfig, AutoModel
|
|
2
|
+
|
|
3
|
+
from .configuration_flashmap import FlashMapConfig
|
|
4
|
+
from .modeling_flashmap import FlashMapCache, FlashMapDraftModel, FlashMapGenerateOutput
|
|
5
|
+
|
|
6
|
+
# With the package imported, AutoModel loads FlashMap folders without trust_remote_code,
|
|
7
|
+
# and save_pretrained writes the model code next to the weights, so that the folder
|
|
8
|
+
# loads with trust_remote_code=True alone.
|
|
9
|
+
AutoConfig.register(FlashMapConfig.model_type, FlashMapConfig, exist_ok=True)
|
|
10
|
+
AutoModel.register(FlashMapConfig, FlashMapDraftModel, exist_ok=True)
|
|
11
|
+
FlashMapConfig.register_for_auto_class()
|
|
12
|
+
FlashMapDraftModel.register_for_auto_class("AutoModel")
|
|
13
|
+
|
|
14
|
+
__all__ = ["FlashMapCache", "FlashMapConfig", "FlashMapDraftModel", "FlashMapGenerateOutput"]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Configuration of a FlashMap draft model."""
|
|
2
|
+
|
|
3
|
+
from transformers import PretrainedConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Jump grids of the "decoding_error" time schedule (Flow Map Language Models,
|
|
7
|
+
# eq. 25) at the Qwen3 vocabulary size, with the endpoints set to exactly 0 and 1.
|
|
8
|
+
# A draft with ``n`` steps jumps from grid[i] to grid[i + 1] for i < n.
|
|
9
|
+
DEFAULT_JUMP_GRIDS = {
|
|
10
|
+
"1": [0.0, 1.0],
|
|
11
|
+
"2": [0.0, 0.8172569274902344, 1.0],
|
|
12
|
+
"3": [0.0, 0.8010796308517456, 0.8310279846191406, 1.0],
|
|
13
|
+
"4": [0.0, 0.7906050682067871, 0.8172569274902344, 0.8379518389701843, 1.0],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FlashMapConfig(PretrainedConfig):
|
|
18
|
+
r"""A FlashMap drafter for speculative decoding.
|
|
19
|
+
|
|
20
|
+
FlashMap drafts a block of ``block_size`` tokens after the last verified one
|
|
21
|
+
(the anchor) with a flow map: a DiT that jumps from Gaussian noise over the
|
|
22
|
+
target's vocabulary to the block's per-slot token distributions, conditioned on
|
|
23
|
+
the target model's hidden states through cross-attention.
|
|
24
|
+
|
|
25
|
+
The drafter borrows the target's token embedding and lm_head, which are not part
|
|
26
|
+
of its weights.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
target_model (`str`): Hub id of the target model the drafter was trained for.
|
|
30
|
+
target_hidden_size (`int`): Hidden size of the target model.
|
|
31
|
+
vocab_size (`int`): Vocabulary size of the target model.
|
|
32
|
+
target_layer_ids (`list[int]`): Target decoder layers whose outputs (that is,
|
|
33
|
+
`hidden_states[i + 1]` of a transformers forward) are the drafter's context.
|
|
34
|
+
block_size (`int`): Number of draft tokens per block.
|
|
35
|
+
hidden_size (`int`): Width of the DiT.
|
|
36
|
+
num_hidden_layers (`int`): Number of DiT blocks.
|
|
37
|
+
num_attention_heads (`int`): Attention heads of the DiT.
|
|
38
|
+
intermediate_size (`int`): MLP width of the DiT.
|
|
39
|
+
cond_dim (`int`): Width of the time conditioning.
|
|
40
|
+
time_embed_dim (`int`): Size of the sinusoidal time features.
|
|
41
|
+
time_scale (`float`): Times in [0, 1] are multiplied by this before their
|
|
42
|
+
sinusoidal embedding.
|
|
43
|
+
rope_theta (`float`): RoPE base. RoPE rotates interleaved pairs of channels.
|
|
44
|
+
rms_norm_eps (`float`): Epsilon of the context, anchor and state RMSNorms.
|
|
45
|
+
qk_norm_eps (`float`): Epsilon of the query/key RMSNorms.
|
|
46
|
+
layer_norm_eps (`float`): Epsilon of the adaptive LayerNorms.
|
|
47
|
+
noise_gate (`str`): How the noise term of the state embedding is weighted:
|
|
48
|
+
"jump" by `noise_gate * (t - s)` for a jump from `s` to `t`, "constant" by
|
|
49
|
+
`noise_gate` alone.
|
|
50
|
+
time_schedule (`str`): The schedule the jump grids come from.
|
|
51
|
+
num_steps (`int`): Default number of jumps per draft.
|
|
52
|
+
jump_grids (`dict[str, list[float]]`): Jump times for each number of steps.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
model_type = "flashmap"
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
target_model: str = "Qwen/Qwen3-8B",
|
|
60
|
+
target_hidden_size: int = 4096,
|
|
61
|
+
vocab_size: int = 151936,
|
|
62
|
+
target_layer_ids=(1, 9, 17, 25, 33),
|
|
63
|
+
block_size: int = 16,
|
|
64
|
+
hidden_size: int = 2048,
|
|
65
|
+
num_hidden_layers: int = 6,
|
|
66
|
+
num_attention_heads: int = 16,
|
|
67
|
+
intermediate_size: int = 8192,
|
|
68
|
+
cond_dim: int = 256,
|
|
69
|
+
time_embed_dim: int = 256,
|
|
70
|
+
time_scale: float = 1000.0,
|
|
71
|
+
rope_theta: float = 10000.0,
|
|
72
|
+
rms_norm_eps: float = 1e-6,
|
|
73
|
+
qk_norm_eps: float = 1e-5,
|
|
74
|
+
layer_norm_eps: float = 1e-6,
|
|
75
|
+
noise_gate: str = "jump",
|
|
76
|
+
time_schedule: str = "decoding_error",
|
|
77
|
+
num_steps: int = 1,
|
|
78
|
+
jump_grids=None,
|
|
79
|
+
**kwargs,
|
|
80
|
+
):
|
|
81
|
+
self.target_model = target_model
|
|
82
|
+
self.target_hidden_size = target_hidden_size
|
|
83
|
+
self.vocab_size = vocab_size
|
|
84
|
+
self.target_layer_ids = list(target_layer_ids)
|
|
85
|
+
self.block_size = block_size
|
|
86
|
+
self.hidden_size = hidden_size
|
|
87
|
+
self.num_hidden_layers = num_hidden_layers
|
|
88
|
+
self.num_attention_heads = num_attention_heads
|
|
89
|
+
self.intermediate_size = intermediate_size
|
|
90
|
+
self.cond_dim = cond_dim
|
|
91
|
+
self.time_embed_dim = time_embed_dim
|
|
92
|
+
self.time_scale = time_scale
|
|
93
|
+
self.rope_theta = rope_theta
|
|
94
|
+
self.rms_norm_eps = rms_norm_eps
|
|
95
|
+
self.qk_norm_eps = qk_norm_eps
|
|
96
|
+
self.layer_norm_eps = layer_norm_eps
|
|
97
|
+
self.noise_gate = noise_gate
|
|
98
|
+
self.time_schedule = time_schedule
|
|
99
|
+
self.num_steps = num_steps
|
|
100
|
+
self.jump_grids = dict(DEFAULT_JUMP_GRIDS if jump_grids is None else jump_grids)
|
|
101
|
+
kwargs.setdefault("tie_word_embeddings", False)
|
|
102
|
+
super().__init__(**kwargs)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["FlashMapConfig"]
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
"""FlashMap: speculative decoding with a flow-map drafter.
|
|
2
|
+
|
|
3
|
+
FlashMap drafts the ``K`` tokens after the last verified token (the anchor) in one
|
|
4
|
+
network evaluation, like DFlash, but from a generative model that can couple the
|
|
5
|
+
slots of a block. A block is a point ``x`` in ``R^{K x V}`` (``V`` the target's
|
|
6
|
+
vocabulary) moving along the path ``x_s = (1 - s) x0 + s onehot(y)`` from Gaussian
|
|
7
|
+
noise ``x0`` at ``s = 0`` to the one-hot tokens ``y`` at ``s = 1``. The drafter is a
|
|
8
|
+
*flow map* (Flow Map Language Models, arXiv:2602.16813): a network that jumps along
|
|
9
|
+
this path from time ``s`` to time ``t``, distilled from a flow trained on it. Its
|
|
10
|
+
output is a distribution over the vocabulary for every slot. Drafting with one step
|
|
11
|
+
is a single jump from ``s = 0`` to ``t = 1``:
|
|
12
|
+
|
|
13
|
+
x0 ~ N(0, I) -> logits = map(x0; 0 -> 1) -> y_k ~ softmax(logits_k) per slot.
|
|
14
|
+
|
|
15
|
+
Given the noise the slots are independent draws, so ``softmax(logits)`` is an exact
|
|
16
|
+
proposal distribution for lossless speculative (rejection) sampling. Across noise
|
|
17
|
+
draws the slots can be correlated, since the same noise conditions all of them; one
|
|
18
|
+
jump captures this joint structure only partly, and more jumps capture more. More
|
|
19
|
+
steps jump over a grid ``0 = t_0 < t_1 < ... < t_n = 1``, moving the state to
|
|
20
|
+
``x <- ((1 - t) x + (t - s) softmax(logits)) / (1 - s)`` between jumps.
|
|
21
|
+
|
|
22
|
+
The network is a small DiT. It reads the block's state through the target's token
|
|
23
|
+
embedding ``E`` (the noise enters as ``x0 @ E``), conditions on the jump's start and
|
|
24
|
+
end times through adaptive LayerNorms, self-attends across the block's slots, and
|
|
25
|
+
cross-attends to the target's hidden states at earlier positions (from a few of its
|
|
26
|
+
layers) and to the anchor's embedding. Its output goes through the target's lm_head.
|
|
27
|
+
The target's embedding and lm_head are not part of the drafter's weights.
|
|
28
|
+
|
|
29
|
+
``FlashMapDraftModel.spec_generate`` runs lossless speculative decoding with a
|
|
30
|
+
transformers target model. This module is inference-only.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import math
|
|
34
|
+
import warnings
|
|
35
|
+
from contextlib import contextmanager
|
|
36
|
+
from dataclasses import dataclass, field
|
|
37
|
+
from typing import Optional
|
|
38
|
+
|
|
39
|
+
import torch
|
|
40
|
+
import torch.nn.functional as F
|
|
41
|
+
from torch import nn
|
|
42
|
+
from transformers import DynamicCache, PreTrainedModel
|
|
43
|
+
|
|
44
|
+
from .configuration_flashmap import FlashMapConfig
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def rope_tables(positions: torch.Tensor, head_dim: int, theta: float) -> tuple[torch.Tensor, torch.Tensor]:
|
|
48
|
+
"""``cos`` and ``sin`` of the rotary angles at ``positions (B, T)``, each
|
|
49
|
+
``(B, T, 1, head_dim // 2)`` in float32; computed once and shared by all layers."""
|
|
50
|
+
inv_freq = 1.0 / theta ** (torch.arange(0, head_dim, 2, device=positions.device).float() / head_dim)
|
|
51
|
+
angles = positions.float()[..., None, None] * inv_freq
|
|
52
|
+
return angles.cos(), angles.sin()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def apply_rope(x: torch.Tensor, rope: tuple[torch.Tensor, torch.Tensor]) -> torch.Tensor:
|
|
56
|
+
"""Rotate the interleaved channel pairs ``(x[2i], x[2i + 1])`` of ``x (B, T, heads,
|
|
57
|
+
head_dim)`` by the angles of ``rope_tables``, in float32."""
|
|
58
|
+
cos, sin = rope
|
|
59
|
+
x1, x2 = x.float().unflatten(-1, (-1, 2)).unbind(-1)
|
|
60
|
+
return torch.stack([x1 * cos - x2 * sin, x2 * cos + x1 * sin], dim=-1).flatten(-2).type_as(x)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FlashMapRMSNorm(nn.Module):
|
|
64
|
+
"""RMSNorm whose scale ``weight / rms(x)`` is rounded to the input's dtype before
|
|
65
|
+
it multiplies the input (the order the released weights were trained with)."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, dim: int, eps: float):
|
|
68
|
+
super().__init__()
|
|
69
|
+
self.weight = nn.Parameter(torch.ones(dim))
|
|
70
|
+
self.eps = eps
|
|
71
|
+
|
|
72
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
73
|
+
rms = torch.rsqrt(x.float().pow(2).mean(dim=-1, keepdim=True) + self.eps)
|
|
74
|
+
return x * (self.weight.float() * rms).to(x.dtype)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class FlashMapTimeEmbedding(nn.Module):
|
|
78
|
+
"""Sinusoidal features of a time in [0, 1], then an MLP."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, config: FlashMapConfig):
|
|
81
|
+
super().__init__()
|
|
82
|
+
self.dim, self.scale = config.time_embed_dim, config.time_scale
|
|
83
|
+
self.linear_1 = nn.Linear(config.time_embed_dim, config.cond_dim)
|
|
84
|
+
self.linear_2 = nn.Linear(config.cond_dim, config.cond_dim)
|
|
85
|
+
|
|
86
|
+
def forward(self, t: torch.Tensor) -> torch.Tensor:
|
|
87
|
+
half = self.dim // 2
|
|
88
|
+
freqs = torch.exp(-math.log(10000.0) * torch.arange(half, device=t.device).float() / half)
|
|
89
|
+
args = (t.float() * self.scale)[..., None] * freqs
|
|
90
|
+
emb = torch.cat([args.cos(), args.sin()], dim=-1).to(self.linear_1.weight.dtype)
|
|
91
|
+
return self.linear_2(F.silu(self.linear_1(emb)))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class FlashMapAdaLayerNorm(nn.Module):
|
|
95
|
+
"""LayerNorm modulated by the time conditioning (AdaLN-zero)."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, config: FlashMapConfig):
|
|
98
|
+
super().__init__()
|
|
99
|
+
self.linear = nn.Linear(config.cond_dim, 3 * config.hidden_size)
|
|
100
|
+
self.eps = config.layer_norm_eps
|
|
101
|
+
|
|
102
|
+
def modulation(self, cond: torch.Tensor) -> tuple[torch.Tensor, ...]:
|
|
103
|
+
"""``(scale, shift, gate)`` for the time conditioning ``cond``."""
|
|
104
|
+
return self.linear(F.silu(cond)).chunk(3, dim=-1)
|
|
105
|
+
|
|
106
|
+
def forward(self, x: torch.Tensor, modulation) -> tuple[torch.Tensor, torch.Tensor]:
|
|
107
|
+
"""``norm(x) * (1 + scale) + shift``, and the gate of the residual branch."""
|
|
108
|
+
scale, shift, gate = modulation
|
|
109
|
+
return F.layer_norm(x, x.shape[-1:], eps=self.eps) * (1 + scale) + shift, gate
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class FlashMapAttention(nn.Module):
|
|
113
|
+
"""Multi-head attention with RMS-normed queries and keys and rotary positions.
|
|
114
|
+
|
|
115
|
+
Keys and values come from ``project_kv`` so that cross-attention can project its
|
|
116
|
+
context once and cache it."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, config: FlashMapConfig):
|
|
119
|
+
super().__init__()
|
|
120
|
+
dim = config.hidden_size
|
|
121
|
+
self.num_heads = config.num_attention_heads
|
|
122
|
+
self.head_dim = dim // self.num_heads
|
|
123
|
+
self.q_proj = nn.Linear(dim, dim, bias=False)
|
|
124
|
+
self.k_proj = nn.Linear(dim, dim, bias=False)
|
|
125
|
+
self.v_proj = nn.Linear(dim, dim, bias=False)
|
|
126
|
+
self.o_proj = nn.Linear(dim, dim, bias=False)
|
|
127
|
+
self.q_norm = FlashMapRMSNorm(self.head_dim, config.qk_norm_eps)
|
|
128
|
+
self.k_norm = FlashMapRMSNorm(self.head_dim, config.qk_norm_eps)
|
|
129
|
+
|
|
130
|
+
def _heads(self, x: torch.Tensor) -> torch.Tensor:
|
|
131
|
+
return x.view(*x.shape[:2], self.num_heads, self.head_dim)
|
|
132
|
+
|
|
133
|
+
def project_kv(self, x: torch.Tensor, rope) -> tuple[torch.Tensor, torch.Tensor]:
|
|
134
|
+
"""Keys and values ``(B, heads, T, head_dim)`` of ``x (B, T, dim)``."""
|
|
135
|
+
keys = apply_rope(self.k_norm(self._heads(self.k_proj(x))), rope)
|
|
136
|
+
return keys.transpose(1, 2), self._heads(self.v_proj(x)).transpose(1, 2)
|
|
137
|
+
|
|
138
|
+
def forward(self, x, rope, keys, values) -> torch.Tensor:
|
|
139
|
+
queries = apply_rope(self.q_norm(self._heads(self.q_proj(x))), rope)
|
|
140
|
+
out = F.scaled_dot_product_attention(queries.transpose(1, 2), keys, values)
|
|
141
|
+
return self.o_proj(out.transpose(1, 2).flatten(2))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class FlashMapMLP(nn.Module):
|
|
145
|
+
def __init__(self, config: FlashMapConfig):
|
|
146
|
+
super().__init__()
|
|
147
|
+
self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
|
|
148
|
+
self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
|
|
149
|
+
|
|
150
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
151
|
+
return self.fc2(F.gelu(self.fc1(x)))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class FlashMapLayer(nn.Module):
|
|
155
|
+
"""A DiT block: self-attention across the block's slots, cross-attention to the
|
|
156
|
+
context, and an MLP, each behind a time-modulated LayerNorm and a gated residual."""
|
|
157
|
+
|
|
158
|
+
def __init__(self, config: FlashMapConfig):
|
|
159
|
+
super().__init__()
|
|
160
|
+
self.self_attn_adaln = FlashMapAdaLayerNorm(config)
|
|
161
|
+
self.self_attn = FlashMapAttention(config)
|
|
162
|
+
self.cross_attn_adaln = FlashMapAdaLayerNorm(config)
|
|
163
|
+
self.cross_attn = FlashMapAttention(config)
|
|
164
|
+
self.mlp_adaln = FlashMapAdaLayerNorm(config)
|
|
165
|
+
self.mlp = FlashMapMLP(config)
|
|
166
|
+
|
|
167
|
+
def forward(self, x, modulations, rope, context: tuple[torch.Tensor, torch.Tensor]) -> torch.Tensor:
|
|
168
|
+
self_mod, cross_mod, mlp_mod = modulations
|
|
169
|
+
h, gate = self.self_attn_adaln(x, self_mod)
|
|
170
|
+
x = x + gate * self.self_attn(h, rope, *self.self_attn.project_kv(h, rope))
|
|
171
|
+
h, gate = self.cross_attn_adaln(x, cross_mod)
|
|
172
|
+
x = x + gate * self.cross_attn(h, rope, *context)
|
|
173
|
+
h, gate = self.mlp_adaln(x, mlp_mod)
|
|
174
|
+
return x + gate * self.mlp(h)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class FlashMapCache:
|
|
178
|
+
"""Cross-attention keys and values of the drafter's context, for every DiT layer.
|
|
179
|
+
|
|
180
|
+
Positions ``[0, length)`` hold the target's hidden states at the tokens verified
|
|
181
|
+
so far. Position ``length`` belongs to the anchor, the last token, whose hidden
|
|
182
|
+
state the target has not computed yet: drafting writes the anchor's embedding
|
|
183
|
+
there, and the next ``extend_context`` overwrites it with its hidden state.
|
|
184
|
+
Create it on the drafter's device and dtype.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def __init__(self, config: FlashMapConfig, batch_size: int, max_length: int, device=None, dtype=None):
|
|
188
|
+
head_dim = config.hidden_size // config.num_attention_heads
|
|
189
|
+
shape = (config.num_hidden_layers, 2, batch_size, config.num_attention_heads, max_length, head_dim)
|
|
190
|
+
self.kv = torch.zeros(shape, device=device, dtype=dtype)
|
|
191
|
+
self.length = 0
|
|
192
|
+
|
|
193
|
+
def write(self, layer: int, start: int, keys: torch.Tensor, values: torch.Tensor) -> None:
|
|
194
|
+
end = start + keys.shape[2]
|
|
195
|
+
if end > self.kv.shape[4]:
|
|
196
|
+
raise ValueError(f"FlashMapCache holds {self.kv.shape[4]} positions, writing up to {end}")
|
|
197
|
+
self.kv[layer, 0, :, :, start:end] = keys
|
|
198
|
+
self.kv[layer, 1, :, :, start:end] = values
|
|
199
|
+
|
|
200
|
+
def context(self, end: int) -> list[tuple[torch.Tensor, torch.Tensor]]:
|
|
201
|
+
return [(kv[0, :, :, :end], kv[1, :, :, :end]) for kv in self.kv]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class FlashMapGenerateOutput:
|
|
206
|
+
"""``sequences``: prompt and generated tokens, ``(1, length)``.
|
|
207
|
+
``acceptance_lengths``: tokens committed by each target forward pass (accepted
|
|
208
|
+
draft tokens plus the target's own token). The last pass counts in full even
|
|
209
|
+
where it runs past ``max_new_tokens``, as in the evaluation of the paper."""
|
|
210
|
+
|
|
211
|
+
sequences: torch.LongTensor
|
|
212
|
+
num_new_tokens: int
|
|
213
|
+
acceptance_lengths: list = field(default_factory=list)
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def acceptance_length(self) -> float:
|
|
217
|
+
"""Mean tokens per target forward pass (the paper's gamma)."""
|
|
218
|
+
return sum(self.acceptance_lengths) / max(len(self.acceptance_lengths), 1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class FlashMapDraftModel(PreTrainedModel):
|
|
222
|
+
config_class = FlashMapConfig
|
|
223
|
+
base_model_prefix = "flashmap"
|
|
224
|
+
_no_split_modules = ["FlashMapLayer"]
|
|
225
|
+
_supports_sdpa = True
|
|
226
|
+
|
|
227
|
+
def __init__(self, config: FlashMapConfig):
|
|
228
|
+
super().__init__(config)
|
|
229
|
+
dim, target_dim = config.hidden_size, config.target_hidden_size
|
|
230
|
+
self.head_dim = dim // config.num_attention_heads
|
|
231
|
+
# Context: the target's hidden states at target_layer_ids, and the anchor.
|
|
232
|
+
self.fc = nn.Linear(len(config.target_layer_ids) * target_dim, dim, bias=False)
|
|
233
|
+
self.hidden_norm = FlashMapRMSNorm(dim, config.rms_norm_eps)
|
|
234
|
+
self.anchor_proj = nn.Linear(target_dim, dim)
|
|
235
|
+
self.anchor_norm = FlashMapRMSNorm(dim, config.rms_norm_eps)
|
|
236
|
+
# State featurization (see embed_state).
|
|
237
|
+
self.state_norm = FlashMapRMSNorm(target_dim, config.rms_norm_eps)
|
|
238
|
+
self.noise_norm = FlashMapRMSNorm(target_dim, config.rms_norm_eps)
|
|
239
|
+
self.noise_gate = nn.Parameter(torch.ones(1))
|
|
240
|
+
# The DiT.
|
|
241
|
+
self.x_embed = nn.Linear(target_dim, dim)
|
|
242
|
+
self.time_embed = FlashMapTimeEmbedding(config) # the jump's start time s
|
|
243
|
+
self.jump_embed = FlashMapTimeEmbedding(config) # the jump's end time t
|
|
244
|
+
self.layers = nn.ModuleList(FlashMapLayer(config) for _ in range(config.num_hidden_layers))
|
|
245
|
+
self.final_adaln = FlashMapAdaLayerNorm(config)
|
|
246
|
+
self.head = nn.Linear(dim, target_dim)
|
|
247
|
+
self._clear_caches()
|
|
248
|
+
self.post_init()
|
|
249
|
+
|
|
250
|
+
# Values computed from the weights once and reused by every draft: the AdaLN
|
|
251
|
+
# modulations of each jump, and constants of the target's embedding. Moving or
|
|
252
|
+
# reloading the weights clears them.
|
|
253
|
+
|
|
254
|
+
def _clear_caches(self) -> None:
|
|
255
|
+
self._time_cache, self._target_cache = {}, {}
|
|
256
|
+
|
|
257
|
+
def _apply(self, fn, *args, **kwargs):
|
|
258
|
+
self._clear_caches()
|
|
259
|
+
return super()._apply(fn, *args, **kwargs)
|
|
260
|
+
|
|
261
|
+
def load_state_dict(self, *args, **kwargs):
|
|
262
|
+
self._clear_caches()
|
|
263
|
+
return super().load_state_dict(*args, **kwargs)
|
|
264
|
+
|
|
265
|
+
@torch.no_grad()
|
|
266
|
+
def time_conditioning(self, s: float, t: float):
|
|
267
|
+
"""The AdaLN modulations of the jump from ``s`` to ``t``, for every norm of the
|
|
268
|
+
DiT: per layer (self-attention, cross-attention, MLP), then the final norm. They
|
|
269
|
+
depend on the two times alone, so each jump computes them once."""
|
|
270
|
+
if (s, t) not in self._time_cache:
|
|
271
|
+
times = torch.tensor([s, t], device=self.device)
|
|
272
|
+
cond = (self.time_embed(times[:1]) + self.jump_embed(times[1:]))[None]
|
|
273
|
+
layers = [
|
|
274
|
+
[norm.modulation(cond) for norm in (block.self_attn_adaln, block.cross_attn_adaln, block.mlp_adaln)]
|
|
275
|
+
for block in self.layers
|
|
276
|
+
]
|
|
277
|
+
self._time_cache[(s, t)] = (layers, self.final_adaln.modulation(cond))
|
|
278
|
+
return self._time_cache[(s, t)]
|
|
279
|
+
|
|
280
|
+
def forward(self, inputs_embeds, s: float, t: float, position_ids, context) -> torch.Tensor:
|
|
281
|
+
"""The jump from time ``s`` to ``t``: the featurized state ``inputs_embeds (B, K,
|
|
282
|
+
target_hidden)`` of blocks at ``position_ids (B, K)`` -> hidden states ``(B, K,
|
|
283
|
+
target_hidden)`` whose target lm_head logits are the distribution it lands on.
|
|
284
|
+
``context`` holds each layer's cross-attention keys and values."""
|
|
285
|
+
layer_modulations, final_modulation = self.time_conditioning(s, t)
|
|
286
|
+
rope = rope_tables(position_ids, self.head_dim, self.config.rope_theta)
|
|
287
|
+
h = self.x_embed(inputs_embeds)
|
|
288
|
+
for layer, modulations, layer_context in zip(self.layers, layer_modulations, context):
|
|
289
|
+
h = layer(h, modulations, rope, layer_context)
|
|
290
|
+
h, _ = self.final_adaln(h, final_modulation)
|
|
291
|
+
return self.head(h)
|
|
292
|
+
|
|
293
|
+
# ---- context ------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
def select_target_hidden(self, hidden_states) -> torch.Tensor:
|
|
296
|
+
"""The drafter's features from a target forward's ``hidden_states`` tuple: the
|
|
297
|
+
outputs of layers ``target_layer_ids``, concatenated."""
|
|
298
|
+
return torch.cat([hidden_states[i + 1] for i in self.config.target_layer_ids], dim=-1)
|
|
299
|
+
|
|
300
|
+
@contextmanager
|
|
301
|
+
def target_features(self, target: PreTrainedModel):
|
|
302
|
+
"""Forward hooks that keep the outputs of the target's layers ``target_layer_ids``.
|
|
303
|
+
Yields a function returning them, concatenated, for the target's last forward.
|
|
304
|
+
Unlike ``output_hidden_states``, this does not keep every layer's output."""
|
|
305
|
+
layers, outputs = target.get_decoder().layers, {}
|
|
306
|
+
|
|
307
|
+
def keep(i):
|
|
308
|
+
def hook(module, args, output):
|
|
309
|
+
outputs[i] = output[0] if isinstance(output, tuple) else output
|
|
310
|
+
|
|
311
|
+
return hook
|
|
312
|
+
|
|
313
|
+
hooks = [layers[i].register_forward_hook(keep(i)) for i in self.config.target_layer_ids]
|
|
314
|
+
try:
|
|
315
|
+
yield lambda: torch.cat([outputs[i] for i in self.config.target_layer_ids], dim=-1)
|
|
316
|
+
finally:
|
|
317
|
+
for hook in hooks:
|
|
318
|
+
hook.remove()
|
|
319
|
+
|
|
320
|
+
@torch.no_grad()
|
|
321
|
+
def extend_context(self, cache: FlashMapCache, target_hidden: torch.Tensor) -> None:
|
|
322
|
+
"""Append the target's features ``(B, n, ...)`` at the next ``n`` verified positions."""
|
|
323
|
+
start, n = cache.length, target_hidden.shape[1]
|
|
324
|
+
context = self.hidden_norm(self.fc(target_hidden.to(self.dtype)))
|
|
325
|
+
positions = torch.arange(start, start + n, device=context.device).expand(context.shape[0], -1)
|
|
326
|
+
rope = rope_tables(positions, self.head_dim, self.config.rope_theta)
|
|
327
|
+
for i, layer in enumerate(self.layers):
|
|
328
|
+
cache.write(i, start, *layer.cross_attn.project_kv(context, rope))
|
|
329
|
+
cache.length += n
|
|
330
|
+
|
|
331
|
+
# ---- drafting -----------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
def embed_state(self, state, s: float, t: float, embedding: torch.Tensor) -> torch.Tensor:
|
|
334
|
+
"""DiT input for a vocabulary-sized state ``(B, K, V)`` at time ``s`` jumping to ``t``.
|
|
335
|
+
|
|
336
|
+
The state is read through the target's token embedding twice: as the posterior
|
|
337
|
+
over clean tokens it implies, ``softmax(s x / (1 - s)^2)``, and linearly, weighted
|
|
338
|
+
by the jump. At ``s = 0`` the posterior is uniform whatever the noise, so the
|
|
339
|
+
linear term is how a jump from noise sees which noise it starts from."""
|
|
340
|
+
if s == 0:
|
|
341
|
+
posterior_embeds = self.target_constants(embedding)[0]
|
|
342
|
+
else:
|
|
343
|
+
posterior = (state.float() * (s / max(1.0 - s, 1e-3) ** 2)).softmax(dim=-1)
|
|
344
|
+
posterior_embeds = posterior.to(embedding.dtype) @ embedding
|
|
345
|
+
return self.mix_state(posterior_embeds, state.to(embedding.dtype) @ embedding, s, t)
|
|
346
|
+
|
|
347
|
+
def mix_state(self, posterior_embeds, state_embeds, s: float, t: float) -> torch.Tensor:
|
|
348
|
+
"""``embed_state`` from the state's two projections onto the embedding."""
|
|
349
|
+
# the noise term's weight: noise_gate times the jump ("jump") or alone ("constant")
|
|
350
|
+
jump = 1.0 if self.config.noise_gate == "constant" else t - s
|
|
351
|
+
gate = (self.noise_gate.float() * jump).to(self.dtype)
|
|
352
|
+
posterior_term = self.state_norm(posterior_embeds.to(self.dtype))
|
|
353
|
+
return posterior_term + gate * self.noise_norm(state_embeds.to(self.dtype))
|
|
354
|
+
|
|
355
|
+
@torch.no_grad()
|
|
356
|
+
def target_constants(self, embedding: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
|
357
|
+
"""What drafting needs of the target's embedding ``E (V, H)``: the embedded
|
|
358
|
+
uniform posterior (the posterior of any state at ``s = 0``), and ``L`` with
|
|
359
|
+
``L L^T = E^T E``.
|
|
360
|
+
|
|
361
|
+
A one-step draft reads its noise only as ``x0 @ E`` with ``x0 ~ N(0, I_V)``,
|
|
362
|
+
a Gaussian with covariance ``E^T E``; ``z @ L^T`` with ``z ~ N(0, I_H)`` has the
|
|
363
|
+
same distribution and needs no vocabulary-sized noise."""
|
|
364
|
+
key = (embedding.data_ptr(), tuple(embedding.shape), embedding.device, embedding.dtype)
|
|
365
|
+
if key not in self._target_cache:
|
|
366
|
+
vocab_size = embedding.shape[0]
|
|
367
|
+
uniform = torch.full((1, vocab_size), 1.0 / vocab_size, device=embedding.device)
|
|
368
|
+
# E^T E in float32 where E is; its (H, H) factor in float64 on the CPU, as
|
|
369
|
+
# many accelerators lack fast (or any) float64.
|
|
370
|
+
gram = sum(rows.float().T @ rows.float() for rows in embedding.split(16384))
|
|
371
|
+
factor = torch.linalg.cholesky(gram.cpu().double())
|
|
372
|
+
uniform_embeds = uniform.to(embedding.dtype) @ embedding
|
|
373
|
+
self._target_cache = {key: (uniform_embeds, factor.to(self.device, self.dtype))}
|
|
374
|
+
return self._target_cache[key]
|
|
375
|
+
|
|
376
|
+
@torch.no_grad()
|
|
377
|
+
def draft(
|
|
378
|
+
self,
|
|
379
|
+
anchor_ids: torch.LongTensor,
|
|
380
|
+
cache: FlashMapCache,
|
|
381
|
+
embed_tokens: nn.Module,
|
|
382
|
+
lm_head: nn.Module,
|
|
383
|
+
num_steps: Optional[int] = None,
|
|
384
|
+
noise: Optional[torch.Tensor] = None,
|
|
385
|
+
generator: Optional[torch.Generator] = None,
|
|
386
|
+
) -> torch.Tensor:
|
|
387
|
+
"""Draft logits ``(B, block_size, V)`` for the block after ``anchor_ids (B,)``.
|
|
388
|
+
|
|
389
|
+
``cache`` holds the target's features at every position before the anchor.
|
|
390
|
+
``embed_tokens`` and ``lm_head`` are the target's. ``noise (B, block_size, V)``
|
|
391
|
+
is the starting state ``x0``; by default it is drawn, and a one-step draft draws
|
|
392
|
+
only its embedding (see ``target_constants``). Given the noise, the block's
|
|
393
|
+
tokens are independent draws from ``softmax(logits)``, one per slot."""
|
|
394
|
+
num_steps = num_steps or self.config.num_steps
|
|
395
|
+
if str(num_steps) not in self.config.jump_grids:
|
|
396
|
+
supported = ", ".join(sorted(self.config.jump_grids, key=int))
|
|
397
|
+
raise ValueError(f"num_steps must be one of {supported} (the config's jump_grids), got {num_steps}")
|
|
398
|
+
grid = self.config.jump_grids[str(num_steps)]
|
|
399
|
+
batch_size, block_size = anchor_ids.shape[0], self.config.block_size
|
|
400
|
+
embedding, device = embed_tokens.weight, anchor_ids.device
|
|
401
|
+
|
|
402
|
+
# The anchor's embedding enters the context at the anchor's position.
|
|
403
|
+
anchor = cache.length
|
|
404
|
+
anchor_context = self.anchor_norm(self.anchor_proj(embed_tokens(anchor_ids[:, None]).to(self.dtype)))
|
|
405
|
+
anchor_position = torch.full((batch_size, 1), anchor, device=device)
|
|
406
|
+
anchor_rope = rope_tables(anchor_position, self.head_dim, self.config.rope_theta)
|
|
407
|
+
for i, layer in enumerate(self.layers):
|
|
408
|
+
cache.write(i, anchor, *layer.cross_attn.project_kv(anchor_context, anchor_rope))
|
|
409
|
+
context = cache.context(anchor + 1)
|
|
410
|
+
positions = anchor + 1 + torch.arange(block_size, device=device).expand(batch_size, -1)
|
|
411
|
+
|
|
412
|
+
state = noise
|
|
413
|
+
if state is None and num_steps == 1:
|
|
414
|
+
uniform_embeds, factor = self.target_constants(embedding)
|
|
415
|
+
shape = (batch_size, block_size, factor.shape[0])
|
|
416
|
+
z = torch.randn(shape, device=device, dtype=factor.dtype, generator=generator)
|
|
417
|
+
inputs = self.mix_state(uniform_embeds, z @ factor.T, 0.0, grid[1])
|
|
418
|
+
elif state is None:
|
|
419
|
+
shape = (batch_size, block_size, embedding.shape[0])
|
|
420
|
+
state = torch.randn(shape, device=device, generator=generator)
|
|
421
|
+
|
|
422
|
+
for s, t in zip(grid[:-1], grid[1:]):
|
|
423
|
+
if state is not None:
|
|
424
|
+
inputs = self.embed_state(state, s, t, embedding)
|
|
425
|
+
logits = lm_head(self(inputs, s, t, positions, context).to(lm_head.weight.dtype))
|
|
426
|
+
if t < 1.0:
|
|
427
|
+
# move to where the jump lands (eq. 19 of arXiv:2602.16813)
|
|
428
|
+
state = ((1.0 - t) * state + (t - s) * logits.float().softmax(dim=-1)) / (1.0 - s)
|
|
429
|
+
return logits
|
|
430
|
+
|
|
431
|
+
# ---- speculative decoding -----------------------------------------------------
|
|
432
|
+
|
|
433
|
+
def check_target(self, target: PreTrainedModel) -> None:
|
|
434
|
+
"""Raise if ``target`` cannot serve as this drafter's target; warn if it is not
|
|
435
|
+
the model the drafter was trained for (the output stays lossless, but drafts
|
|
436
|
+
from another model's hidden states are poor)."""
|
|
437
|
+
config = target.config.get_text_config()
|
|
438
|
+
embedding, lm_head = target.get_input_embeddings().weight, target.get_output_embeddings().weight
|
|
439
|
+
expected = (self.config.vocab_size, self.config.target_hidden_size)
|
|
440
|
+
if tuple(embedding.shape) != expected or tuple(lm_head.shape) != expected:
|
|
441
|
+
raise ValueError(
|
|
442
|
+
f"this drafter needs a target with vocabulary {expected[0]} and hidden size {expected[1]} "
|
|
443
|
+
f"({self.config.target_model}), got an embedding of shape {tuple(embedding.shape)}"
|
|
444
|
+
)
|
|
445
|
+
if max(self.config.target_layer_ids) >= config.num_hidden_layers - 1:
|
|
446
|
+
layers = config.num_hidden_layers
|
|
447
|
+
raise ValueError(f"target_layer_ids {self.config.target_layer_ids} need more than {layers} target layers")
|
|
448
|
+
name = (getattr(config, "_name_or_path", None) or "").rstrip("/")
|
|
449
|
+
if name and name.split("/")[-1] != self.config.target_model.split("/")[-1]:
|
|
450
|
+
warnings.warn(f"this drafter was trained for {self.config.target_model}, not {name}")
|
|
451
|
+
devices = {self.device, embedding.device, lm_head.device}
|
|
452
|
+
if len(devices) > 1:
|
|
453
|
+
raise ValueError(f"the drafter and the target's embedding and lm_head must share a device, got {devices}")
|
|
454
|
+
|
|
455
|
+
@torch.no_grad()
|
|
456
|
+
def spec_generate(
|
|
457
|
+
self,
|
|
458
|
+
target: PreTrainedModel,
|
|
459
|
+
input_ids: torch.LongTensor,
|
|
460
|
+
max_new_tokens: int = 256,
|
|
461
|
+
temperature: float = 0.0,
|
|
462
|
+
num_steps: Optional[int] = None,
|
|
463
|
+
stop_token_ids: Optional[list] = None,
|
|
464
|
+
generator: Optional[torch.Generator] = None,
|
|
465
|
+
) -> FlashMapGenerateOutput:
|
|
466
|
+
"""Lossless speculative decoding of ``target`` (a transformers causal LM) from
|
|
467
|
+
the prompt ``input_ids (1, P)``.
|
|
468
|
+
|
|
469
|
+
Every round drafts a block after the last token, has the target score it in one
|
|
470
|
+
forward pass, and keeps the longest prefix the target accepts plus one token of
|
|
471
|
+
the target's own. At ``temperature`` 0 the output is the target's greedy
|
|
472
|
+
decoding; above it, tokens are distributed exactly as the target samples them
|
|
473
|
+
(rejection sampling against the draft's per-slot probabilities). Generation stops
|
|
474
|
+
after ``max_new_tokens`` or at a token in ``stop_token_ids`` (default: the
|
|
475
|
+
target's ``eos_token_id``). The drafter, the target and ``generator`` (which
|
|
476
|
+
makes sampling reproducible) must be on one device."""
|
|
477
|
+
if input_ids.shape[0] != 1:
|
|
478
|
+
raise ValueError("spec_generate decodes one sequence at a time")
|
|
479
|
+
self.check_target(target)
|
|
480
|
+
if generator is not None: # a "cuda" generator (no index) is on the current device
|
|
481
|
+
device = generator.device
|
|
482
|
+
if device.type == "cuda" and device.index is None:
|
|
483
|
+
device = torch.device("cuda", torch.cuda.current_device())
|
|
484
|
+
if (device.type, device.index or 0) != (self.device.type, self.device.index or 0):
|
|
485
|
+
raise ValueError(f"generator must be on {self.device}, got one on {generator.device}")
|
|
486
|
+
if stop_token_ids is None:
|
|
487
|
+
eos = target.generation_config.eos_token_id
|
|
488
|
+
stop_token_ids = [] if eos is None else [eos] if isinstance(eos, int) else list(eos)
|
|
489
|
+
input_ids = input_ids.to(self.device)
|
|
490
|
+
stop = torch.tensor(stop_token_ids, device=self.device, dtype=torch.long)
|
|
491
|
+
embed_tokens, lm_head = target.get_input_embeddings(), target.get_output_embeddings()
|
|
492
|
+
block_size, prompt_length = self.config.block_size, input_ids.shape[1]
|
|
493
|
+
|
|
494
|
+
def sample(logits): # (1, n, V) -> tokens (1, n) and their distributions
|
|
495
|
+
if temperature <= 0:
|
|
496
|
+
return logits.argmax(dim=-1), None
|
|
497
|
+
probs = (logits.float() / temperature).softmax(dim=-1)
|
|
498
|
+
return torch.multinomial(probs[0], 1, generator=generator).T, probs
|
|
499
|
+
|
|
500
|
+
with self.target_features(target) as target_features:
|
|
501
|
+
target_cache = DynamicCache()
|
|
502
|
+
out = target(input_ids, past_key_values=target_cache, use_cache=True, logits_to_keep=1)
|
|
503
|
+
tokens = sample(out.logits[:, -1:])[0]
|
|
504
|
+
max_length = prompt_length + max_new_tokens + block_size + 1 # the last round may overshoot
|
|
505
|
+
cache = FlashMapCache(self.config, 1, max_length, device=self.device, dtype=self.dtype)
|
|
506
|
+
self.extend_context(cache, target_features())
|
|
507
|
+
acceptance_lengths = []
|
|
508
|
+
|
|
509
|
+
while tokens.shape[1] < max_new_tokens and not torch.isin(tokens[0, -1], stop):
|
|
510
|
+
anchor = tokens[:, -1]
|
|
511
|
+
draft_logits = self.draft(anchor, cache, embed_tokens, lm_head, num_steps, generator=generator)
|
|
512
|
+
draft, draft_probs = sample(draft_logits)
|
|
513
|
+
position = prompt_length + tokens.shape[1] - 1 # the anchor's
|
|
514
|
+
out = target(
|
|
515
|
+
torch.cat([anchor[:, None], draft], dim=1),
|
|
516
|
+
position_ids=torch.arange(position, position + block_size + 1, device=draft.device)[None],
|
|
517
|
+
past_key_values=target_cache,
|
|
518
|
+
use_cache=True,
|
|
519
|
+
)
|
|
520
|
+
if temperature <= 0:
|
|
521
|
+
target_tokens = out.logits.argmax(dim=-1)
|
|
522
|
+
accepted = int((draft == target_tokens[:, :-1]).cumprod(dim=1).sum())
|
|
523
|
+
bonus = target_tokens[:, accepted]
|
|
524
|
+
else:
|
|
525
|
+
target_probs = (out.logits.float() / temperature).softmax(dim=-1)[0]
|
|
526
|
+
p = target_probs[:-1].gather(-1, draft[0, :, None])[:, 0]
|
|
527
|
+
q = draft_probs[0].gather(-1, draft[0, :, None])[:, 0]
|
|
528
|
+
u = torch.rand(q.shape, device=q.device, generator=generator)
|
|
529
|
+
accepted = int((u * q < p).cumprod(dim=0).sum())
|
|
530
|
+
residual = target_probs[accepted]
|
|
531
|
+
if accepted < block_size: # resample the rejected slot from max(p - q, 0)
|
|
532
|
+
residual = (residual - draft_probs[0, accepted]).clamp_min(0)
|
|
533
|
+
if residual.sum() <= 0:
|
|
534
|
+
residual = target_probs[accepted]
|
|
535
|
+
bonus = torch.multinomial(residual, 1, generator=generator)
|
|
536
|
+
new_tokens = torch.cat([draft[:, :accepted], bonus.view(1, 1)], dim=1)
|
|
537
|
+
hits = torch.isin(new_tokens[0], stop).nonzero()
|
|
538
|
+
if len(hits):
|
|
539
|
+
new_tokens = new_tokens[:, : int(hits[0]) + 1]
|
|
540
|
+
tokens = torch.cat([tokens, new_tokens], dim=1)
|
|
541
|
+
acceptance_lengths.append(new_tokens.shape[1])
|
|
542
|
+
# keep the anchor and the accepted drafts; the bonus token is the next anchor
|
|
543
|
+
excess = target_cache.get_seq_length() - (position + accepted + 1)
|
|
544
|
+
if excess > 0: # crop(-n) drops n tokens in every transformers version
|
|
545
|
+
target_cache.crop(-excess)
|
|
546
|
+
self.extend_context(cache, target_features()[:, : accepted + 1])
|
|
547
|
+
|
|
548
|
+
tokens = tokens[:, :max_new_tokens]
|
|
549
|
+
return FlashMapGenerateOutput(
|
|
550
|
+
sequences=torch.cat([input_ids, tokens], dim=1),
|
|
551
|
+
num_new_tokens=tokens.shape[1],
|
|
552
|
+
acceptance_lengths=acceptance_lengths,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
__all__ = ["FlashMapCache", "FlashMapDraftModel", "FlashMapGenerateOutput"]
|