247 lines
12 KiB
Markdown
247 lines
12 KiB
Markdown
# Kandinsky 6 TI2VA — H100 (v1, single GPU, CPU offload)
|
|||
|
|
|
||
|
|
> Text/image-to-video-and-audio serving with Kandinsky 6 Pro
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
- Vendor: Kandinsky Lab ([`kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers`](https://huggingface.co/kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers))
|
||
|
|
- Model: Kandinsky 6 Pro (TI2VA — text/image-to-video-and-audio)
|
||
|
|
- Task: Joint text/image-to-video-and-audio generation
|
||
|
|
- Mode: Offline (`Omni(...)`) and online serving with the OpenAI-compatible
|
||
|
|
`/v1/videos` API
|
||
|
|
- Hardware: 1x NVIDIA H100 80GB HBM3 with component-level CPU offload
|
||
|
|
- Maintainer: Community
|
||
|
|
|
||
|
|
## When to use this recipe
|
||
|
|
|
||
|
|
Use this recipe to serve Kandinsky 6's TI2VA pipeline — a single DiT that
|
||
|
|
jointly denoises video and audio latents, conditioned on a text prompt and,
|
||
|
|
optionally, a reference image (image-to-video). Tensor parallel, CFG
|
||
|
|
parallel, Ulysses, ring, pipeline parallel, HSDP, layerwise offload,
|
||
|
|
MagCache, TeaCache, Cache-DiT, FP8, and tiled VAE patch parallel are wired.
|
||
|
|
NaviCache and a registered NABLA backend are not. TeaCache coefficients are
|
||
|
|
uncalibrated. See "Known limitations".
|
||
|
|
|
||
|
|
## Supported model contract
|
||
|
|
|
||
|
|
| Task | Entrypoint | Input | Output |
|
||
|
|
|---|---|---|---|
|
||
|
|
| Text-to-video-and-audio | `vllm serve kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers --omni` / `Omni(model="kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers")` | text prompt | synchronized MP4 (H.264 video + AAC 44.1 kHz mono audio) |
|
||
|
|
| Image-to-video-and-audio | same | text prompt + reference image | synchronized MP4, first frame conditioned on the reference image |
|
||
|
|
|
||
|
|
- Audio generation is on by default (`sample_audio=True`); pass
|
||
|
|
`extra_args.sample_audio=false` to generate video only.
|
||
|
|
- Batch size is 1 request per generation call (upstream limitation of the
|
||
|
|
ported denoise loop; concurrent requests are still served by vLLM-Omni's
|
||
|
|
own request scheduler, just not batched together within one DiT forward
|
||
|
|
pass yet).
|
||
|
|
- Serving defaults follow the Pro production geometry registered in
|
||
|
|
`vllm_omni/model_extras/kandinsky6.py`: **864x480, 125 frames @ 24 fps
|
||
|
|
(5.2 s), 50 steps, CFG 5.0**. Any `4k+1` frame count and 16-divisible
|
||
|
|
resolution can be requested explicitly via `height`/`width`/`num_frames`/
|
||
|
|
`num_inference_steps` — the model is *not* fixed-duration.
|
||
|
|
- Audio is always 44.1 kHz; its length is derived from the video duration.
|
||
|
|
- Negative prompt defaults to the pipeline's built-in K6 negative prompt
|
||
|
|
when omitted.
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- Hub checkpoint: [`kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers`](https://huggingface.co/kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers)
|
||
|
|
- Joint video+audio native-model reference: [`recipes/MiniMaxAI/MiniMax-H3.md`](../MiniMaxAI/MiniMax-H3.md)
|
||
|
|
- Offline example: [`examples/offline_inference/text_to_video/text_to_video.py`](../../examples/offline_inference/text_to_video/text_to_video.py)
|
||
|
|
- Image-to-video example: [`examples/offline_inference/image_to_video/image_to_video.py`](../../examples/offline_inference/image_to_video/image_to_video.py)
|
||
|
|
|
||
|
|
## Hardware
|
||
|
|
|
||
|
|
- Accelerator model and per-device memory: NVIDIA H100 80GB HBM3
|
||
|
|
(driver 570.133.20)
|
||
|
|
- Number of devices: 1 (multi-GPU tensor parallelism is implemented in the
|
||
|
|
transformer's linear layers but not yet validated end-to-end)
|
||
|
|
- Device interconnect: N/A (single-device path)
|
||
|
|
- Host memory: 1.4 TB installed; the bf16 DiT (~56 GB) plus the text
|
||
|
|
encoders are staged on the host under `--enable-cpu-offload`, so budget
|
||
|
|
at least ~90 GB of free host RAM.
|
||
|
|
- Qualification scope: single-GPU T2VA at the Pro default geometry and at a
|
||
|
|
reduced smoke geometry, offline and via `vllm serve`. Numbers below were
|
||
|
|
measured once on the shared box described above; treat them as
|
||
|
|
indicative, not as a benchmark.
|
||
|
|
|
||
|
|
## Software environment
|
||
|
|
|
||
|
|
- OS: Ubuntu 22.04.5 LTS
|
||
|
|
- Python: 3.12.13
|
||
|
|
- Driver / runtime: CUDA 12.9 (torch 2.13.0+cu129)
|
||
|
|
- vLLM version: 0.29.0
|
||
|
|
- vLLM-Omni version or commit: this repository, `vllm_omni/diffusion/models/kandinsky6/`
|
||
|
|
- diffusers 0.40.0, transformers 5.14.1
|
||
|
|
- Attention: FlashAttention-3 on this box (`flash_attn_interface`). The Pro
|
||
|
|
timings below use it. Without FA3, `attention_engine: "auto"` falls back
|
||
|
|
to PyTorch SDPA; an earlier SDPA Pro run on the same GPU was 26.9 s/it
|
||
|
|
and 1377 s.
|
||
|
|
|
||
|
|
## Weights
|
||
|
|
|
||
|
|
`Kandinsky6TI2VAPipeline._load_components` loads the public Diffusers layout
|
||
|
|
from [`kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers`](https://huggingface.co/kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers)
|
||
|
|
(`model_index.json`, `transformer/`, `vae/`, `text_encoder/`, `tokenizer/`,
|
||
|
|
`text_encoder_2/`, `tokenizer_2/`, `audio_vae/`, `scheduler/`). Pass that
|
||
|
|
repo id (or a local snapshot of it) to `vllm serve` / `Omni(model=...)`.
|
||
|
|
The Hub repo is gated; export `HF_TOKEN` if download returns 401.
|
||
|
|
|
||
|
|
A raw Kandinsky 6 weight tree is not required. If you already have a local
|
||
|
|
Diffusers-layout snapshot, point `--model` at that directory instead of the
|
||
|
|
Hub id.
|
||
|
|
|
||
|
|
Each weight folder is loaded on its own (`transformer/`, `vae/`, `text_encoder/`, `text_encoder_2/`, `audio_vae/`). The DiT is built with initialization skipped, then the framework loader fills it from `transformer/`.
|
||
|
|
|
||
|
|
## Command
|
||
|
|
|
||
|
|
```bash
|
||
|
|
vllm serve kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers --omni \
|
||
|
|
--host 127.0.0.1 --port 8091 \
|
||
|
|
--num-gpus 1 --enable-cpu-offload
|
||
|
|
```
|
||
|
|
|
||
|
|
`--enable-cpu-offload` enables model-level offload with mutual exclusion
|
||
|
|
between the transformer and the two text encoders (they never co-reside on
|
||
|
|
the GPU). Without it the bf16 DiT (~56 GB) plus Qwen2.5-VL-7B (~16 GB) plus
|
||
|
|
the VAEs and activations do not fit an 80 GB device at the Pro geometry.
|
||
|
|
|
||
|
|
Offline (writes an MP4 with the audio track muxed in):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python examples/offline_inference/text_to_video/text_to_video.py \
|
||
|
|
--model kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers \
|
||
|
|
--prompt "A golden retriever runs along a sunny beach, waves crashing, cinematic footage" \
|
||
|
|
--seed 42 --enable-cpu-offload --output kandinsky6_t2va.mp4
|
||
|
|
# Video only: add --extra-body '{"sample_audio": false}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Image-to-video-and-audio uses the shared image-to-video example:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python examples/offline_inference/image_to_video/image_to_video.py \
|
||
|
|
--model kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers \
|
||
|
|
--image first_frame.png \
|
||
|
|
--prompt "A golden retriever runs along a sunny beach, waves crashing, cinematic footage" \
|
||
|
|
--seed 42 --enable-cpu-offload --output kandinsky6_i2va.mp4
|
||
|
|
```
|
||
|
|
|
||
|
|
The generic scripts pick up the Pro defaults from
|
||
|
|
`vllm_omni/model_extras/kandinsky6.py`.
|
||
|
|
|
||
|
|
## Verification
|
||
|
|
|
||
|
|
`/v1/videos` takes multipart form fields (not a JSON body). Submit, poll,
|
||
|
|
then download:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
VID=$(curl -s -X POST http://127.0.0.1:8091/v1/videos \
|
||
|
|
-F prompt="A golden retriever runs along a sunny beach, waves crashing" \
|
||
|
|
-F size=864x480 -F num_frames=125 -F num_inference_steps=50 -F seed=42 \
|
||
|
|
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
|
||
|
|
|
||
|
|
# Poll until "status": "completed"
|
||
|
|
curl -s http://127.0.0.1:8091/v1/videos/$VID
|
||
|
|
|
||
|
|
curl -s -o kandinsky6.mp4 http://127.0.0.1:8091/v1/videos/$VID/content
|
||
|
|
# Expect an MP4 with an H.264 video stream (125 frames, 24 fps, 864x480)
|
||
|
|
# and an AAC audio stream (44100 Hz, mono) of matching duration.
|
||
|
|
```
|
||
|
|
|
||
|
|
Smoke-size request for quick validation (~30 s end-to-end on one H100):
|
||
|
|
`-F size=512x320 -F num_frames=25 -F num_inference_steps=10`.
|
||
|
|
|
||
|
|
## Measurements
|
||
|
|
|
||
|
|
Single H100 80GB, seed 42, prompt
|
||
|
|
"A golden retriever runs along a sunny beach, waves crashing, cinematic footage",
|
||
|
|
negative prompt left at the pipeline default, MagCache off, prompt expansion
|
||
|
|
off. Pro geometry: 864x480, 125 frames, 50 steps, CFG 5.0, audio on.
|
||
|
|
|
||
|
|
vLLM-Omni: `text_to_video.py --model kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers --enable-cpu-offload --model-class-name Kandinsky6TI2VAPipeline`,
|
||
|
|
FlashAttention-3.
|
||
|
|
|
||
|
|
| Wall / generation | Step time | Peak GPU | After load |
|
||
|
|
|---|---|---|---|
|
||
|
|
| 751.7 s generation (12.5 min); engine init 41 s | 14.4 s/it average | 78.39 GiB reserved (78.0 GiB nvidia-smi) | 17.74 GiB |
|
||
|
|
|
||
|
|
An earlier SDPA vLLM-Omni Pro run on the same GPU was 26.9 s/it and 1377 s,
|
||
|
|
peak reserved 74.97 GiB. A 512x320 / 25-frame / 10-step SDPA smoke was 1.4 s/it
|
||
|
|
and 29.5 s at the same reserved peak.
|
||
|
|
|
||
|
|
The MP4 is H.264 864x480, 125 frames at 24 fps (5.22 s), plus AAC 44.1 kHz
|
||
|
|
mono (230400 samples): a golden retriever on a sunny beach with breaking waves.
|
||
|
|
|
||
|
|
vLLM-Omni's VAE decode logged two failed CUDA allocations (7.15 GiB, then
|
||
|
|
4.27 GiB) and still wrote a complete file.
|
||
|
|
|
||
|
|
Audio is peak-normalized to full scale by default; pass
|
||
|
|
`extra_args.audio_normalization="clip"` to keep the raw decoded amplitude
|
||
|
|
instead.
|
||
|
|
|
||
|
|
## Notes
|
||
|
|
|
||
|
|
- Key flags: `extra_args.sample_audio` (bool, default true),
|
||
|
|
`extra_args.audio_normalization` (`"normalize"` default | `"clip"`),
|
||
|
|
`extra_args.visual_cond_scheme` (defaults to `pretrain` for text-only,
|
||
|
|
`tail_cond_first_frame` when an image is supplied).
|
||
|
|
- The post-process payload is a flat dict (`video`, `audio` as float32 in
|
||
|
|
`[-1, 1]`, `audio_sample_rate=44100`, `fps`) so the serving muxer picks
|
||
|
|
up the correct sample rate.
|
||
|
|
- Acceleration that is wired:
|
||
|
|
- Tensor parallel: the DiT loader narrows full checkpoint tensors onto
|
||
|
|
each rank's `ColumnParallelLinear` / `RowParallelLinear` shard.
|
||
|
|
- CFG parallel (`--cfg-parallel-size 2`): rank 0 runs the conditional
|
||
|
|
DiT, rank 1 the unconditional one, then both all-gather video and
|
||
|
|
audio velocities.
|
||
|
|
- HSDP: `_hsdp_shard_conditions` covers the indexed transformer blocks.
|
||
|
|
Do not combine with tensor parallel.
|
||
|
|
- Sequence parallel: visual tokens (and their RoPE) are sharded;
|
||
|
|
Ulysses all-to-all runs inside visual self-attention, Ring uses the
|
||
|
|
PyTorch ring kernel. Text stays replicated. Audio queries gather the
|
||
|
|
full visual sequence.
|
||
|
|
- Pipeline parallel: `visual_transformer_blocks` are split with
|
||
|
|
`PPMissingLayer`. Embeddings stay on the first stage, output heads on
|
||
|
|
the last. Activations and the final velocity are sent between stages.
|
||
|
|
- VAE patch parallel: Hunyuan decode splits the latent width when
|
||
|
|
`vae_patch_parallel_size > 1` and tiling is on.
|
||
|
|
- MagCache / TeaCache / Cache-DiT are registered. MagCache reuses the
|
||
|
|
Pro `mag_ratios`. TeaCache coefficients are **uncalibrated** (copied
|
||
|
|
from Qwen-Image) so skips are not quality-neutral. Cache-DiT targets
|
||
|
|
`visual_transformer_blocks` only and falls back to the same step cache
|
||
|
|
if the fused `(video, audio)` block return is rejected.
|
||
|
|
- FP8: online quantization runs in the DiT loader via
|
||
|
|
`process_weights_after_loading` after the sharded assign.
|
||
|
|
- `attention_engine: auto` uses FlashAttention-3 when
|
||
|
|
`flash_attn_interface` imports.
|
||
|
|
- Known limitations:
|
||
|
|
- NABLA sparse attention is not registered in the framework attention
|
||
|
|
backend. NaviCache is not wired.
|
||
|
|
- Prompt expansion, NF4 Qwen, and the super-resolution cascade are not
|
||
|
|
part of this port.
|
||
|
|
- Distributed layerwise offload still uses one process unless another
|
||
|
|
parallel axis sets `world_size > 1` (for example tensor parallel).
|
||
|
|
- `_load_components` loads the Diffusers checkpoint directly rather than the
|
||
|
|
streamed-prefetch loader.
|
||
|
|
|
||
|
|
## Supported features
|
||
|
|
|
||
|
|
| Feature | Status | Guide |
|
||
|
|
|---|---|---|
|
||
|
|
| Text-to-video-and-audio | Supported (validated on H100) | — |
|
||
|
|
| Image-to-video-and-audio | Supported (not yet validated on hardware) | — |
|
||
|
|
| CPU offload (`--enable-cpu-offload`) | Supported (required on 80 GB) | — |
|
||
|
|
| Layerwise offload (`--enable-layerwise-offload`) | Supported (about 36 GiB peak on the smoke geometry) | — |
|
||
|
|
| Tensor parallelism | Wired (sharded DiT load) | — |
|
||
|
|
| CFG parallelism | Wired (`--cfg-parallel-size 2`) | — |
|
||
|
|
| Ulysses / Ring sequence parallel | Wired (visual tokens only) | — |
|
||
|
|
| Pipeline parallelism | Wired (visual block split) | — |
|
||
|
|
| HSDP | Wired (not with tensor parallel) | — |
|
||
|
|
| VAE patch parallel | Wired (width-split decode) | — |
|
||
|
|
| MagCache | Wired (Pro ratios, step reuse) | — |
|
||
|
|
| TeaCache | Wired, coefficients uncalibrated | — |
|
||
|
|
| Cache-DiT | Wired on `visual_transformer_blocks` | — |
|
||
|
|
| FP8 | Wired (online quant; smoke peak about 46 GiB vs 75 GiB bf16) | [`FP8 quantization`](../../docs/user_guide/quantization/fp8.md) |
|
||
|
|
| NaviCache | Not supported | — |
|
||
|
|
| LoRA | Not supported | — |
|