Update README.md
Browse files
README.md
CHANGED
|
@@ -40,6 +40,13 @@ tensor-parallel 4, which is exactly why 4× 48 GB Ada cards are such an
|
|
| 40 |
attractive target — and exactly why it is painful that the default serving
|
| 41 |
stacks refuse to run there.
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
## Measured performance
|
| 44 |
|
| 45 |
4× RTX 6000 Ada (48 GB, PCIe-only, TP=4), `fp8_ds_mla` KV cache,
|
|
@@ -53,6 +60,12 @@ stacks refuse to run there.
|
|
| 53 |
The BF16 MTP head drafts nearly perfectly on this checkpoint; a ~1.5–1.65×
|
| 54 |
speedup from a single speculative token is about as good as MTP-k=1 gets.
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
## Why Ada needs special handling
|
| 57 |
|
| 58 |
The stock DeepSeek-V4 serving paths gate on newer architectures at three
|
|
@@ -96,8 +109,11 @@ Everything below is automated by
|
|
| 96 |
(forcing the FlashInfer backend crashes on an SWA-cache shape mismatch),
|
| 97 |
`--kv-cache-dtype fp8_ds_mla --block-size 256`, and the `deepseek_v4`
|
| 98 |
tokenizer / reasoning-parser / tool-call-parser modes.
|
| 99 |
-
- **MTP**: `--speculative-config '{"method":"mtp","num_speculative_tokens":1}'
|
| 100 |
-
--max-num-batched-tokens 2048`
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
## The one required source patch (MTP only)
|
| 103 |
|
|
@@ -119,11 +135,54 @@ a single tiny layer, so the unfused fallback costs nothing measurable. The
|
|
| 119 |
patcher is idempotent, backs up the original, and refuses to apply against
|
| 120 |
any file that doesn't match the `dev145` layout.
|
| 121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
## Quickstart
|
| 123 |
|
| 124 |
```bash
|
| 125 |
# 1. Build the environment (venv, pinned wheels, patch, verification,
|
| 126 |
-
# generated serve.sh / canary.sh / systemd unit):
|
| 127 |
./setup_deepseek_v4_sm89.sh ~/deepseek-v4-serve SinclairSchneider/DeepSeek-V4-Flash-W4A16-FP8-MTP-Ada
|
| 128 |
# (the original repo id works identically:
|
| 129 |
# ./setup_deepseek_v4_sm89.sh ~/deepseek-v4-serve canada-quant/DeepSeek-V4-Flash-W4A16-FP8-MTP)
|
|
@@ -138,10 +197,14 @@ any file that doesn't match the `dev145` layout.
|
|
| 138 |
~/deepseek-v4-serve/canary.sh
|
| 139 |
```
|
| 140 |
|
| 141 |
-
Expect ~80 s of weight loading plus ~5 s for the MTP drafter,
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
## Gotchas the scripts already handle
|
| 147 |
|
|
|
|
| 40 |
attractive target — and exactly why it is painful that the default serving
|
| 41 |
stacks refuse to run there.
|
| 42 |
|
| 43 |
+
> **262k is the checkpoint's context ceiling, not the recommended serving
|
| 44 |
+
> length on 48 GB Ada.** After weights + activations + CUDA-graph pools, only
|
| 45 |
+
> ~1 GiB/GPU is left for KV cache, so a full 262k context fits only at high
|
| 46 |
+
> `--gpu-memory-utilization` and leaves almost no headroom. For concurrent or
|
| 47 |
+
> agentic workloads the generated defaults cap `--max-model-len` lower (98304)
|
| 48 |
+
> to keep KV *and* JIT headroom — see [Serving on 48 GB Ada](#serving-on-48-gb-ada-memory-concurrency-and-context) below.
|
| 49 |
+
|
| 50 |
## Measured performance
|
| 51 |
|
| 52 |
4× RTX 6000 Ada (48 GB, PCIe-only, TP=4), `fp8_ds_mla` KV cache,
|
|
|
|
| 60 |
The BF16 MTP head drafts nearly perfectly on this checkpoint; a ~1.5–1.65×
|
| 61 |
speedup from a single speculative token is about as good as MTP-k=1 gets.
|
| 62 |
|
| 63 |
+
These figures are **single-stream throughput at the maximum-context operating
|
| 64 |
+
point** (262k, high utilization, `--max-num-batched-tokens 2048`). That is the
|
| 65 |
+
right point to *quote* peak tok/s, but not the right point to *run* a
|
| 66 |
+
concurrent or agentic workload on 48 GB cards — see the next section for why,
|
| 67 |
+
and for the defaults the setup script actually generates.
|
| 68 |
+
|
| 69 |
## Why Ada needs special handling
|
| 70 |
|
| 71 |
The stock DeepSeek-V4 serving paths gate on newer architectures at three
|
|
|
|
| 109 |
(forcing the FlashInfer backend crashes on an SWA-cache shape mismatch),
|
| 110 |
`--kv-cache-dtype fp8_ds_mla --block-size 256`, and the `deepseek_v4`
|
| 111 |
tokenizer / reasoning-parser / tool-call-parser modes.
|
| 112 |
+
- **MTP**: `--speculative-config '{"method":"mtp","num_speculative_tokens":1}'`.
|
| 113 |
+
`--max-num-batched-tokens` is `2048` at the peak-throughput point above; the
|
| 114 |
+
generated `serve.sh` raises it to `4096` for the agentic-serving defaults
|
| 115 |
+
(which enables chunked prefill — see the memory section, as its buffers come
|
| 116 |
+
out of the KV pool).
|
| 117 |
|
| 118 |
## The one required source patch (MTP only)
|
| 119 |
|
|
|
|
| 135 |
patcher is idempotent, backs up the original, and refuses to apply against
|
| 136 |
any file that doesn't match the `dev145` layout.
|
| 137 |
|
| 138 |
+
## Serving on 48 GB Ada: memory, concurrency, and context
|
| 139 |
+
|
| 140 |
+
On these cards **KV cache is the thin slice** — weights, activations, and
|
| 141 |
+
CUDA-graph pools consume almost the whole 48 GB, leaving on the order of
|
| 142 |
+
~1 GiB/GPU for KV. Two failure modes sit on opposite sides of a narrow tuning
|
| 143 |
+
window, and the generated defaults are the corner that clears both:
|
| 144 |
+
|
| 145 |
+
- **JIT-OOM (too little headroom).** The Triton sparse-MLA path compiles a
|
| 146 |
+
kernel specialization *per prompt shape*, lazily, and loading each freshly
|
| 147 |
+
compiled cubin (`cuModuleLoadData`) needs driver-level free VRAM *outside*
|
| 148 |
+
the torch pool. At high utilization a never-before-seen prompt shape
|
| 149 |
+
arriving mid-run can find no room to load its kernel and crash a worker
|
| 150 |
+
(observed in `_tf32_hc_prenorm_gemm`), taking the server down. Two things
|
| 151 |
+
defuse this: the generated `warmup.sh` compiles the shape space at startup
|
| 152 |
+
while memory is free, and the systemd unit uses `Restart=always` (a JIT-OOM
|
| 153 |
+
can exit *cleanly* from systemd's view, so `on-failure` is not enough).
|
| 154 |
+
- **KV-fit refusal (too little KV).** vLLM refuses to boot if a single full
|
| 155 |
+
`--max-model-len` sequence won't fit the KV pool. At the headroom-safe
|
| 156 |
+
utilizations below, full 262k does **not** fit; `--max-model-len 98304` does,
|
| 157 |
+
with room for the KV a long agentic trajectory actually uses.
|
| 158 |
+
|
| 159 |
+
**Concurrency: use `--max-num-seqs 1` for agentic workloads.** With several
|
| 160 |
+
30–100k-token trajectories in flight, they cannot co-reside in the KV pool, so
|
| 161 |
+
vLLM preempts and *re-prefills* evicted sequences from scratch — prefix-cache
|
| 162 |
+
hit rate collapses below 40%, generation throughput drops to single digits
|
| 163 |
+
while prefill spikes, and wall-clock blows up roughly 10×. A single resident
|
| 164 |
+
stream (prefix hit ~85–91%) decoding continuously is dramatically faster in
|
| 165 |
+
practice. One stream that never evicts beats four that thrash.
|
| 166 |
+
|
| 167 |
+
**Utilization.** `0.97` maximizes the KV pool and works for a *watched* run
|
| 168 |
+
backed by warmup + auto-restart, but leaves only ~0.5–0.8 GiB/GPU free under a
|
| 169 |
+
long trajectory. For **unattended or shared** operation prefer
|
| 170 |
+
`--gpu-memory-utilization 0.95`: it restores ~2.4 GiB of headroom at identical
|
| 171 |
+
seq=1 throughput. The setup script exposes both as parameters; pick per how
|
| 172 |
+
closely you'll be watching.
|
| 173 |
+
|
| 174 |
+
**Smaller Ada cards (RTX 4090, 24 GB).** This recipe is validated on 4× 48 GB
|
| 175 |
+
(RTX 6000 Ada). A 159 GB checkpoint under TP=4 needs ~38 GiB/GPU for weights
|
| 176 |
+
alone, so it does **not** fit four 24 GB 4090s — you would need more cards
|
| 177 |
+
(e.g. TP=8) and an even tighter KV budget, and none of the memory figures here
|
| 178 |
+
transfer directly. The `rtx-4090` tag reflects the SM89 architecture the stack
|
| 179 |
+
targets, not a claim that this checkpoint fits on 4090-class VRAM.
|
| 180 |
+
|
| 181 |
## Quickstart
|
| 182 |
|
| 183 |
```bash
|
| 184 |
# 1. Build the environment (venv, pinned wheels, patch, verification,
|
| 185 |
+
# generated serve.sh / warmup.sh / canary.sh / systemd unit):
|
| 186 |
./setup_deepseek_v4_sm89.sh ~/deepseek-v4-serve SinclairSchneider/DeepSeek-V4-Flash-W4A16-FP8-MTP-Ada
|
| 187 |
# (the original repo id works identically:
|
| 188 |
# ./setup_deepseek_v4_sm89.sh ~/deepseek-v4-serve canada-quant/DeepSeek-V4-Flash-W4A16-FP8-MTP)
|
|
|
|
| 197 |
~/deepseek-v4-serve/canary.sh
|
| 198 |
```
|
| 199 |
|
| 200 |
+
Expect ~80 s of weight loading plus ~5 s for the MTP drafter, then a Triton
|
| 201 |
+
shape-warmup pass (`warmup.sh`, also generated) that compiles the kernel
|
| 202 |
+
specializations up front, then steady-state throughput. For unattended
|
| 203 |
+
operation the setup script generates a systemd unit
|
| 204 |
+
(see [`deepseek-v4-flash.service`](./deepseek-v4-flash.service)) that runs the
|
| 205 |
+
warmup automatically after every start (`ExecStartPost`) and uses
|
| 206 |
+
`Restart=always` so a mid-run kernel-load OOM self-heals rather than leaving a
|
| 207 |
+
dead port.
|
| 208 |
|
| 209 |
## Gotchas the scripts already handle
|
| 210 |
|