Skip to content

[None][feat] Wan 2.2 serve: extend video generation API#13783

Closed
wu6u3tw wants to merge 11 commits into
NVIDIA:mainfrom
wu6u3tw:wan22-serve-fields
Closed

[None][feat] Wan 2.2 serve: extend video generation API#13783
wu6u3tw wants to merge 11 commits into
NVIDIA:mainfrom
wu6u3tw:wan22-serve-fields

Conversation

@wu6u3tw

@wu6u3tw wu6u3tw commented May 5, 2026

Copy link
Copy Markdown
Contributor

Description

Adds four serve-side capabilities to the Wan 2.2 video generation pipeline.
All are independent, backward-compatible additive extensions to existing
public-facing schemas (no internal API changes).

  1. VideoGenerationRequest knobs: guidance_scale_2, boundary_ratio,
    num_frames. guidance_scale_2/boundary_ratio are routed through
    extra_params to match WanPipeline.infer's extra.get(...) reads.
    Also defaults guidance_scale_2 = 3.0 on Wan 2.2 A14B when unset
    (was = guidance_scale, which is wrong for two-stage CFG).

  2. fixed_latent_path: VisualGenArgs option to load a pre-computed
    latent tensor and substitute it for sampled latents at request time —
    used for MLPerf-deterministic generation. Lazy-loaded on first
    inference (pipeline construction runs inside MetaInitMode, where
    torch.load on a meta tensor would raise). Mirrors the existing
    extra_attrs pattern used by spatial_upsampler_path /
    distilled_lora_path. Warmup passes _use_fixed_latent=False.

  3. response_format = "video_path": server returns JSON with the
    on-disk path instead of streaming bytes — for filesystem-shared
    deployments (e.g. Lustre). Avoids holding HTTP open through encode

    • transfer of multi-MB payloads. Default remains "video_bytes".
  4. output_format = "raw": uncompressed uint8 RGB byte dump
    (T·H·W·3 layout, .rgb.bin). No ffmpeg dependency, no encode
    latency. Worst-case payload ~224 MB at 720x1280x81. Default remains
    "auto".

Test Coverage

  • Existing tests/unittest/_torch/visual_gen/test_wan22_t2v_pipeline.py
    exercises the WAN pipeline path; new fields are additive and do not
    change existing test behavior.
  • Manual verification: trtllm-serve <Wan2.2-T2V-A14B-Diffusers> then
    POST VideoGenerationRequest with each new field combination.

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why.

  • PR Follows TRT-LLM CODING GUIDELINES.

  • Test cases provided for new code paths (additive backward-compatible fields; no new code paths require dedicated tests beyond manual server verification).

  • Any new dependencies have been scanned for license and vulnerabilities (none added).

  • CODEOWNERS updated if ownership changes (no ownership change).

  • Documentation updated as needed.

  • Update tava architecture diagram if there is a significant design change in PR (n/a).

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

@wu6u3tw
wu6u3tw requested review from a team as code owners May 5, 2026 23:57
@wu6u3tw
wu6u3tw requested review from Wanli-Jiang, moraxu and syuoni May 5, 2026 23:57
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds MLPerf-deterministic video generation support via fixed latent tensors, extends the video generation API with diffusion controls (guidance_scale_2, boundary_ratio, num_frames), and introduces raw RGB binary video export. Changes flow through config, model pipeline, OpenAI protocol, server, and utilities layers.

Changes

Fixed Latent Support

Layer / File(s) Summary
Config & Field Definition
tensorrt_llm/_torch/visual_gen/config.py
VisualGenArgs adds fixed_latent_path: Optional[str] to specify a pre-computed latent tensor path. Field is propagated to extra_attrs in from_pretrained for downstream access.
Pipeline Implementation
tensorrt_llm/_torch/visual_gen/models/wan/pipeline_wan.py
Wan pipeline initializes fixed_latent_path and fixed_latent attributes before superclass setup. Forward method signature adds _use_fixed_latent: bool = True control flag. Latent preparation lazily loads fixed latent when available and enabled; warmup disables fixed latent usage. guidance_scale_2 default behavior adjusted to 3.0 for Wan 2.2 A14B two-stage when undefined.

