Bump to SGLang 0.5.17-cu129, add Blackwell support, and fix five defects - #34
Merged
Conversation
…ser options The REASONING_PARSER options listed llama3/llama4/mistral/qwen25/deepseekv3, none of which SGLang accepts for --reasoning-parser: selecting any of them makes sglang.launch_server exit at startup. TOOL_CALL_PARSER offered llama4, which is not in SGLang 0.5.2's ToolCallParserEnum either, and omitted seven parsers that are. Both lists now match SGLang 0.5.2: - tool call: llama3, qwen25, mistral, deepseekv3, deepseekv31, pythonic, kimi_k2, qwen3_coder, glm45, step3, gpt-oss - reasoning: deepseek-r1, deepseek-v3, glm45, gpt-oss, kimi, qwen3, qwen3-thinking, step3 HF_TOKEN was added to hub.json in 771d3b6 and dropped again in #26, so gated and private models could not be deployed from the Hub UI. Restored. README also claimed a MODEL_NAME default that does not exist; the worker requires it.
… startup failures
Four defects found while validating the 2.0.2 image on a live endpoint:
1. handler.py crashed on the README's own "List Models" example.
{"input": {"openai_route": "/v1/models"}} has no openai_input, so
openai_input.get("stream") raised AttributeError: 'NoneType'. Bodyless
routes now issue a GET instead.
2. utils.format_chunk re-serialized each chunk with indent=4 and prefixed
every resulting line with "data: ", so one chat.completion.chunk became
~25 "data:" lines. No OpenAI-compatible client can parse that. Chunks are
now emitted compactly, one data line each.
3. When sglang.launch_server dies during startup (for example CUDA "no kernel
image is available" on sm_120 hardware), wait_for_server kept polling for
the full 900s. The platform marks the worker ready in the meantime, so it
accepts jobs it can never serve and the caller is billed for a request that
sits IN_QUEUE. wait_for_server now reaps the child and fails immediately.
4. PYTHONUNBUFFERED was unset, so a worker stuck in startup emitted no
container logs at all, which made (3) invisible.
The worker was pinned to lmsysorg/sglang:v0.5.2-cu126, released ~9 months ago. That build carries no sm_120 kernels, so on RTX PRO 6000 Blackwell hardware model load died with "CUDA error: no kernel image is available for execution on the device", confirmed on a live endpoint. Blackwell MIG slices sit in the AMPERE_24 and ADA_48_PRO pools, so this was reachable from ordinary GPU selections, not just from a Blackwell-named pool. The cu129 base ships sm_120, and Blackwell now serves. Verified end to end on RTX PRO 6000 Blackwell Server Edition and on RTX 4090. Hardware constraints are now documented in the README, which previously said nothing about CUDA or GPU requirements at all. allowedCudaVersions moves from [12.9, 12.8, 12.7, 12.6] to [13.0, 12.9]: - The cu129 base declares NVIDIA_REQUIRE_CUDA=cuda>=12.9, so older hosts cannot start the container. They fail in the nvidia runtime hook before the handler runs, and the worker crash-loops without ever being marked unhealthy, so jobs just sit in the queue. - allowedCudaVersions is an exact-match list, not a floor. Under the SERVERLESS product 12.9 is available only on 3090/3090Ti while 13.0 covers 4090, A100, H100/H200, L40S and every Blackwell MIG slice, so 13.0 is what actually buys breadth. worker-vllm pins 13.0 for the same reason. (minCudaVersion would express this properly but is dropped on the Hub deploy path - see CON-1157.) gpuIds widens from the single ADA_24 pool to seven pools spanning 24GB to 80GB, so the scheduler has somewhere to place a worker. ADA_24 alone is 4090 only and throttled heavily during testing. BLACKWELL_96 is deliberately left out of the default at $3.49/hr; Blackwell is still reachable via the MIG slices in AMPERE_24. Also reconciled against 0.5.17's server_args: - ENABLE_FLASHINFER_MLA dropped; --enable-flashinfer-mla no longer exists and passing it fails startup. MLA now comes from --attention-backend. - Tool call parsers: 11 -> 33 entries. Reasoning parsers: 8 -> 25. - QUANTIZATION dropped "squeezellm", which SGLang has never accepted in either 0.5.2 or 0.5.17, and picked up the modelopt/mxfp4/w8a8 methods. - uv pip install needs --break-system-packages: the cu129 base adds a PEP 668 EXTERNALLY-MANAGED marker that cu126 did not have. Every other flag engine.py passes still exists in 0.5.17.
…HTTP errors
BREAKING: non-streaming responses are now yielded as objects rather than as
raw JSON text.
The README documents driving this worker with the official openai SDK against
/openai/v1. That never worked. The handler yielded response text, and the
platform's passthrough returns whatever the handler yields, so the client
received a JSON *string*:
>>> client.chat.completions.create(...)
AttributeError: 'str' object has no attribute 'choices'
Non-streaming responses now yield response.json(). Verified against the real
SDK, streaming and non-streaming. This matches worker-vllm, which yields
parsed JSON for non-streaming and raw SSE only while streaming.
Also, Cases 1 and 2 never checked the status code, so an SGLang 4xx/5xx was
handed back as if it were a successful completion. All routes now yield a
structured error on >=400, which is what open PR #32 asks for.
Request resolution is consolidated into one function so the three input shapes
and the /generate fallback share the status handling and streaming behaviour,
and streaming now sets stream=True on the request instead of buffering the
whole response first.
The README went straight from the banner into an environment-variable reference, so nothing told a new user what they actually have to set. It now opens with what is required to deploy (MODEL_NAME always, HF_TOKEN for gated models), the container-disk and VRAM checks that matter for larger models, the CUDA constraint needed when deploying the image outside the Hub, and the local docker compose flow. The Response Format example also showed a bare completion for both APIs. The queue API wraps it in the job envelope under "output", which is the shape callers actually parse, and failures now surface as a FAILED job carrying the SGLang status and body. Both are documented.
Building the cu129 base through the blacksmith cache fails every time on the same 5.7GB layer: failed commit on ref "layer-sha256:045d2658...": unexpected commit digest sha256:1e8cedfc..., expected sha256:045d2658...: failed precondition Three runs produced three *different* wrong digests for that one layer, so the blob is being mangled in transit rather than a bad entry being cached. Isolated it against the same pipeline: blacksmith cache + cu126 base (main, unmodified) -> passes in 5m blacksmith cache + cu129 base -> fails 3/3 no blacksmith cache + cu129 base -> passes in 6m Only the cache is implicated, and it buys about a minute on a build dominated by pulling a ~19GB base, so removing it costs effectively nothing here. The standard docker/setup-buildx-action builder already configured in the job is used instead. Applied to release.yml too, which builds the same image on tag push.
justinwlin
force-pushed
the
chore/bump-sglang-0.5.17-cu129
branch
from
August 19, 2026 21:42
256fbff to
b8f7503
Compare
The step was removed in the previous commit but nothing in the repo said so, so a later tidy-up could re-add it and reintroduce a failure that takes ~20 minutes per attempt to reproduce. Leaves a pointer at DR-1446 where the step used to sit, in both workflows.
JessicaGarson
requested changes
Aug 20, 2026
| ) | ||
| body = job_input.get("openai_input") | ||
| # Read-only routes such as /v1/models carry no body and are GET-only. | ||
| return job_input["openai_route"], ("POST" if body else "GET"), body |
There was a problem hiding this comment.
Shouldn't this be a POST request instead of a GET request
Contributor
Author
There was a problem hiding this comment.
TLDR: This is just ternary if it has a body use post, otherwise use GET. But I change it to be more explicit if user wanted to drive the method themselves rather than implicit derivation.
method = (job_input.get("method") or ("POST" if body else "GET")).upper()
Ex.
Someone will do /v1/models with no body should be get. But if they give a body, they probably trying to do a POST with their route. But modified it so user has option to override.
__pycache__/utils.cpython-314.pyc was committed by accident in 942c6fe. It came from running the module locally while testing format_chunk - cpython-314 is a local interpreter, not the one in the image - and got swept in by a broad git add. The Dockerfile COPYs source files by name, so the stray file never reached the image, but it should not be in the tree. .gitignore had no Python entries at all, so this would recur for anyone running the module locally.
Review feedback on #34: the verb for openai_route was inferred from whether a body was present, which is correct for every route SGLang exposes but was implicit and undocumented. The default is unchanged - bodies POST, body-less requests GET, so /v1/models and /health keep working without spelling it out, and SGLang registers those as GET-only so a POST would 405. Callers can now pass "method" to override, which also covers the one case the heuristic gets arguably wrong: an explicitly empty openai_input is falsy and would otherwise route to GET. README documents the default and the override.
JessicaGarson
approved these changes
Aug 21, 2026
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.
Upgrades the worker to SGLang 0.5.17, adds Blackwell support, and fixes five defects found while validating the current 2.0.2 release on live endpoints.
Contributes to DR-1441.
Why
The worker was pinned to
lmsysorg/sglang:v0.5.2-cu126, released ~9 months ago. That build has nosm_120kernels, so on RTX PRO 6000 Blackwell hardware model load dies withCUDA error: no kernel image is available for execution on the device. Blackwell MIG slices sit in theAMPERE_24andADA_48_PROpools, so this is reachable from ordinary GPU selections.Validating that turned up four more problems that were already broken on
main.What changed
v0.5.2-cu126→v0.5.17-cu129. Blackwell now works.allowedCudaVersions[12.9, 12.8, 12.7, 12.6]→[13.0, 12.9]gpuIdsADA_24→ seven pools, 24GB–80GBHF_TOKENhub.json(dropped by #26)REASONING_PARSERoption was invalid; corrected.gitignore__pycache__/and*.py[cod], which had no entries at allmethodoverrideopenai_routecallers can now force the HTTP verbBreaking change
Non-streaming
/runand/runsyncoutput changes from a list of JSON strings to a list of objects. This is what makes the documented OpenAI SDK usage possible at all, but anyone parsingjson.loads(output[0])today needs to drop the inner parse. Suggest cutting this as a major release.The defects, in detail
1. The OpenAI SDK never worked
The README documents driving the worker with the official
openaiSDK against/openai/v1. The platform's passthrough returns whatever the handler yields, and the handler yielded response text, so the client received a JSON string:Non-streaming responses now yield
response.json(). This matchesworker-vllm, which yields parsed JSON for non-streaming and raw SSE only while streaming.2.
openai_routewithout a body crashed the handlerThe README's own "List Models" example:
{ "input": { "openai_route": "/v1/models" } }had no
openai_input, soopenai_input.get("stream")raisedAttributeError: 'NoneType'. Body-less routes now issue a GET.3. Streaming emitted unparseable SSE
format_chunkre-serialized each chunk withindent=4and prefixed every resulting line withdata:, turning onechat.completion.chunkinto ~25data:lines. No OpenAI-compatible client can read that. Chunks are now emitted compactly, onedata:line each.4. A dead SGLang server silently ate jobs
When
sglang.launch_serverdied during startup,wait_for_serverkept polling for the full 900s. The platform marks the worker ready in the meantime, so it accepts jobs it can never serve and the caller is billed for a request that sitsIN_QUEUE.PYTHONUNBUFFEREDwas also unset, so the container produced no logs at all while this happened.wait_for_servernow reaps the child and fails immediately, and logging is unbuffered.5.
hub.jsonoffered values SGLang rejectsSelecting any of these in the Hub UI made
launch_serverexit at startup:REASONING_PARSERoptions (llama3,llama4,mistral,qwen25,deepseekv3) — none are in SGLang'sDetectorMap.TOOL_CALL_PARSER: llama4— not inToolCallParserEnum.QUANTIZATION: squeezellm— never accepted, in 0.5.2 or 0.5.17.Lists are now generated from 0.5.17's source: 33 tool-call parsers, 25 reasoning parsers.
HF_TOKENis also restored — it was added in 771d3b6 and dropped again by #26, so gated models could not be deployed from the UI.Why
allowedCudaVersionsnarrows to[13.0, 12.9]The cu129 base declares
NVIDIA_REQUIRE_CUDA=cuda>=12.9, so 12.6–12.8 hosts cannot start the container — they fail in the nvidia runtime hook before the handler runs.allowedCudaVersionsis an exact-match list, not a floor. Under the SERVERLESS product, 12.9 exists only on 3090/3090Ti, while 13.0 covers 4090, A100, H100/H200, L40S and every Blackwell MIG slice — so 13.0 is what actually buys breadth.worker-vllmpins["13.0"]for the same reason.minCudaVersionwould express this properly but is dropped on the Hub deploy path — tracked in CON-1157.CI
The blacksmith layer cache cannot handle this base image. Every build failed on the same 5.7GB layer:
Three runs produced three different wrong digests for that one layer, so the blob is mangled in transit rather than a bad entry being cached. Isolated against the same pipeline:
main)Only the cache is implicated. It buys about a minute on a build dominated by pulling a ~19GB base, so dropping it costs effectively nothing — the
docker/setup-buildx-actionbuilder already in the job is used instead. Applied torelease.ymltoo, which builds the same image on tag push, and both workflows carry a comment where the step used to sit so it does not get silently re-added.This is a bug in their cache, not something this repo can fix properly — tracked in DR-1446.
Verification
Built and pushed a test image from this branch, then deployed it on two real endpoints pinned to
["13.0","12.9"]— one onBLACKWELL_96, one onADA_24— withMODEL_NAME=HuggingFaceTB/SmolLM2-1.7B-Instruct.The image is public, so you can reproduce any of the results below without building:
(CI on this branch also publishes
runpod/worker-sglang:chore-bump-sglang-0.5.17-cu129, which is the same build from the official pipeline.)Reproduce it
Create the endpoint.
allowedCudaVersionshas to be set at creation — the API rejects a PATCH that swaps it forminCudaVersion, and the console/MCP create path setsminCudaVersionby default:First request cold-starts in ~3-4 min (18.8 GB pull). Swap
BLACKWELL_96forADA_24to check the Ada path.The OpenAI SDK check, which fails on
mainwith'str' object has no attribute 'choices':Build it yourself with
docker buildx build --platform linux/amd64 -t <your-tag> ./generateopenai_route: /v1/modelsFAILEDEvidence
Blackwell, chat completions — the hardware that fails on
main:{"delayTime":256124,"executionTime":854,"status":"COMPLETED", "output":[{"choices":[{"message":{"role":"assistant","content":"Paris"}, "finish_reason":"stop"}],"model":"HuggingFaceTB/SmolLM2-1.7B-Instruct"}]}OpenAI SDK, both modes, against
/openai/v1:openai_routewith no body — crashes onmain:{"status":"COMPLETED","output":[{"object":"list","data":[ {"id":"HuggingFaceTB/SmolLM2-1.7B-Instruct","max_model_len":8192}]}]}Streaming — 11 chunks, one
data:line each (mainproduces ~25 lines per chunk).Upstream error handling:
{"status":"FAILED","error":"SGLang returned HTTP 400: temperature must be a non-negative finite number, got -99.0."}What the failure looks like on
main, from a worker on cu126 Blackwell:Docker build.
uv pip install --systemfails on the new base — cu129 adds a PEP 668EXTERNALLY-MANAGEDmarker that cu126 lacked:Fixed with
--break-system-packages, and the install target confirmed importable by the interpreter that runs the handler:All 34 flags
engine.pypasses were checked against 0.5.17'sserver_argsdataclass. One is gone —--enable-flashinfer-mla, removed upstream, and passing it fails startup — soENABLE_FLASHINFER_MLAis dropped fromhub.jsonandengine.py. Every other flag still exists.Both test endpoints have been deleted.
Review feedback
__pycache__/utils.cpython-314.pycslipped in via a broadgit addwhile testing locally. Deleted, and.gitignorenow covers__pycache__/and*.py[cod]— it previously had no Python entries. The DockerfileCOPYs source by name, so it never reached the image.openai_routeis now explicit. The default is unchanged and correct: bodies POST, body-less requests GET, because SGLang registers/v1/modelsand/healthas GET-only (@app.get("/v1/models"),http_server.py:1823) and POSTing to them was the crash this PR fixes. But the rule was implicit, so callers can now passmethodto override it, and the README documents both. This also covers the one edge the heuristic arguably gets wrong — an explicitly emptyopenai_inputis falsy and would otherwise route to GET.Notes for the reviewer
BLACKWELL_96is deliberately not in the defaultgpuIds— $3.49/hr serverless. Blackwell is still reachable via the MIG slices inAMPERE_24at $0.69.What was and wasn't exercised
Covered on both Blackwell and Ada: chat completions via the queue API. Covered on Blackwell only (shared code path, but worth knowing the table's dashes are gaps, not passes):
/v1/models, native/generate, streaming, both OpenAI SDK modes, and error handling.Not exercised at all:
ToolCallParserEnumandDetectorMapin 0.5.17's source — no model was deployed with a parser actually set.HF_TOKENagainst a gated model. Restored tohub.json, never used against a real gated repo.hub.jsonandtests.jsonchanged substantially, but they're consumed by the Hub builder and this listing isn't published, so nothing validated them end to end.TENSOR_PARALLEL_SIZE > 1), quantization, and large models. All verification used a single GPU and a 1.7B model, chosen to keep cold starts short — which means nothing here says anything about memory pressure or parallelism.Testing was serverless only; no pod deploys.