Skip to content

feat(engine): [image, audio] sglang image vllm asr engines - #5121

Merged
OliverBryant merged 14 commits into
xorbitsai:mainfrom
OliverBryant:feat/sglang-image-vllm-asr-engines
Jul 28, 2026
Merged

feat(engine): [image, audio] sglang image vllm asr engines#5121
OliverBryant merged 14 commits into
xorbitsai:mainfrom
OliverBryant:feat/sglang-image-vllm-asr-engines

Conversation

@OliverBryant

Copy link
Copy Markdown
Collaborator

@XprobeBot XprobeBot added this to the v2.x milestone Jul 2, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@XprobeBot XprobeBot modified the milestones: v2.x, v3.x Jul 19, 2026
@OliverBryant OliverBryant changed the title Feat/sglang image vllm asr engines Feat: sglang image vllm asr engines Jul 22, 2026
@qinxuye qinxuye changed the title Feat: sglang image vllm asr engines feat(engine): [image, audio] sglang image vllm asr engines Jul 22, 2026
…-dev

Implement the SGLangImageModel stub with sglang-diffusion offline API
(sglang.multimodal_gen.DiffGenerator) so text-to-image models can run on
the SGLang engine on Linux with NVIDIA GPUs. Supported models: Qwen-Image,
Qwen-Image-2512, Z-Image, Z-Image-Turbo and FLUX.1-dev, matching the
sglang-diffusion compatibility matrix. Virtualenv specs install
sglang[diffusion] when the SGLang engine is selected.

Ref xorbitsai#4896
Add an engine abstraction layer for audio models (mirroring the image
module design): AUDIO_ENGINES registry, engine matching and virtualenv
marker support. Families without registered engines keep the legacy
hardcoded dispatch, so existing behavior is unchanged.

Qwen3-ASR-0.6B/1.7B register two engines: the default transformers
engine and, on Linux with NVIDIA GPUs, a vLLM engine backed by
qwen_asr's Qwen3ASRModel.LLM which shares the same transcribe API.
/v1/engines/audio/<model_name> now returns engine params for audio
models.

Ref xorbitsai#4896
Show the engine select in the launch dialog for audio models that
support engine selection (e.g. Qwen3-ASR on transformers/vLLM), and
include audio in the model types that query /v1/engines. Audio models
without registered engines keep the previous form unchanged.

Ref xorbitsai#4896
@OliverBryant
OliverBryant force-pushed the feat/sglang-image-vllm-asr-engines branch from 7e346ee to 77a64ad Compare July 22, 2026 07:48
Comment thread xinference/model/image/sglang/core.py
Implement the vLLM image engine placeholder with a real engine backed by
vllm-omni's Omni text-to-image entrypoint. The engine matches on Linux
with NVIDIA GPUs for the same model set as the SGLang engine: Qwen-Image,
Qwen-Image-2512, Z-Image, Z-Image-Turbo and FLUX.1-dev. Add vllm-omni
virtualenv markers to the builtin specs so engine-scoped installs work.

Ref xorbitsai#4896
vllm-omni does not declare vllm as a dependency but requires a vllm with
the same major.minor version (its patch module imports private vllm APIs),
so an unpinned marker resolved vllm-omni against whatever vllm the parent
environment had and crashed on import. Pin the vllm-omni/vllm pair in the
virtualenv packages, surface the original import error with the pairing
requirement in the failure message, and assert the pins stay in sync in
tests.

Ref xorbitsai#4896
The vLLM image engine serialized all requests: VLLMDiffusionModel had no
allow_batch flag, so ModelActor wrapped every text_to_image call in its
per-model asyncio lock, and each call went through the blocking
Omni.generate. The vllm-omni engine itself schedules requests from a
queue and pipelines stages, so the serialization was purely on the
xinference side.

Omni.generate cannot simply be called concurrently: it drains the shared
engine output queue and drops messages belonging to other requests, and
per-request sampling params (seed, size, steps) cannot be expressed
through one batched generate call since sampling_params_list is
per-stage, not per-prompt.

