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.
@@ -0,0 +1,268 @@
1
+ """FlashMap drafting in vLLM, as a vLLM general plugin (``pip install flashmap[vllm]``).
2
+
3
+ Installing flashmap registers ``register`` under the ``vllm.general_plugins`` entry
4
+ point, which vLLM calls in every process before it parses a model. It teaches vLLM,
5
+ without patching its source:
6
+
7
+ * to read a FlashMap release folder (``model_type: "flashmap"`` with
8
+ ``speculators_model_type: "flashmap"`` in its config.json) as a draft model on
9
+ vLLM's DFlash runtime (``method="dflash"``) with the ``FlashMapDraftModel``
10
+ architecture; speculators-format FlashMap checkpoints load the same way;
11
+ * the ``FlashMapDraftModel`` architecture (model.py);
12
+ * the speculator that drives it on the V2 model runner (speculator.py), chosen by
13
+ architecture the way vLLM chooses its DFlash2 speculator.
14
+
15
+ Every hook delegates to vLLM's own code unless a FlashMap checkpoint is in use. The
16
+ plugin supports vLLM 0.31.x; on other versions, or if registering fails, it logs a
17
+ warning and leaves vLLM untouched.
18
+
19
+ Serve with ``vllm serve <target> --speculative-config '{"model": "<FlashMap folder or
20
+ Hub id>", "num_speculative_tokens": 16}'``; the plugin fills in ``"method": "dflash"``.
21
+
22
+ The speculators fork trains FlashMap drafters and converts them to the release format;
23
+ it has no serving code of its own.
24
+ """
25
+
26
+ import logging
27
+ import sys
28
+
29
+ __all__ = ["FLASHMAP_ARCHITECTURE", "SUPPORTED_VLLM", "register", "update_flashmap"]
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ FLASHMAP_ARCHITECTURE = "FlashMapDraftModel"
34
+ # vLLM (major, minor) versions whose internals the plugin hooks into.
35
+ SUPPORTED_VLLM = ((0, 31),)
36
+
37
+ # Speculators config fields the vLLM draft config carries over.
38
+ _FLASHMAP_FIELDS = (
39
+ "target_hidden_size",
40
+ "draft_vocab_size",
41
+ "block_size",
42
+ "timestep_embed_dim",
43
+ "timestep_frequency_dim",
44
+ "time_scale",
45
+ "qk_norm_eps",
46
+ "adaln_eps",
47
+ "noise_gate",
48
+ "time_schedule",
49
+ "num_steps",
50
+ "time_grids",
51
+ )
52
+
53
+
54
+ # A released FlashMap config holds the DiT's dimensions at its top level; vLLM's
55
+ # draft config reads them under the transformer_layer_config names.
56
+ _RELEASE_DIT_FIELDS = (
57
+ "hidden_size",
58
+ "num_hidden_layers",
59
+ "num_attention_heads",
60
+ "intermediate_size",
61
+ "vocab_size",
62
+ "rms_norm_eps",
63
+ )
64
+
65
+
66
+ def release_config_fields(config_dict: dict) -> dict:
67
+ """The speculators-config view of a released FlashMap config.json
68
+ (``model_type: "flashmap"``, served as it is): its DiT dimensions, the aux
69
+ layers vLLM captures (DeepSpec's ``target_layer_ids`` + 1) and its field
70
+ names mapped onto this plugin's."""
71
+ dim, heads = config_dict["hidden_size"], config_dict["num_attention_heads"]
72
+ grids = config_dict.get("jump_grids") or {}
73
+ fields = {key: config_dict[key] for key in _RELEASE_DIT_FIELDS}
74
+ fields.update(
75
+ num_key_value_heads=heads,
76
+ head_dim=dim // heads,
77
+ hidden_act="gelu",
78
+ tie_word_embeddings=False,
79
+ max_position_embeddings=config_dict.get("max_position_embeddings", 131072),
80
+ rope_parameters={
81
+ "rope_type": "default",
82
+ "rope_theta": float(config_dict.get("rope_theta", 10000.0)),
83
+ },
84
+ aux_hidden_state_layer_ids=[i + 1 for i in config_dict["target_layer_ids"]],
85
+ timestep_embed_dim=config_dict.get("cond_dim", 256),
86
+ timestep_frequency_dim=config_dict.get("time_embed_dim"),
87
+ adaln_eps=config_dict.get("layer_norm_eps", 1e-6),
88
+ time_grids=[grids[str(n)] for n in range(1, len(grids) + 1)] or None,
89
+ )
90
+ return fields
91
+
92
+
93
+ def update_flashmap(config_dict: dict, pre_trained_config: dict) -> None:
94
+ """Map a ``flashmap`` speculators config, or a released FlashMap config
95
+ with ``speculators_model_type: "flashmap"``, onto the dict vLLM builds the
96
+ draft model's config from (cf. vLLM's ``update_dflash``)."""
97
+ if "aux_hidden_state_layer_ids" not in config_dict:
98
+ released = release_config_fields(config_dict)
99
+ config_dict = {**config_dict, **released}
100
+ pre_trained_config.update(
101
+ {
102
+ key: released[key]
103
+ for key in (
104
+ *_RELEASE_DIT_FIELDS,
105
+ "num_key_value_heads",
106
+ "head_dim",
107
+ "hidden_act",
108
+ "tie_word_embeddings",
109
+ "max_position_embeddings",
110
+ "rope_parameters",
111
+ )
112
+ }
113
+ )
114
+ if config_dict.get("flow_map") is False:
115
+ raise ValueError(
116
+ "This FlashMap checkpoint is a stage-1 flow (flow_map: false), which "
117
+ "drafts only by integrating over many steps; serve the flow map "
118
+ "distilled from it."
119
+ )
120
+ pre_trained_config["architectures"] = [FLASHMAP_ARCHITECTURE]
121
+ aux_layer_ids = config_dict["aux_hidden_state_layer_ids"]
122
+ pre_trained_config["eagle_aux_hidden_state_layer_ids"] = aux_layer_ids
123
+ pre_trained_config["dflash_config"] = {
124
+ # FlashMap's slots are not embedded token ids; any valid id fills them.
125
+ "mask_token_id": config_dict.get("mask_token_id") or 0,
126
+ "target_layer_ids": [i - 1 for i in aux_layer_ids],
127
+ "causal": False,
128
+ }
129
+ for key in _FLASHMAP_FIELDS:
130
+ if config_dict.get(key) is not None:
131
+ pre_trained_config[key] = config_dict[key]
132
+
133
+
134
+ def _init_speculator(init_speculator):
135
+ def init_flashmap_or_other(vllm_config, device):
136
+ spec = vllm_config.speculative_config
137
+ if (
138
+ spec is not None
139
+ and spec.method == "dflash"
140
+ and FLASHMAP_ARCHITECTURE in (spec.draft_model_config.architectures or [])
141
+ ):
142
+ from flashmap.vllm.speculator import ( # noqa: PLC0415
143
+ FlashMapSpeculator,
144
+ )
145
+
146
+ return FlashMapSpeculator(vllm_config, device)
147
+ return init_speculator(vllm_config, device)
148
+
149
+ init_flashmap_or_other.flashmap = True # type: ignore[attr-defined]
150
+ return init_flashmap_or_other
151
+
152
+
153
+ def _flashmap_config(model: object, revision: str | None) -> dict | None:
154
+ """The config.json of ``model`` if it is a FlashMap drafter vLLM can serve."""
155
+ if not isinstance(model, str) or model in ("ngram", "[ngram]"):
156
+ return None
157
+ try:
158
+ from transformers import PretrainedConfig # noqa: PLC0415
159
+
160
+ config_dict, _ = PretrainedConfig.get_config_dict(model, revision=revision)
161
+ except Exception: # noqa: BLE001
162
+ return None
163
+ if config_dict.get("speculators_model_type") != "flashmap":
164
+ return None
165
+ return config_dict
166
+
167
+
168
+ def _post_init(post_init):
169
+ """Let ``--speculative-config '{"model": <FlashMap>}'`` leave out ``method``
170
+ (FlashMap runs on vLLM's DFlash runtime) and ``num_speculative_tokens`` (the
171
+ drafter's block size)."""
172
+
173
+ def post_init_flashmap(self):
174
+ if self.method is None and not self._is_custom_proposer_path(self.model):
175
+ config_dict = _flashmap_config(self.model, self.revision)
176
+ if config_dict is not None:
177
+ self.method = "dflash"
178
+ if self.num_speculative_tokens is None:
179
+ self.num_speculative_tokens = int(config_dict["block_size"])
180
+ post_init(self)
181
+
182
+ post_init_flashmap.flashmap = True # type: ignore[attr-defined]
183
+ return post_init_flashmap
184
+
185
+
186
+ def _vllm_version() -> tuple[int, int] | None:
187
+ from vllm import __version__ # noqa: PLC0415
188
+
189
+ try:
190
+ major, minor = __version__.split(".")[:2]
191
+ return int(major), int(minor)
192
+ except ValueError:
193
+ return None
194
+
195
+
196
+ def register() -> None:
197
+ """vLLM general plugin entry point (idempotent, never raises)."""
198
+ try:
199
+ version = _vllm_version()
200
+ if version not in SUPPORTED_VLLM:
201
+ logger.warning(
202
+ "FlashMap's vLLM plugin supports vLLM %s, not %s; FlashMap "
203
+ "checkpoints will not load.",
204
+ " / ".join(f"{a}.{b}.x" for a, b in SUPPORTED_VLLM),
205
+ version,
206
+ )
207
+ return
208
+ _register()
209
+ except Exception: # noqa: BLE001
210
+ logger.warning(
211
+ "FlashMap's vLLM plugin failed to register; FlashMap checkpoints will "
212
+ "not load.",
213
+ exc_info=True,
214
+ )
215
+
216
+
217
+ def _register() -> None:
218
+ from vllm import ModelRegistry # noqa: PLC0415
219
+ from vllm.transformers_utils.configs.speculators import ( # noqa: PLC0415
220
+ algos,
221
+ base,
222
+ )
223
+
224
+ algos.SUPPORTED_SPECULATORS_TYPES["flashmap"] = update_flashmap
225
+
226
+ from vllm.transformers_utils import config as config_utils # noqa: PLC0415
227
+
228
+ # vLLM reads a config.json as a speculators checkpoint only when it has no
229
+ # model_type; a release folder's is "flashmap" (for transformers), so route
230
+ # that model_type the same way: SpeculatorsConfig, through update_flashmap.
231
+ config_utils._CONFIG_REGISTRY.setdefault("flashmap", "SpeculatorsConfig") # noqa: SLF001
232
+ config_utils._SPECULATIVE_DECODING_CONFIGS.add("flashmap") # noqa: SLF001
233
+
234
+ build = base.SpeculatorsConfig.build_vllm_speculative_config
235
+ if not getattr(build, "flashmap", False):
236
+ # FlashMap runs on vLLM's DFlash runtime.
237
+ def build_vllm_speculative_config(cls, config_dict):
238
+ result = build.__func__(cls, config_dict)
239
+ if result["method"] == "flashmap":
240
+ result["method"] = "dflash"
241
+ return result
242
+
243
+ build_vllm_speculative_config.flashmap = True # type: ignore[attr-defined]
244
+ base.SpeculatorsConfig.build_vllm_speculative_config = classmethod( # type: ignore[method-assign]
245
+ build_vllm_speculative_config
246
+ )
247
+
248
+ from vllm.config.speculative import SpeculativeConfig # noqa: PLC0415
249
+
250
+ if not getattr(SpeculativeConfig.__post_init__, "flashmap", False):
251
+ SpeculativeConfig.__post_init__ = _post_init( # type: ignore[method-assign]
252
+ SpeculativeConfig.__post_init__
253
+ )
254
+
255
+ if FLASHMAP_ARCHITECTURE not in ModelRegistry.get_supported_archs():
256
+ ModelRegistry.register_model(
257
+ FLASHMAP_ARCHITECTURE,
258
+ "flashmap.vllm.model:FlashMapDraftModel",
259
+ )
260
+
261
+ from vllm.v1.worker.gpu import spec_decode # noqa: PLC0415
262
+
263
+ if not getattr(spec_decode.init_speculator, "flashmap", False):
264
+ spec_decode.init_speculator = _init_speculator(spec_decode.init_speculator)
265
+ # The model runner binds init_speculator at import.
266
+ runner = sys.modules.get("vllm.v1.worker.gpu.model_runner")
267
+ if runner is not None and not getattr(runner.init_speculator, "flashmap", False):
268
+ runner.init_speculator = spec_decode.init_speculator
flashmap/vllm/fold.py ADDED
@@ -0,0 +1,128 @@
1
+ """Load-time folds of FlashMap's one-jump draft (pure PyTorch, no vLLM).
2
+
3
+ ``fold_noise``: a one-jump draft embeds its starting noise ``x0 ~ N(0, I_V)`` as
4
+ ``x_embed(noise_norm(x0 @ E))`` (plus a constant). ``x0 @ E`` is Gaussian with
5
+ covariance ``E^T E = Q Lambda Q^T``, so it is drawn as ``z @ A`` from
6
+ ``z ~ N(0, I_H)`` with ``A = sqrt(Lambda) Q^T``; since ``A A^T = Lambda`` the
7
+ noise norm's mean square is ``sum(lambda z^2) / H``, elementwise in ``z``, and
8
+ ``A``, the noise norm's weight and ``x_embed``'s weight fold into one matrix:
9
+
10
+ x_embed(noise_norm(z @ A)) = rsqrt(z^2 @ lambda / H + eps) * (z @ M^T) + b,
11
+ M = W_x diag(w) A^T.
12
+
13
+ ``fold_head``: the DiT's output head (``D -> H`` with bias) and the target's LM
14
+ head (``H -> V``) have no nonlinearity between them, so the draft reads one
15
+ ``(V, D)`` matrix and bias instead of both.
16
+ """
17
+
18
+ import math
19
+
20
+ import torch
21
+
22
+ __all__ = [
23
+ "fold_head",
24
+ "fold_noise",
25
+ "noise_factor",
26
+ "quantize_fp8_per_channel",
27
+ "split_argmax",
28
+ "timestep_embedding",
29
+ ]
30
+
31
+
32
+ def timestep_embedding(t: torch.Tensor, dim: int, max_period: float = 10000.0):
33
+ """Sinusoidal embedding of ``t``: ``cat([cos(t f), sin(t f)])`` in float32."""
34
+ half = dim // 2
35
+ freqs = torch.exp(
36
+ -math.log(max_period)
37
+ * torch.arange(half, dtype=torch.float32, device=t.device)
38
+ / half
39
+ )
40
+ args = t.float()[..., None] * freqs
41
+ return torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
42
+
43
+
44
+ @torch.no_grad()
45
+ def noise_factor(
46
+ embedding: torch.Tensor, chunk: int = 16384
47
+ ) -> tuple[torch.Tensor, torch.Tensor]:
48
+ """``(A, lambda)`` in float64 for ``embedding (V, H)``: ``A = sqrt(Lambda)
49
+ Q^T`` with ``E^T E = Q Lambda Q^T``, so ``A^T A = E^T E`` and ``A A^T =
50
+ Lambda``."""
51
+ hidden = embedding.shape[1]
52
+ gram = torch.zeros(hidden, hidden, device=embedding.device, dtype=torch.float32)
53
+ for rows in embedding.split(chunk):
54
+ gram += rows.float().T @ rows.float()
55
+ eigvals, eigvecs = torch.linalg.eigh(gram.double())
56
+ eigvals = eigvals.clamp_min(0)
57
+ return eigvals.sqrt()[:, None] * eigvecs.T, eigvals
58
+
59
+
60
+ @torch.no_grad()
61
+ def fold_noise(
62
+ embedding: torch.Tensor,
63
+ noise_norm_weight: torch.Tensor,
64
+ x_embed_weight: torch.Tensor,
65
+ chunk: int = 16384,
66
+ ) -> tuple[torch.Tensor, torch.Tensor]:
67
+ """``(M, lambda / H)`` for ``embedding (V, H)``: ``M (D, H)`` in the
68
+ embedding's dtype, as ``F.linear`` takes it, and ``lambda / H (H,)`` in
69
+ float32 (see the module docstring)."""
70
+ factor, eigvals = noise_factor(embedding, chunk)
71
+ weight = noise_norm_weight.double()
72
+ folded = x_embed_weight.double() @ (factor * weight[None, :]).T
73
+ return folded.to(embedding.dtype), (eigvals / embedding.shape[1]).float()
74
+
75
+
76
+ @torch.no_grad()
77
+ def fold_head(
78
+ lm_head: torch.Tensor,
79
+ head_weight: torch.Tensor,
80
+ head_bias: torch.Tensor,
81
+ rows: torch.Tensor | None = None,
82
+ chunk: int = 8192,
83
+ ) -> tuple[torch.Tensor, torch.Tensor]:
84
+ """``lm_head[rows] @ head`` as one ``(len(rows), D)`` weight and bias in the
85
+ LM head's dtype, folded ``chunk`` rows at a time in float32."""
86
+ count = lm_head.shape[0] if rows is None else rows.numel()
87
+ weight = torch.empty(
88
+ count, head_weight.shape[1], dtype=lm_head.dtype, device=lm_head.device
89
+ )
90
+ bias = torch.empty(count, dtype=lm_head.dtype, device=lm_head.device)
91
+ head_weight, head_bias = head_weight.float(), head_bias.float()
92
+ for start in range(0, count, chunk):
93
+ part = (
94
+ lm_head[start : start + chunk]
95
+ if rows is None
96
+ else lm_head[rows[start : start + chunk]]
97
+ ).float()
98
+ weight[start : start + chunk] = (part @ head_weight).to(lm_head.dtype)
99
+ bias[start : start + chunk] = (part @ head_bias).to(lm_head.dtype)
100
+ return weight, bias
101
+
102
+
103
+ @torch.no_grad()
104
+ def quantize_fp8_per_channel(
105
+ weight: torch.Tensor, dtype: torch.dtype = torch.float8_e4m3fn
106
+ ) -> tuple[torch.Tensor, torch.Tensor]:
107
+ """``(w_q, scale)`` for a ``(N, K)`` weight: ``w_q (K, N)`` in ``dtype`` (a
108
+ column-major view, as scaled-mm kernels take it) and ``scale (N, 1)`` in
109
+ float32 with ``weight ~= (w_q.T * scale)``, one scale per output channel."""
110
+ finfo = torch.finfo(dtype)
111
+ w = weight.float()
112
+ scale = w.abs().amax(dim=1, keepdim=True).clamp_min(1e-12) / finfo.max
113
+ w_q = (w / scale).clamp(finfo.min, finfo.max).to(dtype)
114
+ return w_q.t(), scale.contiguous()
115
+
116
+
117
+ def split_argmax(logits: torch.Tensor, chunks: int = 128) -> torch.Tensor:
118
+ """``logits.argmax(-1)`` for ``(rows, vocab)`` logits, reduced over vocabulary
119
+ chunks first so that a few rows still fill the GPU. Ties resolve to the
120
+ first index, as ``argmax`` does."""
121
+ rows, vocab = logits.shape
122
+ while chunks > 1 and vocab % chunks:
123
+ chunks //= 2
124
+ if chunks == 1:
125
+ return logits.argmax(dim=-1)
126
+ values, index = logits.view(rows, chunks, vocab // chunks).max(dim=-1)
127
+ best = values.argmax(dim=-1, keepdim=True)
128
+ return (best * (vocab // chunks) + index.gather(1, best)).squeeze(1)