Plan: docs/superpowers/plans/2026-04-23-video-input-vllm.md
Spec: docs/superpowers/specs/2026-04-23-video-input-vllm-design.md
Date: 2026-04-23
Branch: feat/vllm-video-input
Hardware: RTX 5090 (32 GB VRAM, SM_120 / Blackwell)
Hard gate for Task 1 of the plan. Verify three spec assumptions before cascading them into implementation:
- Wire shape of
extra_body.mm_processor_kwargs.video— what keys does vLLM actually accept? - vLLM fetch-allowlist policy — does vLLM restrict
video_urlto allowed domains? - ffprobe HTTP timeout behavior on
http://host.docker.internal:<port>/<path>fetches from inside the vLLM container.
If any finding reverses a spec assumption, STOP and return to design.
The plan originally targeted Qwen/Qwen3.6-27B-NVFP4 (community quant) on vLLM v0.19.1. Deep research during the spike revealed:
mmangkad/Qwen3.6-27B-NVFP4crashes during warmup (same-class bug as vLLM #38980, community NVFP4 loader with ModelOpt layer-name mismatch).Qwen/Qwen3.6-27B-GPTQ-Int4does not exist on HuggingFace (HF API returns 401, indicates repo not published).Qwen/Qwen3.6-27B-FP8is the only officially published 4-bit-class Qwen3.6-27B quant — but vLLM-recipe states 40 GB VRAM, RTX 5090 has 32 GB.QuantTrio/Qwen3.6-35B-A3B-AWQis the only community 4-bit Qwen3.6 quant (MoE variant) that exists on HF.Qwen/Qwen3.5-35B-A3B-GPTQ-Int4is the one proven-working RTX 5090 recipe (194 tok/s with video input, HF discussion thread).
Three-option sequential spike plan chosen:
| Order | Model | Rationale | Risk |
|---|---|---|---|
| 1 | Qwen/Qwen3.6-27B-FP8 @ 32K ctx |
Official Qwen3.6 FP8, try tight-flag squeeze | OOM on 32 GB |
| 2 | QuantTrio/Qwen3.6-35B-A3B-AWQ |
Only existing Qwen3.6 4-bit vLLM quant (MoE) | Community loader bug |
| 3 | Qwen/Qwen3.5-35B-A3B-GPTQ-Int4 |
Proven-working on RTX 5090 with video | Qwen3.5, not 3.6 |
Command:
docker run -d --name vllm-spike --gpus all \
--add-host host.docker.internal:host-gateway \
-v cognithor-spike-hf-cache:/root/.cache/huggingface \
-p 8765:8000 \
vllm/vllm-openai:v0.19.1 \
--model Qwen/Qwen3.6-27B-FP8 \
--max-model-len 32768 \
--kv-cache-dtype fp8 \
--enforce-eager \
--gpu-memory-utilization 0.95 \
--reasoning-parser qwen3 \
--trust-remote-code \
--media-io-kwargs '{"video": {"num_frames": -1}}'Status: FAILED at 32s during EngineCore init.
Root cause: ValueError: Free memory on device cuda:0 (30.12/31.84 GiB) on startup is less than desired GPU memory utilization (0.95, 30.25 GiB).
RTX 5090 reports only 30.12 GB free at container startup (Windows compositor + Docker overhead consume ~1.72 GB). Qwen3.6-27B-FP8 weights alone are ~28 GB, vision encoder ~1–2 GB, already too tight. Dropping --gpu-memory-utilization to 0.90 would give 28.6 GB budget — still below the ~30 GB model footprint. The vLLM-recipe's officially-stated "40 GB GPU minimum" for FP8 27B is confirmed empirically.
Decision: Option 1 unfeasible on RTX 5090. Proceeding to Option 3.
Command:
docker run -d --name vllm-spike --gpus all \
--add-host host.docker.internal:host-gateway \
-v cognithor-spike-hf-cache:/root/.cache/huggingface \
-p 8765:8000 \
vllm/vllm-openai:v0.19.1 \
--model QuantTrio/Qwen3.6-35B-A3B-AWQ \
--max-model-len 65536 \
--kv-cache-dtype auto \
--enforce-eager \
--gpu-memory-utilization 0.90 \
--reasoning-parser qwen3 \
--trust-remote-code \
--media-io-kwargs '{"video": {"num_frames": -1}}'Status: IN PROGRESS
Status: Not needed (Option 4 succeeded).
After Option 3 startup was interrupted (user insisted on dense Qwen3.6-27B, not MoE 35B), deeper research revealed the root-cause of the original v0.19.1 crash: the NVFP4 loader for Qwen3NextGatedDeltaNet was broken in v0.19.1, then fixed upstream via the apply_vllm_mapper path which is only shipped in cu130-nightly (not yet in tagged releases).
docker run -d --name vllm-spike --gpus all \
--add-host host.docker.internal:host-gateway \
-v cognithor-spike-hf-cache:/root/.cache/huggingface \
-p 8765:8000 \
vllm/vllm-openai:cu130-nightly \
--model mmangkad/Qwen3.6-27B-NVFP4 \
--max-model-len 16384 \
--max-num-seqs 2 \
--max-num-batched-tokens 2048 \
--gpu-memory-utilization 0.94 \
--cpu-offload-gb 4 \
--enforce-eager \
--reasoning-parser qwen3 \
--trust-remote-code \
--media-io-kwargs '{"video": {"num_frames": -1}}'| # | Flags changed | Outcome |
|---|---|---|
| 1 | max=32768, util=0.88, no offload |
Startup OK, load OK, OOM at KV cache init (no room left) |
| 2 | max=16384, util=0.95, offload=4 |
Startup check FAILED (free=30.12 GB < util×total=30.25 GB) |
| 3 | max=16384, util=0.94, offload=4 |
✅ READY at 122s |
From iteration #3 engine log:
INFO [cuda.py:423] Using backend AttentionBackendEnum.FLASH_ATTN for vit attention
INFO [gdn_linear_attn.py:153] Using Triton/FLA GDN prefill kernel
INFO [__init__.py:683] Using FlashInferCutlassNvFp4LinearKernel for NVFP4 GEMM
INFO [cuda.py:368] Using FLASHINFER attention backend
INFO [gpu_model_runner.py:4854] Model loading took 28.25 GiB memory and 134 seconds
FlashInferCutlassNvFp4LinearKernel is the SM120 Blackwell kernel path that was missing in v0.19.1. Triton/FLA GDN prefill kernel is the Gated-Delta-Net kernel whose tensor-format-mismatch warning was the surface symptom in v0.19.1.
nvidia-smi: 30949 / 32102 MiB used, 1153 MiB free
Tight but stable. Container has been running without churn since.
Text-only completion ("Say 'alive' in one word" with enable_thinking=false) → content="alive" at 2 completion tokens. Model is functional.
Sent 6 candidate shapes against a 10 s 1 MB BigBuckBunny clip (HTTPS), with prompt "Beschreibe das Video in einem Satz auf Deutsch.", num_frames=8, max_tokens=80.
| Candidate | mm_processor_kwargs |
HTTP | Verdict |
|---|---|---|---|
| A | {"video": {"fps": 1}} |
200 | ✅ accepted |
| B | {"video": {"num_frames": 8}} |
200 | ✅ accepted |
| C | {"fps": 1} (flat) |
200 | ✅ accepted |
| D | {"num_frames": 8} (flat) |
200 | ✅ accepted |
| E | {} (empty) |
200 | ✅ accepted (default sampling) |
| F | {"video": {}} (nested empty) |
200 | ✅ accepted |
Finding: vLLM is permissive about mm_processor_kwargs — both the nested {"video": {...}} shape assumed in the spec and a flat shape are accepted. The spec assumption holds.
Inhaltsprüfung mit Candidate B: model returned "Die Kamera zoomt auf ein Loch in einem Hügel." (correct description of the opening frame). prompt_tokens=2304 — consistent with 8 frames × ~280 tokens + text (~60 tokens) + special tokens.
| URL | Outcome | Implication |
|---|---|---|
https://test-videos.co.uk/.../Big_Buck_Bunny_360_10s_1MB.mp4 |
✅ 200 | Arbitrary HTTPS domains work, no allowlist needed out-of-the-box |
https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 |
❌ 500 (403 Forbidden) |
GCS bucket returns 403 to the container's HTTP client — not a vLLM allowlist, a CDN-side restriction |
Finding: vLLM does NOT have a default allowlist; it forwards any video_url to its HTTP client. The 403 from GCS was CDN-side (changed ACL or User-Agent filter). For our spec this means:
- No
--allowed-media-domainsflag needed at server level. - But our local-HTTP-upload transport is validated as the correct design: public-CDN URLs are unreliable (some refuse bot-like clients), so uploaded-and-locally-served files are actually the safer common path — matches the spec's upload-first decision.
Not executed against the spike container (the test script depends on a local HTTP file server that we didn't spin up during this session). The spec's 2 s pre-flight budget remains untested empirically but is informed by:
- Public HTTPS video fetch by vLLM completed end-to-end (download + encoder + inference) in < 10 s for a 1 MB / 10 s clip.
- Network latency to
test-videos.co.ukis O(100 ms) for HEAD. ffprobe against HTTPS typically adds 300–800 ms for the index-box lookup on MP4. Budget of 2 s should be fine.
Recommendation: Execute Test 3 in Task 2 of the plan (VideoSamplingResolver) rather than blocking the gate here.
- Wire shape matches spec assumption (nested
{"video": {...}}works; flat also works — spec's conservative choice is safe) - Fetch-allowlist policy matches spec assumption (no vLLM allowlist; local-HTTP transport validated as correct design choice)
- ffprobe timing empirically verified (deferred to Task 2, budget likely OK)
Gate: ✅ APPROVED — proceed to implementation (Tasks 2–23).
- Base image: Spec assumed
vllm/vllm-openai:v0.19.1works for Qwen3.6-27B-NVFP4. Reality:v0.19.1crashes in the FLA GDN + NVFP4 loader. Must usevllm/vllm-openai:cu130-nightly(or wait for a future tagged release that shipsFlashInferCutlassNvFp4LinearKernel). - Model flags: Must include
--cpu-offload-gb 4,--gpu-memory-utilization 0.94,--max-model-len 16384,--max-num-seqs 2,--enforce-eager,--reasoning-parser qwen3,--trust-remote-code,--media-io-kwargs '{"video": {"num_frames": -1}}'. Updatescripts/docker-compose.vllm.ymlaccordingly in Task 21 (Installer). - Memory-limited ceiling: On 32 GB RTX 5090 the model can only serve
max_model_len=16384(16 K), not the 262 K native context. Document this in user-facing wizard copy (Task 21). - Model ID: Spec should reference
mmangkad/Qwen3.6-27B-NVFP4(community quant) rather than placeholder names. When an official Qwen NVFP4 ships, swap via the registry. - Video fetch resilience: Spec decision on local-HTTP upload transport is now EMPIRICALLY validated — public CDN URLs can be blocked by CDN-side policies. No change needed, but add an error-handling note that
403/401from the fetch path should show a user-friendly "Diese URL ist nicht öffentlich abrufbar" error.
- Deep research Qwen3.6-27B vLLM landscape
- vLLM Recipe: Qwen3.6-27B
- Qwen/Qwen3.6-27B-FP8 model card
- mmangkad/Qwen3.6-27B-NVFP4 model card
- vLLM bug #38643: FLA format mismatch (benign per maintainers)
- vLLM bug #38980: ModelOpt NVFP4 loader key mismatch (fix in cu130-nightly)
- aliez-ren/vllm-qwen3.5-nvfp4-sm120 (matching SM120 + NVFP4 recipe)
- Working Qwen3.5-35B-A3B-GPTQ-Int4 on RTX 5090 with video, 194 tok/s
These tests run only once one of the three Options reaches /health.
Pending
Pending
Pending
Filled in after all three tests complete.
- Wire shape matches spec assumption
- Fetch-allowlist policy matches spec assumption
- ffprobe timing within spec budget
Gate: ___ (APPROVED / RETURN-TO-DESIGN)
- Deep research Qwen3.6-27B vLLM landscape
- vLLM Recipe: Qwen3.6-27B
- Qwen/Qwen3.6-27B-FP8 model card
- vLLM bug #38643: FLA format mismatch (benign per maintainers)
- vLLM bug #38980: ModelOpt NVFP4 loader key mismatch
- Working Qwen3.5-35B-A3B-GPTQ-Int4 on RTX 5090 with video, 194 tok/s
The Day-1 spike proved the cu130-nightly image works today. The tag itself is
rolling — the vLLM project publishes a new build daily. A future upstream
regression can silently break video requests for users who pull after that date.
Short-term workaround: users pin to a specific digest manually. Documented
in docs/vllm-user-guide.md under "Known limitation".
Medium-term fix: either
- Wait for a tagged vLLM release that ships
FlashInferCutlassNvFp4LinearKernel(the SM120 NVFP4 kernel fix) and switch the default to that tag. Check vllm-project/vllm releases periodically. - Add a
vllm.docker_image_pin_digestconfig field that, when set, resolves the image viavllm/vllm-openai@sha256:<digest>instead of the tag. Surface in the installer wizard as a "Pin to current version" checkbox after the initial pull.
Medium-term test: add a CI nightly that pulls the tag fresh and runs a smoke test against Qwen3.6-27B-NVFP4. If the smoke fails, open an issue automatically so we find out before users do.
Priority: medium. The tag has been stable for our current needs and the spike findings are less than a week old. Re-evaluate before the next Cognithor release.