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,511 @@
|
|
|
1
|
+
"""Importable vLLM 0.17.0 backend classes.
|
|
2
|
+
|
|
3
|
+
The classes live at module scope because vLLM's registry resolves the backend
|
|
4
|
+
by import path inside spawned EngineCore workers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from vllm.v1.attention.backend import AttentionType
|
|
12
|
+
_METAX_IMPL = None
|
|
13
|
+
try:
|
|
14
|
+
# MetaX names its backend class MacaFlashAttentionBackend; importing the
|
|
15
|
+
# upstream name FlashAttentionBackend from vllm_metax raises ImportError
|
|
16
|
+
# and previously fell back to the UPSTREAM vLLM classes, silently making
|
|
17
|
+
# the candidate run the upstream attention implementation instead of the
|
|
18
|
+
# vendor one. Import the real vendor class names here.
|
|
19
|
+
from vllm_metax.v1.attention.backends.flash_attn import (
|
|
20
|
+
FlashAttentionImpl,
|
|
21
|
+
MacaFlashAttentionBackend as FlashAttentionBackend,
|
|
22
|
+
)
|
|
23
|
+
_METAX_IMPL = FlashAttentionImpl
|
|
24
|
+
except ImportError as _metax_import_error: # CPU/mock CI without the vendor distribution.
|
|
25
|
+
from vllm.v1.attention.backends.flash_attn import (
|
|
26
|
+
FlashAttentionBackend,
|
|
27
|
+
FlashAttentionImpl,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# The MetaX attention module can be unavailable during worker import when its
|
|
31
|
+
# optional FA2 probe runs before the C500 runtime is initialized. The vendor
|
|
32
|
+
# utility module remains importable and provides the exact cache op and FA
|
|
33
|
+
# version contract needed by the upstream implementation.
|
|
34
|
+
try:
|
|
35
|
+
import vllm.v1.attention.backends.flash_attn as _upstream_fa
|
|
36
|
+
from vllm_metax.v1.attention.backends import fa_utils as _metax_fa_utils
|
|
37
|
+
|
|
38
|
+
_upstream_fa.get_flash_attn_version = (
|
|
39
|
+
lambda requires_alibi=False, head_size=None: _metax_fa_utils.get_flash_attn_version(
|
|
40
|
+
requires_alibi=requires_alibi, head_size=head_size
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
if hasattr(_metax_fa_utils, "reshape_and_cache_flash"):
|
|
44
|
+
_upstream_fa.reshape_and_cache_flash = _metax_fa_utils.reshape_and_cache_flash
|
|
45
|
+
for _name in ("flash_attn_varlen_func", "flash_attn_with_kvcache"):
|
|
46
|
+
if hasattr(_metax_fa_utils, _name):
|
|
47
|
+
setattr(_upstream_fa, _name, getattr(_metax_fa_utils, _name))
|
|
48
|
+
if hasattr(_metax_fa_utils, "flash_attn_varlen_func"):
|
|
49
|
+
_vendor_varlen = _metax_fa_utils.flash_attn_varlen_func
|
|
50
|
+
|
|
51
|
+
def _compat_varlen(*args, **kwargs):
|
|
52
|
+
out = kwargs.pop("out", None)
|
|
53
|
+
seqused_k = kwargs.pop("seqused_k", None)
|
|
54
|
+
if seqused_k is not None and "cu_seqlens_k" not in kwargs:
|
|
55
|
+
import torch
|
|
56
|
+
|
|
57
|
+
lengths = seqused_k.to(dtype=torch.int32)
|
|
58
|
+
if "cu_seqlens_q" in kwargs:
|
|
59
|
+
kwargs["cu_seqlens_q"] = kwargs["cu_seqlens_q"].to(dtype=torch.int32)
|
|
60
|
+
kwargs["cu_seqlens_k"] = torch.cat(
|
|
61
|
+
(torch.zeros(1, dtype=lengths.dtype, device=lengths.device), lengths.cumsum(0))
|
|
62
|
+
)
|
|
63
|
+
if "cu_seqlens_k" in kwargs:
|
|
64
|
+
kwargs["cu_seqlens_k"] = kwargs["cu_seqlens_k"].to(dtype=torch.int32)
|
|
65
|
+
if "cu_seqlens_q" in kwargs:
|
|
66
|
+
kwargs["cu_seqlens_q"] = kwargs["cu_seqlens_q"].to(dtype=torch.int32)
|
|
67
|
+
for _unsupported in (
|
|
68
|
+
"fa_version", "q_descale", "k_descale", "v_descale",
|
|
69
|
+
"num_splits", "scheduler_metadata",
|
|
70
|
+
):
|
|
71
|
+
kwargs.pop(_unsupported, None)
|
|
72
|
+
result = _vendor_varlen(*args, **kwargs)
|
|
73
|
+
if isinstance(result, tuple):
|
|
74
|
+
result = result[0]
|
|
75
|
+
if out is not None:
|
|
76
|
+
out.copy_(result)
|
|
77
|
+
return out
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
_upstream_fa.flash_attn_varlen_func = _compat_varlen
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
from integrations.vllm.backend import _record_event
|
|
85
|
+
from integrations.vllm.decoder_adapter import DecoderAdapterBlocked, decode_forward
|
|
86
|
+
from mxflashattn import flash_attn_func, flash_attn_varlen_func, flash_attn_with_kvcache
|
|
87
|
+
from mxflashattn.dispatch import get_last_dispatch_info
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
_worker_event_count = 0
|
|
93
|
+
|
|
94
|
+
def _record_worker_event(event: dict[str, str]) -> None:
|
|
95
|
+
"""Persist worker-owned evidence even when backend module state is isolated."""
|
|
96
|
+
global _worker_event_count
|
|
97
|
+
_worker_event_count += 1
|
|
98
|
+
sample = int(os.getenv("MXFLASHATTN_DISPATCH_SAMPLE", "0") or 0)
|
|
99
|
+
if sample > 1 and _worker_event_count % sample != 1:
|
|
100
|
+
return
|
|
101
|
+
path = os.getenv("MXFLASHATTN_DISPATCH_LOG")
|
|
102
|
+
if not path:
|
|
103
|
+
plugin_log = os.getenv("MXFLASHATTN_PLUGIN_IMPORT_LOG")
|
|
104
|
+
if plugin_log:
|
|
105
|
+
path = plugin_log.removesuffix(".plugin.log") + ".dispatch.jsonl"
|
|
106
|
+
if not path:
|
|
107
|
+
return
|
|
108
|
+
try:
|
|
109
|
+
import json
|
|
110
|
+
with open(path, "a", encoding="utf-8") as stream:
|
|
111
|
+
stream.write(json.dumps(event, sort_keys=True) + "\n")
|
|
112
|
+
except OSError:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
_plugin_log = os.getenv("MXFLASHATTN_PLUGIN_IMPORT_LOG")
|
|
116
|
+
if _plugin_log:
|
|
117
|
+
with open(_plugin_log, "a", encoding="utf-8") as stream:
|
|
118
|
+
stream.write(
|
|
119
|
+
f"plugin_imported pid={os.getpid()} impl_module={FlashAttentionImpl.__module__}\n"
|
|
120
|
+
)
|
|
121
|
+
if _METAX_IMPL is None and "_metax_import_error" in globals():
|
|
122
|
+
stream.write(f"metax_import_error={_metax_import_error!r}\n")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
_official_varlen = None
|
|
126
|
+
_official_kvcache = None
|
|
127
|
+
|
|
128
|
+
def _mx_varlen_compat(*args, **kwargs):
|
|
129
|
+
_TAG = "varlen_compat"
|
|
130
|
+
if os.getenv("MXFLASHATTN_SPLIT_PROBE", "0") == "1":
|
|
131
|
+
try:
|
|
132
|
+
import torch as _t
|
|
133
|
+
_cap = _t.cuda.is_current_stream_capturing()
|
|
134
|
+
_q = args[0] if args else kwargs.get("q")
|
|
135
|
+
_probe_path = os.getenv(
|
|
136
|
+
"MXFLASHATTN_SPLIT_PROBE_LOG", "/tmp/mxflashattn_probe_split.log"
|
|
137
|
+
)
|
|
138
|
+
with open(_probe_path, "a", encoding="utf-8") as _ps:
|
|
139
|
+
_ps.write("%s capturing=%s qshape=%s kwkeys=%s window=%r causal=%r\n" % (
|
|
140
|
+
_TAG, _cap, tuple(_q.shape) if _q is not None else None,
|
|
141
|
+
sorted(kwargs.keys()), kwargs.get("window_size"), kwargs.get("causal")))
|
|
142
|
+
except Exception as _e:
|
|
143
|
+
pass
|
|
144
|
+
# MXFlashAttn's public varlen API has no paged block-table parameter.
|
|
145
|
+
# Preserve the official MetaX varlen call for paged prefill; only the
|
|
146
|
+
# kvcache decode call is replaced by MXFlashAttn in this experiment.
|
|
147
|
+
if "block_table" in kwargs:
|
|
148
|
+
return _official_varlen(*args, **kwargs)
|
|
149
|
+
out = kwargs.pop("out", None)
|
|
150
|
+
seqused_k = kwargs.pop("seqused_k", None)
|
|
151
|
+
if seqused_k is not None and "cu_seqlens_k" not in kwargs:
|
|
152
|
+
import torch
|
|
153
|
+
lengths = seqused_k.to(dtype=torch.int32)
|
|
154
|
+
kwargs["cu_seqlens_k"] = torch.cat((torch.zeros(1, dtype=torch.int32, device=lengths.device), lengths.cumsum(0)))
|
|
155
|
+
kwargs.pop("s_aux", None)
|
|
156
|
+
kwargs.pop("return_attn_probs", None)
|
|
157
|
+
kwargs.pop("scheduler_metadata", None)
|
|
158
|
+
kwargs.pop("fa_version", None)
|
|
159
|
+
kwargs.pop("q_descale", None); kwargs.pop("k_descale", None); kwargs.pop("v_descale", None)
|
|
160
|
+
result = flash_attn_varlen_func(*args, **kwargs)
|
|
161
|
+
if isinstance(result, tuple):
|
|
162
|
+
result = result[0]
|
|
163
|
+
if out is not None:
|
|
164
|
+
out.copy_(result)
|
|
165
|
+
return out
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
_m1_ext = None
|
|
170
|
+
|
|
171
|
+
def _load_m1_decode_ext():
|
|
172
|
+
"""Lazily import the M1 split-KV decode extension (mxmmain.so, mxcc build)."""
|
|
173
|
+
global _m1_ext
|
|
174
|
+
if _m1_ext is None:
|
|
175
|
+
import importlib.util
|
|
176
|
+
_path = os.path.abspath(os.path.join(
|
|
177
|
+
os.path.dirname(__file__), "..", "..", "csrc_native", "mxmmain.so"))
|
|
178
|
+
_spec = importlib.util.spec_from_file_location("mxmmain", _path)
|
|
179
|
+
_mod = importlib.util.module_from_spec(_spec)
|
|
180
|
+
_spec.loader.exec_module(_mod)
|
|
181
|
+
_m1_ext = _mod
|
|
182
|
+
return _m1_ext
|
|
183
|
+
|
|
184
|
+
_native_ext = None
|
|
185
|
+
|
|
186
|
+
def _load_native_decode_ext():
|
|
187
|
+
"""Lazily import the csrc_native torch extension (built with mxcc)."""
|
|
188
|
+
global _native_ext
|
|
189
|
+
if _native_ext is None:
|
|
190
|
+
import importlib.util
|
|
191
|
+
_path = os.path.abspath(os.path.join(
|
|
192
|
+
os.path.dirname(__file__), "..", "..", "csrc_native", "mxfadecode.so"))
|
|
193
|
+
_spec = importlib.util.spec_from_file_location("mxfadecode", _path)
|
|
194
|
+
_mod = importlib.util.module_from_spec(_spec)
|
|
195
|
+
_spec.loader.exec_module(_mod)
|
|
196
|
+
_native_ext = _mod
|
|
197
|
+
return _native_ext
|
|
198
|
+
|
|
199
|
+
def _try_native_decode(args, kwargs):
|
|
200
|
+
"""Route single-token paged decode to the native MACA kernel.
|
|
201
|
+
|
|
202
|
+
Returns None whenever the call does not match the supported shape so the
|
|
203
|
+
caller falls through to the regular vendor/MXFlashAttn path unchanged.
|
|
204
|
+
"""
|
|
205
|
+
if os.getenv("MXFA_NATIVE_DECODE", "0") != "1":
|
|
206
|
+
return None
|
|
207
|
+
import torch
|
|
208
|
+
|
|
209
|
+
_dbg = os.getenv("MXFA_NATIVE_DEBUG_LOG")
|
|
210
|
+
|
|
211
|
+
def _dbg_write(msg):
|
|
212
|
+
if _dbg:
|
|
213
|
+
try:
|
|
214
|
+
with open(_dbg, "a", encoding="utf-8") as _s:
|
|
215
|
+
_s.write(msg + "\n")
|
|
216
|
+
except OSError:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
q = args[0] if args else kwargs.get("q")
|
|
221
|
+
if q is None or q.dim() not in (3, 4):
|
|
222
|
+
_dbg_write("skip qdim q=%r" % (None if q is None else tuple(q.shape)))
|
|
223
|
+
return None
|
|
224
|
+
bt = kwargs.get("block_table")
|
|
225
|
+
sl = kwargs.get("cache_seqlens")
|
|
226
|
+
if bt is None or sl is None or sl.shape[0] != q.shape[0]:
|
|
227
|
+
_dbg_write("skip bt_sl bt=%r sl=%r q=%r" % (
|
|
228
|
+
None if bt is None else tuple(bt.shape),
|
|
229
|
+
None if sl is None else tuple(sl.shape), tuple(q.shape)))
|
|
230
|
+
return None
|
|
231
|
+
if kwargs.get("alibi_slopes") is not None:
|
|
232
|
+
_dbg_write("skip alibi=%r" % (kwargs.get("alibi_slopes"),))
|
|
233
|
+
return None
|
|
234
|
+
_ws = kwargs.get("window_size")
|
|
235
|
+
if _ws is not None:
|
|
236
|
+
# vLLM passes (-1, -1) (or -1) for "no sliding window"; only a
|
|
237
|
+
# positive window would need masking support we do not provide.
|
|
238
|
+
try:
|
|
239
|
+
_ws_vals = _ws if isinstance(_ws, (tuple, list)) else (_ws,)
|
|
240
|
+
_ws_bad = any(int(v) > 0 for v in _ws_vals)
|
|
241
|
+
except (TypeError, ValueError):
|
|
242
|
+
_ws_bad = True
|
|
243
|
+
if _ws_bad:
|
|
244
|
+
_dbg_write("skip window window=%r" % (_ws,))
|
|
245
|
+
return None
|
|
246
|
+
k_cache = kwargs.get("k_cache")
|
|
247
|
+
if k_cache is None and len(args) > 1:
|
|
248
|
+
k_cache = args[1]
|
|
249
|
+
v_cache = kwargs.get("v_cache")
|
|
250
|
+
if v_cache is None and len(args) > 2:
|
|
251
|
+
v_cache = args[2]
|
|
252
|
+
if k_cache is None or v_cache is None:
|
|
253
|
+
_dbg_write("skip caches kv=%r" % ((k_cache is None, v_cache is None),))
|
|
254
|
+
return None
|
|
255
|
+
if q.dtype not in (torch.float16, torch.bfloat16) or k_cache.dtype != q.dtype:
|
|
256
|
+
_dbg_write("skip dtype q=%r k=%r" % (q.dtype, k_cache.dtype))
|
|
257
|
+
return None
|
|
258
|
+
B, HQ, D = (q.shape[0], q.shape[-2], q.shape[-1])
|
|
259
|
+
if q.shape[-3] != 1 and q.dim() == 4:
|
|
260
|
+
_dbg_write("skip qlen q=%r" % (tuple(q.shape),))
|
|
261
|
+
return None
|
|
262
|
+
ext = _load_native_decode_ext()
|
|
263
|
+
scale = kwargs.get("softmax_scale") or (D ** -0.5)
|
|
264
|
+
max_sl_bound = int(bt.shape[1]) * int(k_cache.shape[1])
|
|
265
|
+
stream = torch.cuda.current_stream().cuda_stream
|
|
266
|
+
if os.getenv("MXFA_M1_DECODE", "0") == "1":
|
|
267
|
+
# M1 split-KV path: kernel handles GQA D in {64,128}, GQ in [2,8];
|
|
268
|
+
# any unsupported shape raises and falls through to the v7 ext below.
|
|
269
|
+
_m1 = _load_m1_decode_ext()
|
|
270
|
+
_scale = kwargs.get("softmax_scale") or (D ** -0.5)
|
|
271
|
+
_msl = int(bt.shape[1]) * int(k_cache.shape[1])
|
|
272
|
+
_base = B * int(k_cache.shape[2])
|
|
273
|
+
_np = 1
|
|
274
|
+
while _np < 64 and _base * _np < 2048:
|
|
275
|
+
_np *= 2
|
|
276
|
+
_c_len = (_msl + _np - 1) // _np
|
|
277
|
+
# vLLM hands us q as a slice of a fused QKV buffer, and some model
|
|
278
|
+
# families (Llama on this stack) keep the batch stride padded, so
|
|
279
|
+
# reshape() alone yields a non-contiguous view. Both bindings
|
|
280
|
+
# require contiguous q, hence the explicit copy. No-op when the
|
|
281
|
+
# caller already passed a contiguous tensor.
|
|
282
|
+
_qr = q.reshape(B, 1, HQ, D)
|
|
283
|
+
if not _qr.is_contiguous():
|
|
284
|
+
_qr = _qr.contiguous()
|
|
285
|
+
out = _m1.paged_decode_split(
|
|
286
|
+
_qr, k_cache, v_cache,
|
|
287
|
+
bt.to(torch.int32), sl.to(torch.int32), _scale,
|
|
288
|
+
torch.cuda.current_stream().cuda_stream, _np, _c_len,
|
|
289
|
+
)
|
|
290
|
+
provided = kwargs.get("out")
|
|
291
|
+
if provided is not None:
|
|
292
|
+
provided.copy_(out.reshape_as(provided))
|
|
293
|
+
result = provided
|
|
294
|
+
else:
|
|
295
|
+
result = out.reshape(q.shape)
|
|
296
|
+
_dbg_write("M1_FIRE q=%s np=%d c_len=%d" % (tuple(q.shape), _np, _c_len))
|
|
297
|
+
_record_worker_event({"path": "m1_split_decode",
|
|
298
|
+
"operation": "paged_decode_split",
|
|
299
|
+
"fallback": "false", "np": _np})
|
|
300
|
+
return result
|
|
301
|
+
out = ext.paged_decode(
|
|
302
|
+
q.reshape(B, 1, HQ, D).contiguous(), k_cache, v_cache,
|
|
303
|
+
bt.to(torch.int32), sl.to(torch.int32), scale, max_sl_bound, stream,
|
|
304
|
+
)
|
|
305
|
+
provided = kwargs.get("out")
|
|
306
|
+
if provided is not None:
|
|
307
|
+
provided.copy_(out.reshape_as(provided))
|
|
308
|
+
result = provided
|
|
309
|
+
else:
|
|
310
|
+
result = out.reshape(q.shape)
|
|
311
|
+
_dbg_write("FIRE q=%s" % (tuple(q.shape),))
|
|
312
|
+
_record_worker_event({"path": "native_decode", "operation": "paged_decode", "fallback": "false"})
|
|
313
|
+
return result
|
|
314
|
+
except Exception as _exc: # native path must never take the model down
|
|
315
|
+
_dbg_write("EXC %r" % (_exc,))
|
|
316
|
+
_record_worker_event({"path": "native_decode", "fallback": "true", "fallback_reason": repr(_exc)})
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
def _mx_kvcache_compat(*args, **kwargs):
|
|
320
|
+
_TAG = "kvcache_compat"
|
|
321
|
+
if os.getenv("MXFLASHATTN_SPLIT_PROBE", "0") == "1":
|
|
322
|
+
try:
|
|
323
|
+
import torch as _t
|
|
324
|
+
_cap = _t.cuda.is_current_stream_capturing()
|
|
325
|
+
_q = args[0] if args else kwargs.get("q")
|
|
326
|
+
_probe_path = os.getenv(
|
|
327
|
+
"MXFLASHATTN_SPLIT_PROBE_LOG", "/tmp/mxflashattn_probe_split.log"
|
|
328
|
+
)
|
|
329
|
+
with open(_probe_path, "a", encoding="utf-8") as _ps:
|
|
330
|
+
_ps.write("%s capturing=%s qshape=%s kwkeys=%s window=%r causal=%r\n" % (
|
|
331
|
+
_TAG, _cap, tuple(_q.shape) if _q is not None else None,
|
|
332
|
+
sorted(kwargs.keys()), kwargs.get("window_size"), kwargs.get("causal")))
|
|
333
|
+
except Exception as _e:
|
|
334
|
+
pass
|
|
335
|
+
kwargs.pop("s_aux", None)
|
|
336
|
+
kwargs.pop("return_softmax_lse", None)
|
|
337
|
+
kwargs.pop("scheduler_metadata", None)
|
|
338
|
+
kwargs.pop("fa_version", None)
|
|
339
|
+
kwargs.pop("q_descale", None); kwargs.pop("k_descale", None); kwargs.pop("v_descale", None)
|
|
340
|
+
_native = _try_native_decode(args, kwargs)
|
|
341
|
+
if _native is not None:
|
|
342
|
+
return _native
|
|
343
|
+
# The public MXFlashAttn API intentionally rejects unsupported head
|
|
344
|
+
# dimensions and GQA ratios. During the official MetaX split, those
|
|
345
|
+
# shapes must return to the original vendor implementation instead of
|
|
346
|
+
# surfacing the public API ValueError and taking down vLLM.
|
|
347
|
+
if _official_kvcache is not None:
|
|
348
|
+
return _official_kvcache(*args, **kwargs)
|
|
349
|
+
return flash_attn_with_kvcache(*args, **kwargs)
|
|
350
|
+
|
|
351
|
+
def _forward_official_split_with_mx(self, layer, query, key, value, kv_cache, metadata, output, output_scale, output_block_scale):
|
|
352
|
+
fn = FlashAttentionImpl.forward
|
|
353
|
+
glb = fn.__globals__
|
|
354
|
+
global _official_varlen, _official_kvcache
|
|
355
|
+
old_var = glb.get("flash_attn_varlen_func")
|
|
356
|
+
old_kv = glb.get("flash_attn_with_kvcache")
|
|
357
|
+
_official_varlen = old_var
|
|
358
|
+
_official_kvcache = old_kv
|
|
359
|
+
glb["flash_attn_varlen_func"] = _mx_varlen_compat
|
|
360
|
+
glb["flash_attn_with_kvcache"] = _mx_kvcache_compat
|
|
361
|
+
dispatch_event = {
|
|
362
|
+
"path": "vendor_direct",
|
|
363
|
+
"operation": "official_split_mx",
|
|
364
|
+
"fallback": "false",
|
|
365
|
+
}
|
|
366
|
+
try:
|
|
367
|
+
try:
|
|
368
|
+
result = super(MXFlashAttnImpl, self).forward(
|
|
369
|
+
layer, query, key, value, kv_cache, metadata, output,
|
|
370
|
+
output_scale, output_block_scale
|
|
371
|
+
)
|
|
372
|
+
except Exception as exc:
|
|
373
|
+
# MetaX's official split wrapper can reject otherwise valid long
|
|
374
|
+
# prefill inputs with an accelerator-side "invalid argument".
|
|
375
|
+
# Retry once with the untouched vendor functions so the project
|
|
376
|
+
# remains a safe compatibility layer instead of taking down vLLM.
|
|
377
|
+
if "invalid argument" not in str(exc).lower():
|
|
378
|
+
raise
|
|
379
|
+
glb["flash_attn_varlen_func"] = old_var
|
|
380
|
+
glb["flash_attn_with_kvcache"] = old_kv
|
|
381
|
+
result = super(MXFlashAttnImpl, self).forward(
|
|
382
|
+
layer, query, key, value, kv_cache, metadata, output,
|
|
383
|
+
output_scale, output_block_scale
|
|
384
|
+
)
|
|
385
|
+
dispatch_event = {
|
|
386
|
+
"path": "vendor_direct",
|
|
387
|
+
"operation": "official_split_invalid_argument_fallback",
|
|
388
|
+
"fallback": "true",
|
|
389
|
+
"fallback_reason": "official_split_invalid_argument",
|
|
390
|
+
}
|
|
391
|
+
finally:
|
|
392
|
+
glb["flash_attn_varlen_func"] = old_var
|
|
393
|
+
glb["flash_attn_with_kvcache"] = old_kv
|
|
394
|
+
_official_varlen = None
|
|
395
|
+
_official_kvcache = None
|
|
396
|
+
_record_worker_event(dispatch_event)
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
class MXFlashAttnImpl(FlashAttentionImpl):
|
|
400
|
+
def __init__(self, *args, **kwargs):
|
|
401
|
+
super().__init__(*args, **kwargs)
|
|
402
|
+
if _plugin_log:
|
|
403
|
+
with open(_plugin_log, "a", encoding="utf-8") as stream:
|
|
404
|
+
stream.write(f"impl_constructed pid={os.getpid()}\n")
|
|
405
|
+
|
|
406
|
+
def forward(
|
|
407
|
+
self, layer, query, key, value, kv_cache, attn_metadata, output=None,
|
|
408
|
+
output_scale=None, output_block_scale=None,
|
|
409
|
+
):
|
|
410
|
+
if _plugin_log:
|
|
411
|
+
try:
|
|
412
|
+
with open(_plugin_log, "a", encoding="utf-8") as stream:
|
|
413
|
+
stream.write(
|
|
414
|
+
f"forward_entered pid={os.getpid()} "
|
|
415
|
+
f"q_shape={tuple(query.shape)} "
|
|
416
|
+
f"max_query_len={getattr(attn_metadata, 'max_query_len', None)} "
|
|
417
|
+
f"num_decodes={getattr(attn_metadata, 'num_decodes', None)} "
|
|
418
|
+
f"num_decode_tokens={getattr(attn_metadata, 'num_decode_tokens', None)} "
|
|
419
|
+
f"decode_seq_shape={getattr(getattr(attn_metadata, 'decode_seq_lens', None), 'shape', None)} "
|
|
420
|
+
f"decode_bt_shape={getattr(getattr(attn_metadata, 'decode_block_table', None), 'shape', None)} "
|
|
421
|
+
f"num_actual={getattr(attn_metadata, 'num_actual_tokens', None)} "
|
|
422
|
+
f"q_start_shape={getattr(getattr(attn_metadata, 'query_start_loc', None), 'shape', None)}\n"
|
|
423
|
+
)
|
|
424
|
+
except OSError:
|
|
425
|
+
pass
|
|
426
|
+
if attn_metadata is None:
|
|
427
|
+
if output is None:
|
|
428
|
+
raise ValueError("vLLM must provide an output buffer for profiling")
|
|
429
|
+
return output.fill_(0)
|
|
430
|
+
# Keep the official MetaX metadata split and output writeback, while
|
|
431
|
+
# replacing only the underlying attention calls with MXFlashAttn.
|
|
432
|
+
if os.getenv("MXFLASHATTN_SPLIT_PURE", "0") == "1":
|
|
433
|
+
return super().forward(
|
|
434
|
+
layer, query, key, value, kv_cache, attn_metadata,
|
|
435
|
+
output, output_scale, output_block_scale
|
|
436
|
+
)
|
|
437
|
+
if os.getenv("MXFLASHATTN_GRAPH_OFFICIAL_SPLIT", "0") == "1":
|
|
438
|
+
return _forward_official_split_with_mx(
|
|
439
|
+
self, layer, query, key, value, kv_cache, attn_metadata,
|
|
440
|
+
output, output_scale, output_block_scale
|
|
441
|
+
)
|
|
442
|
+
if self.attn_type in (AttentionType.ENCODER_ONLY, AttentionType.ENCODER) and attn_metadata is not None:
|
|
443
|
+
if output is None:
|
|
444
|
+
raise ValueError("vLLM must provide an output buffer")
|
|
445
|
+
result = flash_attn_func(
|
|
446
|
+
query[: attn_metadata.num_actual_tokens].unsqueeze(0),
|
|
447
|
+
key[: attn_metadata.num_actual_tokens].unsqueeze(0),
|
|
448
|
+
value[: attn_metadata.num_actual_tokens].unsqueeze(0),
|
|
449
|
+
causal=False, softmax_scale=self.scale,
|
|
450
|
+
).reshape(-1, self.num_heads * self.head_size)
|
|
451
|
+
_record_event({"path": "mxflashattn_encoder", "operation": "flash_attn_func", "fallback": "false"})
|
|
452
|
+
output[: result.shape[0]].copy_(result)
|
|
453
|
+
return output
|
|
454
|
+
try:
|
|
455
|
+
result, _ = decode_forward(
|
|
456
|
+
query, kv_cache, attn_metadata, softmax_scale=self.scale,
|
|
457
|
+
causal=bool(attn_metadata.causal),
|
|
458
|
+
)
|
|
459
|
+
except DecoderAdapterBlocked as exc:
|
|
460
|
+
if _plugin_log:
|
|
461
|
+
try:
|
|
462
|
+
with open(_plugin_log, "a", encoding="utf-8") as stream:
|
|
463
|
+
stream.write(f"adapter_blocked pid={os.getpid()} reason={exc}\n")
|
|
464
|
+
except OSError:
|
|
465
|
+
pass
|
|
466
|
+
event = {"path": "vendor_prefill_or_blocked", "reason": str(exc)}
|
|
467
|
+
_record_event(event)
|
|
468
|
+
_record_worker_event(event)
|
|
469
|
+
if "single_token_decode" in str(exc):
|
|
470
|
+
return super().forward(layer, query, key, value, kv_cache, attn_metadata, output, output_scale, output_block_scale)
|
|
471
|
+
raise
|
|
472
|
+
dispatch_info = get_last_dispatch_info()
|
|
473
|
+
if dispatch_info is None:
|
|
474
|
+
raise RuntimeError("MXFlashAttn decoder returned without dispatch evidence")
|
|
475
|
+
if dispatch_info.backend == "vendor_direct":
|
|
476
|
+
event = {"path": "vendor_direct", "operation": dispatch_info.operation, "fallback": "false"}
|
|
477
|
+
elif dispatch_info.backend == "mxmac_aten_correctness":
|
|
478
|
+
event = {"path": "mxmac_aten_correctness", "operation": dispatch_info.operation, "fallback": "true", "fallback_reason": dispatch_info.fallback_reason or "unspecified"}
|
|
479
|
+
else:
|
|
480
|
+
event = {"path": "reference", "operation": dispatch_info.operation, "fallback": "true", "fallback_reason": dispatch_info.fallback_reason or "unspecified"}
|
|
481
|
+
_record_event(event)
|
|
482
|
+
_record_worker_event(event)
|
|
483
|
+
if output is None:
|
|
484
|
+
raise ValueError("vLLM must provide an output buffer")
|
|
485
|
+
target = output[: result.shape[0]]
|
|
486
|
+
if target.numel() != result.numel():
|
|
487
|
+
raise RuntimeError(
|
|
488
|
+
f"MXFlashAttn decoder output size mismatch: target={tuple(target.shape)} "
|
|
489
|
+
f"result={tuple(result.shape)}"
|
|
490
|
+
)
|
|
491
|
+
target.copy_(result.reshape_as(target))
|
|
492
|
+
return output
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class MXFlashAttnBackend(FlashAttentionBackend):
|
|
496
|
+
@staticmethod
|
|
497
|
+
def get_name() -> str:
|
|
498
|
+
# vLLM Attention layers index the backend enum by this name. Keep the
|
|
499
|
+
# standard enum key while the registry path points at our class.
|
|
500
|
+
return "FLASH_ATTN"
|
|
501
|
+
|
|
502
|
+
@staticmethod
|
|
503
|
+
def get_impl_cls():
|
|
504
|
+
return MXFlashAttnImpl
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
if _METAX_IMPL is not None:
|
|
508
|
+
# The MetaX implementation supplies the cache-update helper with its
|
|
509
|
+
# vendor-specific symbols. Keep it even if import order made the selected
|
|
510
|
+
# base class resolve to upstream vLLM.
|
|
511
|
+
MXFlashAttnImpl.do_kv_cache_update = _METAX_IMPL.do_kv_cache_update
|
mxflashattn/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""FlashAttention-compatible APIs with explicit MXMACA backend dispatch."""
|
|
2
|
+
|
|
3
|
+
from .api import flash_attn_func, flash_attn_varlen_func, flash_attn_with_kvcache
|
|
4
|
+
from .dispatch import (
|
|
5
|
+
BackendUnavailableError,
|
|
6
|
+
DispatchInfo,
|
|
7
|
+
get_last_dispatch_info,
|
|
8
|
+
)
|
|
9
|
+
from .prefill import prefill_chunked_reference, prefill_chunked_reference_fallback
|
|
10
|
+
from .accuracy import ErrorSummary, summarize_error
|
|
11
|
+
from .policy import AdaptiveDispatchPolicy, DispatchDecision, DispatchPolicyConfig, WorkloadShape
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BackendUnavailableError",
|
|
15
|
+
"DispatchInfo",
|
|
16
|
+
"flash_attn_func",
|
|
17
|
+
"flash_attn_varlen_func",
|
|
18
|
+
"flash_attn_with_kvcache",
|
|
19
|
+
"get_last_dispatch_info",
|
|
20
|
+
"prefill_chunked_reference",
|
|
21
|
+
"prefill_chunked_reference_fallback",
|
|
22
|
+
"AdaptiveDispatchPolicy",
|
|
23
|
+
"DispatchDecision",
|
|
24
|
+
"DispatchPolicyConfig",
|
|
25
|
+
"ErrorSummary",
|
|
26
|
+
"WorkloadShape",
|
|
27
|
+
"summarize_error",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
__version__ = "1.4.0"
|
mxflashattn/accuracy.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ErrorSummary:
|
|
11
|
+
sample_count: int
|
|
12
|
+
finite: bool
|
|
13
|
+
max_abs: float
|
|
14
|
+
mean_abs: float
|
|
15
|
+
p95_abs: float
|
|
16
|
+
p99_abs: float
|
|
17
|
+
max_relative: float
|
|
18
|
+
|
|
19
|
+
def as_dict(self) -> dict[str, Any]:
|
|
20
|
+
return asdict(self)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def summarize_error(actual: torch.Tensor, expected: torch.Tensor) -> ErrorSummary:
|
|
24
|
+
"""Summarize tensor error for local correctness and long-context reports."""
|
|
25
|
+
if not isinstance(actual, torch.Tensor) or not isinstance(expected, torch.Tensor):
|
|
26
|
+
raise TypeError("actual and expected must be torch.Tensor values")
|
|
27
|
+
if actual.shape != expected.shape:
|
|
28
|
+
raise ValueError("actual and expected must have identical shapes")
|
|
29
|
+
if actual.numel() == 0:
|
|
30
|
+
raise ValueError("cannot summarize an empty tensor")
|
|
31
|
+
|
|
32
|
+
actual_f = actual.detach().float()
|
|
33
|
+
expected_f = expected.detach().float()
|
|
34
|
+
finite = bool(torch.isfinite(actual_f).all() and torch.isfinite(expected_f).all())
|
|
35
|
+
abs_error = (actual_f - expected_f).abs().reshape(-1)
|
|
36
|
+
relative_error = abs_error / expected_f.abs().reshape(-1).clamp_min(1e-6)
|
|
37
|
+
# Keep the report machine-readable when a kernel emits NaN/Inf: the
|
|
38
|
+
# finite flag records the root failure and the metrics remain comparable.
|
|
39
|
+
abs_error = torch.nan_to_num(abs_error, nan=float("inf"), posinf=float("inf"), neginf=float("inf"))
|
|
40
|
+
relative_error = torch.nan_to_num(
|
|
41
|
+
relative_error, nan=float("inf"), posinf=float("inf"), neginf=float("inf")
|
|
42
|
+
)
|
|
43
|
+
return ErrorSummary(
|
|
44
|
+
sample_count=abs_error.numel(),
|
|
45
|
+
finite=finite,
|
|
46
|
+
max_abs=float(abs_error.max().item()),
|
|
47
|
+
mean_abs=float(abs_error.mean().item()),
|
|
48
|
+
p95_abs=float(torch.quantile(abs_error, 0.95).item()),
|
|
49
|
+
p99_abs=float(torch.quantile(abs_error, 0.99).item()),
|
|
50
|
+
max_relative=float(relative_error.max().item()),
|
|
51
|
+
)
|