mxflashattn 1.4.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.
- benchmarks/__init__.py +1 -0
- benchmarks/compare.py +105 -0
- benchmarks/configs/c500.yaml +10 -0
- benchmarks/fair_matrix.py +191 -0
- benchmarks/fair_report.py +71 -0
- benchmarks/model_fair_report.py +60 -0
- benchmarks/plot_summary.py +119 -0
- benchmarks/report.py +139 -0
- benchmarks/run.py +328 -0
- integrations/__init__.py +1 -0
- integrations/vllm/README.md +7 -0
- integrations/vllm/__init__.py +1 -0
- integrations/vllm/backend.py +158 -0
- integrations/vllm/compat.py +46 -0
- integrations/vllm/config.py +11 -0
- integrations/vllm/decoder_adapter.py +286 -0
- integrations/vllm/probe_compat.py +22 -0
- integrations/vllm/probe_decoder_layout.py +91 -0
- integrations/vllm/probe_registration.py +24 -0
- integrations/vllm/probe_v017.py +55 -0
- integrations/vllm/smoke_generate.py +190 -0
- integrations/vllm/vllm_plugin.py +511 -0
- mxflashattn/__init__.py +30 -0
- mxflashattn/accuracy.py +51 -0
- mxflashattn/api.py +286 -0
- mxflashattn/dispatch.py +172 -0
- mxflashattn/policy.py +111 -0
- mxflashattn/prefill.py +108 -0
- mxflashattn/reference.py +139 -0
- mxflashattn/validation.py +100 -0
- mxflashattn-1.4.0.dist-info/METADATA +242 -0
- mxflashattn-1.4.0.dist-info/RECORD +35 -0
- mxflashattn-1.4.0.dist-info/WHEEL +5 -0
- mxflashattn-1.4.0.dist-info/licenses/LICENSE +201 -0
- mxflashattn-1.4.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
VLLM_TARGET_VERSION = "0.17.0"
|
|
2
|
+
QUICK_MODEL = "/mnt/moark-models/Qwen3-0.6B"
|
|
3
|
+
SHOWCASE_MODEL = "Qwen/Qwen2.5-7B-Instruct"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def validate_vllm_version(version: str) -> None:
|
|
7
|
+
if version != VLLM_TARGET_VERSION:
|
|
8
|
+
raise RuntimeError(
|
|
9
|
+
f"MXFlashAttn currently targets vLLM {VLLM_TARGET_VERSION}; found {version}. "
|
|
10
|
+
"Confirm compatibility before selecting an attention backend."
|
|
11
|
+
)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Small, explicit bridge for vLLM 0.17 paged decoder metadata.
|
|
2
|
+
|
|
3
|
+
This adapter intentionally supports one-token decode only. It converts the
|
|
4
|
+
packed vLLM tensors and the ``[2, blocks, block_size, kv_heads, dim]`` cache
|
|
5
|
+
layout into ``flash_attn_with_kvcache``. Prefill, cascade attention, FP8,
|
|
6
|
+
and scheduler-specific metadata are rejected until they have dedicated tests.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import torch
|
|
16
|
+
|
|
17
|
+
from mxflashattn import flash_attn_with_kvcache
|
|
18
|
+
|
|
19
|
+
# inspect_layout() reads query_start_loc / seq_lens back to the host, which is a
|
|
20
|
+
# device synchronisation. Inside vLLM this runs once per layer per decode step
|
|
21
|
+
# (28 layers x N steps x prompts), so it dominates end-to-end latency. The cheap
|
|
22
|
+
# shape/device/dtype checks always run; the host-syncing inspection is gated and
|
|
23
|
+
# enabled by the test suite via MXFLASHATTN_STRICT_LAYOUT=1.
|
|
24
|
+
STRICT_LAYOUT = os.getenv("MXFLASHATTN_STRICT_LAYOUT", "0") == "1"
|
|
25
|
+
def _transparent_enabled() -> bool:
|
|
26
|
+
# Resolve at call time so vLLM workers and tests can configure the mode
|
|
27
|
+
# after importing this module.
|
|
28
|
+
return os.getenv("MXFLASHATTN_TRANSPARENT", "0") == "1"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class DecoderLayout:
|
|
33
|
+
num_actual_tokens: int
|
|
34
|
+
batch_size: int
|
|
35
|
+
query_lens: tuple[int, ...]
|
|
36
|
+
max_query_len: int
|
|
37
|
+
seq_lens: tuple[int, ...]
|
|
38
|
+
query_start_loc: tuple[int, ...]
|
|
39
|
+
block_table_shape: tuple[int, int]
|
|
40
|
+
kv_cache_shape: tuple[int, int, int, int, int]
|
|
41
|
+
scheduler_metadata_present: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DecoderAdapterBlocked(RuntimeError):
|
|
45
|
+
"""Raised when vLLM metadata is outside the verified adapter contract."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def inspect_layout(query: torch.Tensor, kv_cache: torch.Tensor, attn_metadata: Any) -> DecoderLayout:
|
|
49
|
+
"""Validate and record the vLLM 0.17 decoder tensor layout."""
|
|
50
|
+
required = (
|
|
51
|
+
"num_actual_tokens", "max_query_len", "query_start_loc", "max_seq_len",
|
|
52
|
+
"seq_lens", "block_table", "scheduler_metadata", "causal", "use_cascade",
|
|
53
|
+
)
|
|
54
|
+
missing = [name for name in required if not hasattr(attn_metadata, name)]
|
|
55
|
+
if missing:
|
|
56
|
+
raise DecoderAdapterBlocked(f"metadata_missing_fields:{','.join(missing)}")
|
|
57
|
+
if query.ndim != 3:
|
|
58
|
+
raise DecoderAdapterBlocked(f"query_rank:{query.ndim},expected:3")
|
|
59
|
+
if kv_cache.ndim != 5 or kv_cache.shape[0] != 2:
|
|
60
|
+
raise DecoderAdapterBlocked(
|
|
61
|
+
f"kv_cache_shape:{tuple(kv_cache.shape)},expected:[2,blocks,block_size,kv_heads,head_dim]"
|
|
62
|
+
)
|
|
63
|
+
starts = tuple(int(x) for x in attn_metadata.query_start_loc.detach().cpu().tolist())
|
|
64
|
+
seq_lens = tuple(int(x) for x in attn_metadata.seq_lens.detach().cpu().tolist())
|
|
65
|
+
if len(starts) != len(seq_lens) + 1 or starts[0] != 0 or starts[-1] != int(attn_metadata.num_actual_tokens):
|
|
66
|
+
raise DecoderAdapterBlocked("query_start_loc_does_not_cover_num_actual_tokens")
|
|
67
|
+
query_lens = tuple(right - left for left, right in zip(starts, starts[1:]))
|
|
68
|
+
block_table = attn_metadata.block_table
|
|
69
|
+
if block_table.ndim != 2 or block_table.shape[0] != len(seq_lens):
|
|
70
|
+
raise DecoderAdapterBlocked("block_table_rows_do_not_match_batch")
|
|
71
|
+
return DecoderLayout(
|
|
72
|
+
num_actual_tokens=int(attn_metadata.num_actual_tokens),
|
|
73
|
+
batch_size=len(seq_lens),
|
|
74
|
+
query_lens=query_lens,
|
|
75
|
+
max_query_len=int(attn_metadata.max_query_len),
|
|
76
|
+
seq_lens=seq_lens,
|
|
77
|
+
query_start_loc=starts,
|
|
78
|
+
block_table_shape=(int(block_table.shape[0]), int(block_table.shape[1])),
|
|
79
|
+
kv_cache_shape=tuple(int(x) for x in kv_cache.shape),
|
|
80
|
+
scheduler_metadata_present=attn_metadata.scheduler_metadata is not None,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def fast_layout(query: torch.Tensor, kv_cache: torch.Tensor, attn_metadata: Any) -> DecoderLayout:
|
|
85
|
+
"""Cheap layout capture that performs no device-to-host synchronisation."""
|
|
86
|
+
required = (
|
|
87
|
+
"num_actual_tokens", "max_query_len", "query_start_loc", "max_seq_len",
|
|
88
|
+
"seq_lens", "block_table", "scheduler_metadata", "causal", "use_cascade",
|
|
89
|
+
)
|
|
90
|
+
missing = [name for name in required if not hasattr(attn_metadata, name)]
|
|
91
|
+
if missing:
|
|
92
|
+
raise DecoderAdapterBlocked(f"metadata_missing_fields:{','.join(missing)}")
|
|
93
|
+
if query.ndim != 3:
|
|
94
|
+
raise DecoderAdapterBlocked(f"query_rank:{query.ndim},expected:3")
|
|
95
|
+
if kv_cache.ndim != 5 or kv_cache.shape[0] != 2:
|
|
96
|
+
raise DecoderAdapterBlocked(
|
|
97
|
+
f"kv_cache_shape:{tuple(kv_cache.shape)},expected:[2,blocks,block_size,kv_heads,head_dim]"
|
|
98
|
+
)
|
|
99
|
+
block_table = attn_metadata.block_table
|
|
100
|
+
if block_table.ndim != 2:
|
|
101
|
+
raise DecoderAdapterBlocked("block_table_rows_do_not_match_batch")
|
|
102
|
+
batch = int(block_table.shape[0])
|
|
103
|
+
max_query_len = int(attn_metadata.max_query_len)
|
|
104
|
+
return DecoderLayout(
|
|
105
|
+
num_actual_tokens=int(attn_metadata.num_actual_tokens),
|
|
106
|
+
batch_size=batch,
|
|
107
|
+
query_lens=tuple([max_query_len] * batch),
|
|
108
|
+
max_query_len=max_query_len,
|
|
109
|
+
seq_lens=(),
|
|
110
|
+
query_start_loc=(),
|
|
111
|
+
block_table_shape=(batch, int(block_table.shape[1])),
|
|
112
|
+
kv_cache_shape=tuple(int(x) for x in kv_cache.shape),
|
|
113
|
+
scheduler_metadata_present=attn_metadata.scheduler_metadata is not None,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def decode_forward(
|
|
118
|
+
query: torch.Tensor,
|
|
119
|
+
kv_cache: torch.Tensor,
|
|
120
|
+
attn_metadata: Any,
|
|
121
|
+
*,
|
|
122
|
+
softmax_scale: float,
|
|
123
|
+
causal: bool = True,
|
|
124
|
+
) -> tuple[torch.Tensor, DecoderLayout]:
|
|
125
|
+
"""Run the verified one-token paged decode path through MXFlashAttn.
|
|
126
|
+
|
|
127
|
+
vLLM updates the cache before calling attention. Consequently ``k`` and
|
|
128
|
+
``v`` are omitted here and ``seq_lens`` describes the already populated
|
|
129
|
+
cache. This is stricter than vLLM's general FlashAttention implementation
|
|
130
|
+
so an unsupported prefill path cannot silently delegate.
|
|
131
|
+
"""
|
|
132
|
+
if os.getenv("MXFLASHATTN_GRAPH_DECODE", "0") == "1":
|
|
133
|
+
# Graph-safe path is decoder-only. Prefill may have a static padded
|
|
134
|
+
# query shape, but its metadata is not one cache sequence per query
|
|
135
|
+
# row; sending it to the paged KV decoder corrupts outputs.
|
|
136
|
+
# In full CUDA-graph mode vLLM pads q to a capture size while keeping
|
|
137
|
+
# one metadata row per logical request. A singleton seq_lens/block row
|
|
138
|
+
# therefore identifies a padded one-token decode, even when
|
|
139
|
+
# max_query_len contains the capture/prefill value (for example 15).
|
|
140
|
+
has_padded_decode = (
|
|
141
|
+
attn_metadata.seq_lens.ndim == 1
|
|
142
|
+
and attn_metadata.block_table.ndim == 2
|
|
143
|
+
and attn_metadata.seq_lens.numel() == attn_metadata.block_table.shape[0]
|
|
144
|
+
and attn_metadata.seq_lens.numel() < query.shape[0]
|
|
145
|
+
)
|
|
146
|
+
has_split_decode = (
|
|
147
|
+
int(getattr(attn_metadata, "num_decode_tokens", 0) or 0) > 0
|
|
148
|
+
and int(getattr(attn_metadata, "num_decodes", 0) or 0) > 0
|
|
149
|
+
and getattr(attn_metadata, "decode_seq_lens", None) is not None
|
|
150
|
+
and getattr(attn_metadata, "decode_block_table", None) is not None
|
|
151
|
+
)
|
|
152
|
+
if int(getattr(attn_metadata, "max_query_len", 0)) != 1 and not has_padded_decode and not has_split_decode:
|
|
153
|
+
raise DecoderAdapterBlocked(
|
|
154
|
+
f"only_single_token_decode_supported:max_query_len={int(getattr(attn_metadata, 'max_query_len', 0))}"
|
|
155
|
+
)
|
|
156
|
+
return _decode_forward_graph(query, kv_cache, attn_metadata,
|
|
157
|
+
softmax_scale=softmax_scale, causal=causal)
|
|
158
|
+
if _transparent_enabled():
|
|
159
|
+
# Transparent mode still only owns the verified one-token decoder
|
|
160
|
+
# contract. Prefill has one seq_lens entry but many query tokens, so
|
|
161
|
+
# forwarding it to the paged KV API produces a batch-size mismatch;
|
|
162
|
+
# let vLLM's native implementation handle that path.
|
|
163
|
+
if int(attn_metadata.max_query_len) != 1:
|
|
164
|
+
raise DecoderAdapterBlocked(
|
|
165
|
+
f"only_single_token_decode_supported:max_query_len={int(attn_metadata.max_query_len)}"
|
|
166
|
+
)
|
|
167
|
+
key_cache, value_cache = kv_cache.unbind(0)
|
|
168
|
+
n = int(attn_metadata.num_actual_tokens)
|
|
169
|
+
q = query[:n].reshape(n, 1, query.shape[1], query.shape[2])
|
|
170
|
+
# vLLM capture/replay metadata may expose seq_lens as a singleton
|
|
171
|
+
# leading-dimension view (for example [1, batch]). The public
|
|
172
|
+
# flash-attn contract is strictly one-dimensional, so normalize the
|
|
173
|
+
# view without reading it back to the host.
|
|
174
|
+
seq_lens = attn_metadata.seq_lens.reshape(-1)
|
|
175
|
+
cache_seqlens = (
|
|
176
|
+
seq_lens
|
|
177
|
+
if (seq_lens.device == query.device and seq_lens.dtype == torch.int32)
|
|
178
|
+
else seq_lens.to(device=query.device, dtype=torch.int32)
|
|
179
|
+
)
|
|
180
|
+
bt_raw = attn_metadata.block_table
|
|
181
|
+
block_table = bt_raw if (bt_raw.device == query.device and bt_raw.dtype == torch.int32) else bt_raw.to(device=query.device, dtype=torch.int32)
|
|
182
|
+
result = flash_attn_with_kvcache(q, key_cache, value_cache, cache_seqlens=cache_seqlens, block_table=block_table, softmax_scale=softmax_scale, causal=causal)
|
|
183
|
+
return result.reshape(n, -1), None
|
|
184
|
+
layout = inspect_layout(query, kv_cache, attn_metadata) if STRICT_LAYOUT else fast_layout(query, kv_cache, attn_metadata)
|
|
185
|
+
if not bool(attn_metadata.causal) and causal:
|
|
186
|
+
raise DecoderAdapterBlocked("metadata_causal_flag_is_false")
|
|
187
|
+
if bool(attn_metadata.use_cascade):
|
|
188
|
+
raise DecoderAdapterBlocked("cascade_attention_not_supported_by_adapter")
|
|
189
|
+
if layout.max_query_len != 1:
|
|
190
|
+
import os as _os
|
|
191
|
+
if _os.getenv("MXFLASHATTN_GRAPH_PROBE") == "1":
|
|
192
|
+
try:
|
|
193
|
+
with open("/tmp/graph_probe.log", "a") as _lf:
|
|
194
|
+
_lf.write(
|
|
195
|
+
f"max_query_len={layout.max_query_len} "
|
|
196
|
+
f"query_shape={tuple(query.shape)} "
|
|
197
|
+
f"n_actual={int(attn_metadata.num_actual_tokens)} "
|
|
198
|
+
f"meta_type={type(attn_metadata).__name__} "
|
|
199
|
+
f"has_capture_meta={hasattr(attn_metadata,'attention_metadata_for_capture')} "
|
|
200
|
+
f"q_start_loc_present={hasattr(attn_metadata,'query_start_loc')} "
|
|
201
|
+
f"seq_lens_shape={tuple(getattr(attn_metadata,'seq_lens',__import__('torch').empty(0)).shape) if hasattr(attn_metadata,'seq_lens') else 'NA'}\n"
|
|
202
|
+
)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
raise DecoderAdapterBlocked(
|
|
206
|
+
f"only_single_token_decode_supported:max_query_len={layout.max_query_len}"
|
|
207
|
+
)
|
|
208
|
+
if kv_cache.dtype not in (torch.float16, torch.bfloat16):
|
|
209
|
+
raise DecoderAdapterBlocked(f"kv_cache_dtype_not_supported:{kv_cache.dtype}")
|
|
210
|
+
if query.shape[1] % kv_cache.shape[3]:
|
|
211
|
+
raise DecoderAdapterBlocked("query_and_kv_head_counts_are_not_gqa_compatible")
|
|
212
|
+
key_cache, value_cache = kv_cache.unbind(0)
|
|
213
|
+
batch = layout.batch_size
|
|
214
|
+
q = query[: layout.num_actual_tokens].reshape(batch, 1, query.shape[1], query.shape[2])
|
|
215
|
+
# vLLM 0.17 already keeps these on the device in int32. Calling .to()
|
|
216
|
+
# unconditionally costs a Python/dispatch round trip per layer per step, and
|
|
217
|
+
# .clone() cost even more. Only convert when the contract is actually broken.
|
|
218
|
+
seq_lens = attn_metadata.seq_lens
|
|
219
|
+
cache_seqlens = (
|
|
220
|
+
seq_lens if seq_lens.device == query.device and seq_lens.dtype == torch.int32
|
|
221
|
+
else seq_lens.to(device=query.device, dtype=torch.int32)
|
|
222
|
+
)
|
|
223
|
+
block_table_raw = attn_metadata.block_table
|
|
224
|
+
block_table = (
|
|
225
|
+
block_table_raw
|
|
226
|
+
if block_table_raw.device == query.device and block_table_raw.dtype == torch.int32
|
|
227
|
+
else block_table_raw.to(device=query.device, dtype=torch.int32)
|
|
228
|
+
)
|
|
229
|
+
result = flash_attn_with_kvcache(
|
|
230
|
+
q, key_cache, value_cache, cache_seqlens=cache_seqlens,
|
|
231
|
+
block_table=block_table, softmax_scale=softmax_scale, causal=causal,
|
|
232
|
+
)
|
|
233
|
+
return result.reshape(layout.num_actual_tokens, -1), layout
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _decode_forward_graph(
|
|
237
|
+
query: torch.Tensor,
|
|
238
|
+
kv_cache: torch.Tensor,
|
|
239
|
+
attn_metadata: Any,
|
|
240
|
+
*,
|
|
241
|
+
softmax_scale: float,
|
|
242
|
+
causal: bool = True,
|
|
243
|
+
):
|
|
244
|
+
"""Run the static-shape graph decode using MetaX's split decode metadata.
|
|
245
|
+
|
|
246
|
+
vLLM/MetaX keeps padded prefill metadata in ``seq_lens``/``block_table``
|
|
247
|
+
and exposes the actual decode subset separately. Using the former and
|
|
248
|
+
broadcasting it to ``query.shape[0]`` makes a graph-shaped call execute
|
|
249
|
+
with the wrong KV pages.
|
|
250
|
+
"""
|
|
251
|
+
key_cache, value_cache = kv_cache.unbind(0)
|
|
252
|
+
decode_n = int(getattr(attn_metadata, "num_decode_tokens", 0) or 0)
|
|
253
|
+
decode_batch = int(getattr(attn_metadata, "num_decodes", 0) or 0)
|
|
254
|
+
decode_seq = getattr(attn_metadata, "decode_seq_lens", None)
|
|
255
|
+
decode_bt = getattr(attn_metadata, "decode_block_table", None)
|
|
256
|
+
if decode_n > 0 and decode_batch > 0 and decode_seq is not None and decode_bt is not None:
|
|
257
|
+
q_tokens = query[:decode_n]
|
|
258
|
+
if decode_n % decode_batch:
|
|
259
|
+
raise DecoderAdapterBlocked("decode_tokens_not_divisible_by_num_decodes")
|
|
260
|
+
q = q_tokens.reshape(decode_batch, decode_n // decode_batch, query.shape[1], query.shape[2]).contiguous()
|
|
261
|
+
cache_seqlens = decode_seq.reshape(-1)
|
|
262
|
+
block_table = decode_bt
|
|
263
|
+
batch = decode_batch
|
|
264
|
+
else:
|
|
265
|
+
raw_seq = attn_metadata.seq_lens.reshape(-1)
|
|
266
|
+
raw_bt = attn_metadata.block_table
|
|
267
|
+
# Capture padding belongs to the graph buffer, not to the logical
|
|
268
|
+
# attention batch. Use one q row per metadata row and leave padded q
|
|
269
|
+
# rows out of the vendor call.
|
|
270
|
+
batch = int(raw_seq.numel()) if raw_seq.numel() < query.shape[0] else query.shape[0]
|
|
271
|
+
q = query[:batch].reshape(batch, 1, query.shape[1], query.shape[2]).contiguous()
|
|
272
|
+
cache_seqlens = raw_seq
|
|
273
|
+
block_table = raw_bt
|
|
274
|
+
if cache_seqlens.device != query.device or cache_seqlens.dtype != torch.int32:
|
|
275
|
+
cache_seqlens = cache_seqlens.to(device=query.device, dtype=torch.int32)
|
|
276
|
+
if block_table.device != query.device or block_table.dtype != torch.int32:
|
|
277
|
+
block_table = block_table.to(device=query.device, dtype=torch.int32)
|
|
278
|
+
if cache_seqlens.numel() != batch or block_table.shape[0] != batch:
|
|
279
|
+
raise DecoderAdapterBlocked("decode_metadata_batch_mismatch")
|
|
280
|
+
cache_seqlens = cache_seqlens.contiguous()
|
|
281
|
+
block_table = block_table.contiguous()
|
|
282
|
+
result = flash_attn_with_kvcache(
|
|
283
|
+
q, key_cache, value_cache, cache_seqlens=cache_seqlens,
|
|
284
|
+
block_table=block_table, softmax_scale=softmax_scale, causal=causal,
|
|
285
|
+
)
|
|
286
|
+
return result.reshape(q.shape[0] * q.shape[1], -1), None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Write a non-invasive vLLM compatibility report for the current environment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from integrations.vllm.compat import classify_vllm, detect_vllm_version
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> None:
|
|
12
|
+
parser = argparse.ArgumentParser()
|
|
13
|
+
parser.add_argument("--output", type=Path, default=Path("artifacts/vllm-compatibility.json"))
|
|
14
|
+
args = parser.parse_args()
|
|
15
|
+
result = classify_vllm(detect_vllm_version())
|
|
16
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
args.output.write_text(json.dumps(result.__dict__, indent=2) + "\n", encoding="utf-8")
|
|
18
|
+
print(json.dumps(result.__dict__, indent=2))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Capture vLLM 0.17 decoder layout evidence and exercise the strict adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
from integrations.vllm.decoder_adapter import DecoderAdapterBlocked, decode_forward, inspect_layout
|
|
15
|
+
from mxflashattn.dispatch import get_last_dispatch_info
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Metadata:
|
|
20
|
+
num_actual_tokens: int
|
|
21
|
+
max_query_len: int
|
|
22
|
+
query_start_loc: torch.Tensor
|
|
23
|
+
max_seq_len: int
|
|
24
|
+
seq_lens: torch.Tensor
|
|
25
|
+
block_table: torch.Tensor
|
|
26
|
+
slot_mapping: torch.Tensor
|
|
27
|
+
use_cascade: bool = False
|
|
28
|
+
common_prefix_len: int = 0
|
|
29
|
+
cu_prefix_query_lens: torch.Tensor | None = None
|
|
30
|
+
prefix_kv_lens: torch.Tensor | None = None
|
|
31
|
+
suffix_kv_lens: torch.Tensor | None = None
|
|
32
|
+
scheduler_metadata: torch.Tensor | None = None
|
|
33
|
+
causal: bool = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main() -> None:
|
|
37
|
+
parser = argparse.ArgumentParser()
|
|
38
|
+
parser.add_argument("--output", type=Path, default=Path("artifacts/decoder-layout.json"))
|
|
39
|
+
parser.add_argument("--log", type=Path, default=Path("artifacts/decoder-layout.log"))
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
os.environ.setdefault("MXFLASHATTN_ALLOW_FALLBACK", "1")
|
|
42
|
+
torch.manual_seed(7)
|
|
43
|
+
# MetaX PyTorch exposes C500 through the CUDA-compatible device namespace.
|
|
44
|
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
45
|
+
dtype = torch.float16
|
|
46
|
+
batch, q_heads, kv_heads, dim = 2, 4, 2, 64
|
|
47
|
+
block_size, blocks, seq_len = 16, 4, 17
|
|
48
|
+
query = torch.randn(batch, q_heads, dim, device=device, dtype=dtype)
|
|
49
|
+
kv_cache = torch.randn(2, blocks, block_size, kv_heads, dim, device=device, dtype=dtype)
|
|
50
|
+
metadata = Metadata(
|
|
51
|
+
num_actual_tokens=batch, max_query_len=1,
|
|
52
|
+
query_start_loc=torch.tensor([0, 1, 2], device=device, dtype=torch.int32),
|
|
53
|
+
max_seq_len=seq_len,
|
|
54
|
+
seq_lens=torch.tensor([seq_len, seq_len - 1], device=device, dtype=torch.int32),
|
|
55
|
+
block_table=torch.tensor([[0, 1], [2, 3]], device=device, dtype=torch.int32),
|
|
56
|
+
slot_mapping=torch.tensor([0, 1], device=device, dtype=torch.int32),
|
|
57
|
+
)
|
|
58
|
+
started = time.perf_counter()
|
|
59
|
+
record: dict[str, object] = {
|
|
60
|
+
"schema_version": 1, "kind": "vllm_decoder_layout_probe", "target_vllm": "0.17.0",
|
|
61
|
+
"device": str(device), "dtype": str(dtype), "contract": "single_token_decode_paged_kv",
|
|
62
|
+
"source_layout": {
|
|
63
|
+
"query": "[num_tokens,num_heads,head_size]",
|
|
64
|
+
"kv_cache": "[2,num_blocks,block_size,num_kv_heads,head_size]",
|
|
65
|
+
"metadata": ["num_actual_tokens", "max_query_len", "query_start_loc", "max_seq_len",
|
|
66
|
+
"seq_lens", "block_table", "slot_mapping", "scheduler_metadata", "use_cascade"],
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
try:
|
|
70
|
+
layout = inspect_layout(query, kv_cache, metadata)
|
|
71
|
+
output, layout = decode_forward(query, kv_cache, metadata, softmax_scale=dim ** -0.5)
|
|
72
|
+
info = get_last_dispatch_info()
|
|
73
|
+
record.update({"status": "adapter_exercised", "layout": layout.__dict__,
|
|
74
|
+
"output_shape": list(output.shape),
|
|
75
|
+
"dispatch": info.__dict__ if info else None,
|
|
76
|
+
"elapsed_ms": (time.perf_counter() - started) * 1000,
|
|
77
|
+
"fallback_allowed": True})
|
|
78
|
+
except DecoderAdapterBlocked as exc:
|
|
79
|
+
record.update({"status": "blocked", "blocker": str(exc), "dispatch": None})
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
info = get_last_dispatch_info()
|
|
82
|
+
record.update({"status": "adapter_runtime_error", "error": f"{type(exc).__name__}: {exc}",
|
|
83
|
+
"dispatch": info.__dict__ if info else None})
|
|
84
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
args.output.write_text(json.dumps(record, indent=2, default=str), encoding="utf-8")
|
|
86
|
+
args.log.write_text(json.dumps(record, indent=2, default=str) + "\n", encoding="utf-8")
|
|
87
|
+
print(json.dumps(record, indent=2, default=str))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Validate the vLLM 0.17 v1 registration hook in an isolated process."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from integrations.vllm.backend import register_vllm_v1_backend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> None:
|
|
12
|
+
try:
|
|
13
|
+
version = importlib.metadata.version("vllm")
|
|
14
|
+
except importlib.metadata.PackageNotFoundError:
|
|
15
|
+
version = None
|
|
16
|
+
result = {"vllm_version": version, "registration": register_vllm_v1_backend()}
|
|
17
|
+
output = Path("artifacts/vllm-0.17-registration.json")
|
|
18
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
output.write_text(json.dumps(result, indent=2), encoding="utf-8")
|
|
20
|
+
print(f"Wrote registration probe to {output}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
main()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Inspect the installed vLLM attention extension points without mutating vLLM."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
import inspect
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _describe(module_name: str, names: list[str]) -> dict:
|
|
12
|
+
result = {"module": module_name, "imports": {}}
|
|
13
|
+
try:
|
|
14
|
+
module = importlib.import_module(module_name)
|
|
15
|
+
except Exception as exc: # pragma: no cover - depends on installed vLLM
|
|
16
|
+
result["import_error"] = f"{type(exc).__name__}: {exc}"
|
|
17
|
+
return result
|
|
18
|
+
for name in names:
|
|
19
|
+
try:
|
|
20
|
+
value = getattr(module, name)
|
|
21
|
+
result["imports"][name] = {
|
|
22
|
+
"type": type(value).__name__,
|
|
23
|
+
"signature": str(inspect.signature(value)) if callable(value) else None,
|
|
24
|
+
"public_attributes": sorted(name for name in dir(value) if not name.startswith("_"))[:80],
|
|
25
|
+
}
|
|
26
|
+
except Exception as exc:
|
|
27
|
+
result["imports"][name] = {"error": f"{type(exc).__name__}: {exc}"}
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> None:
|
|
32
|
+
try:
|
|
33
|
+
version = importlib.metadata.version("vllm")
|
|
34
|
+
except importlib.metadata.PackageNotFoundError:
|
|
35
|
+
version = None
|
|
36
|
+
result = {
|
|
37
|
+
"vllm_version": version,
|
|
38
|
+
"modules": [
|
|
39
|
+
_describe("vllm.attention.backends.abstract", ["AttentionBackend", "AttentionMetadata", "AttentionType"]),
|
|
40
|
+
_describe("vllm.attention.backends.registry", ["AttentionBackendEnum", "register_backend", "get_backend" ]),
|
|
41
|
+
_describe("vllm.attention.backends.flash_attn", ["FlashAttentionBackend", "FlashAttentionMetadata"]),
|
|
42
|
+
_describe("vllm.attention.layer", ["Attention" ]),
|
|
43
|
+
_describe("vllm.v1.attention.backend", ["AttentionBackend", "AttentionMetadata", "AttentionImpl"]),
|
|
44
|
+
_describe("vllm.v1.attention.backends.registry", ["AttentionBackendEnum", "register_backend", "get_attn_backend"]),
|
|
45
|
+
_describe("vllm.v1.attention.backends.flash_attn", ["FlashAttentionBackend", "FlashAttentionImpl", "FlashAttentionMetadata"]),
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
output = Path("artifacts/vllm-0.17-api-probe.json")
|
|
49
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
output.write_text(json.dumps(result, indent=2), encoding="utf-8")
|
|
51
|
+
print(f"Wrote vLLM API probe to {output}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|