patrickbdevaney commited on
Commit
465905a
Β·
verified Β·
1 Parent(s): e7e677d

Create README.md

Browse files
Files changed (1) hide show
  1. README.md +285 -0
README.md ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ library_name: vllm
4
+ pipeline_tag: text-generation
5
+ tags:
6
+ - dflash
7
+ - speculative-decoding
8
+ - block-diffusion
9
+ - jetson-thor
10
+ - sm110a
11
+ - nvfp4
12
+ - edge-inference
13
+ - qwen
14
+ ---
15
+
16
+ # DFlash on NVIDIA Jetson AGX Thor (SM110a) β€” Proven Single-Node Deployment Guide
17
+
18
+ Block-diffusion speculative decoding (**[DFlash](https://github.com/z-lab/dflash)**) for Qwen3.5 /
19
+ Qwen3.6 **NVFP4** models, running on a **single NVIDIA Jetson AGX Thor** β€” aarch64, compute
20
+ **sm_110a**, ~117 GB unified LPDDR5X, CUDA 13, L4T r38. This repo is the **field guide for actually
21
+ getting these models stable on Thor**: the from-source vLLM image, per-model launch configs, the
22
+ 122B Marlin-crash discovery and its fix, full k-sweeps, and conc=1 decode benchmarks for four
23
+ model sizes.
24
+
25
+ > This is Thor-specific. The upstream DFlash drafters and the general model cards live at
26
+ > [z-lab](https://huggingface.co/z-lab). Numbers here are **measured on Thor at concurrency 1**,
27
+ > not the B200 numbers from the paper.
28
+
29
+ ---
30
+
31
+ ## TL;DR β€” what works
32
+
33
+ | Model | Active | MoE backend | Attn backend | Optimal k | Peak DFlash tok/s (conc=1) | vs AR |
34
+ |---|---|---|---|---|---|---|
35
+ | Qwen3.5-4B-NVFP4 (GDA-hybrid) | 4B | n/a (dense) | flash_attn | 15 | **155.8** | **3.2Γ—** |
36
+ | Qwen3.6-35B-A3B-NVFP4 (MoE) | 3B | **marlin** | flash_attn | 12 | **139.1** | β€” |
37
+ | Qwen3.6-27B-NVFP4 (dense) | 27B | n/a (dense) | flash_attn | 15 | **50.1** | β€” |
38
+ | Qwen3.5-122B-A10B-NVFP4 (MoE) | 12B | **cutlass** ⚠️ | **TRITON_ATTN** ⚠️ | 10 | **52.6** | **1.7Γ—** |
39
+
40
+ ⚠️ **The 122B is the special case.** Marlin (the backend that's *fastest* for the 35B)
41
+ **hard-crashes** loading the 256-expert 122B. It must run **cutlass MoE + TRITON_ATTN**. See
42
+ [the 122B section](#the-122b-marlin-crash-and-the-cutlass--triton_attn-fix).
43
+
44
+ ---
45
+
46
+ ## The image: vLLM from source + fastsafetensors
47
+
48
+ The stock `nvidia-ai-iot/vllm:latest-jetson-thor` is vLLM 0.19.0 with **no DFlash**. DFlash landed
49
+ after 0.19.0, so the image is built **from source for sm_110a**:
50
+
51
+ - **Source:** vLLM **PR #40898** head β€” `vllm @ git+https://github.com/vllm-project/vllm.git@refs/pull/40898/head`
52
+ (DFlash block-diffusion + interleaved SWA support). Version string: `0.20.0.dev0+dflash`.
53
+ - **Arch:** `TORCH_CUDA_ARCH_LIST=11.0a` β†’ native **sm_110a SASS** in `_C.abi3.so` and `_moe_C.abi3.so`.
54
+ - **Flash-attn:** native **sm_110 FA2** copied from the stock Thor image. **FA3 crashes on Thor**
55
+ (`CUTE_ARCH_TMA_SM90_ENABLED` β†’ `cudaErrorLaunchFailure`; FA3 needs Hopper SM90 TMA). vLLM
56
+ selects FA2, so this is transparent. FA2-vs-FA3 makes **no decode difference** (decode is
57
+ GEMM/bandwidth-bound, not attention-bound).
58
+ - **fastsafetensors 0.3.2** layer (`Dockerfile.fastsafe`): image tag `vllm-dflash-thor:fastsafe`
59
+ (identical image ID to `fa-native`, plus the fastsafetensors wheel).
60
+
61
+ **Why fastsafetensors is mandatory for the 122B.** The default safetensors loader mmaps the weight
62
+ file (CPU) *then* copies to GPU. On Thor's single 117 GB unified pool both copies coexist: for the
63
+ 122B that's 72 GB(GPU) + up to 72 GB(CPU mmap) = **144 GB > 117 GB β†’ the box crashes during load**.
64
+ fastsafetensors streams disk→GPU directly (no CPU staging), peaking near 72 GB. `FASTSAFETENSORS_NOGDS=1`
65
+ forces the no-GDS fallback (GPU Direct Storage is unsupported on Thor; it still avoids CPU staging).
66
+
67
+ Prebuilt image tarballs (aarch64 / sm_110a only) are published alongside this work:
68
+ ```bash
69
+ docker load < vllm-dflash-thor-fastsafe.tar.gz # 19 GB, has fastsafetensors 0.3.2
70
+ # or the base (no fastsafetensors): vllm-dflash-thor-fa-native.tar.gz
71
+ ```
72
+
73
+ Full build + debug narrative: **[BUILD-AND-DEBUG.md](BUILD-AND-DEBUG.md)**. All numbers + the raw
74
+ debug log: **[RESULTS.md](RESULTS.md)**. Per-model detail: **[benchmarks/](benchmarks/)**.
75
+
76
+ ---
77
+
78
+ ## How to run (proven configs)
79
+
80
+ All four use the same image, `--load-format fastsafetensors`, `--quantization compressed-tensors`
81
+ (the 4B is declared `modelopt` but loads fine as compressed-tensors), `--trust-remote-code`,
82
+ `--language-model-only` (the 4B and 122B are VLM wrappers), and these env vars:
83
+ ```
84
+ -e VLLM_USE_FLASHINFER_MOE_FP4=0 # mandatory: FlashInfer FP4 MoE has no sm_110a kernel
85
+ -e LD_PRELOAD=/usr/lib/aarch64-linux-gnu/nvidia/libcuda.so.1 # else import vllm._C dies
86
+ -e FASTSAFETENSORS_NOGDS=1 -e HF_HUB_DISABLE_XET=1 -e NCCL_IGNORE_CPU_AFFINITY=1
87
+ -e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
88
+ -e VLLM_USE_FLASHINFER_SAMPLER=1 -e CUDA_DEVICE_MAX_CONNECTIONS=1 # free, output-safe (see Optimizations)
89
+ ```
90
+ Pre-flight before every launch: `for c in $(docker ps -q); do docker kill $c; done; sudo sync;
91
+ sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'`. **Never** `docker stop` (hangs, leaks page cache)
92
+ or `sudo fuser -k /dev/nvidia*` (kills Xorg/RustDesk, not CUDA).
93
+
94
+ Exact, copy-pasteable launch commands for all four models are in **[REVERT.md](REVERT.md)**, and the
95
+ wrapper scripts are `scripts/serve-{35b,27b,122b}.sh`. The key per-model differences:
96
+
97
+ ```bash
98
+ # 35B-A3B (MoE) β€” marlin is fastest
99
+ --moe-backend marlin --attention-backend flash_attn \
100
+ --gpu-memory-utilization 0.78 --max-model-len 65536 --max-num-seqs 4 \
101
+ --speculative-config '{"method":"dflash","num_speculative_tokens":12,"model":"/draft"}'
102
+
103
+ # 27B (dense) β€” needs Qwen2Tokenizer overlay (ships tokenizer_class=TokenizersBackend)
104
+ --attention-backend flash_attn --tokenizer /tokenizer \
105
+ --gpu-memory-utilization 0.85 --max-model-len 65536 --max-num-seqs 4 \
106
+ --speculative-config '{"method":"dflash","num_speculative_tokens":15,"model":"/draft"}'
107
+
108
+ # 122B-A10B (MoE) β€” CUTLASS + TRITON_ATTN (NOT marlin/flashinfer), draft KV capped
109
+ --moe-backend cutlass --attention-backend TRITON_ATTN \
110
+ --gpu-memory-utilization 0.78 --max-model-len 16384 --max-num-seqs 2 \
111
+ --speculative-config '{"method":"dflash","num_speculative_tokens":10,"model":"/draft","max_model_len":1024}'
112
+
113
+ # 4B (dense GDA-hybrid VLM)
114
+ --attention-backend flash_attn \
115
+ --gpu-memory-utilization 0.40 --max-model-len 32768 --max-num-seqs 1 \
116
+ --speculative-config '{"method":"dflash","num_speculative_tokens":15,"model":"/draft"}'
117
+ ```
118
+
119
+ ---
120
+
121
+ ## The 122B Marlin crash, and the cutlass + TRITON_ATTN fix
122
+
123
+ This is the central Thor discovery. The 122B-A10B (**256 experts Γ— 48 layers**) crashed on every
124
+ launch for weeks. Root cause, established by reproduction + `cuobjdump`:
125
+
126
+ - After weights load (~60 s), the EngineCore dies with **exit 255, no Python traceback, Docker
127
+ OOMKilled=false, no host OOM line**, at exactly `nvfp4.py:491 Using MoEPrepareAndFinalizeNoDPEPModular`
128
+ β€” i.e. the **Marlin NVFP4 MoE weight-repack**.
129
+ - It is **not** DFlash / SWA / OOM / config β€” the identical crash reproduces with the **base model
130
+ alone** (no speculative config).
131
+ - It is **not** a missing kernel image: `_moe_C.abi3.so` *does* contain `sm_110a`, and the MoE dims
132
+ are 128-aligned. It is a **genuine in-kernel fault in the Marlin FP4 MoE kernel at 256-expert
133
+ scale** on Thor (matches open vLLM issues #35566 / #35519 / #35922). **A rebuild does not fix it.**
134
+
135
+ **The fix:**
136
+ 1. `--moe-backend cutlass` β€” cutlass (VLLM_CUTLASS) processes all 256 experts cleanly (70.46 GiB
137
+ load, no crash). It is the **only** NVFP4 MoE backend that loads the 122B on Thor.
138
+ (`triton` is rejected for NVFP4; `flashinfer_*` have no sm_110a kernels.)
139
+ This is the **opposite** of the 35B, where marlin is ~10% *faster* than cutlass β€” marlin only
140
+ *crashes* at the large expert count.
141
+ 2. `--attention-backend TRITON_ATTN` β€” cutlass + flashinfer dies in attention warmup with
142
+ `BatchDecodeWithPagedKVCacheWrapper.run() got an unexpected keyword 'kv_cache_sf'` (a FlashInfer
143
+ API mismatch in this fork). TRITON_ATTN avoids it.
144
+ 3. `--gpu-memory-utilization 0.78` + draft `"max_model_len":1024` β€” the DFlash draft eats ~2 GB KV;
145
+ at 0.72 the 16384 KV request fails ("estimated max length 8960"); 0.90 trips the startup
146
+ precheck (needs 110.5 > ~108 GB free). 0.78 yields ~71k-token KV.
147
+
148
+ Result: base 122B 10.9 tok/s β†’ DFlash **27–42 tok/s** (Ο„ 4.2–6.5), **1.7Γ—+**, stable, coherent.
149
+
150
+ ### cuobjdump arch facts (this image)
151
+ | extension | arches | meaning |
152
+ |---|---|---|
153
+ | `_moe_C.abi3.so` (Marlin MoE) | sm_110a, sm_80 | has Thor image, but **faults at 256 experts** |
154
+ | `_C.abi3.so` | sm_110a, sm_80, sm_90 | core kernels |
155
+ | flashinfer `fused_moe_103.so` | sm_100a, sm_103a | **no sm_110a** β†’ `VLLM_USE_FLASHINFER_MOE_FP4=0` |
156
+ | `gemm_sm120.so` / `trtllm_low_latency_gemm.so` | sm_120 / sm_100a | no Thor |
157
+
158
+ ---
159
+
160
+ ## Benchmarks β€” concurrency 1, single Thor, 120W, T=0
161
+
162
+ 4 coding tasks (sorting / lru / dijkstra / mixed), 3-run median, `tok/s (Ο„)`. Full k-sweeps in
163
+ [RESULTS.md](RESULTS.md); per-model files in [benchmarks/](benchmarks/).
164
+
165
+ **Qwen3.6-35B-A3B (MoE, marlin)** β€” k-sweep optimal **k=12** (avg 116.5 tok/s):
166
+ | k | sorting | lru | dijkstra | mixed | Ο„_avg |
167
+ |---|---|---|---|---|---|
168
+ | 12 | 137.1/8.56 | 100.5/6.08 | 104.0/4.76 | 124.2/5.61 | 6.25 |
169
+ | 15 | 139.1/8.86 | 98.7/6.27 | 98.0/4.81 | 111.1/5.35 | 6.32 |
170
+ MoE profile @k=12: **marlin 117.5 avg > cutlass 106.3** β†’ marlin default.
171
+
172
+ **Qwen3.6-27B (dense)** β€” k-sweep optimal **k=15** (avg 42.3 tok/s):
173
+ | k | sorting | lru | dijkstra | mixed | Ο„_avg |
174
+ |---|---|---|---|---|---|
175
+ | 15 | 50.1/7.04 | 38.4/5.33 | 39.8/5.55 | 40.9/5.68 | 5.90 |
176
+
177
+ **Qwen3.5-122B-A10B (MoE, cutlass+TRITON_ATTN)** β€” optimal **k=10**, base 10.9 tok/s:
178
+ | k | sorting | lru | dijkstra | mixed | Ο„_avg |
179
+ |---|---|---|---|---|---|
180
+ | 10 | 52.6/6.36 | 45.9/5.36 | 40.0/4.49 | 40.5/4.6 | 5.20 |
181
+
182
+ **Qwen3.5-4B (dense GDA-hybrid)** β€” optimal **k=15** (mixed 155.8), AR baseline 47.9 (dijkstra):
183
+ | k | sorting | lru | dijkstra | mixed | Ο„_avg |
184
+ |---|---|---|---|---|---|
185
+ | 15 | 130.6/5.75 | 141.0/4.57 | 135.5/4.37 | 155.8/5.01 | 4.92 |
186
+
187
+ ### Optimal-k pattern across model classes
188
+ `122B k=10 Β· 35B k=12 Β· 27B k=15 Β· 4B k=15`. **Cheaper-per-token models prefer higher k.** A dense
189
+ 27B (or overhead-bound 4B) pays an expensive full-weight forward per verify, so each accepted token
190
+ saves a lot and the draft overhead is small relative to it β†’ push k to the block max. A fast MoE
191
+ (35B, 3B active) hits the acceptance/overhead cliff sooner β†’ lower optimal k.
192
+
193
+ ### Roofline (NVFP4, 273 GB/s)
194
+ | model | active | AR ceiling | best DFlash | vs ceiling |
195
+ |---|---|---|---|---|
196
+ | 4B (GDA-hybrid) | 4B | 136.5 | 155.8 | **114%** |
197
+ | 27B dense | 27B | 20.2 | 50.1 | **248%** |
198
+ | 35B-A3B MoE | 3B | 182.0 | 139.1 | 76% |
199
+ | 122B-A10B MoE | 12B | 45.5 | 52.6 | **116%** |
200
+ DFlash's Ο„ amortization lets three of four models **exceed** their autoregressive bandwidth ceiling
201
+ at conc=1 β€” the verify step processes k+1 tokens per weight load, so effective tok/s β‰ˆ ceiling Γ— Ο„ /
202
+ verify-cost. The 35B sits at 76% because it's already the fastest (3B active, overhead-bound).
203
+
204
+ ---
205
+
206
+ ## Why this matters: conc=1 single-node decode, and DFlash vs MTP
207
+
208
+ The realistic edge / agentic deployment on a single Thor is **concurrency 1** β€” one user, one stream,
209
+ latency-bound. In that regime, *all* of these current-gen NVFP4 models are **overhead- or
210
+ bandwidth-starved**: at conc=1 there's no batch to amortize kernel launches, so a 4B model runs at
211
+ only **35% of its bandwidth ceiling** autoregressively (47.9 / 136.5 tok/s), and a 122B is a slideshow
212
+ at 10.9 tok/s. This is exactly where speculative decoding pays off most, and where the *choice* of
213
+ speculator matters.
214
+
215
+ **DFlash (block diffusion) vs MTP (multi-token prediction) at conc=1:**
216
+ - **MTP** appends a few extra prediction heads (typically `num_speculative_tokens=3–4`) and drafts that
217
+ many tokens autoregressively-cheaply. Its draft *depth* and acceptance are inherently shallow.
218
+ - **DFlash** denoises a whole **block** (block_size 16 β†’ k up to 15) **in parallel**, with a
219
+ non-causal drafter trained for it, reaching **acceptance length Ο„ β‰ˆ 4.5–8.9** here. Each target
220
+ forward then verifies ~5–9 tokens instead of MTP's ~2–3.
221
+ - At conc=1 the per-step target cost is fixed, so throughput β‰ˆ Ο„ / verify-cost. DFlash's **deeper,
222
+ higher-acceptance** drafts amortize that fixed cost across far more accepted tokens than MTP's
223
+ shallow drafts β€” which is why DFlash delivers the larger conc=1 speedups (we measured **1.7Γ—** on
224
+ the 122B and **3.2Γ—** on the overhead-bound 4B; z-lab reports up to **2.9Γ—** vs AR at conc=1).
225
+ MTP's advantage only narrows at high concurrency, where batching already hides launch overhead β€”
226
+ the opposite of the single-Thor edge case.
227
+
228
+ **The practical upshot:** DFlash is what makes a **122B-class model usable interactively on one
229
+ 117 GB edge box** (10.9 β†’ ~52 tok/s peak), and turns a 4B into a 150+ tok/s coding assistant β€” on
230
+ hardware with no discrete VRAM, drawing 120 W. The optimal-k tuning per model (above) is the single
231
+ highest-value lever; the rest of the "optimizations" are noise (next section).
232
+
233
+ ---
234
+
235
+ ## Optimizations that DON'T help (so you don't chase them)
236
+
237
+ Measured honestly on Thor. See [hypotheses.md](hypotheses.md) and the per-model benchmark files.
238
+ - **`cudagraph_mode FULL_AND_PIECEWISE`**: **already the vLLM default** in this fork (confirmed in
239
+ the config dump). Explicitly setting it is a no-op. A thermally-controlled 122B same-session A/B
240
+ of the full opt set measured **+0.2%** (neutral). Apparent 35B/27B "regressions" vs the sweep
241
+ baselines were **thermal drift** from ~9 h continuous load, not the opts.
242
+ - **`VLLM_MARLIN_USE_ATOMIC_ADD=1`**: within run-to-run noise on the 35B (marlin); a **no-op** on the
243
+ 27B (dense β†’ FlashInfer-Cutlass GEMM, not marlin). Kept because it's free.
244
+ - **`VLLM_USE_FLASHINFER_SAMPLER=1`, `CUDA_DEVICE_MAX_CONNECTIONS=1`**: output-safe, neutral β€” kept.
245
+ - **MAXN power mode**: that's removing a power/thermal ceiling (overclocking), not a software lever.
246
+ All benchmarks here are 120W; MAXN is a separate, reversible knob.
247
+ - **Restricting `cudagraph_capture_sizes` to `[1,k+1]`**: no conc=1 benefit and would force eager
248
+ fallback at conc>1 β€” **not** baked into the serve defaults.
249
+ - **Dead ends:** `--moe-backend triton` (rejected for NVFP4), `VLLM_USE_FLASHINFER_MOE_FP4=1`
250
+ (no sm_110a kernel), FA3 (Hopper TMA), prefix-caching with GDA hybrid on long context (risky).
251
+
252
+ ---
253
+
254
+ ## Operational rules (Thor-specific)
255
+ 1. `LD_PRELOAD=/usr/lib/aarch64-linux-gnu/nvidia/libcuda.so.1` β€” or `import vllm._C` dies with
256
+ `undefined symbol: cuPointerGetAttribute`.
257
+ 2. Stop with **`docker kill`** / `pkill -9`, **never `docker stop`** (hangs + page-cache leak).
258
+ 3. **Never `sudo fuser -k /dev/nvidia*`** β€” it kills Xorg/gnome-shell/RustDesk (display, not CUDA);
259
+ `docker kill` + `drop_caches` fully releases the GPU context.
260
+ 4. `gpu_memory_utilization` allocates **unified** memory (= system RAM). Too high starves the OS and
261
+ hard-crashes the box β€” the 122B is the danger case (0.90 trips the precheck; use 0.78).
262
+ 5. On Thor, `nvidia-smi` reports `[N/A]` for GPU memory β€” track `free -h` instead.
263
+ 6. `drop_caches` before every 122B load (the 70 GB weight file fills the unified page cache).
264
+ 7. 27B ships `tokenizer_class=TokenizersBackend` β†’ patch overlay to `Qwen2Tokenizer` (`--tokenizer`).
265
+ 8. `kv-cache-dtype auto` (BF16) for the DFlash draft path; the draft can reject quantized KV.
266
+
267
+ ---
268
+
269
+ ## Files
270
+ - `scripts/serve-{35b,27b,122b}.sh`, `scripts/_common.sh` β€” proven launch wrappers
271
+ - `scripts/ksweep-{4b,35b,27b,122b}.sh`, `scripts/bench.py`, `scripts/test-atomic-add.sh` β€” repro
272
+ - `benchmarks/{4b,35b,27b,122b-a10b}-optimizations.md` β€” per-model detail
273
+ - `moe-profiles/{35b-a3b,122b-a10b}-moe-profile.json` β€” MoE backend selection data
274
+ - `REVERT.md` β€” exact proven launch commands + revert steps Β· `BUILD-AND-DEBUG.md` β€” image build +
275
+ 122B debug journey Β· `hypotheses.md` β€” what was tried Β· `RESULTS.md` β€” everything
276
+
277
+ ## Model weights
278
+ - Bases (NVFP4): `Qwen3.5-4B-NVFP4`, `Qwen3.6-35B-A3B-NVFP4`, `Qwen3.6-27B-NVFP4`,
279
+ resharded `Qwen3.5-122B-A10B-NVFP4`
280
+ - DFlash drafts (`hf download z-lab/<name>`): `z-lab/Qwen3.5-4B-DFlash`, `z-lab/Qwen3.6-35B-A3B-DFlash`,
281
+ `z-lab/Qwen3.6-27B-DFlash`, `z-lab/Qwen3.5-122B-A10B-DFlash`
282
+
283
+ ## Credits
284
+ DFlash is by [z-lab](https://github.com/z-lab/dflash) (Chen, Liang, Liu β€” arXiv:2602.06036). This
285
+ repo is the Thor SM110a port, debugging, and benchmarking. vLLM PR #40898.