mjolnir-kernel 0.1.1__tar.gz → 0.2.0__tar.gz
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.
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/PKG-INFO +31 -4
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/README.md +30 -3
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/__init__.py +1 -1
- mjolnir_kernel-0.2.0/mjolnir/arch/__init__.py +7 -0
- mjolnir_kernel-0.2.0/mjolnir/arch/thor.py +42 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/backend.py +21 -3
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/calibrate.py +2 -2
- mjolnir_kernel-0.2.0/mjolnir/platforms.py +103 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/.gitignore +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/LICENSE +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/NOTICE +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/banner.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/draftvocab.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/kvfp6.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/kvscales.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/lmhead.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/py.typed +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/qpack.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/quant.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/runner.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/t5_call.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/t5_const.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/t5_decode.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/t5_harness.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/mjolnir/t5_plan.py +0 -0
- {mjolnir_kernel-0.1.1 → mjolnir_kernel-0.2.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mjolnir-kernel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Mjolnir: a paged decode-attention kernel for NVIDIA Jetson AGX Thor (sm_110) on tcgen05 / TMEM / TMA (CuTe DSL), with fp8 and 6-bit (e2m3) KV caches — packaged as the vLLM MJOLNIR attention backend.
|
|
5
5
|
Project-URL: Homepage, https://github.com/thor-arena/mjolnir
|
|
6
6
|
Project-URL: Documentation, https://github.com/thor-arena/mjolnir/tree/main/docs/kernel
|
|
@@ -141,7 +141,7 @@ runs on the fallback backend.
|
|
|
141
141
|
| `MJOLNIR_ATTN_DRAFT_T5` | `1` | `0`: the drafter's layers (MTP / EAGLE) run on the fallback — isolates the draft-step cost (`mjolnir verify profile`). fp8 KV only: refused on fp6 KV, whose fallback cannot be CUDA-graphed |
|
|
142
142
|
| `MJOLNIR_ATTN_NONCAUSAL` | `1` | `0`: a parallel drafter's non-causal block steps (DSpark / DFlash) go to the fallback instead of the decode kernel — fp8 KV only for the same reason as above |
|
|
143
143
|
| `MJOLNIR_ATTN_FUSED_DRAFT` | `1` | `0`: tell vLLM the backend cannot advance draft metadata in place → one graph replay per draft step instead of the fused draft loop |
|
|
144
|
-
| `MJOLNIR_LM_HEAD` | – | `fp8`: quantize the lm_head to e4m3 per row at load, Triton fp8-weight GEMM for the verify and every draft (`mjolnir.lmhead`; config `
|
|
144
|
+
| `MJOLNIR_LM_HEAD` | – | `fp8`: quantize the lm_head to e4m3 per row at load, Triton fp8-weight GEMM for the verify and every draft (`mjolnir.lmhead`; config `NVFP4_MJOLNIR_E2M3_LMH8_VT64K`); the bf16 copy is freed automatically (untied heads only, +2.5 GB KV) |
|
|
145
145
|
| `MJOLNIR_DRAFT_VOCAB` | – | path to a `draft_vocab.json` (`mjolnir calibrate --draft-vocab`): the drafter's logits only over those tokens (VocabTrim); `MJOLNIR_DRAFT_VOCAB_SIZE` keeps the first N (a shorter file is padded with the lowest unused ids, with a warning). The kept head stays in the drafter's native quant (NVFP4 4-bit in-kernel, FP8 row-slice) — never up-scaled to bf16; a bf16 head is the one exception (fp8 only when `MJOLNIR_LM_HEAD=fp8` is set). `MJOLNIR_DRAFT_VOCAB_NATIVE=0` forces the dequant→fp8 path |
|
|
146
146
|
| `MJOLNIR_ATTN_VERBOSE` | – | `1`: print the kernel's compile-time notes (ring stages, TMEM columns) — the standalone harness always does |
|
|
147
147
|
| `MJOLNIR_ATTN_BANNER` | `1` | `0`: skip the Mjolnir hammer banner in the vLLM boot log (`mjolnir.banner.emit`, once per start, coloured with the server's formatter) |
|
|
@@ -166,8 +166,8 @@ expands the pages a prefill / mixed step reads to e4m3 for the FA4 fallback
|
|
|
166
166
|
(channel scales applied, one descale per head). The scales come from a calibration run on fp8 KV:
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
|
-
python -m mjolnir.calibrate --config <serve.yaml> --out kv_scales.json # in the image
|
|
170
|
-
MJOLNIR_ATTN_KV_SCALES=kv_scales.json vllm serve ... --kv-cache-dtype fp6_e2m3 --attention-backend MJOLNIR
|
|
169
|
+
python -m mjolnir.calibrate --config <serve.yaml> --out kv_scales/e2m3.json # in the image
|
|
170
|
+
MJOLNIR_ATTN_KV_SCALES=kv_scales/e2m3.json vllm serve ... --kv-cache-dtype fp6_e2m3 --attention-backend MJOLNIR
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
(`mjolnir calibrate` / `mjolnir serve` do both for you —
|
|
@@ -185,6 +185,31 @@ How they compare in speed and accuracy:
|
|
|
185
185
|
[`quant`](https://github.com/thor-arena/mjolnir/blob/main/docs/kernel/quant.md).
|
|
186
186
|
Wiring a vLLM cache-write path for fp6 is an open contribution.
|
|
187
187
|
|
|
188
|
+
## Platforms and architectures
|
|
189
|
+
|
|
190
|
+
The kernel is written for one architecture today — Thor (`sm_110`) — but the
|
|
191
|
+
package is organized by platform: `mjolnir/arch/` is the implemented-kernel
|
|
192
|
+
registry (`mjolnir.arch.thor` re-exports the exact runner surface the backend
|
|
193
|
+
consumes), and `mjolnir/platforms.py` is the torch-free leaf that maps a
|
|
194
|
+
compute capability to it. Thor is implemented; DGX Spark (`sm_121`) and Jetson
|
|
195
|
+
Orin (`sm_87`) are declared research — named in the matrix, no kernel yet.
|
|
196
|
+
|
|
197
|
+
`kernel_for((11, 0))` returns `mjolnir.arch.thor`; any other compute
|
|
198
|
+
capability raises `PlatformNotSupported` naming the requested `sm_XX` and the
|
|
199
|
+
shipped platforms. The vLLM backend never sees that exception: its metadata
|
|
200
|
+
builder consults the pure `unsupported_reason()` and takes the normal
|
|
201
|
+
ineligible-config path — that layer's decode steps run on the fallback
|
|
202
|
+
backend, the serve continues. On a host without a usable CUDA device,
|
|
203
|
+
`MJOLNIR_ARCH` (`sm_110` or `11.0`) pins the architecture instead of the
|
|
204
|
+
torch read (the standalone harness).
|
|
205
|
+
|
|
206
|
+
There is no remembered platform state anywhere in the stack: the CLI side
|
|
207
|
+
resolves its platform per invocation (flag > `$MJOLNIR_PLATFORM` > host
|
|
208
|
+
detection — four source classes with provenance, the `/tmp/nv_jetson_model`
|
|
209
|
+
model copy corroboration-only), so the kernel is always resolved from the
|
|
210
|
+
device the run actually happens on. See
|
|
211
|
+
[docs/faq/platforms.md](../../docs/faq/platforms.md).
|
|
212
|
+
|
|
188
213
|
## Tests
|
|
189
214
|
|
|
190
215
|
From a checkout, on a Thor:
|
|
@@ -213,6 +238,8 @@ is printed as information.
|
|
|
213
238
|
| `mjolnir/t5_decode.py` | the CuTe-DSL kernel, the split-KV merge, the harness and the emulator |
|
|
214
239
|
| `mjolnir/runner.py` | plan buffers, compile cache, launch, KV views (no vLLM import) |
|
|
215
240
|
| `mjolnir/backend.py` | the vLLM backend, metadata builder, impl |
|
|
241
|
+
| `mjolnir/platforms.py` | arch → kernel resolution (`kernel_for`, `PlatformNotSupported`), `MJOLNIR_ARCH` detection — torch-free |
|
|
242
|
+
| `mjolnir/arch/thor.py` | the Thor (`sm_110`) kernel surface: re-exports of the runner |
|
|
216
243
|
| `mjolnir/__init__.py` | `register()` — the vLLM plugin entry point |
|
|
217
244
|
|
|
218
245
|
Apache-2.0, except `mjolnir/t5_decode.py`, which is derived from an NVIDIA
|
|
@@ -113,7 +113,7 @@ runs on the fallback backend.
|
|
|
113
113
|
| `MJOLNIR_ATTN_DRAFT_T5` | `1` | `0`: the drafter's layers (MTP / EAGLE) run on the fallback — isolates the draft-step cost (`mjolnir verify profile`). fp8 KV only: refused on fp6 KV, whose fallback cannot be CUDA-graphed |
|
|
114
114
|
| `MJOLNIR_ATTN_NONCAUSAL` | `1` | `0`: a parallel drafter's non-causal block steps (DSpark / DFlash) go to the fallback instead of the decode kernel — fp8 KV only for the same reason as above |
|
|
115
115
|
| `MJOLNIR_ATTN_FUSED_DRAFT` | `1` | `0`: tell vLLM the backend cannot advance draft metadata in place → one graph replay per draft step instead of the fused draft loop |
|
|
116
|
-
| `MJOLNIR_LM_HEAD` | – | `fp8`: quantize the lm_head to e4m3 per row at load, Triton fp8-weight GEMM for the verify and every draft (`mjolnir.lmhead`; config `
|
|
116
|
+
| `MJOLNIR_LM_HEAD` | – | `fp8`: quantize the lm_head to e4m3 per row at load, Triton fp8-weight GEMM for the verify and every draft (`mjolnir.lmhead`; config `NVFP4_MJOLNIR_E2M3_LMH8_VT64K`); the bf16 copy is freed automatically (untied heads only, +2.5 GB KV) |
|
|
117
117
|
| `MJOLNIR_DRAFT_VOCAB` | – | path to a `draft_vocab.json` (`mjolnir calibrate --draft-vocab`): the drafter's logits only over those tokens (VocabTrim); `MJOLNIR_DRAFT_VOCAB_SIZE` keeps the first N (a shorter file is padded with the lowest unused ids, with a warning). The kept head stays in the drafter's native quant (NVFP4 4-bit in-kernel, FP8 row-slice) — never up-scaled to bf16; a bf16 head is the one exception (fp8 only when `MJOLNIR_LM_HEAD=fp8` is set). `MJOLNIR_DRAFT_VOCAB_NATIVE=0` forces the dequant→fp8 path |
|
|
118
118
|
| `MJOLNIR_ATTN_VERBOSE` | – | `1`: print the kernel's compile-time notes (ring stages, TMEM columns) — the standalone harness always does |
|
|
119
119
|
| `MJOLNIR_ATTN_BANNER` | `1` | `0`: skip the Mjolnir hammer banner in the vLLM boot log (`mjolnir.banner.emit`, once per start, coloured with the server's formatter) |
|
|
@@ -138,8 +138,8 @@ expands the pages a prefill / mixed step reads to e4m3 for the FA4 fallback
|
|
|
138
138
|
(channel scales applied, one descale per head). The scales come from a calibration run on fp8 KV:
|
|
139
139
|
|
|
140
140
|
```bash
|
|
141
|
-
python -m mjolnir.calibrate --config <serve.yaml> --out kv_scales.json # in the image
|
|
142
|
-
MJOLNIR_ATTN_KV_SCALES=kv_scales.json vllm serve ... --kv-cache-dtype fp6_e2m3 --attention-backend MJOLNIR
|
|
141
|
+
python -m mjolnir.calibrate --config <serve.yaml> --out kv_scales/e2m3.json # in the image
|
|
142
|
+
MJOLNIR_ATTN_KV_SCALES=kv_scales/e2m3.json vllm serve ... --kv-cache-dtype fp6_e2m3 --attention-backend MJOLNIR
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
(`mjolnir calibrate` / `mjolnir serve` do both for you —
|
|
@@ -157,6 +157,31 @@ How they compare in speed and accuracy:
|
|
|
157
157
|
[`quant`](https://github.com/thor-arena/mjolnir/blob/main/docs/kernel/quant.md).
|
|
158
158
|
Wiring a vLLM cache-write path for fp6 is an open contribution.
|
|
159
159
|
|
|
160
|
+
## Platforms and architectures
|
|
161
|
+
|
|
162
|
+
The kernel is written for one architecture today — Thor (`sm_110`) — but the
|
|
163
|
+
package is organized by platform: `mjolnir/arch/` is the implemented-kernel
|
|
164
|
+
registry (`mjolnir.arch.thor` re-exports the exact runner surface the backend
|
|
165
|
+
consumes), and `mjolnir/platforms.py` is the torch-free leaf that maps a
|
|
166
|
+
compute capability to it. Thor is implemented; DGX Spark (`sm_121`) and Jetson
|
|
167
|
+
Orin (`sm_87`) are declared research — named in the matrix, no kernel yet.
|
|
168
|
+
|
|
169
|
+
`kernel_for((11, 0))` returns `mjolnir.arch.thor`; any other compute
|
|
170
|
+
capability raises `PlatformNotSupported` naming the requested `sm_XX` and the
|
|
171
|
+
shipped platforms. The vLLM backend never sees that exception: its metadata
|
|
172
|
+
builder consults the pure `unsupported_reason()` and takes the normal
|
|
173
|
+
ineligible-config path — that layer's decode steps run on the fallback
|
|
174
|
+
backend, the serve continues. On a host without a usable CUDA device,
|
|
175
|
+
`MJOLNIR_ARCH` (`sm_110` or `11.0`) pins the architecture instead of the
|
|
176
|
+
torch read (the standalone harness).
|
|
177
|
+
|
|
178
|
+
There is no remembered platform state anywhere in the stack: the CLI side
|
|
179
|
+
resolves its platform per invocation (flag > `$MJOLNIR_PLATFORM` > host
|
|
180
|
+
detection — four source classes with provenance, the `/tmp/nv_jetson_model`
|
|
181
|
+
model copy corroboration-only), so the kernel is always resolved from the
|
|
182
|
+
device the run actually happens on. See
|
|
183
|
+
[docs/faq/platforms.md](../../docs/faq/platforms.md).
|
|
184
|
+
|
|
160
185
|
## Tests
|
|
161
186
|
|
|
162
187
|
From a checkout, on a Thor:
|
|
@@ -185,6 +210,8 @@ is printed as information.
|
|
|
185
210
|
| `mjolnir/t5_decode.py` | the CuTe-DSL kernel, the split-KV merge, the harness and the emulator |
|
|
186
211
|
| `mjolnir/runner.py` | plan buffers, compile cache, launch, KV views (no vLLM import) |
|
|
187
212
|
| `mjolnir/backend.py` | the vLLM backend, metadata builder, impl |
|
|
213
|
+
| `mjolnir/platforms.py` | arch → kernel resolution (`kernel_for`, `PlatformNotSupported`), `MJOLNIR_ARCH` detection — torch-free |
|
|
214
|
+
| `mjolnir/arch/thor.py` | the Thor (`sm_110`) kernel surface: re-exports of the runner |
|
|
188
215
|
| `mjolnir/__init__.py` | `register()` — the vLLM plugin entry point |
|
|
189
216
|
|
|
190
217
|
Apache-2.0, except `mjolnir/t5_decode.py`, which is derived from an NVIDIA
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
import os
|
|
25
25
|
|
|
26
26
|
__all__ = ["BACKEND_NAME", "BACKEND_PATH", "__version__", "register"]
|
|
27
|
-
__version__ = "0.
|
|
27
|
+
__version__ = "0.2.0"
|
|
28
28
|
|
|
29
29
|
BACKEND_NAME = "MJOLNIR"
|
|
30
30
|
BACKEND_PATH = "mjolnir.backend.MjolnirAttentionBackend"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Implemented-kernel registry: one subpackage per supported architecture
|
|
2
|
+
(``mjolnir.arch.thor``).
|
|
3
|
+
|
|
4
|
+
``mjolnir.platforms.kernel_for`` maps a compute capability to a dotted module
|
|
5
|
+
path here. Importing a subpackage pulls the CUTLASS DSL (heavy) — only do it
|
|
6
|
+
when a device is real.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""The Thor (sm_110) kernel: re-exports of the exact runner surface the vLLM
|
|
2
|
+
backend consumes. Adding an architecture = adding a sibling subpackage with
|
|
3
|
+
the same surface and a ``_SHIPPED`` row in ``mjolnir.platforms``:
|
|
4
|
+
|
|
5
|
+
* ``Runner`` — the decode-runner class the backend instantiates; the one
|
|
6
|
+
name a family subpackage must define itself (all others are re-exports of
|
|
7
|
+
shared, family-independent machinery from :mod:`mjolnir.runner`);
|
|
8
|
+
* the shared helpers every family consumes unchanged: ``StepPlan``,
|
|
9
|
+
``SUPPORTED_PAGE_SIZES``, ``detect_kv_layout``, ``env_flag``,
|
|
10
|
+
``split_kv_cache``.
|
|
11
|
+
|
|
12
|
+
The backend resolves the class through :func:`mjolnir.platforms.load_kernel`
|
|
13
|
+
at build time — it never imports a family module directly, so landing a
|
|
14
|
+
family edits nothing but ``platforms._SHIPPED`` and its own subpackage.
|
|
15
|
+
Importing this module pulls the CUTLASS DSL (heavy): only do it when a
|
|
16
|
+
device is real. The compiled-kernel cache fingerprint
|
|
17
|
+
(``runner._cache_fingerprint``) hashes the kernel module the shared runner
|
|
18
|
+
imports — family-blind until the runner itself is split per family: a
|
|
19
|
+
second family must extend the fingerprint (or key its own cache) or it
|
|
20
|
+
would reuse this family's objects.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from mjolnir.runner import (
|
|
24
|
+
SUPPORTED_PAGE_SIZES,
|
|
25
|
+
StepPlan,
|
|
26
|
+
T5DecodeRunner,
|
|
27
|
+
detect_kv_layout,
|
|
28
|
+
env_flag,
|
|
29
|
+
split_kv_cache,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
Runner = T5DecodeRunner
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"SUPPORTED_PAGE_SIZES",
|
|
36
|
+
"Runner",
|
|
37
|
+
"StepPlan",
|
|
38
|
+
"T5DecodeRunner",
|
|
39
|
+
"detect_kv_layout",
|
|
40
|
+
"env_flag",
|
|
41
|
+
"split_kv_cache",
|
|
42
|
+
]
|
|
@@ -35,20 +35,27 @@ from __future__ import annotations
|
|
|
35
35
|
|
|
36
36
|
import copy
|
|
37
37
|
import logging
|
|
38
|
-
from typing import Any, ClassVar
|
|
38
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
39
39
|
|
|
40
40
|
import torch
|
|
41
41
|
|
|
42
42
|
from . import banner
|
|
43
|
+
from .platforms import detect, load_kernel, unsupported_reason
|
|
43
44
|
from .runner import (
|
|
44
45
|
SUPPORTED_PAGE_SIZES,
|
|
45
46
|
StepPlan,
|
|
46
|
-
T5DecodeRunner,
|
|
47
47
|
detect_kv_layout,
|
|
48
48
|
env_flag,
|
|
49
49
|
split_kv_cache,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
# The annotations name the Thor runner; a second family's runner duck-types
|
|
54
|
+
# the same surface and arrives through its own arch subpackage. At runtime
|
|
55
|
+
# the class is resolved via platforms.load_kernel — never by importing a
|
|
56
|
+
# family module here (see mjolnir.arch).
|
|
57
|
+
from .runner import T5DecodeRunner
|
|
58
|
+
|
|
52
59
|
logger = logging.getLogger("vllm.mjolnir") # under vLLM's handler
|
|
53
60
|
|
|
54
61
|
# --------------------------------------------------------------------------
|
|
@@ -238,6 +245,13 @@ def ineligible_reason(kv_cache_spec, layer_names, vllm_config) -> str | None:
|
|
|
238
245
|
return None
|
|
239
246
|
|
|
240
247
|
|
|
248
|
+
def _unsupported_arch_reason(device) -> str | None:
|
|
249
|
+
"""None on a supported compute capability; else the platform registry's
|
|
250
|
+
verdict (torch-free leaf), routed into the same t5=None fallback as every
|
|
251
|
+
ineligible config."""
|
|
252
|
+
return unsupported_reason(torch.cuda.get_device_capability(device))
|
|
253
|
+
|
|
254
|
+
|
|
241
255
|
# --------------------------------------------------------------------------
|
|
242
256
|
# fp6 (e2m3) KV: preconditions and per-channel scales
|
|
243
257
|
# --------------------------------------------------------------------------
|
|
@@ -395,6 +409,10 @@ class MjolnirMetadataBuilder(_BaseBuilder): # type: ignore[misc, valid-type]
|
|
|
395
409
|
self._fp6 = self._kv_format == "e2m3"
|
|
396
410
|
self._page = kv_cache_spec.block_size
|
|
397
411
|
self._t5_reason = ineligible_reason(kv_cache_spec, layer_names, vllm_config)
|
|
412
|
+
if self._t5_reason is None:
|
|
413
|
+
# platform guard (surgical): a GPU without a Mjolnir kernel
|
|
414
|
+
# degrades exactly like any other ineligible config
|
|
415
|
+
self._t5_reason = _unsupported_arch_reason(device)
|
|
398
416
|
# Diagnosis knobs for the draft path (MTP / EAGLE drafter layers):
|
|
399
417
|
# MJOLNIR_ATTN_DRAFT_T5=0 runs every drafter step on the fallback;
|
|
400
418
|
# MJOLNIR_ATTN_FUSED_DRAFT=0 makes vLLM rebuild the draft metadata
|
|
@@ -438,7 +456,7 @@ class MjolnirMetadataBuilder(_BaseBuilder): # type: ignore[misc, valid-type]
|
|
|
438
456
|
q_mode = {"bf16": "bf16", "bfloat16": "bf16", "fp8": "fp8", "e4m3": "fp8"}[q_mode]
|
|
439
457
|
if self._fp6 and q_mode != "fp8":
|
|
440
458
|
raise ValueError("fp6_e2m3 KV runs on the fp8-Q path only: unset MJOLNIR_ATTN_Q_DTYPE")
|
|
441
|
-
self.t5 =
|
|
459
|
+
self.t5 = load_kernel(detect()).Runner(
|
|
442
460
|
num_heads=num_heads,
|
|
443
461
|
num_kv_heads=kv_cache_spec.num_kv_heads,
|
|
444
462
|
head_size=kv_cache_spec.head_size,
|
|
@@ -9,14 +9,14 @@ contexts), and the result is written as a ``mjolnir.kv_scales/2`` file —
|
|
|
9
9
|
what ``MJOLNIR_ATTN_KV_SCALES`` points ``--kv-cache-dtype fp6_e2m3`` at.
|
|
10
10
|
|
|
11
11
|
python -m mjolnir.calibrate --config /configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR/config.yaml \\
|
|
12
|
-
--out /out/
|
|
12
|
+
--out /out/e2m3.json --download-dir /data/models/huggingface
|
|
13
13
|
python -m mjolnir.calibrate --model Qwen/Qwen3-0.6B --out /tmp/scales.json # small models / tests
|
|
14
14
|
|
|
15
15
|
The scales cover every attention layer that runs on the Mjolnir backend,
|
|
16
16
|
drafters included (drafting writes its KV too): the built-in MTP head, and an
|
|
17
17
|
external drafter whose ``speculative-config.attention_backend`` is MJOLNIR
|
|
18
18
|
(DSpark: its 5 layers come out as layers 64-68). ``mjolnir calibrate`` runs this in the image and writes a config
|
|
19
|
-
with its own drafter to
|
|
19
|
+
with its own drafter to ``kv_scales/<method>.kv_scales.json``.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Kernel-side platform leaf: which architecture is this GPU, and which kernel
|
|
2
|
+
implements it.
|
|
3
|
+
|
|
4
|
+
Import-safe everywhere (torch is imported inside :func:`detect` only, never at
|
|
5
|
+
module level), so the vLLM backend can consult it and any host can read the
|
|
6
|
+
registry without pulling the GPU stack.
|
|
7
|
+
|
|
8
|
+
``kernel_for`` resolves a compute capability to a dotted module path under
|
|
9
|
+
:mod:`mjolnir.arch`; :func:`load_kernel` imports it — heavy (the CUTLASS DSL
|
|
10
|
+
comes with it), so only call it when a device is real.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from types import ModuleType
|
|
17
|
+
|
|
18
|
+
# The platforms Mjolnir ships support for: (name, sm, implemented). A row
|
|
19
|
+
# without a kernel (spark / orin) exists so the unsupported-arch error can
|
|
20
|
+
# name the whole matrix; its kernel arrives as mjolnir/arch/<name>.py. The
|
|
21
|
+
# kernel package cannot import the CLI's registry, so the sm rows are
|
|
22
|
+
# mirrored here — the two sides are pinned in sync by
|
|
23
|
+
# tests/cli/test_kernel_platforms.py::test_kernel_and_cli_registries_agree_on_the_sm_rows.
|
|
24
|
+
_SHIPPED: tuple[tuple[str, tuple[int, int], bool], ...] = (
|
|
25
|
+
("thor", (11, 0), True),
|
|
26
|
+
("spark", (12, 1), False),
|
|
27
|
+
("orin", (8, 7), False),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PlatformNotSupported(RuntimeError):
|
|
32
|
+
"""The GPU's compute capability has no Mjolnir kernel."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def detect() -> tuple[int, int]:
|
|
36
|
+
"""The device's compute capability: ``$MJOLNIR_ARCH`` (``sm_110`` or
|
|
37
|
+
``11.0``) wins, else torch reads it off the CUDA device."""
|
|
38
|
+
raw = os.environ.get("MJOLNIR_ARCH", "").strip()
|
|
39
|
+
if raw:
|
|
40
|
+
return _parse_arch(raw)
|
|
41
|
+
try:
|
|
42
|
+
import torch
|
|
43
|
+
except ImportError as e:
|
|
44
|
+
raise RuntimeError(
|
|
45
|
+
"Mjolnir cannot detect the GPU compute capability (torch is not importable): "
|
|
46
|
+
"set MJOLNIR_ARCH, e.g. MJOLNIR_ARCH=sm_110"
|
|
47
|
+
) from e
|
|
48
|
+
if not torch.cuda.is_available():
|
|
49
|
+
raise RuntimeError(
|
|
50
|
+
"Mjolnir cannot detect the GPU compute capability (no CUDA device): "
|
|
51
|
+
"set MJOLNIR_ARCH, e.g. MJOLNIR_ARCH=sm_110"
|
|
52
|
+
)
|
|
53
|
+
cap = torch.cuda.get_device_capability()
|
|
54
|
+
return int(cap[0]), int(cap[1])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _parse_arch(raw: str) -> tuple[int, int]:
|
|
58
|
+
"""``sm_110`` / ``11.0`` / ``8.7`` -> (major, minor)."""
|
|
59
|
+
s = raw.strip().lower().removeprefix("sm_")
|
|
60
|
+
if s.isdigit(): # the sm_ form packs the minor into the last digit: sm_110 = 11.0
|
|
61
|
+
return int(s[:-1]), int(s[-1])
|
|
62
|
+
major, sep, minor = s.partition(".")
|
|
63
|
+
if sep and major.isdigit() and minor.isdigit():
|
|
64
|
+
return int(major), int(minor)
|
|
65
|
+
raise RuntimeError(f"MJOLNIR_ARCH={raw!r}: expected 'sm_<major><minor>' (e.g. sm_110) or '<major>.<minor>'")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def kernel_for(cc: tuple[int, int]) -> str:
|
|
69
|
+
"""The dotted module path of the kernel implementing ``cc`` — currently
|
|
70
|
+
only ``mjolnir.arch.thor``; anything else raises :class:`PlatformNotSupported`."""
|
|
71
|
+
for name, sm, implemented in _SHIPPED:
|
|
72
|
+
if sm == tuple(cc):
|
|
73
|
+
if implemented:
|
|
74
|
+
return f"mjolnir.arch.{name}"
|
|
75
|
+
break # shipped, but the kernel has not landed: unsupported for now
|
|
76
|
+
raise PlatformNotSupported(_unsupported_msg(cc))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_kernel(cc: tuple[int, int]) -> ModuleType:
|
|
80
|
+
"""Import and return the kernel module for ``cc`` (heavy: pulls the
|
|
81
|
+
CUTLASS DSL — only call this when a device is real)."""
|
|
82
|
+
import importlib
|
|
83
|
+
|
|
84
|
+
return importlib.import_module(kernel_for(cc))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def unsupported_reason(cc: tuple[int, int]) -> str | None:
|
|
88
|
+
"""None when ``cc`` has a kernel; else the reason the vLLM backend routes
|
|
89
|
+
into its ineligible-config degrade path (names the requested sm and the
|
|
90
|
+
fallback that takes over). Pure — no device access."""
|
|
91
|
+
for _name, sm, implemented in _SHIPPED:
|
|
92
|
+
if sm == tuple(cc) and implemented:
|
|
93
|
+
return None
|
|
94
|
+
return _unsupported_msg(cc) + " Decode steps run on the fallback backend."
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _unsupported_msg(cc: tuple[int, int]) -> str:
|
|
98
|
+
impl = [f"{n} (sm_{maj}{minr})" for n, (maj, minr), ok in _SHIPPED if ok]
|
|
99
|
+
research = [f"{n} (sm_{maj}{minr})" for n, (maj, minr), ok in _SHIPPED if not ok]
|
|
100
|
+
msg = f"Mjolnir has no kernel for sm_{cc[0]}{cc[1]} — implemented: {', '.join(impl)}"
|
|
101
|
+
if research:
|
|
102
|
+
msg += f"; research (no kernel yet): {', '.join(research)}"
|
|
103
|
+
return msg
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|