Fix DeepSeek Vision-Exp image inference with SSD streaming on Metal - #937
Open
cropduster wants to merge 1 commit into
Open
Fix DeepSeek Vision-Exp image inference with SSD streaming on Metal#937cropduster wants to merge 1 commit into
cropduster wants to merge 1 commit into
Conversation
Two coupled bugs broke DeepSeek Vision-Exp image inference with
--ssd-streaming on Metal:
1. The SSD-streaming span remap cleared ALL global Metal model
views, including the vision encoder sidecar view registered at
startup. Because vision_map_ready stayed true, the lazy re-map
in the vision encode path never ran and every image request
failed with 'Metal model range 0.87..0.87 GiB is not covered'.
Fix: track a model-views generation counter; the encode path
re-registers the sidecar whenever the generation moved.
2. Registering the sidecar via ds4_gpu_set_model_map_range on
Metal overwrote the primary model map globals, breaking the
following main-model prefill ('0.10 GiB not covered'). Fix:
implement ds4_gpu_set_aux_model_map_range on Metal as a true
append-only secondary registration and make
ds4_gpu_set_model_map_spans clear only its own mapping's views,
preserving sidecar views. Also make the vision map failure
message name the actual vision kind and include diagnostic
offsets.
CUDA/ROCm expose a constant zero generation (they never clear
views). Verified on M4 Max 128 GiB, Vision-Exp MXFP4:
first image request after cold start returns 200 with correct
content, zero 'not covered' log lines, text regression at ~20 t/s.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix DeepSeek Vision-Exp image inference with SSD streaming on Metal
Closes the failure mode reported in #928 (image input → HTTP 400 "DeepSeek V4 vision inference failed").
Summary
Two coupled bugs broke every image request when serving the Vision-Exp MXFP4 checkpoint with
--ssd-streaming --vision <encoder>on Metal. Text inference was unaffected.The SSD-streaming span remap cleared the vision sidecar's Metal views.
With SSD streaming, the main model map starts as token-embedding-only (
ds4.c,ds4_gpu_set_model_map_spansat startup), and the vision encoder sidecar map is registered additionally at startup →vision_map_ready = true. The first prefill installs runtime spans viads4_gpu_set_model_map_spans, whose rebuild path calls a globalds4_gpu_model_views_clear()— destroying ALL views, including the encoder's — and rebuilds only main-model spans. The encode path re-maps lazily only underif (e->metal_ready && !e->vision_map_ready)(ds4.c:63887); the flag is stale true, so no re-map happens.ds4_gpu_wrap_model_rangethen fails:(0.868 GiB = exactly the encoder GGUF size) →
ds4_gpu_deepseek4_vision_encodereturns 0 → HTTP 400.Registering the sidecar via
ds4_gpu_set_model_map_rangeoverwrote the primary map globals.Found while verifying fix attempt Fix spelling typos in README #1 live: the naive re-map path (using
ds4_gpu_set_model_map_rangefor the vision map) made the following main-model prefill fail with0.10 GiB not covered. Enriched diagnostics + GGUF header parse identified the missed read aslayers.0.ffn.gate.bias_vl(vision sentinel tensor) on the vision map, read during the main prefill — because on Metalds4_gpu_set_model_map_rangerewritesg_model_map_ptr/g_model_map_sizeetc. with the sidecar map, so the next span remap cleared everything again and the prefill resolved the wrong map context.ds4_gpu_set_aux_model_map_rangewas declared inds4_gpu.has "secondary GGUF mapping without replacing the primary model mapping" but never implemented on Metal. This PR implements it (append-only, no global update) and uses it for both vision registration sites.Changes
g_model_views_generation, bumped inds4_gpu_model_views_clear(); accessords4_gpu_model_views_generation().ds4_gpu_model_views_clear_for_map(map, size): drops only one mapping's views, preserves other mappings' views (sidecar), bumps generation only when something was removed.ds4_gpu_set_model_map_spansnow clears only its own mapping's views instead of all views.ds4_gpu_set_aux_model_map_rangeimplemented: coverage check → append one page-aligned view set for the aux map; updatesg_last_aux_mapbut never the primary globals.ds4_gpu_wrap_model_rangefailure message extended with diagnostic fields (map ptr, primary/aux flags, offset, length, view count).uint64_t vision_map_generation.(!vision_map_ready || vision_map_generation != ds4_gpu_model_views_generation())→ re-map viads4_gpu_set_aux_model_map_range.ds4_gpu_model_views_generation().Diff: 5 files, +132/−15.
Verification (M4 Max 128 GiB, Vision-Exp MXFP4,
--ssd-streaming --vision, ds4-server on 0.0.0.0:8008, switcher menu option 4)Before the fix (commit
110afdd, both bugs present):Every image request failed; text worked.
After fix attempt #1 (generation counter only — documented to justify the second fix):
GGUF header parse: offset
0x68117a0on the vision map =layers.0.ffn.gate.bias_vl(256×F32 = 0x400 bytes) — the vision sentinel read during main prefill, proving the sidecar map was being cleared mid-request and the primary globals had been annexed.After the final fix (both bugs addressed):
Cold start via switcher menu option 4 → first request is an image (8×8 PNG, data URI):
Second image request, content check (64×64 green PNG, 400 max tokens):
Text regression (correctness + speed):
Log check across all post-fix requests: zero "not covered" lines (previously 2 per image request).
Notes
ds4_gpu_wrap_model_rangediagnostic extension is deliberately conservative (one fprintf); happy to split it out if you prefer a minimal diff.--ssd-streaming.