Set allow_batch = True and submit each request directly via
engine.add_request with its own request_id and sampling params. A single
dispatcher thread drains the engine output queue and routes messages
back to per-request futures; a fatal ErrorMessage fails all in-flight
requests, a per-request error fails only its own future. The dispatcher
exits when idle and restarts on the next submission. When the running
vllm-omni does not expose the needed engine internals, fall back to the
previous serial Omni.generate path, now guarded by a model-level lock
because the actor no longer serializes calls.
sglang pins its own torch stack (sglang 0.5.7 requires torch==2.9.1
exactly), so the unscoped #system_torch#/#system_numpy# placeholders in
the vLLM/SGLang image model specs expand to the host torch (e.g. 2.11.0)
and can never co-resolve with sglang[diffusion] in one virtualenv. Scope
the system placeholders (and the transformers floor, which sglang also
pins itself) to the diffusers and vLLM engines so the SGLang venv
resolves sglang[diffusion] self-contained with its own torch.

Note the SGLang install path additionally requires the xoscar
skip_installed fix for requirements with extras: without it,
sglang[diffusion] is treated as satisfied by a bare host sglang and the
venv is left empty.
The SGLang/vLLM image engines copied the full ability list from the
builtin spec, so running instances reported image2image/inpainting
endpoints that these engines do not implement and the image-edit API
could auto-select them. Restrict the advertised abilities to text2image
on an engine-local copy of the family, keeping the shared builtin spec
untouched.
CI's isolated mypy environment types the pydantic copy() as Any, so
reassigning the optional model_spec parameter lost the None narrowing
and failed with union-attr. Build the restricted copy in a local
variable instead.
@OliverBryant
OliverBryant requested a review from qinxuye July 23, 2026 09:30
The worker pickles the constructed model instance into the model actor
subprocess before load(); the concurrent-dispatch locks introduced in
the vLLM engine broke that with 'cannot pickle _thread.lock object'.
Exclude locks and the dispatcher thread from the pickled state and
recreate the locks on unpickling, with a regression test.
The model actor injects request_id as uuid.UUID; SGLang's SamplingParams
accepts the field but its runtime JSON-serializes the params and crashed
with 'Object of type UUID is not JSON serializable'. Convert UUID values
to strings when building sampling params.
sglang JIT-compiles kernels through the ninja binary; the ninja wheel
installs it into the virtualenv's bin directory, which is not on PATH
when the model subprocess is spawned with the venv interpreter, so
generation failed with 'No such file or directory: ninja'. Prepend the
interpreter's bin directory to PATH at load time.
@OliverBryant

OliverBryant commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

End-to-end benchmark: Z-Image on diffusers / vLLM / SGLang

Ran all three image engines for real on a GPU box against this PR's HEAD, same prompt and generation config, measuring client-side wall-clock of POST /v1/images/generations (3 timed runs per engine after warmup, one engine loaded at a time).

