Four launch-path fixes found benchmarking on a real node - #64
Open
webdevtodayjason wants to merge 1 commit into
Open
Four launch-path fixes found benchmarking on a real node#64webdevtodayjason wants to merge 1 commit into
webdevtodayjason wants to merge 1 commit into
Conversation
I set out to benchmark decode against context depth and couldn't get the model loaded onto a second node. Four separate things were wrong, and none of them show up unless you launch through the real path onto a box that isn't a clean room. Engine images are per-model since 0.5.4, so a node can be asked for an image it has never run. Nothing waited for that first pull. Docker started one implicitly, the launch confirmation timed out underneath it, and the caller got a bare "Failed to launch engine" with no mention of an image. The entrypoint probe degraded too, since docker inspect on a missing image returns nothing and we quietly fall back to a default argv prefix. ensure_image() now pulls it first and refuses the launch if it can't. allocate_port() only skipped ports held by our own instances. It had no idea about anything else on the host, so on a node where another service had owned 8000 for weeks it handed out 8000 anyway and vLLM died with EADDRINUSE after a full model load. It socket-probes the host now. The API server never set client_max_size, so aiohttp's 1 MB default was in force. That caps a 262k-context model at roughly 190k tokens of prompt: the proxy 413s the request before the engine sees it, and nothing in the error says which hop refused. That is how I found it, a 200k-token prompt is 1.01 MB. Default is 64 MB now, sized for a 1M-token context plus base64 video parts on the multimodal models. Last one is a feature, not a bug. Some engine settings have no CLI flag at all: the b12x FP4 kernel path is selected purely by environment. Without a way to pass env per model those models can only be reached by hand-rolling a container, which is the thing the launch path exists to prevent. extra_env carries them, and it merges over the computed NCCL env on purpose. Our own default forces VLLM_NVFP4_GEMM_BACKEND=marlin on the pinned image, so a recipe that wants flashinfer-b12x has to win. The tests worth pointing at: one binds a real squatter socket and asserts we skip that port, one proves a recipe env value beats the computed one on the same variable, and one proves start_solo refuses to run a container when the image can't be had. 15 new tests, 723 green. One I caused and fixed. My first cut of the request-ceiling tests built a full create_app() outside a running loop, which bound the module-level download semaphore to the wrong loop and made an unrelated download test flaky. Pulled the arithmetic out into _client_max_bytes() and test that directly. Three full-suite runs clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
I set out to measure decode against context depth and couldn't get the model loaded onto a second node. Four separate things were wrong. None of them show up unless you launch through the real path onto a box that isn't a clean room, which is a decent argument for the dogfood rule on its own.
Missing engine image
Engine images have been per-model since 0.5.4, so a node can be asked for an image it has never run. Nothing waited for that first pull. Docker started one implicitly, the launch confirmation timed out underneath it, and the caller got a bare
{"error": "Failed to launch engine"}with no mention of an image anywhere. The entrypoint probe degraded at the same time, becausedocker inspecton a missing image returns nothing and we quietly fall back to a default argv prefix.ensure_image()pulls it first now, with an hour of headroom since these images run about 20 GB, and refuses the launch if it can't get it.Port collision
allocate_port()only skipped ports held by our own instances. It had no idea about anything else on the host. On a node where an unrelated service had owned 8000 for three weeks it handed out 8000 anyway, and vLLM died withOSError: [Errno 98] Address already in useafter loading the whole model first. It socket-probes the host now, and the probe can be turned off for callers that don't want it.1 MB request bodies
The API server never set
client_max_size, so aiohttp's 1 MB default was in force. That caps a 262k-context model at roughly 190k tokens of prompt. The proxy 413s the request before the engine ever sees it, and nothing in the error tells you which hop refused. Hitting vLLM directly works fine, which is what makes it confusing.That is how I found it. A 200k-token prompt is 1.01 MB. Default is 64 MB now, sized for a 1M-token context plus base64 image and video parts on the multimodal models, and configurable through
max_request_mb.extra_env
This one is a feature rather than a bug. Some engine settings have no CLI flag at all. The b12x FP4 kernel path on GB10 is selected purely by environment variables. Without a way to pass env per model, those models can only be reached by hand-rolling a container, which is exactly what the launch path exists to prevent.
extra_envcarries them throughNodeConfig,ModelInfo,catalog_recipe, and the load API, mirroring howextra_vllm_argsalready works. It merges over the computed NCCL env on purpose: our own default forcesVLLM_NVFP4_GEMM_BACKEND=marlinon the pinned image, so a recipe asking forflashinfer-b12xhas to win or b12x is unreachable.Proof
The tests worth pointing at are the ones that pin the actual contracts. One binds a real squatter socket and asserts we allocate around it. One sets a recipe env value that collides with the computed one and proves the recipe wins. One proves
start_solowon't run a container when the image can't be had. 15 new tests, 723 green.One of these I caused myself. My first cut of the request-ceiling tests built a full
create_app()outside a running loop, which bound the module-level download semaphore to the wrong loop and made an unrelated download test flaky. Pulled the arithmetic out into_client_max_bytes()and tested that directly instead. Three full-suite runs clean afterwards.Not in here
No catalog recipe for b12x yet.
extra_envis the plumbing; the recipe waits on measurements. Nothing changes for any model that already launches, and default behavior is unchanged apart from the request ceiling.🤖 Generated with Claude Code