Repository navigation
Conversation
Single-GPU BF16 support for hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v through the shared GPU flow: difflet run, the Python API and difflet serve. - backends/cuda/hunyuan_video_15/: the DiT adapted from Diffusers with Difflet projections. Eager blocks on a supported GPU run the joint attention with packed QKV, the context trimmed to its valid tokens, a fused Q/K RMSNorm + RoPE Triton op and cuDNN SDPA; this is decided once at load. Compiled blocks attend each sample's valid prefix (one static graph for every prompt length) with deterministic Inductor reductions. The application keeps Diffusers' driver, decodes with Diffusers' native VAE tiles, and encodes prompts with device-local Qwen rotary frequencies and cuDNN SDPA off, as the reference conditioning does. - models/hunyuan_video_15/spec.py and "cuda" in the registry entry; the Trainium path is unchanged. - Serving: a GPU-flow model's metadata comes from its GPU spec, and CFG-parallel stays off unless asked, as on the GPU CLI (the registry's HunyuanVideo 1.5 layout, TP2 x CFG2, is Trainium's). On other backends a model with no serving metadata gets a one-line serving error instead of a traceback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…developer notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mparison The guide now covers usage, constraints and how the CUDA path runs, then the matched comparison with vLLM-Omni on one RTX PRO 6000: both acceptance bars, repeatability, start-up and memory, and a check in the pinned requirements-cuda.lock environment. The serve example raises the request timeout, since a 121-frame request takes longer than the 300 s default. README footnote 8 states the result as measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23fe0e9 to
8955342
Compare
The tiny-transformer parity test asked BF16 output to match Diffusers to 1e-4, which in BF16 means bit for bit. On one x86 CI runner, 2 of 48 elements came out one BF16 ulp apart: that CPU's BF16 GEMM kernels round differently between the two models. The BF16 case now allows at most one ulp per element. A real difference in attention math moves elements by 2 to 4 ulps on the same inputs, so the check still catches it. FP32 stays at 1e-4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comment named an internal shorthand. It now says what the code does: every input is cast to the transformer's dtype, as Diffusers' HunyuanVideo 1.5 driver does, and unlike the original HunyuanVideo adapter the timestep is not kept in FP32. Comment only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Test placement / naming — nothing blocking, all mechanical:
|
- The application test moves to tests/unit/models/hunyuan_video_15/test_cuda_application.py, mirroring difflet/models/hunyuan_video_15/, as every other CUDA model's does. - The GPU spec and public-factory tests fold into that file, as LTX-2's do. - The native-tiling test is dropped: what it checked beyond tiling being on is Diffusers' own enable_tiling() defaults. The application test now asserts that load_companions turns tiling on, which also removes the circular import between the two files. - The conditioning test, CUDA-only, is renamed test_cuda_hunyuan15_conditioning.py. No product code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. All four are done in 20241c9 (test files only, no product code):
The moved tests and the rest of |
Summary
This adds HunyuanVideo 1.5 text-to-video on CUDA for
hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v: one GPU, BF16, throughdifflet run, the Python API anddifflet serve, eager or--torch-compile. On one RTX PRO6000, its output meets a relative accuracy bar against vLLM-Omni in both modes, and its warm
latency is within 10% of vLLM-Omni's.
The model code is in
difflet/backends/cuda/hunyuan_video_15/with its GPU spec indifflet/models/hunyuan_video_15/spec.py, and"cuda"is added to the HunyuanVideo 1.5registry entry. The Trainium and Neuron HunyuanVideo 1.5 paths are unchanged.
attention, loaded strictly from the checkpoint.
valid tokens, Q/K RMSNorm and RoPE in one fused Triton op written for Difflet
(
difflet_hv15::fused_joint_qk_norm_rope), and cuDNN SDPA. Whether a model uses this pathis decided once at load.
each sample's valid token prefix, so one static graph serves every prompt length, with
deterministic Inductor reductions.
GPU-computed Qwen rotary frequencies and cuDNN SDPA off, which matches vLLM-Omni's
conditioning bit for bit.
Shared code
serving/model_registry.py: on GPUs, serving takes a model's metadata from its GPU specand keeps CFG-parallel off unless asked, as the GPU CLI does.
entry's backends. The compile options and CFG-parallel refusal this model uses came in
with feat(ltx2): add single-GPU CUDA audiovisual generation #147.
Behaviour changes for other models
difflet servewith a one-line 400 serving error. Before, it raised a
ValueErrorwith a traceback.difflet servewith the HunyuanVideo 1.5 ID passes the CLI'smodel list and then gets a one-line "not enabled for serving" error. Before, the CLI
said "Unknown model-id".
difflet servenow leaves CFG-parallel off unless--cfg-parallelis passed, asdifflet rundoes. No other model changes: HunyuanVideo 1.5's is the only registry layoutthat turns CFG-parallel on.
Results
Measured against vLLM-Omni
08177216(vLLM 0.32.0) on one RTX PRO 6000 Blackwell (96 GB),with the same Python 3.12 / Torch 2.13 / Diffusers 0.40 runtime for both engines. Each engine
and mode ran in a fresh process, and the whole set ran twice, the second time in reverse
order. G is vLLM-Omni's own test request (832×480×33, 50 steps, seed 42); P is 848×480×121.
Details are in the guide.
Output (ffmpeg SSIM / PSNR through vLLM-Omni's accuracy-test helper). The bar is relative:
on G, Difflet against vLLM-Omni must score at least what vLLM-Omni's own eager and compiled
outputs score against each other. P is reported. vLLM-Omni's own accuracy test uses a fixed
bar of SSIM ≥ 0.94 and PSNR ≥ 28 dB: Difflet's eager G meets it, and compiled G does not.
In eager mode the denoiser is bit-identical to vLLM-Omni's (all 54 blocks, both guidance
branches); the remaining difference is the VAE decode. The fused Q/K op is bit-identical to
vLLM-Omni's in 12 of 12 cases. Every engine and mode reproduced its denoiser digests exactly in
the second pass. An earlier run of the same code gave byte-identical Difflet videos in both
modes; only vLLM-Omni's compiled videos changed, and the bar passed there too (compiled G
0.9041 / 30.20 dB against 0.8343 / 27.46 dB).
The two engines compile differently, so their compiled outputs differ from the first block on:
vLLM-Omni compiles regions with dynamic shapes, and Difflet compiles each block with static
shapes and deterministic reductions. On G, each engine's eager and compiled videos differ by a
similar amount (Difflet 0.8615 / 28.35 dB, vLLM-Omni 0.8412 / 27.51 dB). At 121 frames
compilation moves the output further still: vLLM-Omni's own eager and compiled videos score
0.4999 / 18.04 dB against each other, below Difflet's compiled P score against vLLM-Omni.
Warm latency (s, pass 1; G is the median of three requests, P one request after a
warm-up; the bar is Difflet / vLLM-Omni ≤ 1.10):
Peak device memory is 62.9 GiB for Difflet and 92.9 GiB for vLLM-Omni. Load takes 15–26 s
against 25–28 s.
Testing
5123 passed. The 20 failures are all in tests this PR does not change, and come from the local
machine: Inductor's CPU C++ compile,
torchrunrendezvous on its hostname, no/proconmacOS, too little free disk for a video-service test, and a Qwen CPU golden test.
allows one BF16 ulp per element: one runner's BF16 GEMM kernels rounded 2 of 48 elements one
ulp apart, while a real attention-math difference moves elements by 2 to 4 ulps. The driver
test's BF16 case bounds differences by the peak magnitude. Its FP32 case checks the CFG scale
and negative-prompt wiring; a wrong scale or a dropped negative prompt misses its 1e-4 bound
by two orders of magnitude.
requirements-cuda.lockenvironment (Torch 2.11):tests/unit/backends/test_cuda_hunyuan15_joint_attention.pypassed 20 of 20, including thefused kernel against its reference;
difflet runanddifflet serveeach generated a Gvideo in eager mode; warm latency was within 3% of the Torch 2.13 runs.
branch at
baf10e46, before the rebase onto main. The rebase leaves the HunyuanVideo 1.5code, the GPU application, the ops and serving byte-identical; on this model's path it brings
in only feat(ltx2): add single-GPU CUDA audiovisual generation #147's
--negative-prompthandling in the GPU CLI, which the comparison did not use.Nothing was re-run on a GPU after that: since the rebase, the branch has changed only CPU
tests, docs and one code comment.
ruff checkpasses on the changed files.black --checkpasses on them except for lines inregistry.pyandcli/main.pythat are unformatted on main too; this PR leaves those linesalone.
🤖 Generated with Claude Code