Spaces:
Running on Zero
Running on Zero
agharsallah commited on
Commit Β·
e334e95
1
Parent(s): 6a5dc7f
feat: Handle FP8 KV cache incompatibility with snapshot models in build command
Browse files- docs/adr/0031-fp8-quantization-control.md +12 -0
- modal/docs/deploying.md +8 -0
- modal/service.py +15 -0
- tests/test_modal_build_command.py +39 -0
docs/adr/0031-fp8-quantization-control.md
CHANGED
|
@@ -84,6 +84,18 @@ output quantized, we can pin `quantization="fp8"` on it in the catalogue.
|
|
| 84 |
deploy (no stale-precision restores β the snapshot is keyed to the new function
|
| 85 |
version). A model that can't serve FP8 fails at snapshot *creation*, which is the
|
| 86 |
same loud no-healthy-container failure as the plain path.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
- Possible future unlock: FP8 weights halve the host-RAM needed for sleep level 1,
|
| 88 |
which was the stated blocker for snapshotting `nemotron-3-nano-30b` (~60GB BF16,
|
| 89 |
ADR-0030). Unverified β Nemotron-H may reject on-the-fly FP8 entirely β so this
|
|
|
|
| 84 |
deploy (no stale-precision restores β the snapshot is keyed to the new function
|
| 85 |
version). A model that can't serve FP8 fails at snapshot *creation*, which is the
|
| 86 |
same loud no-healthy-container failure as the plain path.
|
| 87 |
+
- **FP8 KV cache is incompatible with sleep-mode/snapshot models on the pinned vLLM.**
|
| 88 |
+
`--kv-cache-dtype fp8` boots and snapshots fine, but the `/wake_up` path runs
|
| 89 |
+
`init_fp8_kv_scales()` over a post-sleep KV cache that is a *list* of per-layer
|
| 90 |
+
tensors (not one tensor), so `cache_tensor.zero_()` throws and every snapshot restore
|
| 91 |
+
500s β an endpoint that boots but can never wake. This bit `nemotron-3-nano-4b`
|
| 92 |
+
(`gpu_snapshot=True`) under a global `MODAL_LLM_KV_CACHE_DTYPE=fp8` deploy.
|
| 93 |
+
`build_command` therefore **drops an FP8 `kv_cache_dtype` for any `gpu_snapshot`
|
| 94 |
+
model** and warns: snapshot is a structural per-model decision, the KV dtype a deploy
|
| 95 |
+
knob, so snapshot wins and the endpoint serves with full-precision KV cache. Weight
|
| 96 |
+
`--quantization fp8` is a different code path and is unaffected. To actually run FP8
|
| 97 |
+
KV cache on such a model, drop `gpu_snapshot` (trade the fast cold start for the KV
|
| 98 |
+
win) β or revisit once the vLLM pin advances past the bug.
|
| 99 |
- Possible future unlock: FP8 weights halve the host-RAM needed for sleep level 1,
|
| 100 |
which was the stated blocker for snapshotting `nemotron-3-nano-30b` (~60GB BF16,
|
| 101 |
ADR-0030). Unverified β Nemotron-H may reject on-the-fly FP8 entirely β so this
|
modal/docs/deploying.md
CHANGED
|
@@ -233,6 +233,14 @@ uv run scripts/deploy_modal.py nvidia --quantization none
|
|
| 233 |
> `curl <url>/v1/models`); if a model won't start, redeploy that provider without
|
| 234 |
> the flag. This is why all per-model defaults stay `None` for now. See ADR-0031.
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
## Auth
|
| 237 |
|
| 238 |
Modal web endpoints are public by default. Secrets are supplied as environment
|
|
|
|
| 233 |
> `curl <url>/v1/models`); if a model won't start, redeploy that provider without
|
| 234 |
> the flag. This is why all per-model defaults stay `None` for now. See ADR-0031.
|
| 235 |
|
| 236 |
+
> **FP8 KV cache (`--kv-cache-dtype fp8`) is silently dropped for snapshot models.**
|
| 237 |
+
> On the pinned vLLM it crashes the `/wake_up` path (`init_fp8_kv_scales` β
|
| 238 |
+
> `'list' object has no attribute 'zero_'`), so an FP8-KV snapshot model boots but
|
| 239 |
+
> can never wake. `build_command` drops the flag for any `gpu_snapshot=True` model
|
| 240 |
+
> and logs a `β οΈ` line at deploy; the endpoint serves with full-precision KV cache.
|
| 241 |
+
> FP8 *weights* (`--quantization fp8`) are unaffected. To run FP8 KV cache on such a
|
| 242 |
+
> model, set its `gpu_snapshot=False`. See ADR-0031.
|
| 243 |
+
|
| 244 |
## Auth
|
| 245 |
|
| 246 |
Modal web endpoints are public by default. Secrets are supplied as environment
|
modal/service.py
CHANGED
|
@@ -220,6 +220,21 @@ def build_command(cfg: ModelConfig) -> list[str]:
|
|
| 220 |
if quantization:
|
| 221 |
cmd += ["--quantization", quantization]
|
| 222 |
kv_cache_dtype = _resolve_precision(KV_CACHE_DTYPE, cfg.kv_cache_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
if kv_cache_dtype:
|
| 224 |
cmd += ["--kv-cache-dtype", kv_cache_dtype]
|
| 225 |
# Performance / throughput knobs (all data-driven from ModelConfig).
|
|
|
|
| 220 |
if quantization:
|
| 221 |
cmd += ["--quantization", quantization]
|
| 222 |
kv_cache_dtype = _resolve_precision(KV_CACHE_DTYPE, cfg.kv_cache_dtype)
|
| 223 |
+
# FP8 KV cache is incompatible with sleep-mode/snapshot models on the pinned
|
| 224 |
+
# vLLM: the wake path runs init_fp8_kv_scales() over a post-sleep KV cache that
|
| 225 |
+
# is a *list* of per-layer tensors, not one tensor, so cache_tensor.zero_()
|
| 226 |
+
# throws and /wake_up 500s (every snapshot restore dies). Snapshot is a
|
| 227 |
+
# structural per-model decision; the KV dtype is a deploy knob β so snapshot
|
| 228 |
+
# wins. Drop the flag and warn loudly rather than ship an endpoint that boots
|
| 229 |
+
# but can never wake. Weight --quantization is unaffected (different code path).
|
| 230 |
+
if kv_cache_dtype and cfg.gpu_snapshot and kv_cache_dtype.lower().startswith("fp8"):
|
| 231 |
+
print(
|
| 232 |
+
f"β οΈ {cfg.endpoint_name}: dropping --kv-cache-dtype {kv_cache_dtype} β "
|
| 233 |
+
"FP8 KV cache crashes the snapshot wake path on the pinned vLLM (see ADR-0031). "
|
| 234 |
+
"Serving with full-precision KV cache. Drop gpu_snapshot to keep FP8 KV cache.",
|
| 235 |
+
flush=True,
|
| 236 |
+
)
|
| 237 |
+
kv_cache_dtype = None
|
| 238 |
if kv_cache_dtype:
|
| 239 |
cmd += ["--kv-cache-dtype", kv_cache_dtype]
|
| 240 |
# Performance / throughput knobs (all data-driven from ModelConfig).
|
tests/test_modal_build_command.py
CHANGED
|
@@ -85,6 +85,45 @@ def test_kv_cache_env_override(service, monkeypatch):
|
|
| 85 |
assert cmd[cmd.index("--kv-cache-dtype") + 1] == "fp8"
|
| 86 |
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
# ββ deploy script wiring βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 89 |
|
| 90 |
|
|
|
|
| 85 |
assert cmd[cmd.index("--kv-cache-dtype") + 1] == "fp8"
|
| 86 |
|
| 87 |
|
| 88 |
+
# ββ FP8 KV cache Γ snapshot incompatibility (vLLM wake-path crash) βββββββββββββ
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def test_fp8_kv_cache_dropped_for_snapshot_models(service):
|
| 92 |
+
# FP8 KV cache crashes the /wake_up path on snapshot models, so the flag is
|
| 93 |
+
# suppressed when gpu_snapshot is set β the endpoint serves with full-precision
|
| 94 |
+
# KV cache rather than booting into a state it can never wake from.
|
| 95 |
+
cmd = service.build_command(_make(service, kv_cache_dtype="fp8", gpu_snapshot=True))
|
| 96 |
+
assert "--kv-cache-dtype" not in cmd
|
| 97 |
+
# The snapshot flag itself still wins and is emitted.
|
| 98 |
+
assert "--enable-sleep-mode" in cmd
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def test_fp8_kv_cache_env_override_dropped_for_snapshot_models(service, monkeypatch):
|
| 102 |
+
# The global deploy override is the common trigger: it lands on every model in
|
| 103 |
+
# the app, including snapshot ones, which must still drop it.
|
| 104 |
+
monkeypatch.setattr(service, "KV_CACHE_DTYPE", "fp8")
|
| 105 |
+
cmd = service.build_command(_make(service, gpu_snapshot=True))
|
| 106 |
+
assert "--kv-cache-dtype" not in cmd
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def test_fp8_variant_kv_cache_dropped_for_snapshot_models(service):
|
| 110 |
+
# Every fp8 variant hits init_fp8_kv_scales, so fp8_e5m2 is dropped too.
|
| 111 |
+
cmd = service.build_command(_make(service, kv_cache_dtype="fp8_e5m2", gpu_snapshot=True))
|
| 112 |
+
assert "--kv-cache-dtype" not in cmd
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def test_non_fp8_kv_cache_kept_for_snapshot_models(service):
|
| 116 |
+
# The guard only fires on fp8; a non-fp8 dtype passes through even with snapshot.
|
| 117 |
+
cmd = service.build_command(_make(service, kv_cache_dtype="auto", gpu_snapshot=True))
|
| 118 |
+
assert cmd[cmd.index("--kv-cache-dtype") + 1] == "auto"
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def test_fp8_kv_cache_kept_for_non_snapshot_models(service):
|
| 122 |
+
# Without snapshot there's no wake path, so FP8 KV cache stays.
|
| 123 |
+
cmd = service.build_command(_make(service, kv_cache_dtype="fp8", gpu_snapshot=False))
|
| 124 |
+
assert cmd[cmd.index("--kv-cache-dtype") + 1] == "fp8"
|
| 125 |
+
|
| 126 |
+
|
| 127 |
# ββ deploy script wiring βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 128 |
|
| 129 |
|