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,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()