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.
@@ -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
@@ -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"
@@ -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
+ )