Extended Diffusion Controls Protocol

Layer / File(s) Summary
Protocol Definition
tensorrt_llm/serve/openai_protocol.py
VideoGenerationRequest gains guidance_scale_2 (optional float), boundary_ratio (optional float with range constraints), and num_frames (optional int with minimum constraint) to enable extended diffusion control parameters.
Server Request Parsing & Response
tensorrt_llm/serve/openai_server.py
Multipart/form-data parsing in _parse_video_generation_request now extracts new numeric fields (guidance_scale_2, boundary_ratio, num_frames). Synchronous video generation path gains support for response_format == "video_path" to return JSON (video_id and path) instead of streaming bytes.
Parameter Routing
tensorrt_llm/serve/visual_gen_utils.py
parse_visual_gen_params propagates guidance_scale_2 and boundary_ratio from VideoGenerationRequest into extra_params. Adds logic to use num_frames from request when provided, otherwise compute from seconds and fps.

Raw Video Export

Layer / File(s) Summary
Format Registration & Implementation
tensorrt_llm/serve/media_storage.py
resolve_video_format adds "raw" format returning extension ".rgb.bin". MediaStorage.save_video new "raw" branch dumps video tensor as uint8 RGB bytes without encoding, dropping audio and normalizing tensor to uint8 if needed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: extending the video generation API for Wan 2.2 serve with new parameters and features.
Description check ✅ Passed PR description comprehensively covers all changes with clear organization, rationale, and testing approach that aligns with the provided template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tensorrt_llm/serve/openai_server.py (2)

1951-1957: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle raw outputs with the correct content type.

resolve_video_format("raw") produces a .rgb.bin file, but this branch still maps everything except .mp4 to video/x-msvideo. For output_format="raw" the sync endpoint will stream arbitrary RGB bytes with an AVI media type.

Suggested fix
             actual_path = Path(actual_output_path)