Setup

  • GPU: 1x NVIDIA GeForce RTX 3090 Ti (24 GB), model pinned to a single GPU via gpu_idx
  • Host env: Python 3.12, torch 2.11.0, vllm 0.24.0, sglang 0.5.7, transformers 5.14.1
  • Model: Z-Image, defaults from the builtin spec (num_inference_steps=28, guidance_scale=4), 1024*1024, n=1, response_format=b64_json
  • Prompt: A red bicycle leaning against a weathered stone wall at golden hour, cinematic lighting
  • Each engine runs in its own model virtualenv (vLLM venv resolves the vllm-omni==0.24.* + vllm==0.24.* pair from the spec; SGLang venv is self-contained with sglang[diffusion]'s own torch 2.9.1 stack)

Single-request latency

Engine Run 1 Run 2 Run 3 Avg vs diffusers
diffusers 48.9 s 49.7 s 51.2 s 49.9 s 1.00x
vLLM (vllm-omni 0.24.0) 40.3 s 41.3 s 42.6 s 41.4 s 1.21x
SGLang (sglang[diffusion] 0.5.7) 43.9 s 44.9 s 46.6 s 45.2 s 1.10x

Concurrency: 4 simultaneous requests (fired at once after warmup)

Engine Request latencies (sorted) Makespan Per-image under load vs its own serial
diffusers 53.3 / 108.4 / 164.9 / 223.4 s 223.4 s 55.9 s +12%
vLLM 40.2 / 81.3 / 123.7 / 167.4 s 167.4 s 41.9 s +1%
SGLang 44.2 / 89.6 / 136.9 / 185.9 s 185.9 s 46.5 s +3%

Reading the numbers:

  • vLLM is the only engine that accepts requests concurrently: the actor no longer serializes calls (allow_batch=True) and every request is submitted to vLLM-Omni's engine queue immediately, with a single dispatcher routing outputs back by request id. Z-Image's pipeline does not support request batching in vllm-omni, so GPU execution is still one-at-a-time — the win is zero added overhead under load (41.9 s vs 41.4 s serial), immediate admission, and per-request error isolation rather than throughput multiplication.
  • diffusers and SGLang serialize at the model actor: concurrent callers queue outside the engine. They still complete correctly, with diffusers paying ~12% per-image overhead under load.
  • Net effect for 4 requests: vLLM finishes the batch 1.33x faster than diffusers and 1.11x faster than SGLang.

Cross-request GPU batching: sd3.5-medium (vllm-omni pipeline with supports_request_batch=True)

Z-Image's vllm-omni pipeline declares supports_request_batch = False (pipeline_z_image.py), and the engine refuses max_num_seqs > 1 for such pipelines, so its GPU execution is serial upstream. To verify the dispatcher composes with true request batching, sd3.5-medium (SD3 pipeline, supports_request_batch = True) was launched with max_num_seqs=4 and hit with 4 simultaneous requests:

Latencies Makespan
single request 5.5 s --
4 concurrent (steady state) 5.6 / 21.46 / 21.48 / 21.48 s 21.5 s

Requests in the same wave complete with identical timestamps (21.478/21.460/21.478 s) -- they went through the DiT as one batched tensor -- and the engine logs [RequestBatch] engine init max_num_seqs=4. On this compute-saturated 3090 Ti the batched throughput matches serial FLOPs, so the makespan equals serial time; on GPUs where a single request underutilizes compute, the same path yields real throughput gains. sd3.5-medium is now registered for the vLLM engine.

All three engines produced valid, prompt-faithful 1024x1024 images (visually verified).

Bugs found and fixed while testing (pushed to this branch):

  • f0c0293 — vLLM engine failed to launch: the concurrent-dispatch locks made the model instance unpicklable for the actor-subprocess handoff (cannot pickle '_thread.lock' object)
  • 0e75c68 — SGLang generation crashed with Object of type UUID is not JSON serializable: xinference injects request_id as uuid.UUID and SGLang's SamplingParams accepts the field but its runtime JSON-serializes it; now stringified
  • 4449556 — SGLang generation crashed with No such file or directory: 'ninja': the ninja wheel installs the binary into the venv's bin directory, which was not on PATH in the model subprocess; now prepended at load time

Environment notes (outside this PR's scope)

  • The SGLang venv install currently needs XINFERENCE_VIRTUAL_ENV_SKIP_INSTALLED=0 until BUG: resolve specs with extras in uv skip_installed even if base package is installed xoscar#193 lands: with skip-installed on, a bare host sglang is treated as satisfying sglang[diffusion] and the venv comes up without the diffusion runtime
  • On hosts with transformers >= 5 the huggingface-hub<1.0 pin in ENGINE_VIRTUALENV_PACKAGES["diffusers"] cannot co-resolve; the box was tested with it relaxed to <2.0 (pre-existing on main, not touched here)

Comment thread xinference/model/image/vllm/core.py Outdated
A fatal vLLM-Omni error previously only failed the in-flight waiters;
the model stayed open, so the next request passed the closed check, was
submitted to the dead backend and could wait forever. Transition to a
terminal state atomically with draining the waiters (mark closed, record
the fatal error, shut the engine down), and fail subsequent submissions
and text_to_image calls immediately with the recorded cause. Dispatcher
loop failures take the same path. Regression tests cover both.
sd3.5-medium runs on vllm-omni's SD3 pipeline, which supports
request-level batching (supports_request_batch=True), so concurrent
requests are batched on the GPU when launched with max_num_seqs > 1.
Verified end to end on an RTX 3090 Ti: concurrent requests in the same
wave complete with identical timestamps and the engine logs
'[RequestBatch] engine init max_num_seqs=4'.

Ref xorbitsai#4896

@qinxuye qinxuye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@OliverBryant
OliverBryant merged commit 6734afc into xorbitsai:main Jul 28, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants