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,190 @@
|
|
|
1
|
+
"""Preflight the pinned vLLM environment before adding the C500 platform adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import importlib.metadata
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
|
|
16
|
+
from integrations.vllm.config import QUICK_MODEL, validate_vllm_version
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _accelerator_module():
|
|
20
|
+
for name in ("maca", "cuda", "musa"):
|
|
21
|
+
module = getattr(torch, name, None)
|
|
22
|
+
if module is None:
|
|
23
|
+
continue
|
|
24
|
+
available = getattr(module, "is_available", None)
|
|
25
|
+
if available is None or available():
|
|
26
|
+
return module
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _mx_smi_used_bytes() -> int | None:
|
|
31
|
+
try:
|
|
32
|
+
output = subprocess.run(
|
|
33
|
+
["mx-smi", "--show-memory"], capture_output=True, text=True, timeout=5, check=False
|
|
34
|
+
).stdout
|
|
35
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
36
|
+
return None
|
|
37
|
+
values = re.findall(r"vram used\s*:\s*(\d+)\s*KB", output, flags=re.IGNORECASE)
|
|
38
|
+
return int(values[0]) * 1024 if values else None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
43
|
+
parser.add_argument("--model", default=QUICK_MODEL)
|
|
44
|
+
parser.add_argument("--prompt", default="Explain paged KV cache in one sentence.")
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--suite",
|
|
47
|
+
action="store_true",
|
|
48
|
+
help="Run a fixed set of prompts in one loaded model instance.",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument("--prompt-count", type=int, default=4, help="Number of deterministic suite prompts.")
|
|
51
|
+
parser.add_argument("--output", type=Path, default=Path("artifacts/vllm-baseline.json"))
|
|
52
|
+
parser.add_argument("--max-tokens", type=int, default=32)
|
|
53
|
+
parser.add_argument("--baseline-only", action="store_true")
|
|
54
|
+
parser.add_argument("--candidate", action="store_true", help="Register and exercise MXFlashAttn decoder dispatch.")
|
|
55
|
+
args = parser.parse_args()
|
|
56
|
+
if args.baseline_only and args.candidate:
|
|
57
|
+
raise SystemExit("choose only one of --baseline-only or --candidate")
|
|
58
|
+
if not args.baseline_only and not args.candidate:
|
|
59
|
+
raise SystemExit(
|
|
60
|
+
"choose --baseline-only for vendor baseline or --candidate for MXFlashAttn dispatch"
|
|
61
|
+
)
|
|
62
|
+
try:
|
|
63
|
+
version = importlib.metadata.version("vllm")
|
|
64
|
+
except importlib.metadata.PackageNotFoundError as error:
|
|
65
|
+
raise SystemExit("vLLM is not installed in this environment") from error
|
|
66
|
+
validate_vllm_version(version)
|
|
67
|
+
dispatch_module = None
|
|
68
|
+
registration = None
|
|
69
|
+
if args.candidate:
|
|
70
|
+
from integrations.vllm import backend as dispatch_module
|
|
71
|
+
|
|
72
|
+
registration = dispatch_module.register_vllm_v1_backend()
|
|
73
|
+
if registration.get("registered") != "true":
|
|
74
|
+
raise SystemExit(f"MXFlashAttn backend registration failed: {registration}")
|
|
75
|
+
dispatch_log = args.output.with_suffix(".dispatch.jsonl").resolve()
|
|
76
|
+
# Event recording costs measurable time per attention call, and the
|
|
77
|
+
# vendor baseline pays nothing equivalent. MXFLASHATTN_DISABLE_DISPATCH_LOG
|
|
78
|
+
# turns the file sink off (in-memory events still drive the verdict) so
|
|
79
|
+
# throughput can be compared without instrumentation overhead.
|
|
80
|
+
os.environ["MXFLASHATTN_DISPATCH_LOG"] = str(dispatch_log)
|
|
81
|
+
if os.getenv("MXFLASHATTN_DISABLE_DISPATCH_LOG", "0") == "1":
|
|
82
|
+
# Keep the log path so the verdict still has evidence, but write
|
|
83
|
+
# only a sample: instrumentation cost must not distort throughput.
|
|
84
|
+
os.environ["MXFLASHATTN_DISPATCH_SAMPLE"] = os.getenv(
|
|
85
|
+
"MXFLASHATTN_DISPATCH_SAMPLE", "2000"
|
|
86
|
+
)
|
|
87
|
+
os.environ["MXFLASHATTN_AUTOREGISTER"] = "1"
|
|
88
|
+
os.environ["MXFLASHATTN_REGISTRATION_ERROR_LOG"] = str(args.output.with_suffix(".registration-error.log").resolve())
|
|
89
|
+
os.environ["MXFLASHATTN_PLUGIN_IMPORT_LOG"] = str(args.output.with_suffix(".plugin.log").resolve())
|
|
90
|
+
else:
|
|
91
|
+
dispatch_log = None
|
|
92
|
+
from vllm import LLM, SamplingParams
|
|
93
|
+
|
|
94
|
+
_eager = os.getenv("MXFLASHATTN_EAGER", "1") != "0"
|
|
95
|
+
engine = LLM(model=args.model, enforce_eager=_eager, max_model_len=2048)
|
|
96
|
+
prompt_templates = [
|
|
97
|
+
"Explain paged KV cache in one sentence.",
|
|
98
|
+
"List three practical benefits of grouped-query attention.",
|
|
99
|
+
"Give a concise checklist for validating an attention kernel.",
|
|
100
|
+
"Describe how causal masking changes autoregressive decoding.",
|
|
101
|
+
"Explain why variable-length attention needs cumulative offsets.",
|
|
102
|
+
"Compare MQA and GQA for inference memory usage.",
|
|
103
|
+
"Name two checks for a causal attention implementation.",
|
|
104
|
+
"Explain the role of a block table in paged attention.",
|
|
105
|
+
"Give one reason BF16 may differ slightly from FP16.",
|
|
106
|
+
"Describe a safe fallback policy for an accelerator kernel.",
|
|
107
|
+
"Explain why a vendor baseline must be measured separately.",
|
|
108
|
+
"List the metadata needed by a decoder attention adapter.",
|
|
109
|
+
]
|
|
110
|
+
prompts = [f"{prompt_templates[i % len(prompt_templates)]} Case {i:02d}." for i in range(args.prompt_count)] if args.suite else [args.prompt]
|
|
111
|
+
records = []
|
|
112
|
+
dispatch_offset = 0
|
|
113
|
+
for index, prompt in enumerate(prompts):
|
|
114
|
+
if dispatch_module is not None:
|
|
115
|
+
# Only the first prompt truncates the evidence file; later prompts
|
|
116
|
+
# append so the log covers the entire suite instead of its last item.
|
|
117
|
+
dispatch_module.clear_dispatch_events(truncate_log=(index == 0))
|
|
118
|
+
accelerator = _accelerator_module()
|
|
119
|
+
if accelerator is not None:
|
|
120
|
+
reset = getattr(accelerator, "reset_peak_memory_stats", None)
|
|
121
|
+
if reset is not None:
|
|
122
|
+
reset()
|
|
123
|
+
start = time.perf_counter()
|
|
124
|
+
outputs = engine.generate(prompt, SamplingParams(temperature=0.0, max_tokens=args.max_tokens))
|
|
125
|
+
elapsed = time.perf_counter() - start
|
|
126
|
+
request = outputs[0]
|
|
127
|
+
metrics = getattr(request, "metrics", None)
|
|
128
|
+
arrival = getattr(metrics, "arrival_time", None)
|
|
129
|
+
first_token = getattr(metrics, "first_token_time", None)
|
|
130
|
+
finish = getattr(metrics, "finished_time", None)
|
|
131
|
+
generated_tokens = len(request.outputs[0].token_ids)
|
|
132
|
+
prompt_tokens = getattr(request, "prompt_token_ids", None)
|
|
133
|
+
worker_events = []
|
|
134
|
+
if dispatch_log is not None and dispatch_log.exists():
|
|
135
|
+
# The log accumulates across the suite, so take only the lines this
|
|
136
|
+
# prompt appended instead of re-attributing earlier prompts' events.
|
|
137
|
+
lines = [line for line in dispatch_log.read_text(encoding="utf-8").splitlines() if line]
|
|
138
|
+
worker_events = [json.loads(line) for line in lines[dispatch_offset:]]
|
|
139
|
+
dispatch_offset = len(lines)
|
|
140
|
+
record = {
|
|
141
|
+
"index": index,
|
|
142
|
+
"prompt": prompt,
|
|
143
|
+
"prompt_tokens": len(prompt_tokens) if prompt_tokens is not None else None,
|
|
144
|
+
"generated_tokens": generated_tokens,
|
|
145
|
+
"generated_text": request.outputs[0].text,
|
|
146
|
+
"wall_latency_ms": elapsed * 1000,
|
|
147
|
+
"first_token_latency_ms": (first_token - arrival) * 1000 if first_token is not None and arrival is not None else None,
|
|
148
|
+
"request_finish_latency_ms": (finish - arrival) * 1000 if finish is not None and arrival is not None else None,
|
|
149
|
+
"tokens_per_second": generated_tokens / elapsed if elapsed > 0 else None,
|
|
150
|
+
"peak_memory_allocated_bytes": accelerator.max_memory_allocated() if accelerator is not None and hasattr(accelerator, "max_memory_allocated") else None,
|
|
151
|
+
"peak_memory_reserved_bytes": accelerator.max_memory_reserved() if accelerator is not None and hasattr(accelerator, "max_memory_reserved") else None,
|
|
152
|
+
"mx_smi_vram_used_bytes": _mx_smi_used_bytes(),
|
|
153
|
+
"dispatch_events": worker_events if dispatch_module is not None else [],
|
|
154
|
+
}
|
|
155
|
+
records.append(record)
|
|
156
|
+
accelerator = _accelerator_module()
|
|
157
|
+
device = accelerator.get_device_name() if accelerator is not None and hasattr(accelerator, "get_device_name") else "default vLLM platform"
|
|
158
|
+
# v1.1 no longer emits the v0.8 label "mxflashattn_decoder". It records the
|
|
159
|
+
# backend that actually served each decode call: "vendor_direct" for the fast
|
|
160
|
+
# path and "mxmac_aten_correctness" for the validated fallback. Both come
|
|
161
|
+
# only from the MXFlashAttn decode branch, so either proves the adapter ran;
|
|
162
|
+
# a non-fallback vendor_direct additionally proves it used the vendor kernel.
|
|
163
|
+
candidate_dispatch_verified = any(
|
|
164
|
+
event.get("path") in {"vendor_direct", "mxmac_aten_correctness"}
|
|
165
|
+
for record in records
|
|
166
|
+
for event in record.get("dispatch_events", [])
|
|
167
|
+
)
|
|
168
|
+
result = {
|
|
169
|
+
"schema_version": 2,
|
|
170
|
+
"kind": "vllm_mxflashattn_candidate" if args.candidate else "vllm_baseline_only",
|
|
171
|
+
"model": args.model,
|
|
172
|
+
"vllm": version,
|
|
173
|
+
"torch": torch.__version__,
|
|
174
|
+
"device": device,
|
|
175
|
+
"max_tokens": args.max_tokens,
|
|
176
|
+
"suite": args.suite,
|
|
177
|
+
"registration": registration,
|
|
178
|
+
"candidate_requires_mxflashattn_decode": args.candidate,
|
|
179
|
+
"candidate_dispatch_verified": candidate_dispatch_verified,
|
|
180
|
+
"status": ("candidate_dispatch_verified" if candidate_dispatch_verified else "candidate_not_verified") if args.candidate else "baseline_only",
|
|
181
|
+
"runs": records,
|
|
182
|
+
"warning": "Prefill may delegate to vendor; candidate records decoder dispatch events." if args.candidate else "This baseline-only run does not exercise MXFlashAttn.",
|
|
183
|
+
}
|
|
184
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
185
|
+
args.output.write_text(json.dumps(result, indent=2), encoding="utf-8")
|
|
186
|
+
print(f"Wrote vLLM baseline record to {args.output}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
main()
|