-            media_type = "video/mp4" if actual_path.suffix == ".mp4" else "video/x-msvideo"
+            if actual_path.suffix == ".mp4":
+                media_type = "video/mp4"
+            elif actual_path.suffix == ".avi":
+                media_type = "video/x-msvideo"
+            else:
+                media_type = "application/octet-stream"
 
             return FileResponse(
                 actual_output_path,
                 media_type=media_type,
                 filename=actual_path.name,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/serve/openai_server.py` around lines 1951 - 1957, The current
FileResponse branch always uses "video/x-msvideo" for non-.mp4 files, causing
raw outputs like ".rgb.bin" (from resolve_video_format("raw")) to be served with
the wrong MIME type; update the logic around actual_path / actual_output_path to
detect raw output (e.g., check actual_path.suffix == ".rgb.bin" or the local
variable output_format == "raw") and set media_type to a generic binary type
such as "application/octet-stream" (or a more specific raw/RGB MIME if desired)
before returning FileResponse so raw RGB byte streams are served with the
correct content type.

1997-2000: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse response_format from multipart requests too.

The new response_format="video_path" mode is only honored for JSON bodies right now. Multipart requests never copy response_format into data, so they always fall back to the model default and cannot opt into the new path-returning behavior.

Suggested fix
-            for field in ["model", "size", "negative_prompt", "output_format"]:
+            for field in ["model", "size", "negative_prompt", "output_format", "response_format"]:
                 if field in form and form[field]:
                     data[field] = form[field]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/serve/openai_server.py` around lines 1997 - 2000, The multipart
parsing loop that copies optional string fields into data (for field in
["model", "size", "negative_prompt", "output_format"]) omits the new
response_format option, so multipart requests never get data["response_format"];
update that loop to include "response_format" (or otherwise copy
form["response_format"] into data when present) so the
response_format="video_path" mode is honored for multipart requests; look for
the variables form and data in openai_server.py to apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tensorrt_llm/_torch/visual_gen/models/wan/pipeline_wan.py`:
- Around line 483-495: The loaded fixed latent (in the _use_fixed_latent branch
where self.fixed_latent_path is used to set self.fixed_latent) must be validated
against the current expected latent shape (batch, channels, latent_frames,
latent_h, latent_w) before reuse; after torch.load and before assigning latents,
check self.fixed_latent.shape matches the computed target shape (derived from
the current batch size, self.latent_channels, computed latent_frames from
num_frames, and latent spatial dims from input resolution) and if it mismatches
either raise a clear ValueError indicating the expected vs actual shape or
optionally reshape/replicate the tensor only if a safe, documented
transformation is possible; update the logic around
self.fixed_latent.to(device=self.device, dtype=self.dtype) to run after
validation so you never pass an incompatible tensor into the denoiser/decoder
(refer to symbols: _use_fixed_latent, fixed_latent_path, fixed_latent, latents,
device, dtype).

In `@tensorrt_llm/serve/openai_server.py`:
- Around line 2013-2020: The multipart parsing currently treats form values by
truthiness and thus drops valid zero values (e.g., "0" or "0.0"); update the
checks for guidance_scale_2, boundary_ratio, guidance_rescale, and num_frames so
they only skip when the form key is missing or the value is an empty string (not
when the value is "0" or "0.0"), then parse and assign into the data dict as
before; locate the block in openai_server.py that sets data["guidance_scale_2"],
data["boundary_ratio"], data["guidance_rescale"], and data["num_frames"] and
replace the truthiness guards with explicit existence/empty-string checks before
casting.

---

Outside diff comments:
In `@tensorrt_llm/serve/openai_server.py`:
- Around line 1951-1957: The current FileResponse branch always uses
"video/x-msvideo" for non-.mp4 files, causing raw outputs like ".rgb.bin" (from
resolve_video_format("raw")) to be served with the wrong MIME type; update the
logic around actual_path / actual_output_path to detect raw output (e.g., check
actual_path.suffix == ".rgb.bin" or the local variable output_format == "raw")
and set media_type to a generic binary type such as "application/octet-stream"
(or a more specific raw/RGB MIME if desired) before returning FileResponse so
raw RGB byte streams are served with the correct content type.
- Around line 1997-2000: The multipart parsing loop that copies optional string
fields into data (for field in ["model", "size", "negative_prompt",
"output_format"]) omits the new response_format option, so multipart requests
never get data["response_format"]; update that loop to include "response_format"
(or otherwise copy form["response_format"] into data when present) so the
response_format="video_path" mode is honored for multipart requests; look for
the variables form and data in openai_server.py to apply the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 98e8c215-c95f-4c65-9382-a6fe519d8698

📥 Commits

Reviewing files that changed from the base of the PR and between 28b3471 and 6a16992.

📒 Files selected for processing (6)
  • tensorrt_llm/_torch/visual_gen/config.py
  • tensorrt_llm/_torch/visual_gen/models/wan/pipeline_wan.py
  • tensorrt_llm/serve/media_storage.py
  • tensorrt_llm/serve/openai_protocol.py
  • tensorrt_llm/serve/openai_server.py
  • tensorrt_llm/serve/visual_gen_utils.py

Comment thread tensorrt_llm/_torch/visual_gen/models/wan/pipeline_wan.py
Comment thread tensorrt_llm/serve/openai_server.py Outdated
@wu6u3tw wu6u3tw changed the title [None][feat] Wan 2.2 serve: extend video generation API [None][feat] Wan 2.2 serve: extend video generation API May 6, 2026
@wu6u3tw wu6u3tw changed the title [None][feat] Wan 2.2 serve: extend video generation API [None][feat] Wan 2.2 serve: extend video generation API May 6, 2026
@zhenhuaw-me

Copy link
Copy Markdown
Member

@wu6u3tw Can you clarify the motivation of this change? We are refactoring the Python APIs, once we have a "beta" python API, we plan to get back to the serving endpoint changes.

@wu6u3tw wu6u3tw changed the title [None][feat] Wan 2.2 serve: extend video generation API [None][feat] Wan 2.2 serve: extend video generation API May 6, 2026
@wu6u3tw

wu6u3tw commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

@zhenhuaw-me
I am adding support for adding param: gaidence_scale_2 and fixed_latened pt into trtllm-serve which is a supported param in non-serve run

@wu6u3tw

wu6u3tw commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

@zhenhuaw-me Let's sync offline for the api change plan.

@wu6u3tw
wu6u3tw force-pushed the wan22-serve-fields branch from a460b90 to 65ed452 Compare May 11, 2026 23:33
Comment thread tensorrt_llm/_torch/visual_gen/models/wan/pipeline_wan.py Outdated
Comment thread tensorrt_llm/media/encoding.py Outdated
Comment thread tensorrt_llm/serve/openai_protocol.py
@wu6u3tw
wu6u3tw force-pushed the wan22-serve-fields branch from 65ed452 to 43ac610 Compare May 13, 2026 18:38
wu6u3tw added 2 commits May 13, 2026 16:54
…terministic + filesystem-shared deployments

Adds four serve-side capabilities to the Wan 2.2 video generation
pipeline. They are independent extensions to existing public-facing
schemas (no internal API changes).

1. Extra Wan 2.2 generation knobs in VideoGenerationRequest:
   - guidance_scale_2 (second CFG scale for two-stage denoising)
   - boundary_ratio  (timestep ratio for switching guidance scales)
   - num_frames      (overrides implicit int(seconds * fps))
   guidance_scale_2 / boundary_ratio are routed through extra_params
   to match pipeline_wan.WanPipeline.infer's extra.get(...) reads.
   Also defaults guidance_scale_2 to 3.0 on Wan 2.2 A14B when unset
   (was = guidance_scale, which is wrong for two-stage CFG).

2. fixed_latent_path: VisualGenArgs option to load a pre-computed
   latent tensor from disk and substitute it for sampled latents at
   request time. Used for MLPerf-deterministic generation across
   runs. Lazy-loaded on first inference (pipeline construction runs
   inside MetaInitMode, where torch.load on a meta tensor would
   raise). Mirrors the existing extra_attrs propagation pattern used
   by spatial_upsampler_path / distilled_lora_path. Warmup passes
   _use_fixed_latent=False so the multi-shape warmup grid is not
   forced through the fixed_latent shape.

3. response_format = "video_path": when set, the server returns
   JSON with the on-disk path to the generated video instead of
   streaming bytes back. Useful when client and server share a
   filesystem (e.g. Lustre) - avoids holding the HTTP connection
   open through encode + transfer of multi-MB payloads, and avoids
   the per-replica streaming-tail effect that bytes mode exhibits at
   high concurrency. Backward-compatible: default is "video_bytes".

4. output_format = "raw": uncompressed uint8 RGB byte dump
   (T*H*W*3 layout, .rgb.bin extension). No ffmpeg dependency, no
   encode latency. Worst-case payload ~224 MB at 720x1280x81.
   Backward-compatible: default remains "auto".

Files touched
-------------
* tensorrt_llm/serve/openai_protocol.py
* tensorrt_llm/serve/openai_server.py
* tensorrt_llm/serve/visual_gen_utils.py
* tensorrt_llm/serve/media_storage.py
* tensorrt_llm/_torch/visual_gen/config.py
* tensorrt_llm/_torch/visual_gen/models/wan/pipeline_wan.py

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
@wu6u3tw
wu6u3tw force-pushed the wan22-serve-fields branch from 249bdc9 to 8e0a070 Compare May 13, 2026 23:59
wu6u3tw added 3 commits May 13, 2026 17:04
… (.pt)

Per review feedback on NVIDIA#13783:
swap output_format='raw' (uint8 RGB byte stream, .rgb.bin) for
output_format='pt' (torch.save of the video tensor, .pt). The .pt
format is symmetric with fixed_latent_path's torch.load consumer and
makes cross-framework comparisons trivial. Drops the dtype-cast-to-
uint8 step since torch.save preserves the original tensor.

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
…_routes

Three independent fixes on the new multipart/JSON video-generation
path, per CodeRabbit feedback on PR NVIDIA#13783:

1. response_format on multipart: add 'response_format' to the optional
   string-field copy loop so multipart clients can opt into the new
   response_format='video_path' mode. Without this it was silently
   dropped and the request fell back to the model default.

2. Numeric zero values: replace truthiness guards with
   form.get(...) not in (None, '') for the three PR-added numeric
   fields (guidance_scale_2, boundary_ratio, num_frames).
   boundary_ratio explicitly allows 0.0 per its Pydantic constraint
   (ge=0.0, le=1.0); the old guard silently dropped a valid request.
   The pre-existing pattern for older fields (seconds/fps/guidance_scale)
   is left alone to keep the diff surgical.

3. media_type for .pt: when output_format='pt' and
   response_format='video_bytes', the FileResponse was returning the
   tensor with 'video/x-msvideo'. Map .pt to application/octet-stream.

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
Per CodeRabbit review on PR NVIDIA#13783: the fixed_latent tensor is loaded
once and reused across requests, but each request may carry a
different batch_size / num_frames / resolution. Without an upfront
shape check, a mismatch surfaces deep inside denoising or decoding
with an unactionable error.

Compare the loaded tensor against the same shape formula
_prepare_latents uses (in_channels, (num_frames-1)/temporal_scale + 1,
spatial dims) and raise a ValueError pointing at the latent path so
operators know to regenerate it.

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
@wu6u3tw

wu6u3tw commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

Fixed based on the reviews.

wu6u3tw added 3 commits May 18, 2026 14:33
…e for fixed_latent_path

Allows per-run override of the MLPerf deterministic fixed latent path
without editing YAML configs. Env var takes precedence over
VisualGenArgs.fixed_latent_path; both still work, addressing reviewer
feedback that the path may be used as a debug/benchmark knob.

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
Make the VideoGenerationRequest.num_frames docstring explicit that when
set, num_frames takes precedence over both seconds and fps (matches
reviewer's request that the override behavior be documented).

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
…ssignment

Single-line assignment satisfies the ruff-format hook that flagged the
multi-line form added in a42d32d.

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
@wu6u3tw

wu6u3tw commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@zhenhuaw-me friendly ping — when you have a moment, could you take another look?

Since your last round of feedback, I've pushed:

  • a42d32d — made fixed_latent_path a hybrid: env var TLLM_MLPERF_FIXED_LATENT_PATH now takes precedence over the VisualGenArgs.fixed_latent_path config field, so the debug/benchmark workflow you suggested is supported. (Left the discussion open for your call on whether the hybrid is acceptable, or you'd prefer env-var-only.)
  • 933ae99 — added shape validation for the lazy-loaded fixed latent (CodeRabbit's suggestion, confirmed by them).
  • 76d3adc — zero-value-safe multipart parsing for guidance_scale_2 / boundary_ratio / num_frames (CodeRabbit's suggestion, confirmed by them).
  • bc5dd2f — replaced the raw RGB byte dump with torch.save (.pt), matching your pt comment.
  • a796cf1 — made num_frames docstring explicit that it overrides both seconds and fps, per your suggestion.

All CI checks are green. Thanks!

@chang-l chang-l left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approve to unblock — please also wait for @zhenhuaw-me to sign off as well

Comment thread tensorrt_llm/_torch/visual_gen/config.py Outdated
wu6u3tw added 2 commits May 21, 2026 10:31
…-var only

Per reviewer feedback, the fixed-latent path is purely a debug/benchmark
knob for MLPerf-deterministic generation and does not belong on the
public VisualGenArgs config surface. Remove the field and its
extra_attrs propagation; the WAN pipeline now reads the path solely from
the TLLM_MLPERF_FIXED_LATENT_PATH environment variable.

Signed-off-by: Tin-Yin Lai <tinyinl@nvidia.com>
@wu6u3tw

wu6u3tw commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

@zhenhuaw-me I am wondering in you API change, is the extra param wired into your new API?

@wu6u3tw

wu6u3tw commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Covered in other PRs

@wu6u3tw wu6u3tw closed this Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants