mjolnir-kernel 0.1.0__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.0 → mjolnir_kernel-0.2.0}/.gitignore +6 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/PKG-INFO +36 -11
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/README.md +34 -7
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/__init__.py +3 -3
- 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.0 → mjolnir_kernel-0.2.0}/mjolnir/backend.py +23 -12
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/banner.py +2 -2
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/calibrate.py +4 -3
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/draftvocab.py +2 -3
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/kvfp6.py +6 -6
- mjolnir_kernel-0.2.0/mjolnir/lmhead.py +689 -0
- mjolnir_kernel-0.2.0/mjolnir/platforms.py +103 -0
- mjolnir_kernel-0.2.0/mjolnir/py.typed +0 -0
- mjolnir_kernel-0.2.0/mjolnir/quant.py +120 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/runner.py +8 -5
- mjolnir_kernel-0.2.0/mjolnir/t5_call.py +560 -0
- mjolnir_kernel-0.2.0/mjolnir/t5_const.py +58 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/t5_decode.py +94 -1537
- mjolnir_kernel-0.2.0/mjolnir/t5_harness.py +711 -0
- mjolnir_kernel-0.2.0/mjolnir/t5_plan.py +197 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/pyproject.toml +1 -3
- mjolnir_kernel-0.1.0/mjolnir/lmhead.py +0 -379
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/LICENSE +0 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/NOTICE +0 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/kvscales.py +0 -0
- {mjolnir_kernel-0.1.0 → mjolnir_kernel-0.2.0}/mjolnir/qpack.py +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
|
|
@@ -18,12 +18,10 @@ Classifier: Intended Audience :: Developers
|
|
|
18
18
|
Classifier: Intended Audience :: Science/Research
|
|
19
19
|
Classifier: Operating System :: POSIX :: Linux
|
|
20
20
|
Classifier: Programming Language :: Python :: 3
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
23
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.12
|
|
27
25
|
Provides-Extra: dsl
|
|
28
26
|
Requires-Dist: nvidia-cutlass-dsl>=4.2; extra == 'dsl'
|
|
29
27
|
Description-Content-Type: text/markdown
|
|
@@ -36,7 +34,7 @@ packaged as a vLLM attention backend.
|
|
|
36
34
|
|
|
37
35
|
```bash
|
|
38
36
|
vllm serve <model> --attention-backend MJOLNIR # the mjolnir-vllm image
|
|
39
|
-
vllm serve <model> --attention-backend CUSTOM # stock vLLM 0.
|
|
37
|
+
vllm serve <model> --attention-backend CUSTOM # stock vLLM 0.32.0 + this package
|
|
40
38
|
```
|
|
41
39
|
|
|
42
40
|
This package is the kernel only. Serving, calibration and benchmarking are the
|
|
@@ -89,10 +87,10 @@ fused QKV buffer.
|
|
|
89
87
|
|
|
90
88
|
## Install
|
|
91
89
|
|
|
92
|
-
**On the image** (`narandill/mjolnir-vllm:v0.
|
|
90
|
+
**On the image** (`narandill/mjolnir-vllm:v0.32.0`) it is pre-installed and the
|
|
93
91
|
`MJOLNIR` backend name is patched into vLLM — nothing to do.
|
|
94
92
|
|
|
95
|
-
**On a stock vLLM 0.
|
|
93
|
+
**On a stock vLLM 0.32.0** (inside a container that has CUDA 13 and `sm_110`):
|
|
96
94
|
|
|
97
95
|
```bash
|
|
98
96
|
pip install "mjolnir-kernel[dsl]" # [dsl] pulls nvidia-cutlass-dsl
|
|
@@ -143,8 +141,8 @@ runs on the fallback backend.
|
|
|
143
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 |
|
|
144
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 |
|
|
145
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 |
|
|
146
|
-
| `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 `
|
|
147
|
-
| `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) |
|
|
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
|
+
| `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 |
|
|
148
146
|
| `MJOLNIR_ATTN_VERBOSE` | – | `1`: print the kernel's compile-time notes (ring stages, TMEM columns) — the standalone harness always does |
|
|
149
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) |
|
|
150
148
|
| `MJOLNIR_ATTN_KV_LAYOUT` | auto | force the KV view: `BHN2D BNH2D B2NHD B2HND 2BNHD 2BHND` |
|
|
@@ -168,8 +166,8 @@ expands the pages a prefill / mixed step reads to e4m3 for the FA4 fallback
|
|
|
168
166
|
(channel scales applied, one descale per head). The scales come from a calibration run on fp8 KV:
|
|
169
167
|
|
|
170
168
|
```bash
|
|
171
|
-
python -m mjolnir.calibrate --config <serve.yaml> --out kv_scales.json # in the image
|
|
172
|
-
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
|
|
173
171
|
```
|
|
174
172
|
|
|
175
173
|
(`mjolnir calibrate` / `mjolnir serve` do both for you —
|
|
@@ -187,6 +185,31 @@ How they compare in speed and accuracy:
|
|
|
187
185
|
[`quant`](https://github.com/thor-arena/mjolnir/blob/main/docs/kernel/quant.md).
|
|
188
186
|
Wiring a vLLM cache-write path for fp6 is an open contribution.
|
|
189
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
|
+
|
|
190
213
|
## Tests
|
|
191
214
|
|
|
192
215
|
From a checkout, on a Thor:
|
|
@@ -215,6 +238,8 @@ is printed as information.
|
|
|
215
238
|
| `mjolnir/t5_decode.py` | the CuTe-DSL kernel, the split-KV merge, the harness and the emulator |
|
|
216
239
|
| `mjolnir/runner.py` | plan buffers, compile cache, launch, KV views (no vLLM import) |
|
|
217
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 |
|
|
218
243
|
| `mjolnir/__init__.py` | `register()` — the vLLM plugin entry point |
|
|
219
244
|
|
|
220
245
|
Apache-2.0, except `mjolnir/t5_decode.py`, which is derived from an NVIDIA
|
|
@@ -6,7 +6,7 @@ packaged as a vLLM attention backend.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
vllm serve <model> --attention-backend MJOLNIR # the mjolnir-vllm image
|
|
9
|
-
vllm serve <model> --attention-backend CUSTOM # stock vLLM 0.
|
|
9
|
+
vllm serve <model> --attention-backend CUSTOM # stock vLLM 0.32.0 + this package
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
This package is the kernel only. Serving, calibration and benchmarking are the
|
|
@@ -59,10 +59,10 @@ fused QKV buffer.
|
|
|
59
59
|
|
|
60
60
|
## Install
|
|
61
61
|
|
|
62
|
-
**On the image** (`narandill/mjolnir-vllm:v0.
|
|
62
|
+
**On the image** (`narandill/mjolnir-vllm:v0.32.0`) it is pre-installed and the
|
|
63
63
|
`MJOLNIR` backend name is patched into vLLM — nothing to do.
|
|
64
64
|
|
|
65
|
-
**On a stock vLLM 0.
|
|
65
|
+
**On a stock vLLM 0.32.0** (inside a container that has CUDA 13 and `sm_110`):
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
pip install "mjolnir-kernel[dsl]" # [dsl] pulls nvidia-cutlass-dsl
|
|
@@ -113,8 +113,8 @@ 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 `
|
|
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) |
|
|
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
|
+
| `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) |
|
|
120
120
|
| `MJOLNIR_ATTN_KV_LAYOUT` | auto | force the KV view: `BHN2D BNH2D B2NHD B2HND 2BNHD 2BHND` |
|
|
@@ -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
|
|
@@ -23,8 +23,8 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import os
|
|
25
25
|
|
|
26
|
-
__all__ = ["
|
|
27
|
-
__version__ = "0.
|
|
26
|
+
__all__ = ["BACKEND_NAME", "BACKEND_PATH", "__version__", "register"]
|
|
27
|
+
__version__ = "0.2.0"
|
|
28
28
|
|
|
29
29
|
BACKEND_NAME = "MJOLNIR"
|
|
30
30
|
BACKEND_PATH = "mjolnir.backend.MjolnirAttentionBackend"
|
|
@@ -41,7 +41,7 @@ def _registry():
|
|
|
41
41
|
try:
|
|
42
42
|
from vllm.v1.attention.backends.registry import AttentionBackendEnum, register_backend
|
|
43
43
|
except ImportError: # older vLLM layout
|
|
44
|
-
from vllm.attention.backends.registry import (
|
|
44
|
+
from vllm.attention.backends.registry import (
|
|
45
45
|
AttentionBackendEnum,
|
|
46
46
|
register_backend,
|
|
47
47
|
)
|
|
@@ -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
|
# --------------------------------------------------------------------------
|
|
@@ -57,12 +64,12 @@ logger = logging.getLogger("vllm.mjolnir") # under vLLM's handler
|
|
|
57
64
|
try:
|
|
58
65
|
from vllm.v1.attention.backends.registry import AttentionBackendEnum
|
|
59
66
|
except ImportError: # older layout
|
|
60
|
-
from vllm.attention.backends.registry import AttentionBackendEnum
|
|
67
|
+
from vllm.attention.backends.registry import AttentionBackendEnum
|
|
61
68
|
|
|
62
69
|
try:
|
|
63
70
|
from vllm.v1.attention.backend import AttentionCGSupport
|
|
64
71
|
except ImportError:
|
|
65
|
-
from vllm.v1.attention.backends.utils import AttentionCGSupport
|
|
72
|
+
from vllm.v1.attention.backends.utils import AttentionCGSupport
|
|
66
73
|
|
|
67
74
|
FP8_CACHE_DTYPES = ("fp8", "fp8_e4m3")
|
|
68
75
|
FP6_CACHE_DTYPES = ("fp6_e2m3",)
|
|
@@ -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,
|
|
@@ -718,13 +736,6 @@ class MjolnirImpl(_BaseImpl): # type: ignore[misc, valid-type]
|
|
|
718
736
|
row = runner.kv_row_bytes
|
|
719
737
|
if self._kv_layout is None:
|
|
720
738
|
self._kv_layout = detect_kv_layout(kv_cache.shape, runner.h_k, runner.page_size, row)
|
|
721
|
-
# logger.info(
|
|
722
|
-
# "Mjolnir: KV cache %s %s -> layout %s (%s)",
|
|
723
|
-
# tuple(kv_cache.shape),
|
|
724
|
-
# tuple(kv_cache.stride()),
|
|
725
|
-
# self._kv_layout,
|
|
726
|
-
# runner.kv_format,
|
|
727
|
-
# )
|
|
728
739
|
return split_kv_cache(kv_cache, self._kv_layout, row)
|
|
729
740
|
|
|
730
741
|
def _write_kv(self, layer, key, value, kv_cache, k_cache, v_cache, md):
|
|
@@ -122,6 +122,6 @@ if __name__ == "__main__": # `python -m mjolnir.banner [--plain]`: preview with
|
|
|
122
122
|
color=sys.stdout.isatty() and not plain,
|
|
123
123
|
model=os.environ.get("MJOLNIR_CONFIG", "").strip() or None,
|
|
124
124
|
)
|
|
125
|
-
print(
|
|
125
|
+
print()
|
|
126
126
|
print(msg)
|
|
127
|
-
print(
|
|
127
|
+
print()
|
|
@@ -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
|
|
@@ -86,7 +86,8 @@ def engine_kwargs_from_config(path: str) -> dict:
|
|
|
86
86
|
import yaml
|
|
87
87
|
|
|
88
88
|
parser, engine_args = _engine_parser()
|
|
89
|
-
|
|
89
|
+
with open(path) as f:
|
|
90
|
+
data = yaml.safe_load(f) or {}
|
|
90
91
|
known = {k: v for k, v in data.items() if "--" + str(k).replace("_", "-") in parser._option_string_actions}
|
|
91
92
|
with tempfile.NamedTemporaryFile("w", suffix=".yaml", delete=False) as f:
|
|
92
93
|
yaml.safe_dump(known, f)
|
|
@@ -22,6 +22,7 @@ Special tokens (EOS, the chat / think markers) are always included.
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
24
|
import argparse
|
|
25
|
+
import contextlib
|
|
25
26
|
import json
|
|
26
27
|
import os
|
|
27
28
|
import sys
|
|
@@ -116,10 +117,8 @@ def main(argv=None) -> int:
|
|
|
116
117
|
return 1
|
|
117
118
|
tok = llm.get_tokenizer()
|
|
118
119
|
special = set(getattr(tok, "all_special_ids", []) or [])
|
|
119
|
-
|
|
120
|
+
with contextlib.suppress(Exception):
|
|
120
121
|
special |= set(tok.get_added_vocab().values())
|
|
121
|
-
except Exception: # noqa: BLE001
|
|
122
|
-
pass
|
|
123
122
|
vocab = len(tok)
|
|
124
123
|
ranked = rank(counts, special, vocab)
|
|
125
124
|
coverage = held_out_coverage(halves[0], halves[1], special, vocab)
|
|
@@ -47,7 +47,7 @@ def e2m3_values(device) -> torch.Tensor:
|
|
|
47
47
|
"""(64,) fp32: the value of every e2m3 code."""
|
|
48
48
|
key = str(device)
|
|
49
49
|
if key not in _LUTF:
|
|
50
|
-
from .
|
|
50
|
+
from .quant import minifloat_table
|
|
51
51
|
|
|
52
52
|
_LUTF[key] = minifloat_table("e2m3").to(device)
|
|
53
53
|
return _LUTF[key]
|
|
@@ -57,7 +57,7 @@ def e2m3_to_e4m3_lut(device) -> torch.Tensor:
|
|
|
57
57
|
"""(64,) uint8: e4m3fn byte of every e2m3 code (exact)."""
|
|
58
58
|
key = str(device)
|
|
59
59
|
if key not in _LUT:
|
|
60
|
-
from .
|
|
60
|
+
from .quant import minifloat_table
|
|
61
61
|
|
|
62
62
|
vals = minifloat_table("e2m3") # 64 float values, code order
|
|
63
63
|
e4 = vals.to(torch.float8_e4m3fn)
|
|
@@ -229,7 +229,7 @@ def write_kv(
|
|
|
229
229
|
return
|
|
230
230
|
_, h_k, d = key.shape
|
|
231
231
|
_, H, N, C = kv_cache.shape
|
|
232
|
-
assert
|
|
232
|
+
assert h_k == H and 2 * row_bytes(d) == C and kv_cache.stride(3) == 1 and kv_cache.dtype == torch.uint8
|
|
233
233
|
assert key.stride(2) == 1 and value.stride(2) == 1
|
|
234
234
|
_write_kv_kernel[(T, h_k)](
|
|
235
235
|
key,
|
|
@@ -271,7 +271,7 @@ def expand_pages_to_fp8(
|
|
|
271
271
|
P = src_blocks.shape[0]
|
|
272
272
|
_, H, N, C = kv_cache.shape
|
|
273
273
|
d = head_size
|
|
274
|
-
assert
|
|
274
|
+
assert 2 * row_bytes(d) == C and out.shape[1:] == (H, N, 2 * d) and out.stride(3) == 1
|
|
275
275
|
if P == 0:
|
|
276
276
|
return out[:0]
|
|
277
277
|
_expand_kernel[(P, H, N)](
|
|
@@ -303,7 +303,7 @@ def expand_pages_to_fp8(
|
|
|
303
303
|
def quantize_rows_reference(x: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
|
|
304
304
|
"""(..., h_k, d) float -> packed bytes (..., h_k, row_bytes(d)) uint8;
|
|
305
305
|
scale per tensor, per KV head or (h_k, d) per channel."""
|
|
306
|
-
from .
|
|
306
|
+
from .quant import minifloat_quantize, pack_subbyte
|
|
307
307
|
|
|
308
308
|
h_k, d = x.shape[-2], x.shape[-1]
|
|
309
309
|
s = scale.float().cpu()
|
|
@@ -318,7 +318,7 @@ def quantize_rows_reference(x: torch.Tensor, scale: torch.Tensor) -> torch.Tenso
|
|
|
318
318
|
|
|
319
319
|
def decode_rows_reference(packed: torch.Tensor, d: int) -> torch.Tensor:
|
|
320
320
|
"""packed (..., row_bytes) uint8 -> e2m3 code values (..., d) float."""
|
|
321
|
-
from .
|
|
321
|
+
from .quant import minifloat_table
|
|
322
322
|
|
|
323
323
|
b = packed.to(torch.int64).cpu().reshape(*packed.shape[:-1], d // 4, 3)
|
|
324
324
|
w = b[..., 0] | (b[..., 1] << 8) | (b[..., 2] << 16)
|