Launch models with their own engine image and flags (Nemotron 3.5 Lightning, Qwen3.8-27B) - #62
Open
webdevtodayjason wants to merge 6 commits into
Open
Launch models with their own engine image and flags (Nemotron 3.5 Lightning, Qwen3.8-27B)#62webdevtodayjason wants to merge 6 commits into
webdevtodayjason wants to merge 6 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017NziXzqT1L9kj2T1byA3Ak
…unch verification
Models whose published recipe needs flags AINode doesn't model (speculative
decoding, MoE/mamba backends, reasoning + tool-call parsers) could not be
launched through the product at all. Nemotron 3.5 Lightning and Qwen3.8-27B were
both validated on the GB10 fleet only as hand-rolled `docker run` containers,
which left them invisible to the dashboard, to /v1/models, and to the federated
router on the master node.
Problem
- `_build_vllm_serve_args` emitted a fixed flag set with no passthrough.
- The engine image was fleet-global ($NVIDIA_VLLM_IMAGE); both models need
vLLM 0.27.1 while the fleet default is a 0.17 build.
- `--enforce-eager` and the NVFP4 MARLIN env are 0.17-era GB10 workarounds but
were applied unconditionally; on 0.27.1 they only cost throughput.
- `docker run --rm -d` meant an engine that died during startup erased itself,
so failures left no logs and no corpse (~18 self-erased containers observed).
- `start_solo()` returned True once the docker CLI forked, so a crashed engine
still registered as a live instance.
Fix
- NodeConfig gains `extra_vllm_args` and `engine_image`, threaded through
_OVERRIDE_KEYS so they persist and survive restart-replay like every other
per-load override. A caller-supplied flag suppresses the same built-in rather
than duplicating it (vLLM errors on duplicates).
- Legacy GB10 workarounds now apply only to the pinned default image.
- Dropped `--rm`; start_solo() confirms the container reached Running and
surfaces the engine's last output when it did not.
- Catalog entries for both models carry their proven recipe, applied as defaults
on load so a bare {"model": ...} launches correctly.
Tests: 21 new (recipe passthrough, dedup, image gating, launch confirmation);
two nvidia-backend tests updated to the stronger launch contract. 691 pass.
…ache auto for the Qwen3.8 VLM recipe Caught on hardware verifying the load path: 1. vllm/vllm-openai bakes ENTRYPOINT ["vllm","serve"] while the pinned default uses NVIDIA's passthrough shim. Emitting our own "vllm serve" produced `vllm serve vllm serve <model>` and the engine exited with "unrecognized arguments". _serve_argv_prefix() now inspects the image ENTRYPOINT and emits only what's missing; an unreadable image falls back to the legacy prefix so a docker hiccup can't change how the default image launches. Entrypoint is NOT overridden — that would bypass nvidia_entrypoint.sh's CUDA setup. 2. The automatic fp8->auto KV downgrade for multimodal models only fires when the model is on local disk (it reads config.json); Qwen3.8 serves from the HF cache, so it was getting fp8 KV — which corrupts VLM generation on GB10. The recipe now states --kv-cache-dtype auto explicitly (the existing dedup makes it suppress the built-in). The crashed container left a readable corpse, which is the --rm removal from the previous commit working as intended.
CI went red with 683 findings in files nobody touched (import ordering, dict() literals, and so on) while the same command was green on main in July. Cause is the dev dependency `ruff>=0.1.0` being unpinned: newer ruff releases keep widening the DEFAULT rule set, so CI silently started enforcing rules this repo never opted into, and every PR opened today fails the same way. Selecting E4/E7/E9/F explicitly restores the repo's actual intent and makes lint deterministic regardless of which ruff CI resolves. The whole repo passes clean under it. Widening the set is worth doing, but as a deliberate cleanup rather than something a transitive upgrade inflicts on an unrelated PR.
webdevtodayjason
force-pushed
the
fable/0.5.4-native-engines
branch
from
August 15, 2026 15:56
a88fa34 to
5dcb7dc
Compare
… path Two of the three remaining robustness bugs from dogfooding the GB10 fleet. Eject was memory-only. It dropped the instance from the manager but never rewrote the manifest that startup replay reads, so an ejected model came back on the next reboot. On spark-4 an ejected 0.5B reappeared and then blocked a later load through admission control, with nothing in the UI to explain why. Eject now persists the shrunken instance set, and clears the node's config.model claim when the ejected instance was the primary so the master stops advertising a ghost. Losing the manifest write no longer fails the operator's eject. The boot path chose the legacy host-venv VLLMEngine whenever the node was not detected as containerized, regardless of the configured backend. Inside the slim orchestrator image vLLM is deliberately absent, so that engine dies with "No module named 'vllm'" while the banner still prints "Engine starting in background" and the node looks healthy while serving nothing. The dispatch now checks whether vLLM is importable and falls back to the configured container backend instead of launching a certain failure. The third bug (stale fleet-wide advertisement) is NOT fixed here on purpose. The liveness gating already exists at api/server.py:441 and the fleet menu is truthful again after a restart, so the symptom points at stale cluster records on a long-uptime node rather than a missing check. Filed with a concrete repro to run instead of guessing at a fix. Tests: 6 new (eject persistence, primary-claim clearing, stacked isolation, persistence-failure tolerance, boot dispatch ordering). 702 pass, ruff clean.
webdevtodayjason
force-pushed
the
fable/0.5.4-native-engines
branch
from
August 15, 2026 15:56
5dcb7dc to
7a6613e
Compare
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.
Problem
Two models we validated on the GB10 fleet could not be launched by AInode at all, only by hand-rolled
docker runcommands. That meant the two fastest and most capable models on the cluster were invisible to the dashboard, missing from/v1/models, and unreachable through the master node's federated endpoint. If a node rebooted they were simply gone.Three things blocked them:
_build_vllm_serve_argsemitted a fixed flag set with no passthrough, so recipes needing speculative decoding, MoE/mamba backends, or reasoning and tool-call parsers could not be expressed.$NVIDIA_VLLM_IMAGE. Both models need vLLM 0.27.1 while the fleet default is a 0.17 build.--enforce-eagerand the NVFP4 MARLIN env were applied unconditionally. They are workarounds for a FlashInfer bug in the 0.17 build, and on 0.27.1 they only disable CUDA graphs and cost throughput.Two related failure modes made every launch problem undebuggable: engines ran with
docker run --rm -d, so one that died during startup deleted itself and left no logs and no corpse (about 18 self-erased containers were found on spark-4), andstart_solo()returned success as soon as the docker CLI forked, so a crashed engine still registered as a live instance.Fix
NodeConfiggainsextra_vllm_argsandengine_image, threaded through_OVERRIDE_KEYSso they persist and survive restart-replay like every other per-load override. A flag supplied by the caller suppresses the matching built-in rather than appearing twice, since vLLM errors on duplicates. The legacy GB10 workarounds now apply only when the instance runs the pinned default image.Curated catalog entries for both models carry their proven recipe (image, flags, recommended memory fraction), applied as defaults so a bare
{"model": "..."}load, which is what the dashboard sends, launches correctly.Dropped
--rm, andstart_solo()now confirms the container reached Running and surfaces the engine's last output when it did not.Two bugs turned up while verifying on hardware and are fixed here too:
vllm/vllm-openaibakesENTRYPOINT ["vllm","serve"]while the default image uses NVIDIA's passthrough shim, so our args producedvllm serve vllm serve <model>. The launch path now inspects the image entrypoint and emits only the missing tokens. It deliberately does not override the entrypoint, which would bypass CUDA setup, and an unreadable image falls back to the legacy prefix.config.json. Qwen3.8 serves from the HF cache, so it was getting fp8 KV, which corrupts vision output on GB10. The recipe statesautoexplicitly.Tests
696 pass, ruff clean. 26 new tests cover recipe passthrough, flag dedup in both
--flag valueand--flag=valueforms, image gating of the legacy workarounds, entrypoint normalization, and launch confirmation. Two existing nvidia-backend tests were updated to the stronger launch contract, since "the docker CLI forked" no longer counts as a successful start.Verified end to end on spark-3. A bare
POST /api/models/load {"model":"unsloth/Qwen3.8-27B-NVFP4"}produced this command:It served chat, emitted correct tool calls (proving the parser applied), answered a vision prompt correctly, and ran at 18.1 tok/s with MTP speculative decoding. The model now appears on the master node's
/v1/modelsand a request sent there routes to spark-3 and comes back. That was not possible before this change.Nemotron 3.5 Lightning uses the identical mechanism and its recipe is unit-tested, but it has not yet been launched through the API on hardware. That is tracked in FOLLOWUPS.md along with pre-staging its DSpark drafter.
Default behavior is unchanged for every model that does not carry a recipe. The generated command for the pinned default image is byte for byte what it was before.