Buckets:

linoyts's picture
download
raw
7 kB
#!/usr/bin/env python3
"""Launch a vLLM server for Qwen3-Omni captioning.
Runs the actual server via ``uvx`` so that vLLM and its CUDA-tied
dependencies live in their own isolated environment (no impact on this
package's dependency tree).
The captioning script (``caption_videos.py``) talks to the server over its
OpenAI-compatible HTTP API. Once the server is up it stays loaded across
captioning runs; no per-script model warmup cost.
Typical usage::
# Default: dynamic FP8 quantization, listen on 127.0.0.1:8001
uv run python scripts/serve_captioner.py
# Just print the chosen `uvx vllm serve ...` command without running it
uv run python scripts/serve_captioner.py --print-cmd
# Use full bf16 on a GPU with >= 66 GiB free VRAM (slightly more reliable
# numerics but 2x the weight memory)
uv run python scripts/serve_captioner.py --quantization bf16
# Use a different port or expose on all interfaces
uv run python scripts/serve_captioner.py --port 9000 --host 0.0.0.0
"""
import os
import shutil
import subprocess
import sys
from pathlib import Path
import typer
from rich.console import Console
console = Console()
# Model identifier we serve. The captioner client must use the same string.
DEFAULT_MODEL = "Qwen/Qwen3-Omni-30B-A3B-Thinking"
# Pinned vLLM version known to support Qwen3-Omni on CUDA 12.x.
# vLLM 0.20+ requires CUDA 13. Update both as the environment evolves.
# The ``[audio]`` extra is required for Qwen3-Omni to decode audio at all.
DEFAULT_VLLM_SPEC = "vllm[audio]==0.11.2"
# Approximate disk needed for the model download (HF cache structure).
MODEL_DISK_GIB = 65.0
app = typer.Typer(
pretty_exceptions_enable=False,
no_args_is_help=False,
help="Launch a local vLLM server for Qwen3-Omni captioning.",
)
def _query_disk_free_gib(path: Path) -> float:
return shutil.disk_usage(str(path)).free / 1024**3
def _build_vllm_args(
*,
model: str,
host: str,
port: int,
quantization: str,
max_model_len: int,
gpu_memory_utilization: float,
extra_args: list[str],
) -> list[str]:
"""Construct the `vllm serve ...` argv."""
args = [
"vllm",
"serve",
model,
"--host",
host,
"--port",
str(port),
"--dtype",
"bfloat16",
"--max-model-len",
str(max_model_len),
"--gpu-memory-utilization",
str(gpu_memory_utilization),
# Let the server accept ``file://`` URLs pointing at local videos.
"--allowed-local-media-path",
"/",
# The model is a multimodal MoE; cap each input to one of each
# modality to match what our captioner sends.
"--limit-mm-per-prompt",
'{"image": 1, "video": 1, "audio": 1}',
# Small concurrent-sequence cap so KV cache headroom isn't fragmented.
"--max-num-seqs",
"4",
]
if quantization == "fp8":
args += ["--quantization", "fp8"]
args += extra_args
return args
@app.command()
def main(
model: str = typer.Option(DEFAULT_MODEL, "--model", help="Model identifier to serve."),
host: str = typer.Option("127.0.0.1", "--host", help="Listen address. Use 0.0.0.0 for remote access."),
port: int = typer.Option(8001, "--port", help="HTTP port."),
quantization: str = typer.Option(
"fp8",
"--quantization",
"-q",
help=(
"Weight precision. 'fp8' (default, dynamic FP8 -- ~31 GiB weights) is "
"the recommended choice; it fits on 40 GiB GPUs and runs at the same "
"speed as bf16 on H100. 'bf16' uses ~60 GiB of weights -- pick it if "
"you have abundant VRAM and want minimal numerical drift."
),
),
max_model_len: int = typer.Option(
32768,
"--max-model-len",
help="Maximum context length the server accepts (must fit input video tokens + max_tokens).",
),
gpu_memory_utilization: float = typer.Option(
0.9,
"--gpu-memory-utilization",
help="Fraction of GPU memory vLLM may reserve (model + KV cache).",
),
hf_home: Path | None = typer.Option( # noqa: B008
None,
"--hf-home",
help=(
"Override HF_HOME (where the model is downloaded). The model is ~65 GB; "
"by default this follows your environment's HF_HOME or HuggingFace's default."
),
),
vllm_spec: str = typer.Option(
DEFAULT_VLLM_SPEC,
"--vllm-spec",
help="pip-style spec passed to `uvx --from`. Pin a version that matches your CUDA.",
),
print_cmd: bool = typer.Option(
False,
"--print-cmd",
help="Print the chosen command without running it.",
),
extra_args: list[str] | None = typer.Argument( # noqa: B008
None,
help="Additional args passed through to `vllm serve` after `--`.",
),
) -> None:
"""Launch the vLLM server for Qwen3-Omni."""
extra = extra_args or []
if quantization not in ("bf16", "fp8"):
console.print(f"[red]--quantization must be 'bf16' or 'fp8'; got {quantization!r}.[/]")
raise typer.Exit(code=1)
# Disk check (only meaningful before first download).
cache_root = hf_home or Path(os.environ.get("HF_HOME", str(Path.home() / ".cache" / "huggingface")))
cache_root.mkdir(parents=True, exist_ok=True)
free_disk = _query_disk_free_gib(cache_root)
if free_disk < MODEL_DISK_GIB:
console.print(
f"[yellow]\u26a0 Only {free_disk:.1f} GiB free on disk under {cache_root} but the "
f"model needs ~{MODEL_DISK_GIB:.0f} GiB. Either free up space, set --hf-home "
f"to a larger volume, or expect the download to fail mid-way.[/]"
)
vllm_args = _build_vllm_args(
model=model,
host=host,
port=port,
quantization=quantization,
max_model_len=max_model_len,
gpu_memory_utilization=gpu_memory_utilization,
extra_args=extra,
)
# Use ``uvx --from vllm==...`` so vLLM lives in its own throwaway venv
# (or a cached tool venv). The `--` separates uvx args from the command's.
uvx_cmd = ["uvx", "--from", vllm_spec, *vllm_args]
env = os.environ.copy()
# vLLM 0.11.x requires the V0 engine for Qwen3-Omni's multimodal pipeline.
env.setdefault("VLLM_USE_V1", "0")
if hf_home is not None:
env["HF_HOME"] = str(hf_home)
console.print("\n[bold]Command:[/]")
console.print(" " + " ".join(uvx_cmd))
if hf_home is not None:
console.print(f" [dim](with HF_HOME={hf_home})[/]")
if print_cmd:
return
console.print("\n[dim]Launching... (first run downloads the model -- ~5 min on a fast link)[/]\n")
try:
completed = subprocess.run(uvx_cmd, env=env, check=False)
except KeyboardInterrupt:
console.print("\n[yellow]Interrupted.[/]")
return
sys.exit(completed.returncode)
if __name__ == "__main__":
app()

Xet Storage Details

Size:
7 kB
·
Xet hash:
771556f87db4ea0a1522de81d1fbe3798f55ed4cfb37360ce1d1a70cb26476bb

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.