Compare commits

...
Author SHA1 Message Date
59db6a4285 [Rebase] Rebase to vLLM 0.30.0 (#7820)
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
Signed-off-by: Gao Han <hgaoaf@connect.ust.hk>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
2026-09-22 15:52:01 +00:00
yanbao1217andyanbao1217 ca14e635f5 [Doc] Correct the SessionClosed admission-slot guarantee (#7636 Issue 22) (#7869)
Signed-off-by: yanbao1217 <yanliuwebsite493@gmail.com>
Co-authored-by: yanbao1217 <yanliuwebsite493@gmail.com>
2026-09-22 14:57:50 +00:00
Sun d278aa8057 [Bugfix] Mark image edit benchmark stream errors as failed (#8015)
Signed-off-by: levius <2114377220@qq.com>
2026-09-22 14:56:30 +00:00
Si Chen f1057b1e0d [Bugfix] Restore GLM-Image unshifted timesteps after set_timesteps (#7953)
Signed-off-by: sinksilk <785976238@qq.com>
2026-09-22 13:45:55 +00:00
Sun 03ccce9a99 [Bugfix] Mark Omni chat benchmark streams with error events as failed (#8000)
Signed-off-by: levius <2114377220@qq.com>
2026-09-22 06:28:18 -07:00
Clodagh Walsh 7af2c3d351 [CI][Voxtral] Resolve failing test_prepare_speech_generation_awaits_voxtral_async (#7601)
Signed-off-by: Clodagh Walsh <clodaghwalsh17@gmail.com>
2026-09-22 06:07:35 -07:00
Yueqian Lin 3032b4a013 [CI] Bump the public env var snapshot count after #6848 landed (#7964)
Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
2026-09-22 08:38:08 -04:00
ZacheryAUandamy-why-3459 0582844a41 [Frontend][Benchmark] Add duplex performance metrics to refactored full-duplex framework (#7714)
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-22 03:04:22 -07:00
Wang fuyin 9ebef4b1d3 [Feat] Add prefetch for Mooncake cross-stage paged KV transfer in HunyuanImage3 (#7637)
Signed-off-by: Acerak01-fy <wfy2003324@163.com>
2026-09-22 02:21:42 -07:00
vOv f7e28348ad [Bugfix] Stabilize NIXL manager receiver tests (#7984)
Signed-off-by: cr-gao <gaochenrui@sjtu.edu.cn>
2026-09-22 16:11:42 +08:00
vOv 939e6ba56e [CI/Build] Give diffusion CPU tests more timeout headroom (#7983)
Signed-off-by: cr-gao <gaochenrui@sjtu.edu.cn>
2026-09-22 15:47:00 +08:00
Tianyao Wu ba5b7485a7 [Bugfix] Keep resumable duplex prompts out of async-chunk prewarm (#7992)
Signed-off-by: Tianyao Wu <rayroy31@gmail.com>
2026-09-22 15:36:42 +08:00
Armaan Amatya f3f8ebfc25 [CI] Count VLLM_OMNI_SPEAKER_REGISTRATION_POLICY in the inventory snapshot (#7977)
Signed-off-by: Armaan Amatya <armaanamatya2014@gmail.com>
2026-09-22 05:45:23 +00:00
Sy03 c7cd37b61e [Model] Reduce MOSS TTS reference audio preparation overhead (#7725)
Signed-off-by: Sy03 <1370724210@qq.com>
2026-09-22 12:19:18 +08:00
汪志鹏 f8a00b1491 [Misc] Add portable MiniMax H3 skills (#7923)
Signed-off-by: princepride <wangzhipeng628@gmail.com>
2026-09-22 11:10:42 +08:00
Wang fuyin 303edff930 [Doc][skip ci] Add design doc for scheduler-managed diffusion paged KV cache (#6891)
Signed-off-by: Acerak01-fy <wfy2003324@163.com>
2026-09-22 10:53:37 +08:00
wangyu 0570b0da7e [CI][Perf] migrate diffusion DFX benches to vllm bench serve --omni (#7737)
Signed-off-by: wangyu <410167048@qq.com>
2026-09-21 19:16:39 -07:00
a4ad73e08b [Bugfix] Fix GLM-TTS DiT float32 crash with flash-attn backends (#7756)
Signed-off-by: Sworol <171231900+Sworol@users.noreply.github.com>
Co-authored-by: Sworol <171231900+Sworol@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-21 19:13:20 -07:00
akshatvishu 502a86fff7 [Core] Optimize Qwen2.5-Omni Token2Wav buffer loading (#7061)
Signed-off-by: akshatvishu <akshatnayak197@gmail.com>
2026-09-21 19:13:12 -07:00
liuyihua95 36cdeb8fda [Bugfix][TTS] Fix Audio8 voice clone reference resolution (#7815)
Signed-off-by: liuyihua <i@liuyihua.com>
2026-09-21 19:13:03 -07:00
jeffaa729 5152b9bf4c [Bugfix][Model] Fix CoVo-Audio prompt processing and dummy loading (#7909)
Signed-off-by: jeffaa729 <hoiwanglo@gmail.com>
2026-09-21 19:12:54 -07:00
Gawr Gura 0721 6d339671ed [Bugfix] Reject stray <|AUDIO|> placeholder with use_audio_in_video in Qwen2.5-Omni (#7959)
Signed-off-by: RunguoLi <li19107254665@gmail.com>
2026-09-21 19:12:46 -07:00
NATURE c1bfd60e43 [Frontend] Remove obsolete experimental duplex client parameters (#7647)
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
2026-09-21 19:12:36 -07:00
192 changed files with 8647 additions and 1324 deletions

No files matched your search

@@ -0,0 +1,64 @@
---
name: 3d-animation-short-generator
description: Develop stylized 3D narrative shorts through story, character and environment references, timed shot tables, storyboards, generation, and assembly. Use for coherent animated storytelling or its planning stages, not isolated image edits or photorealistic live action.
metadata:
compatibility: File-based agent workflow with sibling h3-prompt-writing. Media generation and editing require available tools or a configured backend.
---
# 3D Animation Short Generator
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md).
For a complete film, progress from story to references, shot table, text storyboard,
clips, and assembly. Keep these as local artifacts, using existing user decisions
and authorization. If only a planning stage is requested, deliver that stage.
## Story and visual contract
Capture premise, desired emotional outcome, duration, ratio, model, and audio mode.
Use warm stylized 3D as a default only when style is unspecified: readable geometric
silhouettes, designed hair clumps, tactile materials, soft skin shading where
appropriate, expressive brows/eyes, and elastic posing. User-selected 2D or other
styles override this preset rather than receiving accidental 3D texture.
Create an active protagonist with a want, obstacle, meaningful choice, and payoff.
Connect the ending to an earlier visual/emotional anchor. Use as many beats as the
duration needs; do not pad a tiny story into an eight-beat formula. Dialogue should
change a relationship or move the action, and use the user's requested language.
## Reference design
- Character references lock name, silhouette, proportions, face, hair, costume,
signature props, and color. Label identity in the production document; clean
rendering anchors should not carry labels that could leak into the video.
- Scene references contain environments only unless the user requested a composite.
Record landmarks, lighting direction/time of day, entrances, exits, and prop state.
- A reference board with multiple views is for design review. Prefer appropriate
clean views for model input so grids and pose sheets do not become final frames.
- When an asset changes, update all affected prompts and clips, with a manifest
identifying the current revision.
## Shot planning
Read [shot-table-spec.md](references/shot-table-spec.md) for the six-column table
and continuity checks. Then read
[storyboard-guidelines.md](references/storyboard-guidelines.md) for the matching
text storyboard and optional pencil previews. The text storyboard is the source
of shot timing; preview art does not silently replace it.
Vary shot size and elastic performance to serve the story. Camera tilts, close-ups,
or exaggeration are choices for specific beats, not quotas. Track off-screen
characters and spatial landmarks so they do not teleport back into later shots.
## Generation and assembly
Before rendering, read [model-selection.md](references/model-selection.md) and
[fallback-policy.md](references/fallback-policy.md). Use the chosen model through
available tools. Compile prompts using the H3 base or reference guide as appropriate.
Remove storyboard-only labels, arrows, panel borders, and sketch styling from
rendering inputs. Tie each clip to the current scene and character references.
Read [qc-checklist.md](references/qc-checklist.md) for assembly and final review.
Keep the planned shot order and audio continuity. Use a continuous score when
requested, duck it under dialogue/SFX, and do not add subtitles unasked. Deliver
the actual film or the requested planning package, with reproducible prompts and
any unresolved identity, scene, or timing defects.
@@ -0,0 +1,4 @@
interface:
display_name: "3D Animation Shorts"
short_description: "Plan stories, character anchors, shots, and edits"
default_prompt: "Use $3d-animation-short-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,21 @@
# Targeted generation recovery
Compare the failed result against the shot's actual identity/spatial anchors.
Classify the failure: wrong input or API mapping, reference drift, overloaded
action, timing, audio, or a render/export failure. Fix transport/configuration
errors before spending another generation attempt.
For drift, quote the relevant anchors and remove unrelated reference roles. For
overloaded action, simplify or split the shot while preserving the requested total
duration and handoffs. For a scene change, use the new scene reference and avoid
conditioning on a previous scene when that would contradict the new setting.
Use the retry/resource limit from the shared portable workflow. A persistent
failure should lead to a concrete choice (simpler action, a supplied reference,
an authorized alternate model, or an explicitly incomplete shot), not automatic
provider switching or an endless loop. Never present a placeholder as a finished
clip or hide an incorrect scene in final assembly.
For optional pencil previews, reduce annotation or panel count. Use the text
storyboard if visualization is not essential; do not rerender correct video
solely because a human-review sketch changed.
@@ -0,0 +1,21 @@
# Model and format selection
Honor an existing model choice. Otherwise prefer MiniMax H3 when available, and
inspect the running backend for partitions, reference modes, audio, resolutions,
duration limits, and long-video options. Do not assume hosted and local services
have identical limits, quality, prices, or parameter names.
If another model is requested, use its real documented input contract. A provider
switch needs the user's authorization; never silently use a different service
because a preset names it as a fallback. For an authorized mixed-model workflow,
record model and settings per shot and check cross-shot style differences.
Keep delivery resolution separate from native generation resolution. Record actual
width/height, fps, frame count, and any crop, retiming, or upscaling. Do not claim
native 2K when the video was generated at 768p and restored later.
Compile shot action, camera, identity, scene, and audio into the chosen model's
prompt format. For H3 use the bundled writing guides. Strip production labels and
preview grids. Prompt for designed silhouettes, elastic action, material and
lighting details when these belong to the user's 3D style; do not add text/UI just
because a model can generate them.
@@ -0,0 +1,19 @@
# Assembly and review
Use current clip revisions in shot-table order. Preserve useful dialogue and SFX;
mix a continuous requested score underneath them. Inspect edits at sound bridges
and dialogue boundaries, avoiding cut vowels and accidental audio gaps. Only add
subtitles, music, or retiming consistent with the brief.
Review identity, landmarks, screen direction, light/time of day, prop state,
emotional payoff, intelligibility, SFX timing, and music balance. Look for leaked
storyboard panels, sketch lines, arrows, labels, timecodes, or watermarks. Verify
the complete file's duration, dimensions, fps, frame count, audio, and decode.
Keep planning documents, references, raw clips, and final edits in distinguishable
folders. Record the exact assets used in the final edit. After a character, scene,
prompt, clip, or score is revised, update its downstream dependents and archive the
previous result; do not silently combine stale and current versions.
Report observed defects and what was repaired, including external editing or
upscaling. A successful API response proves completion, not visual correctness.
@@ -0,0 +1,30 @@
# Six-column shot table
| Shot ID & Duration | Continuity Handoff | Reference Anchors (Spatial + Identity) | Hook Type | Shot Description (Per-Second Directives) | Audio & Dialogue Track |
| --- | --- | --- | --- | --- | --- |
Use stable IDs such as S01. Record incoming and outgoing image state, prop
position, eyeline, motion, and emotional state in Continuity Handoff.
Reference Anchors contains named landmarks and screen-relative positions; each
character's screen position, facing and pose; recently exited characters' locations;
lighting baseline and modifiers; and exact character/scene asset paths or IDs.
An explicit scene/time change can replace these anchors; an accidental flip cannot.
Hook Type names the shot's purpose: setup, reveal, reversal, joke, suspense,
tenderness, chase, or callback. Do not force a joke or cut into a quiet scene.
For each second or meaningful sub-second beat, cover action/pose/expression,
camera, spatial/prop state, sound, and the handoff to the next interval. Use compact
phrases rather than inventing movement in intentionally still moments. Include
anticipation, squash/stretch, overshoot, and follow-through where appropriate.
Keep the complete dialogue/narration script in Audio & Dialogue Track, with
speaker, timing, delivery, and SFX. Distinguish off-screen narration from speaking
characters; a visible listener should not lip-sync the narrator's words.
Before rendering, check full time coverage, supported generation durations, a
readable number of active characters, matching scene/identity references, and
coherent row-to-row handoffs. Mark intentional cuts/time jumps. Fix contradictory
states rather than using a generic continuity assurance. A visual shot may span
multiple generation windows when the verified backend supports continuation.
@@ -0,0 +1,29 @@
# Text storyboard and optional visualization
Maintain one `storyboard.md` with a section per shot. Each section contains:
1. Shot ID, title, duration, and narrative purpose.
2. Exact scene and character reference IDs.
3. Spatial anchors: landmarks, character positions/facing, off-screen characters,
key/fill/rim lighting, and important props.
4. Incoming and outgoing continuity state.
5. Time-ordered beats: pose/expression, camera, audio, and anchor changes.
Use per-second or finer intervals only where meaningful; cover the complete shot.
For narrated moments, specify whether a visible character speaks or only reacts.
Optional ASCII staging diagrams can clarify screen direction but are not images
to supply as rendering references.
For heavy revision of a shot, extract its section to a named file and link it from
the main document. Choose one authoritative version; reintegrate or update the
link after revision so later generation cannot read stale content.
Only generate pencil storyboards when requested or already authorized as previews.
Use equal-sized time-ordered panels, with timecode, pose/expression, camera arrow,
and audio/anchor notes. Suggested layouts: three panels in a strip, four in a 2×2
grid, six in 2×3. More complex scenes may need fewer representative key poses.
Keep shot/character/scene labels readable for human review, but never feed these
annotated grids as clean final frames. The text timeline remains authoritative.
If preview panels fail, simplify annotation or rely on text within the agreed
scope. An optional visualization failure need not block a usable text storyboard.
+53
View File
@@ -0,0 +1,53 @@
# MiniMax H3 skill provenance
Upstream: https://github.com/MiniMax-AI/MiniMax-H3
Source snapshot: `d21241f0a4b3acbb34c97dae47fa417b7065e438`, retrieved 2026-09-19.
Source directory:
https://github.com/MiniMax-AI/MiniMax-H3/tree/d21241f0a4b3acbb34c97dae47fa417b7065e438/skills
The nine upstream skill names are retained. Each local skill corresponds to the
same-named upstream `skills/<name>/` directory. Credit for the original creative
workflows and templates belongs to MiniMax-AI and the upstream contributors.
## Files and adaptation
- `h3-prompt-writing/references/base-format.md` and `ref-format.md` are new,
condensed descriptions of the public prompt interface. They preserve necessary
field names and notation but do not copy the upstream guides or their examples.
The entrypoint adds a portable execution reference and clarifies clip/window
duration for verified long-video backends.
- The eight style-specific `SKILL.md` files are rewritten portable adaptations of
the upstream workflows. Their reference documents retain the relevant creative
methods while removing host-specific execution assumptions.
- The two `co-op-game-intro-generator/references/` templates retain the upstream
creative structure with an added portable contract and explicit user overrides.
Their instructions are normalized to English, and the video template expresses
visible UI copy as localized variables with English defaults. It also removes
an unrequested gender restriction and a fixed yellow/black environment
transformation.
- The five 3D animation references are condensed adaptations of their upstream
counterparts: shot table, storyboards, model selection, fallback, and review.
- Product, papercraft, collage, and music references extract reusable direction
from their corresponding upstream skill. The shared portable workflow, catalog,
and UI metadata are additions for this repository.
No upstream `LICENSE`, `COPYING`, or `NOTICE` file was present in the Git tree at
the pinned snapshot. Its README links to the MiniMax H3 Community License on the
model release. Because that license has redistribution conditions, this package
does not include the upstream prompt guides verbatim. This provenance record
preserves attribution and does not assert that this repository's code license
relicenses upstream material.
## Runtime changes
Canvas documents become local files and asset manifests; choice-card UI becomes
the host's normal clarification mechanism when a decision is actually missing.
Available generation/editing tools replace proprietary dispatch calls. Previous
user choices and authorization persist across steps. Providers, prices, resolution
support, and long-video features are checked rather than hardcoded as guarantees.
User intent overrides style presets, including instrumental/no-text work, selected
language, final morph shape, no-cut sequences, and custom player designs. Native
generation and external processing remain separately identified in delivery.
+96
View File
@@ -0,0 +1,96 @@
# Portable MiniMax H3 Skills
This directory contains one general prompt-writing skill and eight creative
skills adapted from
[MiniMax-AI/MiniMax-H3](https://github.com/MiniMax-AI/MiniMax-H3/tree/main/skills).
See [H3-SOURCES.md](H3-SOURCES.md) for the source revision, attribution, and
adaptation scope.
## Skill catalog
| Skill | Intended use |
| --- | --- |
| [h3-prompt-writing](h3-prompt-writing/SKILL.md) | Structured prompts for T2VA, I2VA, FL2VA, L2VA, and Ref2VA |
| [minimalist-product-ad-generator](minimalist-product-ad-generator/SKILL.md) | Minimal product ads, isolated product references, copy, and pacing |
| [3d-animation-short-generator](3d-animation-short-generator/SKILL.md) | 3D narrative shorts, character and environment design, shot tables, storyboards, and editing |
| [papercraft-stop-motion-explainer](papercraft-stop-motion-explainer/SKILL.md) | Layered paper art, puppets, mechanisms, and miniature educational scenes |
| [brand-promo-video-generator](brand-promo-video-generator/SKILL.md) | Brand promos, verified assets and claims, product interaction, and calls to action |
| [music-video-subtitle-generator](music-video-subtitle-generator/SKILL.md) | Music videos, instrumental visual narratives, beat planning, and optional lyric typography |
| [co-op-game-intro-generator](co-op-game-intro-generator/SKILL.md) | Two-player game menus, confirmation images, and animated world entry |
| [paper-collage-explainer-generator](paper-collage-explainer-generator/SKILL.md) | Halftone photo collage, incremental paper assembly, and visual metaphors |
| [handdrawn-live-video-generator](handdrawn-live-video-generator/SKILL.md) | Live action with rough luminous drawings, physical contact, continuous morphing, and delayed pursuit |
## Usage
Codex discovers repository skills from `.agents/skills/`. Clients that scan
skills only when a session starts may need a new session. Claude Code reads the
same files through the same-named relative symlinks in `.claude/skills/`. Other
agents that support Markdown skills can load the relevant `SKILL.md` directly
and follow its links to supporting references.
Examples:
```text
Use $music-video-subtitle-generator to design a 30-second two-dimensional
fluid-motion graphic for this instrumental track. Do not add lyrics or
subtitles. Preserve leftward motion and initially return only a storyboard and
three H3 prompts.
```
```text
Use $co-op-game-intro-generator to create a two-player game intro. The players
are Lin and Mei, the style is layered paper sculpture, and the title is
Together. Create the confirmation image first and wait for my approval before
generating video.
```
## MV smoke test
Test `music-video-subtitle-generator` together with `h3-prompt-writing` using a
user-owned song with timestamped lyrics and an original adult-character
reference image:
```text
Use $music-video-subtitle-generator and $h3-prompt-writing to turn this song
into a historical-romance music video about a woman and a man. Intercut
narrative acting with the woman's singing performance, do not display lyric
text, split the work according to the backend's verified clip duration, and
preserve the complete original song in the final master.
```
Verify that the global timeline matches the source audio duration, lyrics remain
verbatim, and each generation window records its incoming state, outgoing state,
and local timing. Ref2VA prompts must preserve the six-section field order and
resolve every reference label to a real input. The assembled video must decode
completely, and its final audio must come from the supplied source rather than a
model reconstruction.
When moving the collection to another repository or a user-level skill folder,
copy all nine skill directories and preserve their sibling layout. The eight
creative skills share `h3-prompt-writing/references/portable-workflow.md` and the
H3 format guides, so copying only one `SKILL.md` is insufficient. Keep each
skill's `references/` and `agents/` directories with it. `agents/openai.yaml`
provides optional Codex UI metadata and does not restrict the workflow to an
OpenAI-based agent or require a dedicated plugin.
## Execution boundaries
Prompt writing, storyboarding, and planning require only an agent that can read
and write files. Actual image, video, speech, music, and editing work uses tools
or services available in the current environment. When a capability is missing,
deliver the complete usable production package and identify the unfinished media
step.
The user's style, language, assets, audio, model selection, and existing
authorization take priority. An MV template does not add lyrics to an
instrumental track, and a clean motion-graphics request does not inherit film
grain. A prompt-only request does not start generation. When the user requests
approval of a confirmation image before rendering, preserve that checkpoint.
Treat vLLM-Omni long-video extensions as capabilities of the checked-out branch
and active backend. Do not present a development branch's continuation parameters
as a universal H3 API. Preserve real requests and raw outputs, distinguish model
results from post-production and upscaling, and inspect actual media properties.
These skills contain no model weights and do not automatically start a service
or invoke a paid generation endpoint.
@@ -0,0 +1,66 @@
---
name: brand-promo-video-generator
description: Plan, prompt, or produce brand, product, website, app, and service promotional shorts using traceable assets, accurate claims, timed beats, and clear calls to action. Use for launch and campaign videos, not invented product evidence or long narrative films.
metadata:
compatibility: File-based workflow with sibling h3-prompt-writing and available research, media generation, inspection, and editing tools.
---
# Brand Promo Video Generator
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md).
Use supplied brand assets, source links, and existing session decisions. No
proprietary tool allowlist or canvas is required. A completed promo requires real
generation/editing capability; otherwise deliver an explicitly incomplete production
package with finished prompts and a concrete generation handoff.
## Establish facts and assets
Inspect product images, UI screenshots, logo exports, typography, colors, and
campaign material. Resolve duration, ratio, audience, campaign focus, exact claims,
CTA, copy language, narration, and required deliverable from context. Ask for
missing identity-bearing assets when they are essential; do not fabricate logos,
product UI, screenshots, results, or performance claims.
Build a compact truth sheet from user-supplied originals and official sources.
Record asset ID, role, local path, source URL/note, and verification status. Do not
confuse a retrieved search thumbnail with an original brand asset. Distinguish
verified facts from clearly labeled concept copy. Preserve the requested language;
chat language alone does not justify translating supplied campaign text.
## Design a product-specific story
Choose a spine that exposes real cause and effect:
- Software: user intent → actual interaction → useful output → evidence → CTA.
- Physical product: reveal → interaction → mechanism/detail → use → benefit → CTA.
- Service: situation → process → evidence → outcome → promise.
- Image-led brand: authentic imagery → repeated visual motif → emotional payoff.
Plan time/frame ranges, asset IDs, one main action, product evidence, exact copy,
readable hold, color state, camera, transition, and rhythm. Use the actual output
fps for frame timing. Five to eight major beats can fit a 15-second promo, but
readability takes priority over a fixed beat count.
Let product edges, UI movement, light, cursor paths, or matched geometry motivate
transitions. Use a few energy peaks and deliberate settling moments. Preserve logo
clear space and readable screenshots. Avoid invented HUDs or metrics that look like
product proof. Summarize the truth sheet and plan before rendering; continue under
existing authorization unless the user requested approval at that stage.
## Produce and inspect
Use authentic still/UI plates for exact content. Generated material can supply
conceptual scenes, atmosphere, and transition geometry with clear provenance.
Compile actual H3 references and shots using the appropriate bundled guide.
Check backend resolution, duration, and audio support before calling it.
Use native instrumental/SFX direction when appropriate; do not generate a second
score automatically. Separate narration or music is useful when requested for
control or remixing. Preserve original SFX during assembly when they matter, and
duck music under speech. Do not add subtitles unless requested.
Check claims and exact product names against sources, inspect logo geometry and
copy, verify real UI states, and review transitions, aspect ratio, duration, audio,
and decoding. For wrong identity-bearing content, use the correct supplied/source
asset rather than polishing an imitation. Deliver the video, prompts/timeline,
asset manifest, and any remaining factual or visual limitations.
@@ -0,0 +1,4 @@
interface:
display_name: "Brand Promo Videos"
short_description: "Build brand promos from verified assets and claims"
default_prompt: "Use $brand-promo-video-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,73 @@
---
name: co-op-game-intro-generator
description: Design or generate a two-player co-op game menu and opening animation from player names, title, style, and optional identity references. Use for character-led game intros, not playable game implementation or complex multi-page interfaces.
metadata:
compatibility: File-based workflow with sibling h3-prompt-writing and bundled image/video templates. Rendering uses available generation tools.
---
# Co-op Game Intro Generator
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md).
Before writing a confirmation-image prompt, read
[h3-confirmation-image-template.md](references/h3-confirmation-image-template.md).
Before writing the video prompt, read
[h3-video-prompt-template.md](references/h3-video-prompt-template.md).
Both templates are required package resources, not remote tools.
## Resolve player and style bindings
Reuse or collect the two player names, game title, visual style, desired duration,
ratio, and optional identity references. The template defaults to a 15-second,
16:9 menu-to-world sequence; adapt it to explicit user requirements. Do not assume
the players' gender, body shape, outfits, or equipment from a generic example.
Use each portrait only for recognizable identity anchors: face silhouette, hair,
glasses, proportions, and distinguishing traits. Reinterpret rendering, skin
texture, lighting, and costume in the selected visual style. If a portrait or body
comparison reference is absent, write a text design rather than inventing an asset
label. Maintain player-to-name mapping across all references and frames.
## Confirmation image
Fill the template's style, palette, characters, title, and name variables. Keep
the two characters central, player cards toward the upper left, menu on the right,
and the continue action as the main interaction focus. The title establishes
identity without competing with that focus. Buttons, icons, typography, and all
player/equipment panels share one palette and style. Keep menu labels on one line.
The style changes material, shapes, fonts, and decoration; it should not randomly
rearrange the menu hierarchy. User-requested layout or ratio changes take priority
over template defaults. Preserve the exact supplied title/names and requested copy.
For image-only/prompt-only work, stop at that deliverable. For video production,
generate and inspect the confirmation image if within scope. Honor a request to
approve the image first; otherwise use the established end-to-end authorization
and continue after reporting the selected image and fixing material defects.
## Video event plan
Fill the video template from the current image and player bindings. Its baseline
sequence is menu → first player's light equipment → second player's heavy equipment
→ shared confirmation → world loading → both enter the world. Equipment, world,
body contrast, and timing are editable design choices; preserve requested identity
and style instead of forcing a cyberpunk example onto another setting.
Bind UI reference, each actual portrait, and any real body-comparison image to
distinct roles. Compile this creative template into the H3 mode guide's fields;
the mixed-language template is not itself an API request. Preserve literal visible
UI text in its required language, and remove all unfilled variables before dispatch.
Do not treat a composition reference as an exact first-frame anchor unless the
selected backend actually supports that role.
## Review and repair
Check both names, exact menu strings, layout, player identity, side assignment,
equipment distinction, and the transition into the requested world. Match UI
hover/click, mechanical locks, loading, and footsteps to the visible events.
For identity swaps, strengthen name/position/reference bindings; for weak style,
revise palette, material, character, and typography together. Reduce excessive
UI text if necessary without silently changing required player names.
Deliver the requested prompts/assets or the inspected intro video, with reference
roles and any text/timing limitations. Missing templates block template execution;
missing generation capability still permits a complete prompt handoff.
@@ -0,0 +1,4 @@
interface:
display_name: "Co-op Game Intros"
short_description: "Design two-player menus and world-entry animation"
default_prompt: "Use $co-op-game-intro-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,397 @@
# H3 Confirmation Image Template
## Portable adaptation
This is a creative template, not a tool call. User requirements override its
default ratio, layout, poses, and wording. Fill every relevant variable from the
brief; set `{character_count}` to two for this skill. When portraits are absent,
replace portrait references with explicit character designs. Do not claim to
reference an image that was not supplied. Use the image-generation capability
available in the host environment; no specific image provider is required.
## Core Rule
Generate a polished 16:9 landscape confirmation image for a cooperative game main menu.
The image consists of two independent systems:
• UI Framework (fixed)
• Visual Style (dynamic)
The UI Framework defines the layout, composition, information hierarchy, interaction logic, reading flow, menu structure, and readability. These elements must remain unchanged.
{visual_style} only controls the artistic appearance, including rendering medium, materials, color language, lighting mood, environment style, character design, costume language, UI materials, icon language, typography appearance, decorative motifs, and overall atmosphere.
Changing {visual_style} should feel like applying a different art direction to the same professionally designed game menu, never redesigning the interface itself.
If any default description conflicts with {visual_style}, prioritize {visual_style} while preserving the UI framework.
---
## Design Goal
Create a premium commercial console game main menu mockup in 16:9 landscape format. The image should combine the quality of a polished game promotional artwork with the clarity of a professional game UI. Characters and interface must feel naturally integrated, with strong visual hierarchy, excellent readability, clean composition, and a clear interaction focus.
---
## Overall Style
Follow the user-selected visual style: {visual_style}.
The selected style controls the rendering medium, illustration style, materials, palette language, environment, character appearance, outfit design, UI appearance, icon language, typography treatment, decorative elements, and atmosphere.
All visual elements must belong to one unified artistic language.
Do not mix incompatible visual styles within the same image.
The UI framework, layout, hierarchy, composition, and interaction logic must remain consistent regardless of style.
---
## Color Palette
Derive the entire color system from {visual_style}.
Use:
• {main_background_color} as the primary background color.
• {ui_body_color} as the main UI color.
• {text_color} as the primary text color.
• {functional_accent_color} as the interaction highlight color.
• Red only for warning, danger, or exit actions.
Limit the overall palette to five primary colors.
All buttons, player cards, icons, borders, highlights, decorative elements, typography, and visual effects must follow the same color system.
Avoid introducing unrelated colors outside the selected palette.
---
## Composition
Use a fixed 16:9 landscape layout.
Place {character_count} playable characters in the center as the visual subject.
The UI surrounds the characters without blocking them.
Place the player information cards in the upper-left corner.
Place the main menu vertically on the right.
Place a decorative horizontal strip along the bottom, adapting naturally to {visual_style} (such as ribbon, warning strip, wooden plank, stone slab, energy bar, scroll, etc.).
Keep a clear Z-shaped reading flow:
Player Cards → Characters → Menu → Continue Button.
The Continue button must always remain the primary visual focus.
Maintain generous negative space and excellent readability.
---
## Background
Generate a background naturally derived from {visual_style}.
The background supports the interface rather than competing with it.
Keep enough clean negative space behind all important UI elements.
Use subtle textures, environmental details, and decorative motifs only where appropriate for the selected style.
Avoid excessive visual noise, dense decoration, heavy dirt, scratches, paint splatter, or distracting textures.
The overall background should feel polished, premium, modern, and suitable for a commercial game menu.
---
## Character Style
All characters strictly follow {visual_style}.
The selected style determines:
• rendering medium
• body proportion
• facial stylization
• costume language
• material treatment
• lighting response
• animation style
• overall character quality
Only the character identity anchors and pose remain fixed. Facial rendering, eye design, nose simplification, mouth shape, skin treatment, expression style, and head proportion must be fully reinterpreted in {visual_style}.
No default character style may override {visual_style}.
---
## Character A / PLAYER 1
### Identity
Use character reference image 1 only for identity mapping.
The reference image donates identity anchors only: recognizable face silhouette, hairstyle, glasses if present, relative facial proportions, and distinctive personal traits.
It does not donate photographic realism, real skin texture, real-world lighting, camera quality, or the original image style.
Do not lose the character's identity anchors, but the face must be redesigned into {visual_style}.
All facial rendering, eye design, nose simplification, mouth shape, skin treatment, expression style, and head proportion must follow {visual_style}.
Do not swap identity with other characters.
### Appearance
Character rendering, expression, outfit, materials, and accessories are fully derived from {visual_style} while maintaining a recognizable playable-game-character silhouette.
### Action
Character A sits cross-legged on the ground.
Both hands support the body on the floor.
The upper body leans slightly backward.
Character A looks upward toward the camera.
---
## Character B / PLAYER 2
### Identity
Use character reference image 2 only for identity mapping.
The reference image donates identity anchors only: recognizable face silhouette, hairstyle, glasses if present, relative facial proportions, and distinctive personal traits.
It does not donate photographic realism, real skin texture, real-world lighting, camera quality, or the original image style.
Do not lose the character's identity anchors, but the face must be redesigned into {visual_style}.
All facial rendering, eye design, nose simplification, mouth shape, skin treatment, expression style, and head proportion must follow {visual_style}.
Do not swap identity with other characters.
### Appearance
Character rendering, expression, outfit, materials, and accessories are fully derived from {visual_style} while maintaining a recognizable playable-game-character silhouette.
### Action
Character B sits cross-legged on the ground.
Both hands rest naturally in front of the legs.
The upper body leans slightly forward.
Character B looks directly toward the camera.
---
## Lighting
Lighting follows {visual_style} while preserving clear readability.
Use a primary top light, secondary fill light, soft ambient bounce, contact shadows, gentle rim lighting, and high-quality global illumination where appropriate.
Faces should remain clearly visible.
Characters must integrate naturally into the environment while maintaining strong separation from the background.
---
## Game UI
Create a professional commercial console game main menu.
The menu structure, interaction hierarchy, spacing, composition, reading flow, and information architecture remain fixed.
{visual_style} only changes the artistic appearance of the interface.
The selected style may affect:
• UI materials
• surface textures
• border treatment
• corner shape
• decorative motifs
• color rendering
• lighting treatment
• icon language
• typography appearance
• interaction effects
It must NOT change:
• menu hierarchy
• button order
• button placement
• player-card placement
• composition
• interaction logic
• information architecture
• readability
The entire interface should feel like one professionally designed game UI system rather than separate elements assembled together.
---
## UI Visual Language
Derive the complete UI appearance from {visual_style}.
The selected style determines:
• button materials
• border language
• panel materials
• decorative details
• edge treatment
• visual weight
• highlight style
• interaction feedback
• shadow treatment
• texture language
Possible materials include wood, stone, paper, leather, cloth, glass, crystal, hologram, neon, metal, ceramic, carved surfaces, painted surfaces, magical energy, pixel blocks, watercolor paper, ink brush, comic graphics or any other material naturally belonging to {visual_style}.
Every UI element must share the same material language and artistic style.
---
## Button Layout Rules
Place all menu buttons vertically on the right side.
Maintain a consistent vertical rhythm.
Every button uses the same design family.
Button width automatically adapts to text length.
Keep all menu titles on a single line.
No automatic wrapping.
No stacked text.
Text remains horizontally centered.
Keep consistent padding.
Maintain equal spacing between buttons.
Avoid visual clutter.
---
## Continue Button
The Continue button is always the primary visual focus.
Make it visually dominant using the emphasis method naturally belonging to {visual_style}.
Possible emphasis methods include:
• larger size
• stronger contrast
• brighter color
• unique material
• distinctive border
• lighting
• glow
• engraving
• magical energy
• holographic effect
• embossed frame
• animated visual cue
Do not force glow or neon if they conflict with {visual_style}.
The Continue button should immediately attract the player's attention.
---
## Start New Game Button
Place above Continue.
Use the standard secondary button appearance.
Keep the visual language consistent with the selected style.
Add one small style-appropriate icon if suitable.
Maintain single-line text.
---
## Settings Button
Use the standard secondary button appearance.
Use one settings icon naturally derived from {visual_style}.
Maintain consistent proportions, spacing, and hierarchy.
---
## Exit Game Button
Use the standard secondary button appearance.
Use red only as the danger indicator.
Red should remain an accent rather than the dominant color.
Add one style-appropriate exit icon if suitable.
---
## Player Cards
Create an upper-left player information system for {character_count} players.
The appearance follows {visual_style}.
Card materials, borders, corners, textures, shadows, decorations, and icons are automatically derived from the selected style.
Each player card contains:
• Player label
• Player nickname
• READY status
Player names:
PLAYER 1
{player1_name}
PLAYER 2
{player2_name}
READY always uses the functional accent color.
The cards should remain highly readable and visually connected to the rest of the interface.
---
## Icon System
Derive all icons from {visual_style}.
Use one unified icon family throughout the entire interface.
Possible icon languages include:
• minimal outline
• solid filled
• geometric
• comic
• pixel
• engraved
• carved
• painted
• brush
• rune
• glyph
• holographic
• crystal
• paper-cut
• origami
• embossed
• low-poly
• flat design
• stylized 3D
All icons must share:
• the same artistic style
• the same material language
• the same perspective
• the same lighting treatment
• the same stroke language
• the same color hierarchy
Icons should remain simple, recognizable, and functional.
Each interface block may contain at most one horizontal row of icons.
No wrapping.
No vertical stacking.
Icons should support the interface rather than dominate it.
---
## Typography
Typography hierarchy remains fixed.
Typography appearance follows {visual_style}.
The selected style controls:
• font family
• decorative treatment
• texture
• material
• outline
• lighting
• shadow
• wear pattern
Maintain:
• excellent readability
• strong visual hierarchy
• single-line menu buttons
• consistent spacing
• high contrast
Button width should always adapt to text length.
Never allow menu titles to wrap onto multiple lines.
---
## Game Title
Display the game title:
{game_title}
The title should become the primary branding element of the menu.
Its material, decorative style, typography, and rendering all follow {visual_style}.
Avoid imitating existing commercial game logos.
Create an original title treatment inspired by the selected style.
Keep the title highly readable.
Avoid random symbols or meaningless letters.
---
## Interaction States
Represent the interface as an active game menu.
Buttons may display appropriate interaction feedback naturally belonging to {visual_style}.
Possible interaction feedback includes:
• hover
• pressed state
• magical glow
• energy pulse
• holographic highlight
• carved illumination
• ink spread
• brush bloom
• metallic reflection
• paper emboss
• animated border
• pixel flashing
Only use interaction effects that naturally belong to the selected style.
Avoid forcing modern UI effects into incompatible artistic styles.
---
## Rendering
Generate a polished AAA-quality commercial game menu artwork.
Characters, environment, UI, typography, icons, and decorative elements should feel like one cohesive production.
Maintain premium rendering quality.
Strong visual hierarchy.
Professional commercial presentation.
High readability.
Balanced composition.
Clean finish.
---
## Negative Constraints
No split screen.
No duplicated characters.
No identity swap.
No loss of identity anchors. No photorealistic face unless {visual_style} explicitly requires photorealism. No realistic photo face pasted onto a stylized body. No mismatched face style.
No hairstyle changes.
No unreadable UI.
No distorted buttons.
No overlapping interface.
No random text.
No random symbols.
No watermark.
No copied game logo.
No branded interface.
No inconsistent icon styles.
No mixed UI materials.
No excessive decoration.
No overwhelming background.
No visual clutter.
No broken layout.
No low readability.
---
## Medium Lock
Render as a polished commercial game key art combined with a professional console game main menu mockup.
The interface remains fixed.
Only the artistic appearance changes according to {visual_style}.
@@ -0,0 +1,249 @@
# H3 Video Prompt Template
## Portable adaptation
This is a creative planning template. User requirements override its default
15-second timeline, body proportions, equipment, environment, ratio, and palette.
Retiming must preserve readable event order. Use only supplied reference assets:
omit `{height_ref}` or identity-image bindings when absent and replace their role
with text design constraints. Compile the filled plan into the H3 base or Ref2VA
format before rendering; preserve exact visible UI strings in the user's language.
Use available tools and the actual backend's parameter schema. Audio directions
describe desired content rather than asserting that a particular API supports it.
## Template completion
Resolve the palette variables `{background_color}`, `{ui_color}`, `{text_color}`,
`{functional_accent_color}`, and `{power_accent_color}` from the selected style.
Keep the five values distinct enough for readable UI contrast. Use the power
accent for danger, exit, and warning states unless the user supplies a separate
semantic-color requirement.
Fill visible copy in the user's requested language. The following English values
are defaults only, not mandatory output:
- `{player1_label}` / `{player2_label}`: `PLAYER 1` / `PLAYER 2`
- `{ready_label}`: `READY`
- `{start_label}`, `{continue_label}`, `{settings_label}`, `{exit_label}`:
`START NEW GAME`, `CONTINUE`, `SETTINGS`, `EXIT GAME`
- `{player1_equipment_label}`: `RIGHT ARM EQUIPMENT`
- `{player1_option_a}` / `{player1_option_b}`: `PHANTOM GRIP` / `CHRONOS CLAW`
- `{player2_equipment_label}`: `ARMAMENT CUSTOMIZATION`
- `{hand_label}`, `{forearm_label}`, `{elbow_label}`, `{upper_arm_label}`:
`HAND`, `FOREARM`, `ELBOW`, `UPPER ARM`
- `{confirm_label}` / `{loading_label}`: `CONFIRM CONFIG` / `LOADING`
Before compiling the rendering prompt, search the filled template for every
remaining token enclosed in braces. Replace required values. For an absent optional
reference, remove its binding and rewrite every dependent clause as a text-only
constraint. Do not dispatch a prompt while any template token remains.
## Prompt principle
Use the same method as the GPT confirmation-image prompt:
**Fixed video event framework + dynamic user-style fill + locked character identity + palette-linked UI system.**
The video prompt must not blindly preserve the source prompt's default style. It must preserve the timeline, UI events, player positions, equipment logic, and text structure, while dynamically rewriting visual treatment, palette, character rendering, lighting, UI surface, icons, and city-world style according to the user's selected style.
## Priority order
1. User-selected style: {visual_style}
2. Confirmed image / UI reference: {ui_ref}
3. PLAYER 1 identity reference: {player1_ref}
4. PLAYER 2 identity reference: {player2_ref}
5. Height/body comparison reference: {height_ref}
6. Fixed source video event framework
## Reference roles
- {ui_ref}: confirmed first image. Use it to lock UI layout, menu hierarchy, color system, typography scale, button structure, character-game integration, and overall composition logic.
- {player1_ref}: PLAYER 1 identity anchor. Lock exact face, hairstyle, glasses if present, facial proportions, body identity, and nickname mapping to {player1_name}.
- {player2_ref}: PLAYER 2 identity anchor. Lock exact face, hairstyle, facial proportions, body identity, and nickname mapping to {player2_name}.
- {height_ref}: body comparison anchor. Lock the visible contrast between the two players and prevent identical body proportions.
## Global style baseline
The overall art direction must prioritize the user's selected {visual_style}.
Preserve these fixed qualities: a game main-menu interface, premium game-trailer
finish, deep integration between the UI and characters, modern commercial game
UI design, strong visual impact, clean composition, and restrained decoration.
Derive the remaining visual treatment from {visual_style}: character rendering,
expressions, costume language, color system, lighting temperature, UI materials,
button icons, typography texture, and the world revealed after loading.
## Palette system
Derive the complete video palette from {visual_style}, with linked UI colors:
- Use {background_color} as the primary background and world color.
- Use {ui_color} as the main UI color.
- Use {text_color} as the text color.
- Use {functional_accent_color} as PLAYER 1's functional accent color.
- Use {power_accent_color} as PLAYER 2's power accent and for danger, exit,
and warning states.
- Limit the complete palette to five colors.
- Use a vivid, modern, high-contrast color language consistent with
{visual_style}.
Menus, equipment panels, player cards, buttons, the HUD, loading bars, icons,
and text must share this color system. Do not introduce unrelated colors.
## Character identity and style lock
PLAYER 1 follows the facial identity in {player1_ref}. Preserve the face,
facial proportions, hairstyle, glasses when present, personal identity, and the
mapping to the nickname {player1_name}. Adapt expression, rendering, clothing,
and mechanical equipment to {visual_style}. PLAYER 1 always stays on the left,
with a taller, slender, agile build. Equipment uses the functional accent color,
and the mechanical claw is light, narrow, and flexible.
PLAYER 2 follows the facial identity in {player2_ref}. Preserve the face,
facial proportions, hairstyle, personal identity, and the mapping to the
nickname {player2_name}. Adapt expression, rendering, clothing, and mechanical
equipment to {visual_style}. PLAYER 2 always stays on the right, with a shorter,
broader, power-oriented build. Equipment uses {power_accent_color}, and the
mechanical fist is broad, heavy, and visibly weighty.
Do not swap character identities, merge faces, swap nicknames, or converge the
two body types.
## Fixed timeline framework
### [0-2 seconds] - Two-player main menu
Composition and camera: A high-angle extreme wide shot follows the composition
logic of {ui_ref}. The camera tilts slightly downward and slowly pushes in.
Visuals: PLAYER 1 ({player1_name}) and PLAYER 2 ({player2_name}) sit side by
side at the center. PLAYER 1 remains on the left and PLAYER 2 on the right. They
look up toward the camera with only natural breathing, blinking, and subtle body
movement.
UI: The upper-left player card displays `{player1_label}`, `{player1_name}`, and
`{ready_label}` exactly as filled. The second card in the upper-center or
upper-left two-card system displays `{player2_label}`, `{player2_name}`, and
`{ready_label}` exactly as filled. The vertical menu on the right displays
`{start_label}`, `{continue_label}`, `{settings_label}`, and `{exit_label}`
exactly as filled. `{continue_label}` is the visual center and primary
highlighted button.
Dynamic style fill: Adapt the menu background, ground texture, button shapes,
icons, typography, borders, glow, and sticker treatment to {visual_style}, while
preserving the layout and hierarchy in {ui_ref}.
Sound: Menu ambience, subtle UI-hover sounds, and a low electronic atmosphere
before the click. Ignore this direction when generating silent video.
### [2-4 seconds] - PLAYER 1 right-arm configuration
Composition and camera: In a medium shot, the camera pushes smoothly from the
main menu toward PLAYER 1's right arm. PLAYER 2 remains visible and stable in the
background.
UI: The right-side menu contracts and slides away. A panel with functional-accent
guide lines slides in from the left and displays `{player1_label}` and
`{player1_equipment_label}` exactly as filled. The equipment list first
highlights `{player1_option_a}`, then moves the selection to
`{player1_option_b}`.
Action: PLAYER 1's right cuff opens automatically and a lightweight mechanism
unfolds beneath the forearm. The fingers spread as long claw-like joints slide
into position and lock one by one, briefly revealing fine wiring, miniature
pistons, and metal connectors. Functional-accent LEDs illuminate sequentially
when configuration completes.
Dynamic style fill: Adapt the mechanism, panel material, icons, lines, locking
animation, and light effects to {visual_style}. Keep the result light, precise,
and flexible, proportioned to PLAYER 1 without changing the face, hairstyle, or
main clothing design.
Sound: Precise mechanical unfolding, light UI switching tones, and small locking
clicks.
### [4-7 seconds] - PLAYER 2 heavy-arm configuration
Composition and camera: A medium shot tracks smoothly between the players and
arcs toward PLAYER 2's left side. PLAYER 1 remains in the background, quietly
examining the configured mechanical hand.
UI: A {power_accent_color} panel slides in and displays `{player2_label}` and
`{player2_equipment_label}` exactly as filled. Its grid contains `{hand_label}`,
`{forearm_label}`, `{elbow_label}`, and `{upper_arm_label}`, with the selection
moving quickly but legibly through all four.
Action: PLAYER 2's left sleeve opens in sections. Heavy forearm plates spring
outward, the previous components detach, and new armor slides into place along
guide rails. A thick mechanical bearing replaces the elbow joint, and the broad
mechanical hand reassembles and locks. Thick wiring, hydraulic pistons, and a
dark metal skeleton appear briefly during the replacement. A restrained warm
indicator illuminates as each component locks.
Dynamic style fill: Adapt the heavy arm, UI panel, component icons, materials,
and light effects to {visual_style}. Keep it broad, weighty, and powerful, in
clear contrast with PLAYER 1's lightweight claw.
Sound: Low motor movement, heavy mechanical engagement, and weighty locking
feedback.
### [7-8.5 seconds] - Shared configuration confirmation
Composition and camera: Pull back smoothly to a medium two-player composition,
with PLAYER 1 on the left and PLAYER 2 on the right.
UI: The two equipment panels converge at center to form a shared button that
displays `{confirm_label}` exactly as filled. Adapt its border, glow, icon, and
sticker treatment to {visual_style}, while keeping the hierarchy and text legible.
Action: The cursor clicks the button. A functional-accent energy pulse travels
through PLAYER 1's mechanical claw, while a {power_accent_color} pulse travels
through PLAYER 2's fist. All UI panels contract inward and disappear. Both
players uncross their legs and adjust their posture: PLAYER 1 lifts one knee
lightly and articulates the claw fingers in sequence; PLAYER 2 plants one foot
firmly and slowly closes the heavy fist.
Sound: Confirmation tone, two-color energy pulses, and UI contraction.
### [8.5-10 seconds] - Shared world loading
Composition and camera: A wide shot reveals a shared loading bar at the bottom.
UI: The loading bar displays `{loading_label}` exactly as filled and fills
rapidly from 0% to 100%. Its left half uses {functional_accent_color} and its
right half uses {power_accent_color}. Adapt the HUD and loading-bar shape,
border, texture, and typography to {visual_style}, while keeping them legible.
Environment transformation: Continuously transform the confirmed
{visual_style} menu background into a game world in the same style. Menu strips,
color blocks, and patterns become corresponding roads, structures, and world
elements. Preserve the confirmed palette rather than forcing a yellow-and-black
or cyberpunk city.
Key constraint: The transformation remains continuous and natural, with no hard
cut, smoke occlusion, or identity change.
Sound: Rising loading tone and an ambience transition from menu to game world.
### [10-15 seconds] - Two-player world entry
Composition and camera: Move from an extreme wide shot into a third-person
tracking view. At 100% loading, both characters stand simultaneously. The camera
descends smoothly and arcs behind them into a stable two-player cooperative view.
World style: Generate the complete world from {visual_style}. Preserve the game
opening structure: dense buildings, roads, signal lights, signs, crowds or other
moving background elements, passing vehicles, overhead wires, industrial pipes,
and a distant skyline. Materials, architecture, signage, lighting, and motion
must follow {visual_style}. Do not let the source template's cyberpunk default
override the user unless the selected style is cyberpunk.
Character relationship: Clearly show the players from behind and preserve their
body contrast. PLAYER 1 is on the left, tall and slender, with the lightweight
claw hanging naturally. PLAYER 2 is on the right, shorter and broader, with the
heavy fist slightly raised. PLAYER 1 steps forward first, PLAYER 2 follows, and
they enter the street side by side.
HUD: The HUD fades in. A mini-map appears in the upper right. Separate status
bars labeled `{player1_name}` and `{player2_name}` appear in the lower left.
The first uses {functional_accent_color}; the second uses {power_accent_color}.
A shared objective marker appears in the street ahead.
Sound: City ambience, distant vehicles, footsteps, and a HUD fade-in cue.
## Negative constraints
No extra player, duplicated character, identity or username swapping, merged bodies, unintended face/hair changes, missing player, unrequested split screen or cuts, random camera shake, floating body parts, gruesome dismemberment, unwanted weapons, style-incompatible neon, unreadable UI, random letters, misspelled usernames, extra menu options, copied game logo, or watermark. Preserve the user's chosen character designs and palette.
+42
View File
@@ -0,0 +1,42 @@
---
name: h3-prompt-writing
description: Write MiniMax H3 video generation prompts for T2VA, I2VA, FL2VA, L2VA, and Ref2VA. Use when rewriting multimodal requests into H3 prompt structures, composing integrated_multimodal_description, overall_soundscape, and non_diegetic_music, aligning keyframes, or defining reference labels for images, videos, and audio.
metadata:
compatibility: Portable to any agent that can read local files — no external API calls, MiniMax Hub tools, or proprietary runtime required. The agents/openai.yaml file only adds optional ChatGPT/Codex UI metadata; it does not restrict the skill to OpenAI agents.
---
# H3 Prompt Writing
## Workflow
0. Read [portable-workflow.md](references/portable-workflow.md) for scope, available tools, durable artifacts, and generation handoff. For prompt-only work, no generation tools are required.
1. Identify the input mode: T2VA, I2VA, FL2VA, L2VA, or full-reference Ref2VA.
2. For base text/keyframe modes, read `references/base-format.md` and follow its final prompt structure.
3. For full-reference mode, read `references/ref-format.md` and follow its six-section rewrite format.
4. Preserve the exact field names, section order, labels, and timing notation from the selected guide.
## Base Modes
- T2VA: build the full audiovisual timeline from text.
- I2VA: start from the first frame and develop forward from it.
- FL2VA: describe the continuous path between the first and last frames.
- L2VA: infer a plausible opening and converge to the supplied last frame.
Use `integrated_multimodal_description`, `overall_soundscape`, and `non_diegetic_music` in the order shown in `references/base-format.md`.
## Full-Reference Mode
Ref2VA rewrites use `subject_definitions`, `summary`, `retention_analysis`, `detailed_description`, `overall_soundscape`, and `non_diegetic_music` in that order. Reference labels stay consistent across all sections.
Read `references/ref-format.md` for label rules and retention analysis.
## Output Rules
- Write rewrite sections in English; preserve dialogue, lyrics, and visible scene text in their original language.
- Describe each shot by composition, subjects, environment, actions, camera, sound, and the exact point where referenced content appears.
- Avoid plot summaries, unresolved reference labels, and timing that does not match the requested duration.
## Tips for Better Results
- Match the description to the effective duration of the current clip or generation window. The upstream guides describe 4–15-second clips; longer work requires a verified backend extension or an explicit assembly plan, as described in the portable workflow.
- Keep reference labels consistent (e.g. `<Picture 1>`, `<Video 1>`, `<Audio 1>`) across every section.
- Prefer concrete visual and audio details over abstract words like "cinematic" or "beautiful".
- When using keyframes (I2VA / FL2VA / L2VA), clearly state how the first and/or last frame connects to the timeline.
@@ -0,0 +1,4 @@
interface:
display_name: "H3 Prompt Writing"
short_description: "Write structured H3 prompts for each input mode"
default_prompt: "Use $h3-prompt-writing to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,86 @@
# H3 base-mode prompt format
Use this reference for T2VA, I2VA, FL2VA, and L2VA prompt writing. It is a
compact, repository-authored description of the required output contract, not a
copy of an upstream prompt guide.
## Select the anchor mode
- T2VA has no image anchor. Build the audiovisual sequence from the brief.
- I2VA binds `<Picture 1>` to the first frame at 0.00 seconds.
- FL2VA binds `<Picture 1>` to the first frame and `<Picture 2>` to the final
frame. Describe the observable path between them.
- L2VA binds `<Picture 1>` to the final frame. Infer a compatible opening and
converge to the supplied image.
For an image-anchored mode, put a plain-English alignment statement before the
structured fields. State each picture label, its shot, and its exact target time.
The ending time must match the effective clip duration, formatted to two decimal
places. Do not invent a picture label when no image was supplied.
## Emit three fields in order
```text
integrated_multimodal_description: [Shot 1] ...
overall_soundscape: ...
non_diegetic_music: ...
```
`integrated_multimodal_description` is the playback-ordered visual and diegetic
audio plan. Establish the initial style, framing, subjects, environment, and
spatial relationships, then describe actions and state changes. For keyframe
modes, explicitly preserve or approach the relevant composition, identity,
clothing, colors, objects, and geometry.
`overall_soundscape` summarizes ambience, physical effects, and non-verbal human
sound. Do not repeat dialogue or music already described elsewhere. Use `N/A`
only for explicitly silent output.
`non_diegetic_music` describes music heard by the audience but not the characters.
Specify audible properties such as instrumentation, tempo, rhythm, dynamics, and
ending behavior. Put source-visible music, singing, radio, or instrument playing
in the main description. Use `N/A` when no score is wanted.
## Timeline notation
- Start the opening with `[Shot 1]` and no timestamp.
- Mark each later cut with a sequential label and a strictly increasing time,
for example `[Shot 2] At 00:03.500, ...`.
- Use a new shot for an actual edit or meaningful viewpoint/time change. Keep a
continuous action or morph in one shot and describe its internal timing.
- Describe camera motion as an action: type, direction, useful amplitude, and
speed. Do not append a disconnected list of camera keywords.
- Keep every cut and event within the requested clip duration.
## Speech, text, and sound
Give each speaking or singing source a stable ID such as `(S1)` or `(S2)` and
reuse it across shots. Put only the language tag and literal words inside the
dialogue wrapper:
```text
The courier (S1) says softly: <d>[English] The gate is open.</d>
```
Preserve supplied dialogue, lyrics, punctuation, and visible text verbatim. State
when a voice is off-screen and keep the visible character's lips closed when that
is required. If a line spans a cut, describe the audio continuity on both sides
of the cut. Place visible signs, labels, and interface copy in double quotes.
Tie diegetic sounds to the event that produces them. Avoid describing the same
sound as both diegetic and non-diegetic.
## Anchor-specific checks
- I2VA: begin from the supplied frame rather than merely borrowing its style.
- FL2VA: describe intermediate motion; do not provide two unrelated still-image
descriptions. Reach Picture 2 at the actual end time.
- L2VA: keep the final picture assigned to the last shot, not automatically to
Shot 1, and make the preceding action physically plausible.
- All modes: reference labels must map to supplied assets and remain stable.
Before delivery, verify the field order, shot numbering, timestamps, reference
labels, dialogue fidelity, requested language, duration, and absence of unresolved
template variables.
@@ -0,0 +1,121 @@
# Portable H3 production workflow
This is the runtime contract for the nine H3 skills in this repository. Read it
alongside the selected creative skill. The bundle uses local files and available
tools; no proprietary canvas, choice-card widget, planner, or executor is required.
## Scope and decisions
- Resolve the requested deliverable first: prompt, storyboard, assets, or finished
video. A prompt-only request ends with the prompt; it does not start generation.
- Reuse the user's duration, language, style, model, audio policy, supplied assets,
and existing authorization. State reasonable defaults for optional gaps. Ask
only about missing inputs or choices that materially affect the requested work.
- Creative checkpoints are reviewable artifacts, not mandatory repeated approvals.
Honor an explicit request to approve a preview before rendering. When the user
has authorized the complete workflow or delegated creative choices, show the
plan and continue within that scope without requiring a special UI control.
- A skill never grants permission to publish, send messages, spend outside an
authorized budget, or change models against the user's choice. User constraints
override a preset's style, language, shot count, timing, or audio defaults.
## Tools and durable artifacts
1. Inspect the actual available tools, existing scripts, endpoint documentation,
and supplied media. Do not invent a tool name, model parameter, or returned path.
2. Write plans and prompts to the user-selected project folder. If none is given,
use a new named folder under `outputs/`, outside the skill package. Keep useful
artifacts such as `brief.md`, `storyboard.md`, `prompts/`, `references/`,
`clips/`, and `final/`; create only those needed for this deliverable.
3. Record each reference's role and path: identity, environment, style, typography,
first/last frame, or audio. Tie generated clips to the prompt revision, input
assets, model, settings, and output paths. Update downstream references when an
asset changes; retain previous outputs instead of overwriting user files.
4. Use available image generation/editing tools for requested raster assets. Use
an available video endpoint or local service for clips, and available audio
tooling for requested standalone speech/music. For assembly and inspection,
use an installed editor or tools such as FFmpeg, ffprobe, and OpenCV. Follow the
host's tool policies, including its preferred image generation mechanism.
5. Independent tool jobs may run concurrently when resources permit; this skill
does not require separate agents or delegated generation.
6. If a required capability is missing, finish the useful in-scope preparation:
complete prompts, asset-role manifest, timeline, and verified request example
where possible. State the missing capability and unfinished media step. Do not
silently substitute another model, claim a render happened, or call a written
plan a completed video. Resume generation when the capability becomes available.
## Compile the creative plan into H3 prompts
Read [base-format.md](base-format.md) for T2VA / I2VA / FL2VA / L2VA, or
[ref-format.md](ref-format.md) for Ref2VA. These are writing modes, not guaranteed API
task names. Check the selected backend's input mapping before sending a request.
- Base prompts use `integrated_multimodal_description`, `overall_soundscape`,
`non_diegetic_music`, in that order, with the appropriate keyframe preamble.
- Ref2VA uses `subject_definitions`, `summary`, `retention_analysis`,
`detailed_description`, `overall_soundscape`, `non_diegetic_music`, in that order.
- Every reference label must resolve to a supplied asset, in the same order used
by the backend. Omit nonexistent optional references and their dependencies.
- Keep the creative brief in the user's language. Structured H3 rendering prompts
use English prose and exact field labels, while dialogue, lyrics, and visible
text retain their requested language and wording. If the user explicitly wants
only a same-language creative prompt, deliver that without silently replacing it
with English. Compile a separate rendering prompt when generation is requested.
- `[Shot 1]` has no initial timestamp. Later shot labels mark actual cuts. For a
continuous morph, describe timed actions inside one shot rather than turning
each form into a new shot. Express camera direction, speed, and useful amplitude.
- Separate image style from character identity. A style reference must not inject
its people, scenery, layout, typography, texture, or lighting into unrelated
shots. For literal text fidelity, inspect the rendered result; a prompt is not
proof that the exact lettering or beat synchronization was achieved.
## Longer videos and audio
Use the actual backend's supported duration, dimensions, frame grid, reference
count, audio modes, and model partition. Inspect this checkout's H3 implementation
or serving documentation before building requests. Do not assume that a feature
branch's long-video extension exists on main or on a hosted service.
For a verified vLLM-Omni Ref2VA continuation backend, `long_video=true` with
`long_video_mode=continuation` can accept one `continuation_prompts` entry per
planned window only when the Ref2VA request is executed with its local text
encoder. Step execution and stages that use an external text encoder reject
`continuation_prompts`; omit the field and use the supported per-step inputs in
those configurations. Compute the window count from the aligned total frames,
window size, and overlap. Each prompt uses local window time; track global
shot/music time separately. Repeated overlap is guidance, not additional output
duration. Do not use this mode on a task/partition that does not support it.
Otherwise split into supported clips and assemble them. Same-scene clips may use
tail/head keyframe continuity when supported. Scene changes should have explicit
new scene references; a previous scene's latent tail can preserve the wrong
setting. Choose cuts, morphing, and transitions from the brief rather than imposing
hard cuts whenever a work exceeds 15 seconds. Generation windows and visual shots
are different: one window may contain cuts, and one shot may span multiple windows.
For supplied music, use the selected excerpt as the master timeline unless the
user requests replacement. For native audio, write the musical/SFX direction in
the prompt and verify that the backend actually returns audio. Do not invent a
generic `generate_audio` parameter. If available, locked-source audio latents guide
generation but the audio-VAE reconstruction is not bit-identical to the source.
Use explicit final audio stream-copy when an exact original track is required.
Prompted BPM is a target, not measured timing. Avoid automatically adding lyrics,
voiceover, subtitles, or multiple competing scores to an instrumental request.
## Verification and bounded recovery
Inspect reference images before using them. After rendering, inspect representative
frames, transitions, opening/ending states, text, identities, and scene continuity.
Check audio by listening when possible; report any limit on audio review. Check
duration, dimensions, fps, frame count, audio presence, and decode validity with
media tools. If preserving audio exactly, compare extracted audio stream hashes.
Correct a demonstrated failure with a targeted change to the affected prompt,
reference, or edit. Keep raw generation alongside edited/upscaled versions and
label the difference. Use an agreed retry/resource budget; absent one, allow one
targeted rerender, then report persistent defects and concrete options rather than
entering an unbounded generation loop. Do not switch providers automatically.
Deliver actual artifact paths, the prompts/settings needed to repeat the work,
and material visual/audio limitations. Distinguish requested behavior, observed
model output, and post-production repairs.
@@ -0,0 +1,105 @@
# H3 full-reference prompt format
Use this reference for Ref2VA prompt writing. It is a compact,
repository-authored description of the output contract, not a copy of an
upstream prompt guide.
## Emit six fields in order
```text
subject_definitions: ...
summary: ...
retention_analysis: ...
detailed_description: [Shot 1] ...
overall_soundscape: ...
non_diegetic_music: ...
```
Write the structural prose in English. Preserve the requested language and exact
wording for dialogue, lyrics, and visible text.
## Reference labels
Assign labels only to supplied content and keep every label stable across all six
fields:
- `<Subject N>` identifies reusable visible content such as a person, object,
environment, costume, interface, action, or visual treatment.
- `<Picture N>` identifies an image used as a concrete frame, composition, or
storyboard anchor. If an image only defines a subject, cite it in that subject's
definition instead of inventing a separate target-frame role.
- `<Video N>` identifies a source video being edited or continued, or a video
whose temporal structure is referenced.
- `<Audio N>` identifies a supplied audio signal that is copied or referenced.
Audio and video label numbers are independent even when they came from one file.
In `subject_definitions`, give each tracked item a separate line. State its source,
role, and the features that later sections must preserve or transform. Do not
create labels for assets or roles that do not exist.
## Summary and retention
`summary` is one short paragraph describing the target and the principal reference
relationships. Use only labels already defined. Distinguish these operations:
- a concrete image frame anchor;
- generation guided by identity, style, action, camera, or audio characteristics;
- direct editing of a source video;
- continuation from a source video;
- copying an audio signal;
- referencing audio properties without copying the signal.
`retention_analysis` has one line per label and states where it is used and what
changes. Use consistent machine-readable relationship values:
- Visual: `fully_preserved`, `partially_preserved`, `attribute_transfer`, or
`weak_reference`.
- Audio: `fully_copy`, `partially_copy`, `reference`, or `weak_reference`.
Explain the concrete retained and changed traits after the relationship value.
Do not claim full preservation when identity, geometry, timing, or signal content
is intentionally altered. Do not count newly requested target action as an
unintended loss of reference fidelity.
## Detailed description
`detailed_description` is the playback-ordered rendering plan. For each shot,
cover the current composition, subject appearance and position, environment,
lighting, actions and state changes, camera behavior, diegetic sound, and the
point at which each reference becomes relevant.
- Start with `[Shot 1]` and no timestamp.
- Give later cuts sequential labels with strictly increasing times, such as
`[Shot 2] At 00:03.500, ...`.
- Place relevant reference labels next to the described subject, frame, motion,
or sound; do not collect detached labels at the end.
- Use stable `(S1)`, `(S2)`, and subsequent IDs for vocal sources. Put literal
speech or lyrics inside `<d>[Language] ...</d>`.
- Preserve supplied visible text verbatim and place it in double quotes.
- Describe continuous transformations inside one shot unless an actual cut is
intended. State camera direction, speed, and amplitude when useful.
For video editing, explicitly identify what stays from the source timeline and
what is replaced. For continuation, distinguish the inherited opening state from
new content. For style reference, transfer visual properties without accidentally
copying people, scenery, layout, or typography.
## Audio fields
`overall_soundscape` summarizes ambience, physical action sounds, and non-verbal
human sounds. `non_diegetic_music` describes score audible only to the audience.
Keep dialogue and diegetic music in `detailed_description`. Use `N/A` only when
the corresponding layer is intentionally absent.
If audio is copied, state whether the whole signal or only a range/layer is used.
If it is referenced, describe the specific property—such as voice timbre, tempo,
rhythm, or texture—without claiming signal reuse.
Before delivery, verify the six-field order, one-to-one label definitions,
retention coverage, shot timing, dialogue fidelity, requested language, and
absence of unresolved template variables.
@@ -0,0 +1,69 @@
---
name: handdrawn-live-video-generator
description: Write or generate a continuous live-action scene with a rough luminous hand-drawn entity that contacts real objects, morphs, and escapes a slightly delayed handheld camera. Use for gentle surreal mixed-media shorts, not flat-vector-only animation or horror clips.
metadata:
compatibility: Portable prompt writing with sibling h3-prompt-writing; actual generation requires an available video backend and any referenced media.
---
# Handdrawn Live-Action Fusion
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md).
Keep the user's requested scope and language. A creative-prompt request delivers
the prompt, without generating media. For a requested finished video, prepare
the prompt and proceed under existing authorization using available tools.
## Creative contract
The default is a 15-second, 16:9 continuous scene in an everyday real space. A
flat luminous drawn entity visibly contacts a real hand/object early, transforms
as one traceable being, escapes through connected space, and the handheld camera
reacts slightly late. Duration and ratio follow the user's explicit choices.
Use rough crayon, chalk, colored pencil, pastel, or brush strokes with uneven fill,
frayed edges, line jitter, and frame-by-frame redraw. Keep the live-action setting
physically grounded and the drawn presence visibly planar. Uniform neon tubes,
plush characters, polished CG, and clean vector strokes would change this style.
Choose a fresh setting, entity, palette, contact mechanism, transformation chain,
escape route, camera reaction, and emotional ending within the user's constraints.
Do not ban an otherwise requested motif simply because an upstream example used
it. The tone is warm, playful, and gently surprising rather than threatening.
## Timed action structure
For a 15-second brief, use these intervals as a starting structure:
- 0–3s: establish obvious real/drawn contact, such as landing on a palm or slipping
around a finger. The real participant reacts.
- 3–6s: begin a continuous morph while retaining a color smear, line, curve, or tail
from the previous shape. Give it a playful action.
- 6–10s: let it escape into an adjacent part of the same space. The camera pans,
tilts, or moves only after the entity starts leaving its view.
- 10–13s: add another readable interaction or surprise, keeping the same entity.
- 13–15s: extend an earlier drawn motif across the space into a gentle larger
transformation, then resolve with a small emotional/comic detail.
Retiming changes the intervals proportionally or reorganizes beats; do not leave
15-second timestamps in a shorter prompt. Preserve a user's explicit final form
instead of forcing the spatial finale when it conflicts with their ending.
## Prompt delivery and rendering
For a same-language creative prompt, begin with duration, ratio, real space, and
the live-action/drawing fusion. Then describe phone-camera texture, the timed
action intervals, drawn material, camera lag, specific exclusions, and ambient
sound. Avoid unrelated headings or an essay when the user asked for copy-ready text.
For actual H3 generation, create a separate structured rendering prompt using the
appropriate base/Ref2VA guide: English prose with literal dialogue/visible text in
the requested language. Preserve the creative intent and actual media bindings.
Do not send unsupported model settings or promise exact first-frame conditioning
merely because an image is present.
## Review
Check clear early physical contact, one continuous entity, connected geography,
retained shape motifs, delayed camera reaction, rough planar drawing, and a
non-threatening ending. Avoid jump scares, sudden blackouts, menacing anatomy, and
unrequested extra characters. Deliver the actual prompt/video and state any
remaining contact, continuity, texture, or camera-timing defects.
@@ -0,0 +1,4 @@
interface:
display_name: "Handdrawn Live-Action Fusion"
short_description: "Direct drawn morphs and real-world interactions"
default_prompt: "Use $handdrawn-live-video-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,71 @@
---
name: minimalist-product-ad-generator
description: Plan, prompt, or produce minimalist product advertising shorts from product images, preserving variants, material, color, concise typography, and rhythmic camera motion. Use for product launches and ecommerce films, not presenter-led ads or complex UI demonstrations.
metadata:
compatibility: File-based agent workflow. Requires the sibling h3-prompt-writing package; rendering uses available image, video, audio, and editing tools.
---
# Minimalist Product Ad Generator
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md).
Use [product direction](references/product-direction.md) when planning anchors,
copy, and timed beats. Respect the requested output: prompt-only work needs no
generation tool, while a finished film includes generation, inspection, and assembly.
## Resolve the product brief
Inspect supplied product photos, packaging, renders, and detail shots. Record the
product category, main variant, actual body/accent colors, material finish, usable
mechanisms, and requested copy. Preserve identity and product facts; visual polish
does not authorize recoloring the product or inventing a feature.
Reuse the specified duration, ratio, language, and model. With no constraints,
suggest a 10-second H3 film and one of white studio, dark rim light, brand color
field, or light lifestyle staging. Ask for a missing product reference when exact
identity is required; distinguish a speculative concept from an existing product.
If the user delegates direction, record the adopted choices and proceed.
## Design the film
1. Choose a product-specific spine: launch reveal, mechanism interaction, or color
family. Describe concrete actions such as a lid opening or a crown turning.
2. Write short sensory/benefit copy grounded in supplied facts. Preserve the user's
exact approved wording and language. For newly requested English copy, 3–5 words
usually fit one readable line; do not force English on other-language campaigns.
3. Plan three independent anchors: hero silhouette, material/mechanism detail, and
final product/copy composition. Generate only the assets needed or requested.
Do not combine them into a panel sheet that the video model might reproduce.
4. Keep product color, light direction, ratio, and finish consistent across anchors.
Original photos establish identity; generated anchors must pass that identity
check before they can replace them as rendering references.
5. Build the beat table from the reference. Include setup, action, impact, and a
stable closing composition; vary shot distance only when it reveals something.
## Prompt and render
Compile each beat into the appropriate H3 fields using the sibling writing guide.
Bind every anchor to its actual role. Include exact visible copy, entry/exit times,
colors, and same-line text motion in the rendering prompt, not only in the plan.
Keep typography in intentional negative space without covering the product. A
copy-reference image does not mean its line must persist throughout the whole film.
For a short ad, prefer one coherent full-frame film when the backend supports it.
Keep variants ordered: main variant first, supporting variants later, full family
only when useful at the close. Avoid product walls, grids, or an anchor-sheet ending.
Use native audio when appropriate to the brief. A restrained electronic score may
use crisp plucks, airy pads, kick/sub accents and tactile wooden percussion. Set
tempo and energy from the user's direction. Generate a separate score only if
requested or needed for a disclosed audio repair, using an available tool.
## Review and deliver
Verify product geometry, variant colors, functional action, final copy, aspect
ratio, and closing readability. Listen for excessive bass, dropouts, and unfinished
endings. If editing to supplied music, select a suitable continuous excerpt and
align visual impacts to observed beats; do not claim synchronization from text alone.
For bad typography, first reduce simultaneous text or correct its reference. Exact
lettering may need a disclosed overlay when consistent with the user's goal; keep
the raw output. Deliver the film, prompt/beat plan, used copy and music excerpt,
and any remaining product/text fidelity issues.
@@ -0,0 +1,4 @@
interface:
display_name: "Minimalist Product Ads"
short_description: "Direct product anchors, copy, motion, and audio"
default_prompt: "Use $minimalist-product-ad-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,52 @@
# Product anchors, typography, and beats
## Narrative choices
- Launch: negative space → hero product → material detail → real mechanism action
→ supporting variants → stable product and copy.
- Feature touch: stillness → interaction trigger → visible mechanism → detail
magnification → useful result → closing.
- Color family: main variant alone → supporting colors enter → ordered relation
→ unified material finish → full set and copy.
## Independent anchor briefs
| Anchor | What it locks | Check |
| --- | --- | --- |
| Hero | Main variant, recognizable silhouette, attractive opening view | Match supplied body color and product structure |
| Detail | Material, reflection, texture, visible mechanism | No invented ports, controls, or functionality |
| Closing | Product position, negative space, typography treatment | Real copy, readable contrast, no panel/grid layout |
Each is a standalone full-frame image with the project ratio. Reference roles
protect appearance and layout; they do not replace the storyboard or require three
sequential static-image segments. Simplify the number of anchors if the backend
cannot bind them all, and update the prompt labels accordingly.
## Typography
Use one readable line at a time, with a typeface appropriate to the brand. A clean
semibold sans-serif is a useful default, not a requirement to copy a specific brand.
For a two-part color treatment, use dark text on a light field or light text on a
dark field, then emphasize the second part with a specific real product color.
Check contrast rather than blindly coloring text like the product.
The first phrase may enter gently; the second follows while the first shifts
slightly within the same line. Do not accidentally turn this into stacked lines.
Use actual copy in the prompt and spell out its timing. Avoid arrows, separators,
or placeholder marks that the model may reproduce. For a 10-second ad, one middle
copy moment and one stable closing line are often enough. Omit copy when requested.
## Beat table
| Time range | Shot purpose | Visual lead and camera | Variant state | Exact copy | Text color/motion | Handoff | Rhythm |
| --- | --- | --- | --- | --- | --- | --- | --- |
Use one main action per beat. For 5/10/15-second films, roughly 3–4/5–7/6–9 beats
are planning suggestions, not required cuts. Record the ending product position
and motion vector so the next beat can inherit them. Ensure the closing line has
time to be read rather than using every second for motion.
If replacing music, keep useful SFX unless replacement of the whole track was
requested. Use a continuous excerpt with a clear entry, stable middle, and suitable
ending. A short tail fade may resolve a click; do not hide an incomplete phrase
behind a long fade. Confirm the entire result remains audible.
@@ -0,0 +1,74 @@
---
name: music-video-subtitle-generator
description: Plan, write, audit, or produce music videos with coordinated music, imagery, camera motion, optional performance, and lyric typography. Use for beat-aware MVs and instrumental visual narratives, not ordinary subtitle cleanup or unrelated video edits.
metadata:
compatibility: File-based workflow with sibling h3-prompt-writing; audio analysis, generation, and final assembly use available tools and backends.
---
# Music Video and Lyric Typography
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md)
and, when building a timeline, [music timeline](references/music-timeline.md).
Deliver the requested scope: audit, prompt, storyboard, or complete video.
## Lock the musical and visual intent
Identify duration, ratio, output resolution, music source/excerpt, visual style,
character/scene references, performance mode, and whether text is wanted. Treat a
supplied song as the master track unless the user requests replacement. Preserve
provided lyrics exactly; do not translate or rewrite them unasked. For instrumental
work, do not invent singing, lyrics, a performer, or typography. Generate original
lyrics only when requested or clearly within an authorized songwriting brief.
Measure supplied audio duration and inspect/listen for beat, phrase, breath, drop,
and energy changes using available tools. Label estimated or planned timing when
measurement is unavailable. If the requested excerpt exceeds the audio, resolve
the discrepancy without silently stretching or looping it.
## Reference roles
- Character references control identity, silhouette, wardrobe, and presence.
- Scene references control environment, time of day, spatial anchors, and lighting.
- Typography references control letter treatment, layout, and motion, not people
or scenery visible in those references.
- Music references control the requested track or sound style; distinguish copying
a supplied recording from generating similar instrumentation.
Do not apply a preset's grain, darkness, hard cuts, or constant close-ups to an
incompatible brief. A bright nighttime scene remains night. A continuous flat
vector morph keeps smooth color fields and uses elastic shape motion for accents.
Use hard cuts, slow holds, or dissolves only when appropriate to the user's intent.
## Compose the timeline and H3 prompts
Map visual actions, camera moves, scene changes, and optional text to the music's
global time. Build a causal visual progression instead of defaulting to one person
singing throughout. Allow room to read a shape, gesture, or lyric before changing it.
For longer work, choose a supported continuation or clip-assembly approach through
the portable workflow. Keep visual shot boundaries distinct from generation
windows. Local window prompts must account for overlap without replaying the full
previous action. A new scene may require a new reference rather than inheritance
of the old scene. Record entry/exit states for each planned segment.
Compile the timeline into H3's base or Ref2VA fields, preserving dialogue, lyrics,
and visible text verbatim. Include concrete instrumentation and rhythmic accents,
but treat requested BPM and beat alignment as targets until checked in the output.
## Typography and finishing
When requested, give each shot one main readable text event. Keep words away from
eyes and critical lip motion. Match sung text to the active phrase, and distinguish
spatial typography from subtitle bars. Reduce text complexity if the model cannot
render it, or use a disclosed overlay consistent with the requested deliverable.
For supplied audio, assemble clips to one global master track, maintaining phrase
and lip continuity; avoid cuts inside a vowel. For native audio, inspect actual
continuity across generation windows. Preserve useful SFX and avoid layered duplicate
music. Retiming, grain, grading, interpolation, and upscaling are deliberate editing
choices, not automatic finishing requirements.
Verify transformation/story order, identities, scene changes, requested text,
musical continuity, timing, and full media decode. Label raw and processed versions.
Deliver the final file and current prompt/timeline files, with measured properties
and remaining limitations rather than claims of perfect synchronization.
@@ -0,0 +1,4 @@
interface:
display_name: "Music Video Direction"
short_description: "Plan music-driven scenes and optional lyric text"
default_prompt: "Use $music-video-subtitle-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,40 @@
# Global music and local video timing
Use this planning skeleton, omitting vocal/text fields when not requested:
```text
Global: music/excerpt, duration, ratio, character/style/scene locks, cut policy.
Segment S01 — global start/end; local generation start/end
Incoming state:
Music cue: measured beat/phrase timestamp, or clearly labeled planned accent
Vocal line and speaker (if present):
Visible typography (if requested):
Visual action and camera:
Outgoing state and transition:
References with exact roles:
```
Keep one global music clock. For measured tempo B, the nominal quarter-note spacing
is 60/B seconds, but real tracks may have pickups, swing, tempo changes, or inaccurate
automatic detections. Validate important cut points against the waveform/listening.
An onset detector also finds SFX and melodic attacks; it does not prove drum beats.
Choose a phrase-complete excerpt when possible. Track breaths, lyric pauses, and
vowel continuity for a performance MV. For an instrumental morph, accent the
physical preparation, transformation impact, and release rather than introducing
unrequested cuts. The final shape or story payoff needs a readable ending.
For continuation, maintain a table of absolute output frames and local window
times. Use the verified backend's native alignment and overlap behavior. Opening
overlap continues the inherited state; new actions belong after it. Do not sum
overlap twice or describe every window as a fresh opening shot.
For separately generated clips, use a previous tail frame only when the next shot
continues the same scene and the backend supports that keyframe role. For an
intentional cut, retain appropriate identity references and select the new scene.
Preserve screen direction or a useful match element when it helps the transition.
When text is requested, track exact string, language, entry/exit time, screen
region, and motion. Typography style cards must not donate their depicted characters
or settings. Preserve user text; do not silently shorten lyrics to fit a template.
@@ -0,0 +1,69 @@
---
name: paper-collage-explainer-generator
description: Translate narration, ideas, or knowledge points into editorial halftone paper-collage metaphors, still prompts, storyboards, or stop-motion clips. Use for photographic cutout collage and tactile assembly, distinct from paper-puppet dioramas or generic cartoons.
metadata:
compatibility: File-based workflow with sibling h3-prompt-writing; media creation uses available image/video tools and an editor when needed.
---
# Paper Collage Explainer Generator
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md)
and [collage design](references/collage-design.md) when constructing scenes.
Honor prompt-only and asset-only requests as well as finished video requests.
## Meaning and media policy
Extract each source line's meaning, emotion, action verb, visual metaphor, and
three to six readable object groups. Preserve the user's topic and wording as
context; do not put narration on screen merely because it is available.
Default to tactile paper SFX, with no BGM, voiceover, or subtitles unless requested.
An explainer does not inherently require speech. Existing user requests for those
layers already establish the choice; do not ask again. A short 16:9 clip of roughly
four seconds is a useful default for one assembly beat, subject to backend limits.
## Plan the image and motion together
Create a concise plan of metaphor, palette, object groups, assembly order, duration,
and sound cues. For a series, keep paper texture, halftone treatment, keylines,
shadows, and rhythmic language consistent; vary the metaphor and color field only
where the meaning benefits. A user's no-text or no-music requirement applies to
every downstream asset, prompt, clip, and edit.
Design each still as the completed final composition. Use black-and-white
photographic halftone cutouts, selective colored cardstock, warm cream keylines,
subtle fiber/torn edges, and soft physical layer shadows on a bold color field.
Keep layered depth and negative space; avoid excessive age, dirt, wrinkles, or
automatic brown/kraft backgrounds. Exact editable layers require a compositing
workflow, not a claim that a generated raster video is a layered project.
Generate final-composition stills when requested/needed and inspect them. Follow
any requested approval gate; otherwise continue within the authorized workflow.
## Animate the assembly
Start from a clean paper field matching the planned final color. Base pieces enter,
then the main metaphor, then supporting objects. Each piece slides or pops in,
bounces slightly, presses flat, pauses, and locks. End with a brief hold on the
completed composition. Use stop-motion assembly rather than global fades, smooth
digital drifting, fast spinning, or liquid morphs unless explicitly requested.
Use the supplied final still as a last-frame anchor only when the backend supports
it. Otherwise identify it honestly as composition/style guidance; do not describe
a reference image as a guaranteed final-frame constraint. Compile the actual H3
mode with the sibling guide and bind only real inputs.
Map short paper slides, pops, taps, rustles, and snaps to motion. Preserve those
SFX during assembly. When the user adds music or narration later, mix with the
useful SFX unless they request full replacement.
## Review and deliver
Check the metaphor without relying on text, object count/readability, matching
opening color, visible piece-by-piece construction, final-frame resemblance,
controlled paper texture, and no unwanted UI/letters/music/voiceover. If the
assembly is weak, simplify the groups and make their arrival order concrete.
For multiple clips, preserve the planned order and SFX. Deliver prompts, current
stills, and actual clips/assembly as requested. Explain any missing generation
capability or remaining drift; keep raw output separate from edited fixes.
@@ -0,0 +1,4 @@
interface:
display_name: "Paper Collage Explainers"
short_description: "Animate editorial paper metaphors with tactile SFX"
default_prompt: "Use $paper-collage-explainer-generator to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,33 @@
# Editorial collage design
For each source idea record meaning, emotion, one active verb, a concrete visual
metaphor, three to six object groups, and a tactile sound cue. For example, a
collaboration concept can become separate cutout hands fitting paper routes
together; it need not become a screen full of the word "collaboration".
Style signature: flat bold color field, black-and-white halftone photographic
cutouts, selective colored cardstock accents, warm cream keylines, soft physical
paper shadows, fine uncoated fiber, refined irregular torn edges, layered seams.
Control texture so the video feels handmade without becoming dirty or distressed.
Palette choices may support meaning: orange/red for urgency, mustard for tools or
warning, green for reflection, purple for memory, teal for collaboration, rose for
theatrical contrast. These are suggestions, not universal semantic rules. Use the
user's palette and avoid an unrelated kraft-paper opening.
Still specification: source meaning → metaphor → ratio → background/accent colors
→ object roles → foreground/midground/background → final composition → exclusions.
Motion specification: matching empty color field → base paper structures → main
subject → secondary groups → light bounce/press-flat/pause → completed composition.
Explicitly describe material boundaries and which piece moves at each beat. A
static camera is usually enough to reveal assembly; add movement only when useful.
Sound specification: synchronize restrained slides, pops, taps, rustles, and snaps.
Set `non_diegetic_music: N/A` for the default SFX-only H3 prompt. Do not use silence
as a synonym for no background music. Dialogue and subtitles are optional layers.
Review one established clip as a style/cadence reference for subsequent clips.
Change the specific failure: fewer groups for clutter, more physical arrival
steps for digital drifting, less aging for dirty paper, stronger final state for
composition drift. Do not apply an unrelated aesthetic preset to fix one defect.
@@ -0,0 +1,68 @@
---
name: papercraft-stop-motion-explainer
description: Turn knowledge topics into layered papercraft stop-motion explainers, including visual metaphors, miniature sets, prompts, storyboards, motion and sound plans, or rendered video. Use for tactile paper dioramas and mechanisms, not ordinary vector cartoons or photographic collage.
metadata:
compatibility: Portable file-based workflow with sibling h3-prompt-writing; actual media generation uses tools available in the host environment.
---
# Papercraft Stop-Motion Explainer
Read the [portable workflow](../h3-prompt-writing/references/portable-workflow.md).
For scene/prompt planning read [paper staging](references/paper-staging.md).
Route a single image prompt, image series, short animation prompt, or storyboard
request directly to that artifact; do not force a full film workflow.
## Knowledge and direction
Identify the exact topic, source facts, audience, desired learning outcome, and
output type. Preserve technical terms and causal meaning. A visual metaphor helps
explain a mechanism; it must not turn into a false scientific claim.
Reuse requested duration and ratio. If unspecified, a 30-second, 16:9 general-audience
concept is a reasonable starting point. Offer distinct directions only when useful:
pop-up-book journey, paper laboratory, layered cross-section, miniature nature
theater, or mechanism board. Each should identify one learning outcome, a paper
mechanism that conveys it, and the signature visual moment.
## Build the paper world
Design characters only if they help the explanation. Specify paper construction,
silhouette, joint/hinge design, replaceable facial parts, gestures, and repeatable
identity cues. Keep the central knowledge object readable.
Plan foreground occluders, midground mechanism, background scenery, and a distant
paper plane when the shot benefits from depth. Record each layer's material,
thickness, cut/fold edges, shadow, movement, and educational role. Use real paper
mechanics such as pull-tabs, sliders, rotating discs, page folds, and pinned joints.
Motion is stepped with small pauses and rebounds, not a smooth CG melt.
Create an asset list by function: characters, scientific objects, information cards,
environment, and moving mechanisms. For a complete production, prepare one to three
preview briefs or images when useful and authorized. Respect any preview approval
requested by the user; otherwise continue under the existing end-to-end scope.
## Prompts and timeline
Write a compact storyboard with columns: time, knowledge beat, visual action,
paper movement, camera/transition, sound cue. Each shot explains one knowledge
step, with a readable pause for a necessary label. Keep ratio, material, light
direction, identity, and domain vocabulary consistent across image-series prompts.
For a short image-to-video prompt, begin from the actual image and animate a few
paper mechanisms with restrained push-in or lateral parallax. Preserve thickness,
cut edges, texture, and layer shadows. Compile final rendering prompts using the
bundled H3 mode guide, with truthful references and local clip timing.
## Sound, generation, and review
Map paper flips, cardboard slides, joint taps, soft pops, and rustles to actual
motion. Match any requested music to the topic and cultural context rather than
automatically using the same whimsical score. Keep speech intelligible. If narration
is requested, preserve its approved wording, measure its duration, and adjust the
timeline or script within the user's scope; do not silently speed up the voice.
Use available generation/editing tools, then inspect both educational accuracy
and paper construction: readable mechanism, separate layers/shadows, hinged or
stepped motion, plausible camera movement, and a clear ending takeaway. Check
narration tails, clicks, audio gaps, and music balance. Deliver requested artifacts
and state missing rendering capabilities or unresolved scientific/visual issues.
@@ -0,0 +1,4 @@
interface:
display_name: "Papercraft Explainers"
short_description: "Explain ideas with layered paper mechanisms"
default_prompt: "Use $papercraft-stop-motion-explainer to prepare the requested H3 prompts or video with the available tools."
@@ -0,0 +1,48 @@
# Paper staging and prompt building blocks
## Materials and depth
Use layered matte cardboard, visible thickness, fibers, folds, seams, tabs, brads,
cut edges, and physical shadows. Separate foreground, mechanism, background, and
far background with spacing. Roughly 4–7 layers often suffice; remove layers that
hide the explanation. Place reading-heavy labels on stable planes. Use miniature
macro photography and moderate depth of field without blurring necessary facts.
| Plane | Useful content | Motion | Purpose |
| --- | --- | --- | --- |
| Foreground | Paper leaves, frame, curtain, props | Small parallax or hinged reveal | Depth without concealing the mechanism |
| Midground | Knowledge model, host, arrow/cards | Pull-tab, slider, joint, rotating disc | Main cause and effect |
| Background | Diorama set or schematic layers | Slow paper rail shift | Context and hierarchy |
| Far plane | Paper sky/board | Restrained change | Stable visual support |
## Prompt ingredients
Image: topic and core object → miniature diorama → layered paper construction →
specific material edges → physical light/shadows → clear depth → necessary labels.
One frame should communicate one main idea, not the entire lesson at once.
Image series: repeat the style/identity anchors, then state what changes and why
that state explains the next knowledge step. Keep the same ratio and light direction.
Short animation: preserve the supplied image, make a small paper mechanism move,
include tiny pauses/rebounds, and select one restrained camera move. Small puppet
gestures and background rails can provide life without overwhelming the model.
## Camera and transitions
Use slow push-in, lateral parallax, static demonstration, macro detail, or a slight
top-down cross-section view. A full orbit requires a set built for that view.
Transitions may use a page flip, pop-up unfold, pull-tab, paper cloud, circular
mask, cut-paper door, layer separation, confetti, or sticker reveal. Avoid liquid
morphing, neon glitches, and glass shatter unless explicitly requested as contrast.
## Sound and exclusions
Use a few motion-specific accents: paper flip, scissors, cardboard slide, joint
tap, tape peel, or box opening. A score, when appropriate, can use light percussion,
plucked instruments, or topic-specific instrumentation. Do not infer narration
merely because the video teaches something.
Keep constraints concise and relevant: no glossy plastic, smooth CG morphs, metal
surfaces replacing paper, missing thickness/shadows, or frantic camera movement.
Include them in supported prompt fields; do not invent a negative-prompt API field.
@@ -319,7 +319,7 @@ source_file_dependencies:
diffusion_wan22_perf:
- *wan22
- tests/dfx/perf/scripts/run_diffusion_benchmark.py
- tests/dfx/perf/scripts/run_benchmark.py
- tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json
diffusion_wan22_reliability:
@@ -345,7 +345,7 @@ source_file_dependencies:
diffusion_cosmos3_perf:
- *cosmos3
- tests/dfx/perf/scripts/run_diffusion_benchmark.py
- tests/dfx/perf/scripts/run_benchmark.py
- tests/dfx/perf/tests/test_cosmos3_vllm_omni.json
diffusion_qwen_image_function:
@@ -418,7 +418,7 @@ source_file_dependencies:
diffusion_minimax_h3_perf:
- *minimax_h3
- tests/dfx/perf/scripts/run_diffusion_benchmark.py
- tests/dfx/perf/scripts/run_benchmark.py
- tests/dfx/perf/tests/test_minimax_h3_vllm_omni.json
diffusion_hunyuan_video_function:
@@ -432,7 +432,7 @@ source_file_dependencies:
diffusion_hunyuan_video_perf:
- *hunyuan_video
- tests/dfx/perf/scripts/run_diffusion_benchmark.py
- tests/dfx/perf/scripts/run_benchmark.py
- tests/dfx/perf/tests/test_hunyuanvideo15_t2v_vllm_omni.json
- tests/dfx/perf/tests/test_hunyuanvideo15_i2v_vllm_omni.json
@@ -459,6 +459,11 @@ source_file_dependencies:
- tests/e2e/online_serving/test_lingbot_video_moe.py
- tests/e2e/offline_inference/test_lingbot_world_v2.py
diffusion_lingbot_perf:
- *lingbot_video
- tests/dfx/perf/scripts/run_benchmark.py
- tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json
# Invalid-param jobs: model business code only, plus the reliability scripts.
reliability_invalid_param_h100:
+1 -1
View File
@@ -18,7 +18,7 @@ steps:
mirror_hardwares: l4_1
- label: "Simple · Diffusion Test"
timeout_in_minutes: 20
timeout_in_minutes: 30
commands:
- "pytest -sv tests/diffusion -m 'core_model and cpu'"
mirror_hardwares: l4_1
+63 -90
View File
@@ -53,80 +53,62 @@ steps:
- label: ":full_moon: Omni · Accuracy Test"
source_file_dependencies: omni_qwen3_omni_accuracy
timeout_in_minutes: 180
artifact_paths:
- tests/e2e/accuracy/qwen3_omni/results/qwen_omni_acc/*.json
commands:
- export SEED_TTS_WER_EVAL=1
- export SEED_TTS_EVAL_DEVICE=cuda:1
- |
set +e
pytest -s -v tests/e2e/accuracy/qwen3_omni/test_qwen3_omni.py -m "full_model and H100 and B200 and cards_2" --run-level full_model
EXIT=$$?
buildkite-agent artifact upload "tests/e2e/accuracy/qwen3_omni/results/qwen_omni_acc/*.json"
exit $$EXIT
- pytest -s -v tests/e2e/accuracy/qwen3_omni/test_qwen3_omni.py -m "full_model and H100 and B200 and cards_2" --run-level full_model
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Accuracy Test"
source_file_dependencies: omni_minicpmo_4_5_accuracy
timeout_in_minutes: 180
artifact_paths:
- tests/e2e/accuracy/minicpmo_4_5/results/*.json
commands:
- export SEED_TTS_WER_EVAL=1
- export SEED_TTS_EVAL_DEVICE=cuda:1
- |
set +e
pytest -s -v tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py -m "full_model and H100 and B200 and cuda and (cards_1 or cards_2)" --run-level full_model
EXIT=$$?
buildkite-agent artifact upload "tests/e2e/accuracy/minicpmo_4_5/results/*.json"
exit $$EXIT
- pytest -s -v tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py -m "full_model and H100 and B200 and cuda and (cards_1 or cards_2)" --run-level full_model
- label: ":full_moon: Omni · Perf Test · No Async Chunk"
source_file_dependencies: omni_qwen3_omni_perf
key: nightly-omni-performance-no-async-chunk
timeout_in_minutes: 300
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_no_async_chunk.json -m "H100 and B200 and cards_2"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_no_async_chunk.json -m "H100 and B200 and cards_2"
- label: ":full_moon: Omni · Perf Test · Async Chunk"
source_file_dependencies: omni_qwen3_omni_perf
key: nightly-omni-performance-async-chunk
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json -m "H100 and B200 and full_model and cards_2"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json -m "H100 and B200 and full_model and cards_2"
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Perf Test"
source_file_dependencies: omni_minicpmo_4_5_perf
key: nightly-omni-performance-minicpmo-4-5
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5.json -m "H100 and B200 and cards_1"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5.json -m "H100 and B200 and cards_1"
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Duplex Seed-TTS Perf Test"
source_file_dependencies: omni_minicpmo_4_5_duplex_perf
key: nightly-omni-performance-minicpmo-4-5-duplex-seed-tts
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_duplex_seed_tts.json -m "H100 and B200 and cards_1"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_duplex_seed_tts.json -m "H100 and B200 and cards_1"
- label: ":full_moon: Omni · Multi-Replica Startup Test with 4x H100"
source_file_dependencies: omni_qwen3_omni_function
@@ -153,29 +135,23 @@ steps:
source_file_dependencies: tts_qwen3_tts_perf
key: nightly-tts-performance-single
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json -m "H100 and B200 and cards_1"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json -m "H100 and B200 and cards_1"
- label: ":full_moon: TTS · Perf Test · 2-GPU"
source_file_dependencies: tts_qwen3_tts_perf
key: nightly-tts-performance-2gpu
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json -m "H100 and B200 and cards_2"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json -m "H100 and B200 and cards_2"
- group: ":card_index_dividers: Tiny Model Tests [Multi-GPU]"
key: nightly-tiny-model-test-group
@@ -277,33 +253,27 @@ steps:
source_file_dependencies: diffusion_qwen_image_perf
key: nightly-diffusion-x2iat-performance-qwen-image-single
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/diffusion_result_*.json
- tests/dfx/perf/results/logs/*.log
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export CACHE_DIT_VERSION=1.5.0
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json -m "H100 and B200 and cards_1"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json -m "H100 and B200 and cards_1"
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image · 4-GPU"
source_file_dependencies: diffusion_qwen_image_perf
key: nightly-diffusion-x2iat-performance-qwen-image-4gpu
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/diffusion_result_*.json
- tests/dfx/perf/results/logs/*.log
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export CACHE_DIT_VERSION=1.5.0
# Do not pin DIFFUSION_ATTENTION_BACKEND: H100 auto-selects FLASH_ATTN;
# B200 rejects explicit FLASH_ATTN without FA4 and uses CUDNN/TRTLLM.
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json -m "H100 and B200 and cards_4"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json -m "H100 and B200 and cards_4"
- group: ":card_index_dividers: Diffusion X2V Model Test"
key: nightly-diffusion-x2v-group
@@ -399,15 +369,11 @@ steps:
source_file_dependencies: diffusion_wan22_perf
key: nightly-diffusion-x2v-performance-single
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json -m "H100 and B200 and cards_1"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
- export BENCHMARK_DIR=tests/dfx/perf/results
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json -m "H100 and B200 and cards_1"
- label: ":full_moon: Diffusion X2V · Perf Test · 2-GPU"
source_file_dependencies:
@@ -415,16 +381,16 @@ steps:
- diffusion_cosmos3_perf
key: nightly-diffusion-x2v-performance-2gpu
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json -m "H100 and B200 and cards_2"
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json -m "H100 and B200 and cards_2"
EXIT1=$$?
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json -m "H100 and B200 and cards_2"
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json -m "H100 and B200 and cards_2"
EXIT2=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
if [ $$EXIT1 -ne 0 ]; then exit $$EXIT1; fi
exit $$EXIT2
@@ -432,16 +398,16 @@ steps:
source_file_dependencies: diffusion_hunyuan_video_perf
key: nightly-diffusion-x2v-performance-hunyuanvideo15
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_hunyuanvideo15_t2v_vllm_omni.json -m "H100 and B200 and (cards_1 or cards_2)"
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_hunyuanvideo15_t2v_vllm_omni.json -m "H100 and B200 and (cards_1 or cards_2)"
EXIT1=$$?
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_hunyuanvideo15_i2v_vllm_omni.json -m "H100 and B200 and (cards_1 or cards_2)"
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_hunyuanvideo15_i2v_vllm_omni.json -m "H100 and B200 and (cards_1 or cards_2)"
EXIT2=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
if [ $$EXIT1 -ne 0 ]; then exit $$EXIT1; fi
exit $$EXIT2
@@ -449,15 +415,21 @@ steps:
source_file_dependencies: diffusion_minimax_h3_perf
key: nightly-diffusion-x2v-performance-minimax-h3
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_minimax_h3_vllm_omni.json -m "H100 and B200 and cards_4"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
- export BENCHMARK_DIR=tests/dfx/perf/results
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minimax_h3_vllm_omni.json -m "H100 and B200 and cards_4"
- label: ":full_moon: Diffusion X2V · Perf Test · LingBot"
source_file_dependencies: diffusion_lingbot_perf
key: nightly-diffusion-x2v-performance-lingbot
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_lingbot_video_vllm_omni.json -m "H100 and B200 and cards_1"
- group: ":card_index_dividers: Diffusion Test"
key: nightly-diffusion-test-group
@@ -533,6 +505,9 @@ steps:
- nightly-diffusion-x2v-performance-single
- nightly-diffusion-x2v-performance-2gpu
if: build.env("NIGHTLY") == "1" && build.env("EMAIL_DISTRIBUTION") == "1"
artifact_paths:
- tests/dfx/perf/results/*.xlsx
- tests/dfx/perf/results/*.html
commands:
- pip install openpyxl
- export DEFAULT_INPUT_DIR=tests/dfx/perf/results
@@ -548,7 +523,5 @@ steps:
- python tools/nightly/generate_nightly_perf_excel.py
- python tools/nightly/generate_nightly_perf_html.py
- python tools/nightly/send_nightly_email.py --report-file "tests/dfx/perf/results/*.xlsx, tests/dfx/perf/results/*.html"
- buildkite-agent artifact upload "tests/dfx/perf/results/*.xlsx"
- buildkite-agent artifact upload "tests/dfx/perf/results/*.html"
agents:
queue: "cpu_queue_premerge"
+1 -1
View File
@@ -17,7 +17,7 @@ steps:
mirror_hardwares: l4_1
- label: "Simple · Diffusion Test"
timeout_in_minutes: 20
timeout_in_minutes: 30
commands:
- "pytest -sv tests/diffusion -m 'core_model and cpu'"
mirror_hardwares: l4_1
+11 -19
View File
@@ -12,6 +12,8 @@ steps:
depends_on: upload-weekly-pipeline
if: build.env("WEEKLY") == "1"
timeout_in_minutes: 60
artifact_paths:
- coverage-core-model-cpu-*.xml.gz
commands:
- |
set +e
@@ -19,7 +21,6 @@ steps:
pytest -sv tests/ -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:$${REPORT}
EXIT=$$?
gzip -9 -f "$${REPORT}"
buildkite-agent artifact upload "$${REPORT}.gz"
exit $$EXIT
mirror_hardwares: l4_1
@@ -79,41 +80,32 @@ steps:
source_file_dependencies: omni_qwen3_omni_perf
key: weekly-omni-performance-vllm-text
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_vllm_text.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_vllm_text.json
mirror_hardwares: h100_2
- label: ":full_moon: Omni · Perf Test · Async Chunk · Random"
key: weekly-omni-performance-async-chunk-random
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json -m "H100 and slow"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json -m "H100 and slow"
mirror_hardwares: h100_2
- label: ":full_moon: Omni · Perf Test · Multi-Replica"
source_file_dependencies: omni_qwen3_omni_perf
key: weekly-omni-performance-multi-replicas
timeout_in_minutes: 180
artifact_paths:
- tests/dfx/perf/results/*.json
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_multi_replicas.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_multi_replicas.json
mirror_hardwares: h100_3
- group: ":card_index_dividers: E2E Tests"
+1 -1
View File
@@ -10,7 +10,7 @@ steps:
DOCKER_BUILDKIT: "1"
# Buildkite will automatically replace this with the actual commit hash
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}"
VLLM_VERSION: "v0.29.0"
VLLM_VERSION: "v0.30.0"
priority: 100
timeout_in_minutes: 60
soft_fail: false
+3 -4
View File
@@ -286,11 +286,10 @@ steps:
HF_TOKEN: "${HF_TOKEN}"
DIFFUSION_ATTENTION_BACKEND: TORCH_SDPA
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py -m npu --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py -m npu --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/3d-animation-short-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/brand-promo-video-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/co-op-game-intro-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/h3-prompt-writing
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/handdrawn-live-video-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/minimalist-product-ad-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/music-video-subtitle-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/paper-collage-explainer-generator
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/papercraft-stop-motion-explainer
+8
View File
@@ -60,6 +60,14 @@ self-check.
`test-weekly.yml`), and copy-paste local plus CI-like `pytest` commands; see
`references/test-routing.md` for level-to-command mapping
## MiniMax H3 Creative Skills
The [portable H3 collection](../../.agents/skills/README.md) contains the prompt
writing skill and eight creative workflows for product ads, 3D shorts, paper art,
brand promos, music videos, co-op intros, and hand-drawn/live-action fusion.
Same-named directories here link to the canonical `.agents/skills/` copies, so
Claude Code and Codex use the same instructions and templates.
## Maintenance Guidelines
- Keep skill names short and task-oriented.
+3 -3
View File
@@ -16,9 +16,9 @@ What changed:
`/v1/images/edits` endpoint to produce benchmark outputs in the expected
directory structure.
- The evaluator keeps the same VIEScore-style decomposition:
- `semantics_score`
- `quality_score`
- `overall_score = sqrt(semantics_score * quality_score)`
- `semantics_score`
- `quality_score`
- `overall_score = sqrt(semantics_score * quality_score)`
- Judge calls are routed to a local OpenAI-compatible model served by
`vllm-omni`, not a remote provider.
+7 -7
View File
@@ -12,14 +12,14 @@ Upstream mapping:
What changed:
- Generation calls the local OpenAI-compatible `vllm-omni` endpoints:
- `/v1/images/generations` for text-only frame generation
- `/v1/images/edits` for image-conditioned GUI transition generation
- `/v1/images/generations` for text-only frame generation
- `/v1/images/edits` for image-conditioned GUI transition generation
- Evaluation still keeps the GEBench scoring dimensions:
- `goal`
- `logic`
- `cons`
- `ui`
- `qual`
- `goal`
- `logic`
- `cons`
- `ui`
- `qual`
- Judge calls are also routed to a local OpenAI-compatible model served by
`vllm-omni` instead of a remote service.
@@ -31,6 +31,7 @@ docker run -it \
```
Parameter explanation:
- `--cap-add=SYS_PTRACE`: Allow reading system topology information
- `--cap-add=IPC_LOCK`: Allow memory locking (required for RDMA memory registration)
- `--security-opt seccomp=unconfined`: Disable seccomp restrictions
@@ -60,6 +61,7 @@ When running single-node tests (producer and consumer on the same machine), ensu
InfiniBand devices use LID (Local Identifier) for routing. Different devices have different LIDs and cannot communicate directly. If no device is specified, Mooncake may assign different devices to connectors, causing handshake failures.
Common error:
```
[Handshake] Failed to modify QP to RTR, check mtu, gid, peer lid, peer qp num: Invalid argument [22]
```
@@ -249,7 +251,7 @@ python cross_node_mooncake_transfer_engine.py \
### Transfer Modes
| Mode | Description | Example |
|------|-------------|---------|
| ------ | ------------- | --------- |
| `copy` | Normal path — tensor copied to RDMA pool (default) | `--mode copy` |
| `zerocopy` | Zero-copy path — data created directly in RDMA pool | `--mode zerocopy` |
| `gpu` | GPU transfer — RDMA pool on GPU, uses GPUDirect | `--mode gpu --gpu-id 0` |
@@ -281,7 +283,7 @@ python cross_node_mooncake_transfer_engine.py \
### Cross-Node Test Options
| Option | Description | Default |
|--------|-------------|---------|
| -------- | ------------- | --------- |
| `--role` | `producer` or `consumer` | Required |
| `--local-host` | Local RDMA IP address | Required |
| `--remote-host` | Remote RDMA IP address | Required |
@@ -303,6 +305,7 @@ python cross_node_mooncake_transfer_engine.py \
**Cause**: QP handshake failed, usually due to device configuration mismatch.
**Solution**:
```bash
# Force using the same device
export RDMA_DEVICE_NAME='mlx5_0'
@@ -313,6 +316,7 @@ export RDMA_DEVICE_NAME='mlx5_0'
**Cause**: Mooncake not installed or import failed.
**Solution**:
```bash
# Check Mooncake installation
python -c "from mooncake.engine import TransferEngine; print('OK')"
@@ -329,6 +333,7 @@ uv pip install mooncake-transfer-engine
**Cause**: Container lacks IB device access permissions.
**Solution**:
```bash
docker run --device=/dev/infiniband --cap-add=IPC_LOCK ...
```
@@ -338,6 +343,7 @@ docker run --device=/dev/infiniband --cap-add=IPC_LOCK ...
**Cause**: RDMA connection establishment failed or network latency.
**Solution**:
```bash
# Check network status
ibstat
@@ -349,6 +355,7 @@ ibstatus
**Cause**: CUDA environment not configured or GPU unavailable.
**Solution**:
```bash
# Check CUDA
python -c "import torch; print(torch.cuda.is_available())"
@@ -362,7 +369,7 @@ docker run --gpus all ...
## Environment Variables Reference
| Variable | Description | Example |
|----------|-------------|---------|
| ---------- | ------------- | --------- |
| `RDMA_DEVICE_NAME` | Specify RDMA device name | `mlx5_0` |
| `RDMA_TEST_HOST` | Specify test host IP | `10.0.0.1` |
| `MC_TE_METRIC` | Enable Mooncake metrics | `1` |
@@ -373,7 +380,7 @@ docker run --gpus all ...
## Test Files Overview
| File | Description | Auto-discovered by pytest |
|------|-------------|--------------------------|
| ------ | ------------- | -------------------------- |
| `test_mooncake_transfer_engine_rdma.py` | Integration tests for MooncakeTransferEngineConnector (basic, E2E, lifecycle, GPU) | Yes |
| `test_mooncake_transfer_engine_buffer.py` | Memory pool and buffer management unit tests | Yes |
| `cross_node_mooncake_transfer_engine.py` | Cross-node (multi-machine) testing script — run manually | No (filename does not start with `test_`) |
@@ -381,7 +388,7 @@ docker run --gpus all ...
### test_mooncake_transfer_engine_rdma.py — Test Classes
| Test Class | Memory Pool | Marker | Description |
|------------|-------------|--------|-------------|
| ------------ | ------------- | -------- | ------------- |
| `TestBasicConnector` | CPU | — | Initialization, put tensor/bytes/object, cleanup, pool exhaustion |
| `TestEndToEnd` | CPU | — | E2E RDMA transfer: tensor, bytes, object, zero-copy, large payload (100MB), mixed types, concurrency |
| `TestLifecycle` | CPU | — | Close, context manager, double-close safety |
@@ -391,7 +398,7 @@ docker run --gpus all ...
### test_mooncake_transfer_engine_buffer.py — Test Classes
| Test Class | Marker | Description |
|------------|--------|-------------|
| ------------ | -------- | ------------- |
| `TestBufferAllocator` | — | Basic alloc/free, alignment, exhaustion/recovery, thread safety |
| `TestAllocatorInvariants` | `slow` | Double-free safety, overlap corruption detection, adjacent-block merging, fragmentation/defrag |
| `TestManagedBuffer` | — | Tensor views, context manager |
+6 -6
View File
@@ -5,7 +5,7 @@ Benchmark GLM-Image T2I (text-to-image) and I2I (image-to-image) performance acr
## Benchmarks
| Benchmark | Script | Description |
|-----------|--------|-------------|
| ----------- | -------- | ------------- |
| HuggingFace Baseline | `huggingface/inference.py` | Single-GPU transformers + diffusers pipeline |
| vLLM-Omni Offline | `vllm-omni/inference.py` | Offline inference with continuous batching |
| vLLM-Omni Online | `benchmark_glm_image.py` | Online serving via `/v1/chat/completions` |
@@ -27,7 +27,7 @@ CUDA_VISIBLE_DEVICES=0 python benchmarks/glm_image/huggingface/inference.py \
### Options
| Flag | Default | Description |
|------|---------|-------------|
| ------ | --------- | ------------- |
| `--model-path` | `zai-org/GLM-Image` | Model path |
| `--mode` | `t2i` | `t2i` or `i2i` |
| `--dataset-path` | `prompt/prompt.json` | Path to prompt.json |
@@ -54,7 +54,7 @@ CUDA_VISIBLE_DEVICES=0,1 python benchmarks/glm_image/vllm-omni/inference.py \
### Options
| Flag | Default | Description |
|------|---------|-------------|
| ------ | --------- | ------------- |
| `--model-path` | `zai-org/GLM-Image` | Model path |
| `--deploy-config` | - | Deploy config YAML |
| `--mode` | `t2i` | `t2i` or `i2i` |
@@ -100,7 +100,7 @@ python benchmarks/glm_image/benchmark_glm_image.py \
### Options
| Flag | Default | Description |
|------|---------|-------------|
| ------ | --------- | ------------- |
| `--mode` | `t2i` | `t2i` or `i2i` |
| `--dataset` | `prompt` | `prompt`, `random`, or `custom` |
| `--dataset-path` | - | JSON file path (required for `custom`) |
@@ -134,7 +134,7 @@ Custom datasets use the same JSON format and can be provided via `--dataset-path
All three benchmarks report per-stage pipeline timings (in milliseconds):
| Key | Description |
|-----|-------------|
| ----- | ------------- |
| `preprocess_ms` | Input preprocessing (tokenization, multimodal encoding) |
| `stage_0_gen_ms` | AR (autoregressive) model generation time |
| `ar2diffusion_ms` | AR output to diffusion input conversion |
@@ -148,7 +148,7 @@ The stages are ordered by execution: `preprocess → stage_0 (AR) → ar2diffusi
Tested on 2x GPU with 10 prompts, 1024x1024, 50 denoising steps:
| Backend | Mode | Latency Mean (s) | Throughput (img/s) |
|---------|------|-------------------|--------------------|
| --------- | ------ | ------------------- | -------------------- |
| HuggingFace | T2I | 72.6 | 0.014 |
| HuggingFace | I2I | 70.9 | 0.014 |
| vLLM-Omni Offline | T2I | 35.0 | 0.044 |
+2 -2
View File
@@ -1,10 +1,10 @@
# Target vLLM version: v0.29.0 (tag @ 98dff2a81d74).
# Target vLLM version: v0.30.0 (tag @ ced6857afa0e).
# Use the matching release image and its coherent CUDA/PyTorch/FlashInfer
# runtime. Do not reinstall a commit wheel or repair dependencies on top of
# an older base image at a release boundary. For a future unreleased target,
# restore an explicit wheel pin and validate its complete runtime stack.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
ARG VLLM_BASE_TAG=v0.29.0
ARG VLLM_BASE_TAG=v0.30.0
# We use registry-based layer caching for the dependencies,
# where the tag is determined based on the hash of the contents
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai:v0.29.0
ARG BASE_IMAGE=vllm/vllm-openai:v0.30.0
FROM ${BASE_IMAGE}
ARG COMMON_WORKDIR=/app
+2 -2
View File
@@ -1,7 +1,7 @@
# Keep the ROCm runtime aligned with the vLLM release targeted by
# docker/Dockerfile.ci. A stale base image can build successfully while failing
# at runtime when vLLM-Omni imports APIs added by the newer vLLM release.
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.29.0
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.30.0
# Keep the dependency-heavy portion independent from the source tree. The
# registry cache is keyed on these inputs, while the final source copy can be
@@ -16,7 +16,7 @@ FROM ${BASE_IMAGE} AS deps
# intentional custom source pin must update that regression in the same change,
# and should return to the stable release as soon as one is available.
ARG USE_NIGHTLY_BUILD=0
ARG VLLM_VERSION_OR_COMMIT_HASH=v0.29.0
ARG VLLM_VERSION_OR_COMMIT_HASH=v0.30.0
ARG ARG_PYTORCH_ROCM_ARCH
ENV PYTORCH_ROCM_ARCH=${ARG_PYTORCH_ROCM_ARCH:-${PYTORCH_ROCM_ARCH}}
+2 -2
View File
@@ -1,4 +1,4 @@
# Target vLLM version: v0.29.0. Keep VLLM_VERSION aligned with the target vLLM
# Target vLLM version: v0.30.0. Keep VLLM_VERSION aligned with the target vLLM
# release on every rebase.
#
# This file layers vLLM-Omni on top of the published upstream XPU image, which
@@ -18,7 +18,7 @@
#
# .buildkite/intel/scripts/run-xpu-test.sh automates that pull-then-build fallback.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai-xpu
ARG VLLM_VERSION=v0.29.0
ARG VLLM_VERSION=v0.30.0
ARG VLLM_BASE=${VLLM_BASE_IMAGE}:${VLLM_VERSION}
FROM ${VLLM_BASE} AS vllm-omni
+3
View File
@@ -80,6 +80,8 @@ nav:
- Cache Acceleration:
- TeaCache: user_guide/diffusion/cache_acceleration/teacache.md
- Cache-DiT: user_guide/diffusion/cache_acceleration/cache_dit.md
- KV Cache:
- Scheduler-Managed Paged KV Cache: user_guide/diffusion/paged_kv_cache.md
- Parallelism:
- Overview: user_guide/diffusion/parallelism/overview.md
- CFG Parallel: user_guide/diffusion/parallelism/cfg_parallel.md
@@ -153,6 +155,7 @@ nav:
- Attention Optimization:
- Backend Selection: design/feature/attention_backend_selection.md
- Skip-Softmax: design/feature/skip_softmax.md
- Scheduler-Managed Paged KV Cache: design/feature/diffusion_paged_kv_cache.md
- CPU Offloading:
- Overview: design/feature/offloader/README.md
- Model-Level: design/feature/offloader/module_offload.md
@@ -7,16 +7,17 @@ When you want to add L4-level ***performance test*** cases, add entries to JSON
| Omni (nightly) | `run_benchmark.py` | `test_qwen3_omni_no_async_chunk.json`, `test_qwen3_omni_async_chunk.json` (`full_model` without `slow` in `mark`) |
| Omni (weekly) | `run_benchmark.py` | `test_qwen3_omni_async_chunk.json` (CUDA only), `test_qwen3_omni_vllm_text.json`, `test_qwen3_omni_multi_replicas.json` (`slow` in `mark`; **Perf Test** in `test-weekly.yml`) |
| TTS | `run_benchmark.py` | `test_tts.json`, `test_voxcpm2.json`, `test_higgs_audio_v3.json` |
| Diffusion | `run_diffusion_benchmark.py` | `test_qwen_image_vllm_omni.json`, `test_bagel_vllm_omni.json`, `test_wan22_i2v_vllm_omni.json`, `test_cosmos3_vllm_omni.json`, … |
| Diffusion (`/v1/chat/completions`) | `run_diffusion_benchmark.py` | `test_qwen_image_vllm_omni.json`, `test_bagel_vllm_omni.json`, … |
| Diffusion (`/v1/images/generations`, `/v1/images/edits`, `/v1/videos`) | `run_benchmark.py` | `test_wan22_i2v_vllm_omni.json`, `test_cosmos3_vllm_omni.json`, `test_lingbot_video_vllm_omni.json`, … |
#### How runners pick cases
Without **`--test-config-file`**, each runner scans all `*.json` under `tests/dfx/perf/tests/` but only keeps its own model type:
- **`run_benchmark.py`**: omni and TTS cases only (skips diffusion JSON).
- **`run_diffusion_benchmark.py`**: diffusion cases only (skips omni / TTS JSON).
- **`run_benchmark.py`**: cases whose `benchmark_params` use ``dataset_name`` (omni, TTS, and image/video OpenAI generation via `vllm bench serve --omni`).
- **`run_diffusion_benchmark.py`**: cases whose `benchmark_params` use ``dataset`` (diffusion client schema, including custom jsonl).
Diffusion cases are detected when the JSON has `server_type` (typically `"vllm-omni"`) or `"diffusion"` in the `mark` array. Omni / TTS JSON has neither.
Diffusion-script cases are detected by `is_diffusion_perf_config()`: presence of ``dataset`` (without ``dataset_name``) in `benchmark_params`. Marks / `server_type` / endpoint values are not used for this split.
#### Running perf cases
@@ -60,16 +61,16 @@ Pass **`--test-config-file`** to load one JSON file, or omit it for the bulk sca
##### Overview
| Field | Required | Description |
| ------------------ | -------------- | -------------------------------------------- |
| test_name | Yes | Unique identifier for the test case |
| mark | No | Pytest marks; see **`mark` field** below |
| server_params | Yes | Server-side configuration parameters |
| benchmark_params | Yes | Benchmark running parameters |
| server_type | Diffusion only | Routes case to run_diffusion_benchmark.py |
| benchmark_endpoint | Diffusion only | Benchmark API path |
| Field | Required | Description |
| ------------------ | --------- | ---------------------------------------------------------------------------------------- |
| test_name | Yes | Unique identifier for the test case |
| mark | No | Pytest marks; see **`mark` field** below |
| server_params | Yes | Server-side configuration parameters |
| benchmark_params | Yes | Benchmark running parameters |
| server_type | Diffusion | Only for diffusion-script JSON; omit on omni-bench generation cases |
| benchmark_endpoint | Optional | Legacy diffusion custom-jsonl alias; prefer `benchmark_params[].endpoint` |
Omit `mark` only for configs not meant to be filtered by `-m`. `server_type` is typically `"vllm-omni"`. `benchmark_endpoint` examples: `/v1/videos`, `/v1/images/generations`.
Omit `mark` only for configs not meant to be filtered by `-m`. Cases that call `/v1/images/edits`, `/v1/images/generations`, or `/v1/videos` use the same `benchmark_params` schema as Omni/TTS (`dataset_name`, `endpoint`, `extra_body`) and are executed by `run_benchmark.py` — do not set `server_type` or `task` on those cases. Remaining diffusion cases (usually `/v1/chat/completions`, or custom jsonl) stay on `run_diffusion_benchmark.py` and may keep `server_type`.
#### `mark` field
@@ -100,7 +101,7 @@ Recommended for L4 perf cases:
}
```
Multi-GPU diffusion (example: Cosmos3 with `cfg-parallel-size=2`):
Multi-GPU generation via omni bench (example: Cosmos3 with `cfg-parallel-size=2`):
```JSON
{
@@ -110,10 +111,17 @@ Multi-GPU diffusion (example: Cosmos3 with `cfg-parallel-size=2`):
"full_model",
"diffusion"
],
"server_type": "vllm-omni",
"benchmark_endpoint": "/v1/images/generations",
"server_params": { "...": "..." },
"benchmark_params": [ { "name": "1024x1024_steps4", "...": "..." } ]
"benchmark_params": [
{
"name": "1024x1024_steps4",
"dataset_name": "random",
"endpoint": "/v1/images/generations",
"num_prompts": 3,
"max_concurrency": 1,
"extra_body": { "width": 1024, "height": 1024, "num_inference_steps": 4 }
}
]
}
```
@@ -132,8 +140,8 @@ Result files use the **runtime** hardware label from `get_runtime_resource_label
Examples:
- Omni/TTS: `result_{test_name}_{optional_hw}_{dataset}_....json` under `BENCHMARK_DIR`
- Diffusion: one aggregate `diffusion_result_{config_stem}_{optional_hw}_{timestamp}.json` per source JSON file (array of all runs from that file)
- Omni/TTS and OpenAI generation endpoints (`/v1/images/*`, `/v1/videos`): `result_{test_name}_{optional_hw}_{dataset}_....json` under `BENCHMARK_DIR`
- Remaining diffusion (`run_diffusion_benchmark.py`): one aggregate `diffusion_result_{config_stem}_{optional_hw}_{timestamp}.json` per source JSON file
#### Local commands
@@ -145,6 +153,9 @@ pytest -s -v tests/dfx/perf/scripts/run_benchmark.py -m "full_model and omni and
# Single file (same selectors as the CI Perf steps)
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py \
--test-config-file tests/dfx/perf/tests/test_bagel_vllm_omni.json
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py \
--test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json \
-m "H100 and B200 and cards_2"
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py \
--test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json \
-m "H100 and full_model and not slow"
@@ -203,7 +214,8 @@ You can add any benchmark running parameters you need here. For all optional par
2. For boolean variables in the running parameters, modify them to forms such as ignore_eos: true/false and fill them into the JSON file.
3. Optionally add a `baseline` object (see **Baseline thresholds** below). If you omit `baseline` or leave it empty, the performance test still runs but does not assert metric thresholds from this field.
4. Set `"name"` on each `benchmark_params` entry for stable pytest ids and readable result keys.
5. The qps and concurrency modes are recommended to be mutually exclusive. For detailed explanations, see the table below:
5. Image/video generation cases (`/v1/images/generations`, `/v1/images/edits`, `/v1/videos`) use this same schema: set `endpoint` to the API path, put width/height/steps/frames in `extra_body`, use `dataset_name: random` for text-only inputs or `random-mm` when the request needs a synthetic image/video. Do not set `server_type` or `task`, and do not use `random-request-config` or kebab-case diffusion client fields.
6. The qps and concurrency modes are recommended to be mutually exclusive. For detailed explanations, see the table below:
| Parameter | Type | Required | Example/Values | Description |
| --------------- | ------------- | -------- | -------------------- | ------------------------------------ |
+1 -1
View File
@@ -165,7 +165,7 @@ Failed jobs: 1/2
pytest -sv tests/dfx/perf/scripts/run_benchmark.py -m "full_model and tts and H100"
pytest -sv tests/dfx/perf/scripts/run_diffusion_benchmark.py -m "full_model and diffusion and H100"
pytest -sv tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json
pytest -sv tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json
pytest -sv tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json
```
Nightly **Perf Test** jobs in [``test-nightly.yml``](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/cuda/test-nightly.yml) use ``--test-config-file`` only (no ``-m``). Weekly **Perf Test** in [``test-weekly.yml``](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/cuda/test-weekly.yml) runs ``test_qwen3_omni_vllm_text.json`` and ``test_qwen3_omni_multi_replicas.json`` (JSON ``mark`` includes ``slow``). E2e L4 function tests use ``full_model`` + ``--run-level full_model``. Example:
+2 -1
View File
@@ -159,7 +159,8 @@ When `mark` is present, it must be an **array** with exactly one ``hardware_mark
}
```
- Local bulk load: `pytest -sv tests/dfx/perf/scripts/run_diffusion_benchmark.py -m "full_model and diffusion and H100"`
- Local bulk load: `pytest -sv tests/dfx/perf/scripts/run_benchmark.py -m "full_model and H100"` (omni/TTS and `/v1/images/*` + `/v1/videos` diffusion)
- Diffusion chat-completions remaining cases: `pytest -sv tests/dfx/perf/scripts/run_diffusion_benchmark.py -m "full_model and diffusion and H100"`
- Nightly CI perf steps: `--test-config-file tests/dfx/perf/tests/test_<model>_vllm_omni.json` (file selects cases; no `-m`)
- Result filenames use **runtime** GPU detection (`get_runtime_resource_label`); `H100` is omitted on the default CI pool
@@ -0,0 +1,346 @@
# Scheduler-Managed Paged KV Cache
This document describes the design and lifecycle of Scheduler-managed paged KV cache for diffusion DiT stages.
Paged KV cache is a scheduler-managed KV-cache and memory-management
mechanism. It is separate from attention backend selection: native attention
backends such as FlashAttention or Ascend FIA perform the attention computation,
while paged KV provides the block layout and metadata consumed by those
backends.
The current implementation requires the `FLASH_ATTN` selector; other
attention backends do not currently support `paged_scheduler`.
For operator-facing configuration and examples, see the
[Paged KV Cache user guide](../../user_guide/diffusion/paged_kv_cache.md).
## Table of Contents
1. [Overview](#overview)
2. [Motivation](#motivation)
3. [Architecture](#architecture)
4. [Execution Modes](#execution-modes)
5. [Design](#design)
6. [Model and Platform Integration](#model-and-platform-integration)
7. [Configuration and Compatibility](#configuration-and-compatibility)
8. [Limitations](#limitations)
9. [Related Files](#related-files)
## Overview
Scheduler-managed paged KV provides a page-based memory and data-movement
foundation for diffusion DiT stages. It makes KV capacity explicit, preserves
stable prefixes across denoising steps, and defines page-granular destinations
for future AR-to-DiT transfer. HunyuanImage-3.0 is the current request-mode
reference on CUDA and Ascend NPU; the supported boundaries and follow-up work
are described below.
## Motivation
Paged KV is introduced for three related goals:
1. **Memory management:** make diffusion KV capacity visible to the Scheduler
so logical requests can reserve and release fixed-size blocks while the
Worker owns one reusable physical pool. This improves capacity planning and
memory utilization, and can increase request concurrency and throughput when
the dense path would otherwise reserve separate full tensors.
2. **Transfer optimization:** give a DiT stage stable destination page slots so
a future AR-to-DiT connector can transfer missing KV blocks directly,
instead of gathering a complete tensor into a temporary receiver buffer.
3. **Prefix reuse:** keep the stable prompt/reference prefix resident while
only the timestep/image target is rewritten at each denoising step. The same
page-level contract also provides the foundation for canonical prefix-cache
reuse when a model supplies cacheable block identities.
Request-local memory management and prefix reuse are implemented by the local
request path. Cross-stage transfer and cross-request canonical prefix caching
use the same page interfaces but require model/connector integration that is
outside the current Hunyuan implementation.
The legacy dense path keeps complete model-owned tensors for each request. The
paged path instead maps requests to Scheduler blocks and reuses a Worker page
pool, making capacity and fragmentation explicit. This can improve HBM
utilization and concurrency, although it does not guarantee lower latency for
every shape.
## Architecture
The control plane describes and reserves logical sequences. The data plane
installs the resulting snapshot on each Worker and runs the native attention
backend.
```mermaid
flowchart LR
subgraph CP[Control plane]
R[Diffusion request] --> P[Model preprocessing<br/>layout, CFG rows, spans]
P --> S[Diffusion Scheduler<br/>logical sequences and blocks]
S --> M[DiffusionKVMetadata]
end
M -->|immutable snapshot| X[Executor / RPC]
subgraph DP[Data plane: each Worker rank]
W[Model runner<br/>physical pages and block tables]
W --> A[Paged attention adapter]
A --> C[CUDA FlashAttention / FA3]
A --> N[Ascend FIA]
W --> O[Denoising output]
end
X --> W
```
### Ownership boundary
| Component | Owns | Does not own |
| --- | --- | --- |
| Model preprocessing | Lengths, positions, CFG count, and attention spans | Physical blocks or Worker state |
| Diffusion Scheduler | Request lifecycle, logical sequences, admission, and release | Device tensors or native kernel metadata |
| Executor/RPC | Transport of the immutable allocation snapshot | Allocation decisions or page contents |
| Worker/model runner | Rank-local KV tensors, rows, block tables, slots, and active metadata | Scheduler block lifetime |
| Paged attention adapter | Layout translation, slot metadata, and backend dispatch | Capacity management or request admission |
| Platform backend | Native page geometry, metadata construction, and kernel selection | Model-specific token semantics |
`DiffusionKVCacheManager` is the request-facing Scheduler facade: it groups and
atomically allocates the internal sequences for one request, then publishes
`DiffusionKVMetadata` over the Worker-owned physical pool.
### Request lifecycle
1. **Admission:** preprocessing creates one logical sequence per CFG branch;
the Scheduler reserves all required blocks atomically.
2. **Activation:** the Worker validates the metadata generation, binds rows,
stages block tables, and installs native attention metadata.
3. **Execution:** the first denoising step writes the complete sequence once;
later steps write only the changing target span and read retained pages.
4. **Release:** completion, cancellation, errors, and reinitialization release
Scheduler blocks and clear Worker rows.
Dense requests do not install paged metadata. Paged requests reject legacy
dense KV payloads or stale/incomplete native metadata instead of silently
falling back to dense execution.
### AR-to-DiT transfer contract
When an AR stage and a DiT stage are deployed separately, the previous
model-owned transfer commonly gathers and flattens a complete KV tensor into a
temporary receiver buffer before the DiT model can use it.
The Scheduler-managed contract gives the DiT Worker a destination page layout
before transfer. The current feature implements this DiT-side destination
contract (reservation, layout, and generation/readiness metadata); it does not
execute connector transfers or import AR KV. A future connector can target the
reserved slots directly:
```mermaid
flowchart LR
A[AR Worker<br/>source pages] --> X[Connector<br/>missing blocks]
S[DiT Scheduler<br/>reserve destination blocks] --> D[DiT Worker<br/>destination pages]
X --> D
D --> F[Native DiT attention]
```
The diagram is a target connector boundary, not an enabled runtime path in this
feature. The expected benefits of a future connector using this contract are:
| Concern | Previous model-owned transfer | Page-native destination contract |
| --- | --- | --- |
| Transfer unit | Packed or complete KV tensor | Individual missing page blocks |
| Receiver storage | Temporary contiguous tensor, then model cache | Scheduler-reserved Worker pages |
| Data movement | Gather/flatten plus another copy or concatenation | Direct write to destination slots when a connector is enabled |
| Readiness | Implicit runner-side synchronization | Explicit generation and ready metadata |
| Denoising reuse | Reassemble the stable prefix in the model | Native attention reads resident pages |
| Cleanup | Transfer and model-cache lifetimes are coupled | Source lease, destination reservation, and request release are explicit |
Reserving destination pages up front supplies stable physical targets and an
explicit generation/ready state, so a future connector can move only missing
blocks and pass the same metadata to native attention. Imported AR KV is not
enabled for `paged_scheduler` in this PR; DreamZero and LingBot-World use the
separate `ar_diffusion_kv` contract.
## Execution Modes
Paged KV is a cache feature; request batching and step execution are separate
engine modes. The terminology follows the
[diffusion continuous batching design](diffusion_continuous_batching.md):
| Execution path | Configuration | Scheduler batch unit | Hunyuan `paged_scheduler` status |
| --- | --- | --- | --- |
| Request execution | `step_execution=false`, `max_num_seqs=1` | One complete denoising request | Current supported path |
| Request-level batching | `step_execution=false`, `max_num_seqs>1` | Multiple compatible public requests in one forward | Not supported by the current `paged_scheduler` integration |
| Step execution | `step_execution=true`, `max_num_seqs=1` | One request advances one denoising step per scheduler tick | Paged path rejects it |
| Step continuous batching | `step_execution=true`, `max_num_seqs>1` | Compatible active step states advance together | Separate feature; paged Hunyuan is not implemented |
`max_num_seqs` limits public requests. CFG branches are internal rows of one
request and are controlled by `diffusion_kv_max_rows_per_request`: a request
without CFG uses one row, while the standard positive/negative CFG request uses
two. Atomic CFG admission does not mean that CFG is required.
## Design
### Startup and cache sizing
Before real requests are admitted, the engine and Workers:
1. Resolve `diffusion_kv_mode` and prepare a maximum-shape profile request.
2. Register paged attention layers and collect a native `KVCacheSpec` per KV
group.
3. Run the marked memory profile to determine non-KV memory. This probe is not
a latency sample. On NPU, it may use SDPA only when MindIE-SD is
unavailable; this is the sole intentional fallback and applies only to this
startup profile.
4. Build the native cache configuration and resolve block geometry.
5. Allocate rank-local physical pages and create the Scheduler facade over the
same geometry.
`kv_cache_memory_bytes` is a physical KV-pool budget per Worker rank, not a
request token count. When omitted, the normal utilization path sizes the pool
after subtracting profiled non-KV memory. Reserved pool memory can exceed the
live payload because it also reflects capacity and fragmentation.
### Logical layout
Each sequence has a stable prefix and a changing target:
```text
|-------------------------- allocated seq_len --------------------------|
|---------------- prefix_len ----------------|---- target_len ----|unused|
retained across steps rewritten each step
```
The runner exposes these boundaries to the adapter:
| Phase | `query_len` | `seq_len` | `kv_start_pos` |
| --- | ---: | ---: | ---: |
| First denoising/prefill | `seq_len` | `seq_len` | `0` |
| Later denoising steps | `target_len` | `prefix_len + target_len` | `prefix_len` |
For `block_size`, token position `p` maps to the native slot
`block_id(p // block_size) * block_size + p % block_size`. The Scheduler owns
the block IDs; the Worker turns them into rank-local tables and slots.
### Dense and paged representation
Both modes implement Hunyuan's mixed causal/full attention. They differ in KV
representation and backend input, not in the attention result they describe:
| Aspect | `dense_legacy` | `paged_scheduler` |
| --- | --- | --- |
| KV owner | Model-owned contiguous cache | Worker-owned physical pages |
| Attention input | Contiguous K/V with a dense mask and/or span metadata | Block tables, slots, lengths, and `full_attn_spans` |
| Prefix reuse | Model-owned prefix tensor; cache scope depends on the dense path | Stable prefix pages are retained across denoising steps; canonical cross-request reuse requires cache identities |
| Scheduler state | No diffusion page reservation | Logical sequences, admission, generation, and release |
The current implementation guarantees prefix reuse between the first and later
denoising steps. Native vLLM cross-request prefix caching is not enabled by
this Hunyuan integration: the diffusion KV manager uses
`enable_caching=False`, and Hunyuan does not publish canonical block hashes.
### Attention execution
The two paths preserve the same mixed causal/full spans. In the paged path,
the adapter converts each row into aligned native segments:
| Segment | Query input | KV visibility | Causal flag |
| --- | --- | --- | --- |
| Causal `[s, e)` | `Q[s:e]` | `K[:e]`, `V[:e]` | `true` |
| Full `[a, b)` | Query overlap | `K[:b]`, `V[:b]` | `false` |
The K/V update is deliberately outside the segment loop:
```text
Q/K/V projection
-> write the current K/V span once per layer
-> run all causal/full native segments
-> restore output order
```
CUDA keeps cache-update ownership in its native paged-attention contract.
Ascend prewrites the normal-layout K/V span once, then FIA segment calls read
the persistent pages without receiving K/V again. Output is restored to the
original token order before projection, residual, and MLP layers consume it.
Dense does not imply one universal kernel call. Hunyuan's CUDA dense path can
also use the shared piecewise FlashAttention helper for aligned regions. In
the paged path, the same regions are described as native segments that read
persistent pages. The two modes can therefore have different kernel names and
call counts while preserving the same mask semantics.
### Piecewise planning
The common planner supports both row layouts:
| Row layout | Preparation and output handling |
| --- | --- |
| Homogeneous ranges, such as CFG rows from one request | Keep the batched layout, use contiguous views/direct output-buffer writes, and avoid the large indexed output scatter |
| Heterogeneous lengths or offsets | Gather valid tokens with `index_select`, run native segments, and scatter them back with `index_copy_` |
The homogeneous fast path applies to rows in one attention invocation; it does
not enable arbitrary public-request batching or change Scheduler allocation.
## Model and Platform Integration
### HunyuanImage-3.0
Hunyuan is the reference integration because its self-attention mixes causal
and full regions and its generated-image region changes across denoising
steps. It creates one logical row per conditional or unconditional CFG branch
and supports strict Ulysses SP in request mode. Its runner owns row creation,
phase activation, and page metadata; the model boundary supplies token layout
and attention spans without allocating Scheduler blocks.
### Platform matrix
| Model/integration | Platform | Execution | Backend | Status |
| --- | --- | --- | --- | --- |
| HunyuanImage-3.0 | NVIDIA CUDA | Request-level | `FLASH_ATTN` -> native FlashAttention/FA3 | Implemented and validated |
| HunyuanImage-3.0 | Ascend NPU | Request-level | `FLASH_ATTN` -> Ascend FIA | Implemented and validated |
| DreamZero / LingBot-World | Platform-specific | Separate `ar_diffusion_kv` contract | Imported AR-KV path | Not `paged_scheduler` |
| Other diffusion models | Any | N/A | N/A | No paged integration yet |
The logical `FLASH_ATTN` selector is the only current paged-KV capability
advertised by `FlashAttentionBackend`. Other diffusion backends do not
transparently fall back to dense attention for a paged request.
## Configuration and Compatibility
`paged_scheduler` requires a native cache configuration, a positive row limit,
and a prepared memory-profile request. The main fields are:
| Field | Meaning |
| --- | --- |
| `diffusion_kv_mode` | `dense_legacy` (default) or `paged_scheduler` |
| `diffusion_kv_max_rows_per_request` | Worker row capacity for one public request, including CFG branches |
| `kv_cache_memory_bytes` | Optional explicit physical KV-pool budget per Worker rank |
| `gpu_memory_utilization` | Automatic pool sizing when no byte budget is supplied |
| `diffusion_attention_backend` | Must resolve to `FLASH_ATTN` for the current paged implementation |
The user guide contains the stage YAML and `vllm serve --omni` example.
Use request execution for the current Hunyuan integration; the unsupported
batching and step modes are summarized in [Execution Modes](#execution-modes).
The current paged KV format is unquantized BF16 with identity Q/K/V scales.
Model weight quantization, CPU offload, layerwise offload, and distributed
layerwise offload (DLO) have not been validated with this path. Leave those
options disabled for a validated paged setup; use `dense_legacy` when they are
required.
## Limitations
- Hunyuan `paged_scheduler` is request-level only; request batching and step
execution are not implemented (see [Execution Modes](#execution-modes)).
- Only strict Ulysses SP and the current two-branch CFG layout are supported;
Ring, AllGather-KV, and independent Hunyuan KV contexts are not implemented.
- Imported AR-to-DiT page transfer and cross-request canonical prefix caching
are follow-up integrations; `ar_diffusion_kv` is a separate contract.
- Quantized KV and CPU/DLO offload combinations are untested.
- For a formal `paged_scheduler` request, missing native metadata or backend
support is an explicit error, not a dense or SDPA fallback. The marked
startup memory profile is the only intentional exception.
## Related Files
- Scheduler and allocation: [`diffusion_kv/manager.py`](gh-file:vllm_omni/diffusion/diffusion_kv/manager.py), [`diffusion_kv/request.py`](gh-file:vllm_omni/diffusion/diffusion_kv/request.py), [`diffusion_kv/metadata.py`](gh-file:vllm_omni/diffusion/diffusion_kv/metadata.py)
- Scheduler and runtime: [`sched/base_scheduler.py`](gh-file:vllm_omni/diffusion/sched/base_scheduler.py), [`diffusion_kv/config.py`](gh-file:vllm_omni/diffusion/diffusion_kv/config.py), [`forward_context.py`](gh-file:vllm_omni/diffusion/forward_context.py), [`vllm_config.py`](gh-file:vllm_omni/diffusion/vllm_config.py)
- Executor boundary: [`executor/abstract.py`](gh-file:vllm_omni/diffusion/executor/abstract.py), [`executor/uniproc_executor.py`](gh-file:vllm_omni/diffusion/executor/uniproc_executor.py)
- Worker data plane: [`diffusion_kv/initialization.py`](gh-file:vllm_omni/diffusion/diffusion_kv/initialization.py), [`diffusion_kv/model_runner_backend.py`](gh-file:vllm_omni/diffusion/diffusion_kv/model_runner_backend.py), [`worker/diffusion_model_runner.py`](gh-file:vllm_omni/diffusion/worker/diffusion_model_runner.py)
- Attention adapter and planner: [`diffusion_kv/paged_attention_adapter.py`](gh-file:vllm_omni/diffusion/diffusion_kv/paged_attention_adapter.py), [`attention/layer.py`](gh-file:vllm_omni/diffusion/attention/layer.py), [`attention/backends/flash_attn.py`](gh-file:vllm_omni/diffusion/attention/backends/flash_attn.py), [`attention/backends/utils/piecewise_attn.py`](gh-file:vllm_omni/diffusion/attention/backends/utils/piecewise_attn.py)
- Model boundary: [`models/hunyuan_image3/request_layout.py`](gh-file:vllm_omni/diffusion/models/hunyuan_image3/request_layout.py), [`models/hunyuan_image3/pipeline_hunyuan_image3.py`](gh-file:vllm_omni/diffusion/models/hunyuan_image3/pipeline_hunyuan_image3.py), [`models/hunyuan_image3/hunyuan_image3_transformer.py`](gh-file:vllm_omni/diffusion/models/hunyuan_image3/hunyuan_image3_transformer.py)
- Platform hooks: [`platforms/interface.py`](gh-file:vllm_omni/platforms/interface.py), [`platforms/cuda/platform.py`](gh-file:vllm_omni/platforms/cuda/platform.py), [`platforms/npu/platform.py`](gh-file:vllm_omni/platforms/npu/platform.py)
+5 -2
View File
@@ -286,8 +286,11 @@ output DuplexOrchestrator._intercept_stage_output -> runner.on_stage_output ->
detach DuplexOmni.detach_session -> touch(DETACH): engine-owned disconnect grace; expiry -> SessionExpired
resume DuplexOmni.resume_session(expected_lease_generation) -> lease CAS; the existing handle is re-entered
close DuplexOmni.close_session -> close RPC; the manager tears the runner down, then the stage cleanup
(abort submitted requests, release reserved ids), then SessionClosed, then the RPC result: seeing
the event means the admission slot is free (it is held until the cleanup succeeded)
(abort submitted requests, release reserved ids), then SessionClosed, then the RPC result.
SessionClosed is emitted after the cleanup attempt (in a finally), so it is also sent when the
stage cleanup failed; in that case the admission slot is intentionally retained and the cleanup
is retried by the reaper, and opening a replacement session can still be refused with
resource_exhausted. Seeing the event therefore does not by itself guarantee a free slot.
reap DuplexSessionManager.reaper_loop: idle TTL / disconnect grace expiry, cleanup retries
```
+4
View File
@@ -57,6 +57,10 @@ implementation contract; it is not, by itself, a general support claim.
- [Tensor Parallel](feature/tensor_parallel.md)
- [VAE Patch Parallelism](feature/vae_parallel.md)
#### KV cache and memory management
- [Scheduler-Managed Paged KV Cache for Diffusion DiT Stages](feature/diffusion_paged_kv_cache.md)
#### Attention optimization
The [Diffusion Attention Backends](../user_guide/diffusion/attention_backends.md)
+1 -1
View File
@@ -1,7 +1,7 @@
# Installation
!!! important
vLLM-Omni is released against the matching upstream vLLM major/minor version. The 0.29 development line requires vLLM 0.29.x; the source-install instructions pin vLLM 0.29.0. Published vLLM-Omni 0.28.0 wheels and images still require vLLM 0.28.x.
vLLM-Omni is released against the matching upstream vLLM major/minor version. The 0.30 development line requires vLLM 0.30.x; the source-install instructions pin vLLM 0.30.0. Published vLLM-Omni 0.28.0 wheels and images still require vLLM 0.28.x.
vLLM-Omni supports the following hardware platforms:
+1 -1
View File
@@ -34,7 +34,7 @@ vLLM-Omni is a Python library that supports the following GPU variants. The libr
### Pre-built wheels
The source-install instructions target vLLM 0.29.0. Until vLLM-Omni 0.29.0 artifacts are published, the CUDA/ROCm pre-built examples below remain on the matching 0.28.0 release pair.
The source-install instructions target vLLM 0.30.0. Until vLLM-Omni 0.30.0 artifacts are published, the CUDA/ROCm pre-built examples below remain on the matching 0.28.0 release pair.
Note: Pre-built wheels are currently available for vLLM-Omni 0.11.0rc1, 0.12.0rc1, 0.14.0rc1, 0.14.0, 0.16.0, 0.18.0, 0.20.0, 0.21.0, 0.22.0, 0.23.0, 0.24.0, 0.25.0, 0.26.0, and 0.28.0. If you need a newer unreleased revision, please [build from source](https://docs.vllm.ai/projects/vllm-omni/en/latest/getting_started/installation/gpu/#build-wheel-from-source).
@@ -5,7 +5,7 @@
# --8<-- [end:requirements]
# --8<-- [start:set-up-using-python]
vLLM-Omni depends on the matching major/minor release of vLLM. The 0.29 development line uses vLLM 0.29.x. Published 0.28.0 wheels and images use vLLM 0.28.x.
vLLM-Omni depends on the matching major/minor release of vLLM. The 0.30 development line uses vLLM 0.30.x. Published 0.28.0 wheels and images use vLLM 0.28.x.
!!! note
PyTorch installed via `conda` will statically link `NCCL` library, which can cause issues when vLLM tries to use `NCCL`. See <gh-issue:8420> for more details.
@@ -18,7 +18,7 @@ Therefore, it is recommended to install vLLM and vLLM-Omni with a **fresh new**
#### Installation of vLLM
These pre-built wheel instructions install the published vLLM-Omni 0.28.0 release. For the 0.29 development line, use the source-install instructions below.
These pre-built wheel instructions install the published vLLM-Omni 0.28.0 release. For the 0.30 development line, use the source-install instructions below.
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
@@ -41,13 +41,13 @@ uv pip install 'vllm-omni[demo]==0.28.0'
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.29.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.30.0 release version of the library
```bash
uv pip install vllm==0.29.0 --torch-backend=auto
uv pip install vllm==0.30.0 --torch-backend=auto
```
The 0.29.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
The 0.30.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -68,12 +68,12 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.29.0
git checkout v0.30.0
```
Set up environment variables to get pre-built wheels. If there are internet problems, just download the whl file manually. And set `VLLM_PRECOMPILED_WHEEL_LOCATION` as your local absolute path of whl file.
```bash
#For CUDA 13.0 (the default for v0.29.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.29.0/vllm-0.29.0-cp38-abi3-manylinux_2_28_x86_64.whl
#For CUDA 13.0 (the default for v0.30.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.30.0/vllm-0.30.0-cp38-abi3-manylinux_2_28_x86_64.whl
```
Install vllm with command below (If you have no existing PyTorch).
```bash
@@ -133,7 +133,7 @@ If you want to specify the base vLLM version:
```bash
DOCKER_BUILDKIT=1 docker build \
-f docker/Dockerfile.cuda \
--build-arg BASE_IMAGE=vllm/vllm-openai:v0.29.0 \
--build-arg BASE_IMAGE=vllm/vllm-openai:v0.30.0 \
-t vllm-omni-cuda .
```
@@ -10,8 +10,8 @@
For ROCm, vLLM-Omni currently recommends the setup steps through Docker Images.
vLLM-Omni depends on the matching major/minor release of vLLM. The 0.29
development line uses vLLM 0.29.x. Published 0.28.0 wheels and images use vLLM
vLLM-Omni depends on the matching major/minor release of vLLM. The 0.30
development line uses vLLM 0.30.x. Published 0.28.0 wheels and images use vLLM
0.28.x.
The Dockerfile's `BASE_IMAGE` pin applies only to Docker builds. The
@@ -23,7 +23,7 @@ installing vLLM-Omni, as shown below.
#### Installation of vLLM
These pre-built wheel instructions install the published vLLM-Omni 0.28.0 release. For the 0.29 development line, use the source-install instructions below.
These pre-built wheel instructions install the published vLLM-Omni 0.28.0 release. For the 0.30 development line, use the source-install instructions below.
vLLM-Omni is built based on vLLM. Please install it with command below.
@@ -50,13 +50,13 @@ uv pip install onnxruntime-rocm
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.29.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.30.0 release version of the library
```bash
uv pip install vllm==0.29.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.29.0/rocm723
uv pip install vllm==0.30.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.30.0/rocm723
```
The pre-built 0.29.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
The pre-built 0.30.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
@@ -76,7 +76,7 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.29.0
git checkout v0.30.0
python3 -m pip install -r requirements/rocm.txt
python3 setup.py develop
```
@@ -88,7 +88,7 @@ python3 setup.py develop
#### Build docker image
The source build defaults to the published upstream base image
`vllm/vllm-openai-rocm:v0.29.0`, aligned with the vLLM release used by CI.
`vllm/vllm-openai-rocm:v0.30.0`, aligned with the vLLM release used by CI.
Older or custom bases must provide the vLLM APIs checked by the Dockerfile's
image-build canary. This upstream base is distinct from the prebuilt
`vllm/vllm-omni-rocm` images discussed below; their published-tag availability
@@ -103,7 +103,7 @@ To select the upstream ROCm base explicitly, pass `BASE_IMAGE`:
```bash
DOCKER_BUILDKIT=1 docker build \
-f docker/Dockerfile.rocm \
--build-arg BASE_IMAGE=vllm/vllm-openai-rocm:v0.29.0 \
--build-arg BASE_IMAGE=vllm/vllm-openai-rocm:v0.30.0 \
-t vllm-omni-rocm .
```
@@ -24,7 +24,7 @@ DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.xpu -t vllm-omni-xpu --shm-s
```
This layers vLLM-Omni on top of the published `vllm/vllm-openai-xpu:<VLLM_VERSION>`
base image, which Docker pulls automatically. To target a different vLLM release,
base image (v0.30.0 by default), which Docker pulls automatically. To target a different vLLM release,
pass `--build-arg VLLM_VERSION=<tag>`. If that tag has not been published yet, build
the base from upstream's own Dockerfile first and point the build at it:
+3 -3
View File
@@ -19,10 +19,10 @@ uv venv --python 3.12 --seed
source .venv/bin/activate
# On CUDA
uv pip install vllm==0.29.0 --torch-backend=auto
uv pip install vllm==0.30.0 --torch-backend=auto
# On ROCm
uv pip install vllm==0.29.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.29.0/rocm723
uv pip install vllm==0.30.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.30.0/rocm723
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
@@ -34,7 +34,7 @@ For additional installation methods — please see the [installation guide](inst
!!! note
It is important to install the same major & minor version of vLLM and vLLM Omni, otherwise things may not work as expected. If the versions are misaligned, you will see a warning when you import vLLM Omni.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.29.0` and vLLM Omni `0.29.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
If the `vllm` command does not handle `--omni` correctly, verify that the active environment contains the matching vLLM release and vLLM-Omni installation. vLLM owns the CLI entrypoint and delegates `--omni` requests to vLLM-Omni.
## Offline Inference
+6 -1
View File
@@ -108,7 +108,12 @@ set `DIFFUSION_ATTENTION_BACKEND=TORCH_SDPA` or configure
`diffusion_attention_config.default.backend=TORCH_SDPA` before using
`--max-num-seqs >1`. See the
[HunyuanImage-3.0 recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tencent/HunyuanImage-3.0-Instruct.md)
for its validated configuration. Helios supports single-request step execution only: use
for its validated configuration. For HunyuanImage3, this step-execution
support applies to the `dense_legacy` path; `paged_scheduler` currently
supports request-level execution only. See the
[Scheduler-Managed Paged KV Cache guide](paged_kv_cache.md) for its required
backend and configuration. Helios supports single-request step
execution only: use
`--step-execution --max-num-seqs 1` for Helios. MiniMax H3 supports step-wise
continuous batching by packing co-batched requests into one sequence that keeps
a separate attention document per request; that layout needs a backend which
+172
View File
@@ -0,0 +1,172 @@
# Scheduler-Managed Paged KV Cache
Scheduler-managed paged KV cache stores diffusion DiT key/value tensors in
Worker-owned pages. It is useful when a denoising request should keep its
stable prompt/reference prefix in device memory while rewriting only the
changing image span on later steps. The current HunyuanImage-3.0 integration
is request-level only; it does not silently change an unsupported request to
dense attention.
Paging changes KV storage and scheduling ownership; it does not change the
model's causal/full attention semantics.
## Choose a mode
| Need | Mode |
| --- | --- |
| Broadest compatibility or step execution | `dense_legacy` (default) |
| HunyuanImage-3.0 request-mode serving with Scheduler-managed KV pages | `paged_scheduler` |
Use `dense_legacy` when the model, attention backend, or offload combination
has not been validated for paged KV. HunyuanImage-3.0 paged KV currently accepts
request-level execution only; set `step_execution: false`.
## Supported scope
| Model | Platform | Execution | Backend | Status |
| --- | --- | --- | --- | --- |
| HunyuanImage-3.0 | NVIDIA CUDA | Request-level | `FLASH_ATTN` (native FlashAttention/FA3) | Implemented and validated |
| HunyuanImage-3.0 | Ascend NPU | Request-level | `FLASH_ATTN` (Ascend FIA) | Implemented and validated |
| Other diffusion models | Any | N/A | Any | Not integrated or tested |
Only the logical `FLASH_ATTN` backend currently advertises paged-KV support.
`TORCH_SDPA`, `CUDNN_ATTN`, `FLASHINFER_ATTN`, SAGE, Hub, and other diffusion
backends do not transparently fall back to dense execution for a paged request.
Strict Ulysses SP and two-branch CFG parallel are supported in HunyuanImage-3.0
request mode when the selected device topology has been validated. Ring SP,
AllGather-KV SP, Hunyuan paged step execution, and independent public-request
batching are not supported by this path. DreamZero and LingBot-World use the
separate `ar_diffusion_kv` imported-AR-KV contract.
## Configure
The diffusion KV fields are stage settings. The two
`diffusion_kv_*` fields are diffusion-specific engine fields and are forwarded
through `engine_extras`; `kv_cache_memory_bytes` is a standard stage field and
is placed directly under the stage.
Set `pipeline: hunyuan_image3_dit` to select the standalone DiT pipeline,
where stage `0` is the diffusion stage.
```yaml
pipeline: hunyuan_image3_dit
stages:
- stage_id: 0
max_num_seqs: 1
enforce_eager: true
diffusion_attention_backend: FLASH_ATTN
step_execution: false
kv_cache_memory_bytes: 536870912 # 512 MiB per Worker/rank
engine_extras:
diffusion_kv_mode: paged_scheduler
diffusion_kv_max_rows_per_request: 2
```
| Field | Required | Meaning |
| --- | --- | --- |
| `diffusion_kv_mode` | Yes | `paged_scheduler` enables Scheduler-managed pages; `dense_legacy` is the default |
| `diffusion_kv_max_rows_per_request` | Yes for paged | Positive Worker row limit, including all internal CFG branches; use `1` without CFG and `2` for standard CFG |
| `kv_cache_memory_bytes` | Optional | Explicit physical KV-pool budget per Worker/rank; omit it to use automatic sizing |
| `gpu_memory_utilization` | Optional | Automatic pool-sizing fraction when no explicit byte budget is set |
| `max_num_seqs` | Recommended `1` | Public request capacity; it is separate from the internal CFG row limit |
| `step_execution` | Must be `false` | Hunyuan paged KV is request-level only |
| `diffusion_attention_backend` | `FLASH_ATTN` | Required logical backend for the current paged implementation |
`kv_cache_memory_bytes` describes pool capacity, not live payload. Reserved
memory can therefore exceed the memory currently used by active requests.
The Scheduler allocates all rows belonging to one request together: a request
with CFG disabled has one row, while standard two-branch CFG has two rows. CFG
rows are internal branches, not independent public requests.
Paged KV is currently unquantized BF16. `diffusion_kv_cache_dtype` does not
enable quantized paged KV, and model weight quantization has not been validated
in combination with this mode. CPU offload, layerwise offload, and distributed
layerwise offload (DLO) are currently untested with paged KV. Leave these
options disabled for paged KV; use `dense_legacy` when offload is required.
## Hunyuan request-mode example
Create a small deploy overlay (for example,
`hunyuan_image3_paged.yaml`) with the stage settings above, then start the
validated Hunyuan request-mode path:
```bash
vllm serve tencent/HunyuanImage-3.0-Instruct \
--omni \
--trust-remote-code \
--port 8091 \
--deploy-config /path/to/hunyuan_image3_paged.yaml
```
The base Hunyuan DiT deploy file can be reused and overridden from the command
line when a separate overlay is inconvenient:
```bash
vllm serve tencent/HunyuanImage-3.0-Instruct \
--omni \
--trust-remote-code \
--deploy-config vllm_omni/deploy/hunyuan_image3_dit.yaml \
--stage-overrides \
'{"0":{"diffusion_kv_mode":"paged_scheduler","diffusion_kv_max_rows_per_request":2,"kv_cache_memory_bytes":536870912,"step_execution":false,"diffusion_attention_backend":"FLASH_ATTN"}}'
```
After the server is ready, send the normal Hunyuan image-generation request:
```bash
curl -s http://localhost:8091/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"modalities": ["image"],
"messages": [{"role": "user", "content": "A red ceramic vase on a wooden table"}],
"extra_body": {
"height": 1024,
"width": 1024,
"num_inference_steps": 20,
"guidance_scale": 5.0,
"seed": 1234
}
}' | jq -r '.choices[0].message.content[0].image_url.url' \
| cut -d',' -f2- | base64 -d > hunyuan_image3_paged.png
```
The same configuration is shown in the
[HunyuanImage-3.0 recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Tencent/HunyuanImage-3.0-Instruct.md).
Confirm from startup and worker logs that the resolved backend is
`FLASH_ATTN` and that the paged native path is active. A paged request must
fail explicitly if its Worker metadata or native backend is unavailable; it
must not silently switch to dense attention.
## Parallelism
Hunyuan request-mode paged KV can be combined with tensor parallelism, strict
Ulysses SP, or two-way CFG parallel when the selected topology has been
validated. Configure the topology in the stage's existing `parallel_config`:
| Setting | User-visible effect | Paged-KV requirement |
| --- | --- | --- |
| `tensor_parallel_size` | Shards model weights across ranks | Device count must match the stage topology |
| `sequence_parallel_size` + strict Ulysses | Shards the sequence across ranks | Ring and AllGather-KV modes are not supported |
| `cfg_parallel_size: 2` | Places the positive and negative branches on separate ranks | Set `diffusion_kv_max_rows_per_request` to at least `2` |
`max_num_seqs` controls public request concurrency and is independent of the
internal CFG row limit. Keep `max_num_seqs: 1` for the current Hunyuan paged
path; increasing it does not enable independent request batching.
## Troubleshooting
- `paged_scheduler requires diffusion_kv_max_rows_per_request`: add a positive
stage value and include all CFG rows.
- Unsupported backend error: select `FLASH_ATTN`; the other attention backends
currently do not implement the paged-KV capability.
- Step-mode rejection: remove `step_execution: true` and use request-level
execution, or select `dense_legacy` for step execution.
- Imported AR-KV rejection: `paged_scheduler` does not consume the
`ar_diffusion_kv` contract used by DreamZero or LingBot-World.
- Missing image output: include `"modalities": ["image"]` when using the chat
completions endpoint, and decode the returned data URI as shown above.
For the ownership and lifecycle contract, see the
[Scheduler-managed paged KV design](../../design/feature/diffusion_paged_kv_cache.md).
+1
View File
@@ -248,6 +248,7 @@ The Diffusion Acceleration navigation groups the remaining guides as follows:
| Compatibility | [Feature Compatibility](feature_compatibility.md) |
| CPU offloading | [CPU Offloading](diffusion/cpu_offload.md) |
| Cache acceleration | [TeaCache](diffusion/cache_acceleration/teacache.md), [Cache-DiT](diffusion/cache_acceleration/cache_dit.md) |
| KV cache paging | [Scheduler-Managed Paged KV Cache](diffusion/paged_kv_cache.md) |
| Parallelism | [Parallelism Overview](diffusion/parallelism/overview.md) |
| Attention | [Attention Backends](diffusion/attention_backends.md) |
| Compilation | [Regional Compilation](diffusion/regional_compilation.md) |
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""
This example shows how to use vLLM-Omni for running offline inference
with the correct prompt format on Qwen2.5-Omni
@@ -137,7 +137,9 @@ def get_use_audio_in_video_query(
question = "Describe the content of the video, then convert what the baby say into text."
prompt = (
f"<|im_start|>system\n{default_system}<|im_end|>\n"
"<|im_start|>user\n<|vision_bos|><|VIDEO|><|vision_eos|><|audio_bos|><|AUDIO|><|audio_eos|>"
# With use_audio_in_video=True the processor interleaves the audio into
# the video placeholder, so no separate <|AUDIO|> placeholder is used.
"<|im_start|>user\n<|vision_bos|><|VIDEO|><|vision_eos|>"
f"{question}<|im_end|>\n"
f"<|im_start|>assistant\n"
)
+9 -9
View File
@@ -5,7 +5,7 @@
## Pipelines
| `--model-class-name` | Task | Required checkpoint repositories |
|---|---|---|
| --- | --- | --- |
| `LTX2Pipeline` | LTX-2 one-stage T2V/I2V | `Lightricks/LTX-2` |
| `LTX2TwoStagePipeline` | LTX-2 ordinary two-stage T2V/I2V | `Lightricks/LTX-2` |
| `LTX2DistilledOneStagePipeline` | LTX-2 merged-distilled one-stage T2V/I2V | `rootonchair/LTX-2-19b-distilled` |
@@ -53,7 +53,7 @@ pipe(req, image=image, prompt=prompt)
The consolidation also removes these registry names without aliases:
| Removed name | Replacement |
|---|---|
| --- | --- |
| `LTX23Pipeline` | `LTX2Pipeline`; checkpoint metadata selects LTX-2.3 |
| `LTX2ImageToVideoPipeline` | `LTX2Pipeline` with `image=` |
| `LTX23ImageToVideoPipeline` | `LTX2Pipeline` with `image=`; checkpoint metadata selects LTX-2.3 |
@@ -67,7 +67,7 @@ offline and serving entrypoints already use named fields and are unaffected.
## One-Stage Defaults
| Parameter | LTX-2 | LTX-2.3 |
|---|---:|---:|
| --- | ---: | ---: |
| Width × height | 768 × 512 | 768 × 512 |
| Frames / frame rate | 121 / 24 | 121 / 24 |
| Denoise steps | 40 | 30 |
@@ -85,7 +85,7 @@ default to `121`.
## Two-Stage Defaults
| Parameter | Ordinary | Full-distilled |
|---|---:|---:|
| --- | ---: | ---: |
| Final width × height | 1536 × 1024 | 1536 × 1024 |
| Stage 1 width × height | 768 × 512 | 768 × 512 |
| Frames / frame rate | 121 / 24 | 121 / 24 |
@@ -172,7 +172,7 @@ spatio-temporal guidance (STG), cross-modality guidance, and rescaling.
Distilled stages and ordinary Stage 2 are fixed positive-only.
| Parameter | Default | Effect | Alias |
|---|---:|---|---|
| --- | ---: | --- | --- |
| `video_cfg_scale` | 3.0 | Video text CFG; `1.0` disables it | `video_cfg_guidance_scale` |
| `audio_cfg_scale` | 7.0 | Audio text CFG; `1.0` disables it | `audio_cfg_guidance_scale` |
| `video_stg_scale` | 1.0 | Video STG; `0.0` disables it | `video_stg_guidance_scale` |
@@ -210,7 +210,7 @@ per denoise step: `cond`, `uncond`, `ptb` (STG), and `mod`
(cross-modality). The useful balanced configurations are therefore:
| `--cfg-parallel-size` | Passes per rank | Guidance-slot utilization | Notes |
|---:|---:|---:|---|
| ---: | ---: | ---: | --- |
| `1` | 4 | 100% | Single-rank fused guidance batch |
| `2` | 2 | 100% | Recommended two-rank configuration |
| `4` | 1 | 100% | One guidance pass per rank |
@@ -269,7 +269,7 @@ noted below.
### Complete `forward` Surface
| Argument | Type/default | Meaning and constraints |
|---|---|---|
| --- | --- | --- |
| `req` | `DiffusionRequestBatch`, required | Only positional argument; contains prompts and per-request sampling parameters. |
| `image` | image or batch, `None` | Direct value wins over request images; no image selects T2V. I2V accepts one image per prompt, and a batch cannot mix T2V/I2V. |
| `prompt` | string or list, `None` | Positive-text fallback; request prompts win. Mutually exclusive with `prompt_embeds`. |
@@ -306,7 +306,7 @@ request prompt payload; LTX guidance fields live in sampling `extra_args`.
### Recipe-Specific Request Capabilities
| Override | One-stage | Ordinary two-stage | Distilled two-stage |
|---|---|---|---|
| --- | --- | --- | --- |
| Guidance | Supported | Stage 1 only; Stage 2 is positive-only | Fixed positive-only |
| Negative prompt/embeddings | Supported | Supported by Stage 1 | Rejected |
| `num_inference_steps` | Supported | Controls Stage 1; Stage 2 uses 3 | Fixed at 8 for Stage 1; Stage 2 uses 3 |
@@ -357,7 +357,7 @@ bundled offline CLI do not currently expose `sigmas`.
- The output audio sample rate comes from the loaded components and is not a
request parameter.
- For benchmarks, use `tests/dfx/perf/tests/test_ltx2_vllm_omni.json` with
`tests/dfx/perf/scripts/run_diffusion_benchmark.py`.
`tests/dfx/perf/scripts/run_benchmark.py`.
- Ordinary and full-distilled two-stage T2V/I2V are supported; HQ execution
remains out of scope.
@@ -297,6 +297,56 @@ of:
the TP=4 configuration before increasing batch size. GPU memory utilization
is not a useful primary tuning knob for this DiT-only recipe.
### Scheduler-managed paged KV (request mode)
HunyuanImage-3 can use Scheduler-managed paged KV in request-level execution.
The current paged implementation uses unquantized BF16 KV and requires the
logical `FLASH_ATTN` backend. The following command reuses the standard DiT
deploy file and supplies the paged-only stage settings with
`--stage-overrides`:
For the mode-selection guide, configuration field reference, and parallelism
limits, see the [Scheduler-Managed Paged KV Cache user guide](../../docs/user_guide/diffusion/paged_kv_cache.md).
```bash
vllm serve tencent/HunyuanImage-3.0-Instruct \
--omni \
--trust-remote-code \
--port 8091 \
--deploy-config vllm_omni/deploy/hunyuan_image3_dit.yaml \
--stage-overrides \
'{"0":{"diffusion_kv_mode":"paged_scheduler","diffusion_kv_max_rows_per_request":2,"kv_cache_memory_bytes":536870912,"step_execution":false,"diffusion_attention_backend":"FLASH_ATTN"}}'
```
Here `kv_cache_memory_bytes` reserves a 512 MiB physical KV pool per Worker
and rank, and `diffusion_kv_max_rows_per_request=2` reserves the two internal
rows used by standard positive/negative CFG. Use `1` when CFG is disabled.
Keep `max_num_seqs=1`: this paged Hunyuan path does not currently batch
independent public requests.
Generate an image after `/health` reports ready:
```bash
curl -s http://localhost:8091/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"modalities": ["image"],
"messages": [{"role": "user", "content": "A red ceramic vase on a wooden table"}],
"extra_body": {
"height": 1024,
"width": 1024,
"num_inference_steps": 20,
"guidance_scale": 5.0,
"seed": 1234
}
}' | jq -r '.choices[0].message.content[0].image_url.url' \
| cut -d',' -f2- | base64 -d > hunyuan_image3_paged.png
```
This mode is also implemented on Ascend NPU, where `FLASH_ATTN` resolves to
Ascend FIA. Hunyuan paged step execution, quantized paged KV, and CPU/DLO
offload combinations are not validated; use `dense_legacy` for those cases.
### 2x B200 ModelOpt mixed FP8/NVFP4
#### Environment
+6 -12
View File
@@ -1,16 +1,10 @@
# Common dependencies for all platforms
# transformers: required unified multimodal model classes are unavailable
# before 5.10.1; 5.10.0 is yanked, and 5.15 has a known construction
# regression. transformers 5.9 also changed
# _adjust_missing_and_unexpected_keys from
# list+list to set|set, breaking models that declare
# _keys_to_ignore_on_load_unexpected as a list (e.g. OpenMOSS-Team/MOSS-TTS-Nano)
# with "TypeError: unsupported operand type(s) for |: 'list' and 'set'".
# That breakage is now handled in code instead of by pinning:
# MossTTSNanoForGeneration wraps weight loading in
# transformers_keys_to_ignore_compat (vllm_omni/model_executor/models/utils.py).
# Floor is 5.13.0 so transformers.integrations.hub_kernels LayerRepository
# entries always pass version=/revision= required by kernels>=0.16 (#6971).
# kernels==0.16.1 requires the LayerRepository version=/revision= integration
# introduced in transformers 5.13. Keep the tested upper bound: 5.15 has
# a reported model-construction regression; later versions need validation.
# This range also satisfies vLLM 0.30.0's transformers>=5.10.4 requirement.
# MOSS-TTS-Nano's list/set weight-loading compatibility is handled by
# transformers_keys_to_ignore_compat in model_executor/models/utils.py.
transformers >= 5.13.0, < 5.15
av>=14.0.0
omegaconf>=2.3.0
-2
View File
@@ -1,5 +1,3 @@
-r common.txt
onnxruntime>=1.23.2
fa3-fwd==0.0.3
# nvidia-cublas-cu12 is pinned to 12.9.1.4 via force-reinstall in Dockerfile.ci
# because torch pins its own cublas version, making it unsatisfiable at resolve time.
+96 -7
View File
@@ -6,16 +6,35 @@ Unit tests for metrics.py
"""
import math
from types import SimpleNamespace
import pytest
from vllm.benchmarks.serve import TaskType
from vllm_omni.benchmarks.metrics.metrics import calculate_metrics
from vllm_omni.benchmarks.metrics.metrics import (
aggregate_stage_durations,
calculate_metrics,
print_stage_durations_metrics,
)
from vllm_omni.benchmarks.patch.patch import MixRequestFuncOutput
from vllm_omni.metrics.definitions import stage_modality_flags
pytestmark = [pytest.mark.core_model, pytest.mark.benchmark, pytest.mark.cpu]
def test_stage_modality_flags_match_print_stage():
text = stage_modality_flags("text", "")
audio = stage_modality_flags("", "audio")
stream = stage_modality_flags("latent", "stream")
video_frames = stage_modality_flags("video", "image")
assert text.is_text_stage and not text.is_audio_stage
assert audio.is_audio_stage and not audio.is_internal_stream_stage
assert stream.is_internal_stream_stage and not stream.is_text_stage
assert video_frames.is_video_stage and not video_frames.is_image_stage
assert not stage_modality_flags(None, None).is_text_stage
def test_tpot_matches_mean_itl_per_request():
"""TPOT and pooled ITL should agree when output_len tracks ITL samples."""
output = MixRequestFuncOutput()
@@ -379,12 +398,7 @@ class _EmptyAwareTokenizer:
"""
def __call__(self, text, add_special_tokens=False):
class _R:
pass
r = _R()
r.input_ids = [0] * len(text)
return r
return SimpleNamespace(input_ids=[0] * len(text))
def _make_tts_output(prompt_len: int) -> MixRequestFuncOutput:
@@ -588,5 +602,80 @@ def test_image_with_generated_text_still_reports_text_result(capsys):
assert " Image Result " in out
def test_aggregate_stage_durations_mean_p50_p99() -> None:
ok_a = MixRequestFuncOutput()
ok_a.success = True
ok_a.stage_durations = {"diffuse": 1.0, "vae.decode": 0.2, "stage_0_gen_ms": 50.0}
ok_b = MixRequestFuncOutput()
ok_b.success = True
ok_b.stage_durations = {"diffuse": 3.0, "vae.decode": 0.4, "stage_1_gen_ms": 80.0}
failed = MixRequestFuncOutput()
failed.success = False
failed.stage_durations = {"diffuse": 99.0}
summaries = aggregate_stage_durations([ok_a, ok_b, failed])
assert summaries["stage_durations_mean"]["diffuse"] == pytest.approx(2.0)
assert summaries["stage_durations_p50"]["diffuse"] == pytest.approx(2.0)
assert summaries["stage_durations_mean"]["vae.decode"] == pytest.approx(0.3)
assert "stage_durations_p99" in summaries
assert "stage_0_gen_ms" not in summaries["stage_durations_mean"]
assert "stage_1_gen_ms" not in summaries["stage_durations_mean"]
def test_print_stage_durations_metrics(capsys) -> None:
output = MixRequestFuncOutput()
output.success = True
output.stage_durations = {
"Wan22I2VPipeline.diffuse": 1.25,
"Wan22I2VPipeline.text_encoder.forward": 0.4,
"queue_wait_ms": 0.5,
"stage_0_gen_ms": 1000.0,
}
summaries = aggregate_stage_durations([output])
metrics = SimpleNamespace(**summaries)
print_stage_durations_metrics([99.0], metrics)
out = capsys.readouterr().out
assert "Wan22I2VPipeline" not in out
assert "Mean Diffuse (s):" in out
assert "Median Diffuse (s):" in out
assert "P99 Diffuse (s):" in out
assert "Mean Text Encoder Forward (s):" in out
assert "Mean Queue Wait (ms):" in out
assert "Stage 0 Gen" not in out
assert "stage_0_gen" not in out
def test_profiler_stage_durations_print_without_print_stage(capsys) -> None:
output = MixRequestFuncOutput()
output.success = True
output.prompt_len = 8
output.latency = 1.0
output.stage_durations = {
"Wan22I2VPipeline.diffuse": 1.25,
"queue_wait_ms": 0.5,
"stage_0_gen_ms": 1000.0,
}
common = dict(
input_requests=[],
outputs=[output],
dur_s=1.0,
tokenizer=None,
selected_percentiles=[50.0, 99.0],
goodput_config_dict={},
task_type=TaskType.GENERATION,
selected_percentile_metrics=["e2el"],
max_concurrency=None,
request_rate=float("inf"),
benchmark_duration=1.0,
)
metrics, _ = calculate_metrics(**common, print_stage=False)
shown = capsys.readouterr().out
assert "Mean Diffuse (s):" in shown
assert "Mean Queue Wait (ms):" in shown
assert "Stage 0 Gen" not in shown
assert metrics.stage_durations_mean["Wan22I2VPipeline.diffuse"] == pytest.approx(1.25)
assert "stage_0_gen_ms" not in metrics.stage_durations_mean
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])
+497 -8
View File
@@ -20,14 +20,20 @@ from vllm_omni.benchmarks.data_modules.seed_tts_dataset import (
SeedTTSSampleRequest,
SeedTTSTextSampleRequest,
)
from vllm_omni.benchmarks.patch import patch
from vllm_omni.benchmarks.patch.patch import (
MixRequestFuncOutput,
_add_combined_video_form_references,
_add_video_extra_body_to_form,
_add_video_reference_to_form,
_apply_image_metrics_from_payload,
_apply_stage0_token_timings,
_apply_video_metrics_from_payload,
_attach_seed_tts_to_request_func_input,
_build_benchmark_session,
_extract_stage_durations_from_payload,
_iter_image_reference_inputs,
_iter_video_reference_inputs,
_omni_request_timeout_s,
async_request_openai_chat_omni_completions,
async_request_openai_image_edits_omni,
@@ -429,6 +435,45 @@ async def test_bundled_first_text_chunk_uses_stage0_token_timings(mocker: Mocker
assert output.tpot_measured is True
@pytest.mark.asyncio
async def test_streaming_error_chunk_marks_request_failed(mocker: MockerFixture):
"""HTTP 200 streams can still terminate with an OpenAI error event."""
request_input = RequestFuncInput(
model="test-model",
model_name="test-model",
prompt="test prompt",
api_url="http://test.com/v1/chat/completions",
prompt_len=10,
output_len=20,
)
chunks = [
create_sse_chunk(
{
"choices": [{"delta": {"content": "partial response"}}],
"modality": "text",
}
),
create_sse_chunk(
{
"error": {
"message": "EngineCore encountered an issue",
"type": "BadRequestError",
"code": 400,
}
}
),
b"data: [DONE]\n\n",
]
mock_response = MockResponse(200, chunks)
mock_session = mocker.AsyncMock()
mock_session.post = mocker.MagicMock(return_value=mock_response)
output = await async_request_openai_chat_omni_completions(request_input, mock_session)
assert output.success is False
assert output.error == "EngineCore encountered an issue"
@pytest.mark.asyncio
async def test_positive_client_text_timings_take_precedence_over_stage0(mocker: MockerFixture):
request_input = RequestFuncInput(
@@ -1359,16 +1404,15 @@ async def test_image_edits_defaults_to_non_streaming_json(mocker: MockerFixture)
return None
captured_stream: list[str] = []
real_add_field = None
import aiohttp
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
if name == "stream":
captured_stream.append(str(value))
return real_add_field(self, name, value, **kwargs)
import aiohttp
real_add_field = aiohttp.FormData.add_field
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
mock_session = mocker.AsyncMock()
@@ -1433,6 +1477,42 @@ async def test_image_edits_stream_true_uses_sse_path(mocker: MockerFixture) -> N
assert output.image_count == 1
@pytest.mark.asyncio
async def test_image_edits_stream_error_marks_request_failed(mocker: MockerFixture) -> None:
"""HTTP 200 image-edit streams can terminate with an error event."""
sse_chunk = (
b'data: {"object":"image.edit.chunk","type":"ar_delta","delta":"partial"}\n\n'
b'data: {"object":"error","error":{"message":"image generation failed",'
b'"type":"server_error","code":500}}\n\n'
b"data: [DONE]\n\n"
)
mock_response = MockResponse(200, [sse_chunk])
mock_session = mocker.AsyncMock()
mock_session.post = mocker.MagicMock(return_value=mock_response)
request = RequestFuncInput(
model="multi-stage-edit",
model_name="multi-stage-edit",
prompt="edit",
api_url="http://test.com/v1/images/edits",
prompt_len=2,
output_len=1,
multi_modal_content=[
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{_MIN_PNG_B64}"},
}
],
extra_body={"stream": True},
)
output = await async_request_openai_image_edits_omni(request, mock_session, pbar=None)
assert output.success is False
assert output.error == "image generation failed"
assert output.generated_text == "partial"
@pytest.mark.asyncio
async def test_image_generations_e2el_includes_json_body_consume(mocker: MockerFixture) -> None:
"""E2EL must include body transfer/decode, not stop at HTTP headers."""
@@ -1512,8 +1592,7 @@ def test_video_local_image_reference_not_forwarded_as_raw_extra_field(tmp_path,
"reference",
[
{"image_url": "https://example.com/ref.png"},
{"file_id": "file-abc"},
[{"image_url": "https://example.com/a.png"}, {"file_id": "file-xyz"}],
[{"image_url": "https://example.com/a.png"}, {"image_url": "https://example.com/b.png"}],
],
)
def test_video_structured_image_reference_serialized_to_form(reference: object, mocker: MockerFixture) -> None:
@@ -1541,18 +1620,428 @@ def test_video_structured_image_reference_serialized_to_form(reference: object,
assert field_names.count("image_reference") == 1
assert "input_reference" not in field_names
payload = next(value for name, value in captured if name == "image_reference")
assert isinstance(payload, (str, bytes, bytearray))
assert json.loads(payload) == reference
def test_video_file_id_reference_is_rejected() -> None:
"""file_id is unsupported on the server and must not be sent as image_reference."""
import aiohttp
form = aiohttp.FormData()
with pytest.raises(ValueError, match="file_id is not supported yet"):
_add_video_reference_to_form(form, {"file_id": "file-abc"})
with pytest.raises(ValueError, match="file_id is not supported yet"):
_add_video_reference_to_form(form, [{"image_url": "https://example.com/a.png"}, {"file_id": "file-xyz"}])
with pytest.raises(ValueError, match="file_id is not supported yet"):
_add_combined_video_form_references(form, None, {"video_reference": {"file_id": "file-vid"}})
def test_image_reference_urls_from_random_mm_content(mocker: MockerFixture) -> None:
"""random-mm image_url parts keep an explicit image_reference type."""
import aiohttp
content = [
{
"type": "image_url",
"image_url": {"url": "https://example.com/ref.png"},
},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{_MIN_PNG_B64}"},
},
]
refs = list(_iter_image_reference_inputs(content))
assert refs == [
{"image_url": "https://example.com/ref.png"},
{"image_url": f"data:image/png;base64,{_MIN_PNG_B64}"},
]
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
form = aiohttp.FormData()
assert _add_video_reference_to_form(form, refs[0]) is True
assert _add_video_reference_to_form(form, refs[1]) is True
field_names = [name for name, _ in captured]
assert field_names.count("image_reference") == 2
assert "video_reference" not in field_names
payloads = []
for name, value in captured:
if name != "image_reference":
continue
assert isinstance(value, (str, bytes, bytearray))
payloads.append(json.loads(value))
assert payloads == refs
def test_video_reference_urls_from_random_mm_content(mocker: MockerFixture) -> None:
"""random-mm data:video_url parts stay on video_reference."""
import aiohttp
content = [
{
"type": "video_url",
"video_url": {"url": "data:video/mp4;base64,AAAA"},
}
]
refs = list(_iter_video_reference_inputs(content))
assert refs == [{"video_url": "data:video/mp4;base64,AAAA"}]
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
form = aiohttp.FormData()
assert _add_video_reference_to_form(form, refs[0]) is True
field_names = [name for name, _ in captured]
assert field_names.count("video_reference") == 1
assert "input_references" not in field_names
assert "input_reference" not in field_names
assert "image_reference" not in field_names
payload = next(value for name, value in captured if name == "video_reference")
assert isinstance(payload, (str, bytes, bytearray))
assert json.loads(payload) == {"video_url": "data:video/mp4;base64,AAAA"}
def test_video_reference_https_url_from_random_mm_content(mocker: MockerFixture) -> None:
"""HTTP(S) video_url parts must stay on video_reference, not image_reference."""
import aiohttp
content = [
{
"type": "video_url",
"video_url": {"url": "https://example.com/ref.mp4"},
}
]
refs = list(_iter_video_reference_inputs(content))
assert refs == [{"video_url": "https://example.com/ref.mp4"}]
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
form = aiohttp.FormData()
assert _add_video_reference_to_form(form, refs[0]) is True
field_names = [name for name, _ in captured]
assert field_names.count("video_reference") == 1
assert "image_reference" not in field_names
assert "input_references" not in field_names
payload = next(value for name, value in captured if name == "video_reference")
assert isinstance(payload, (str, bytes, bytearray))
assert json.loads(payload) == {"video_url": "https://example.com/ref.mp4"}
@pytest.mark.parametrize(
("reference", "field_name", "expected"),
[
("data:image/png;base64,AAAA", "image_reference", {"image_url": "data:image/png;base64,AAAA"}),
("data:video/mp4;base64,AAAA", "video_reference", {"video_url": "data:video/mp4;base64,AAAA"}),
("https://example.com/ref.png", "image_reference", {"image_url": "https://example.com/ref.png"}),
("https://example.com/ref.mp4", "video_reference", {"video_url": "https://example.com/ref.mp4"}),
],
)
def test_bare_reference_string_uses_matching_json_field(
reference: str,
field_name: str,
expected: dict[str, str],
mocker: MockerFixture,
) -> None:
"""Image and video URLs use matching JSON fields; neither is a file upload."""
import aiohttp
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
assert _add_video_reference_to_form(aiohttp.FormData(), reference) is True
assert [name for name, _ in captured] == [field_name]
payload = captured[0][1]
assert isinstance(payload, (str, bytes, bytearray))
assert json.loads(payload) == expected
def test_bare_video_string_is_not_collected_as_image() -> None:
content = ["https://example.com/ref.mp4", "data:video/mp4;base64,AAAA"]
assert list(_iter_image_reference_inputs(content)) == []
assert list(_iter_video_reference_inputs(content)) == [
{"video_url": "https://example.com/ref.mp4"},
{"video_url": "data:video/mp4;base64,AAAA"},
]
def test_bare_http_without_media_extension_is_rejected() -> None:
import aiohttp
with pytest.raises(ValueError, match="image or video extension"):
_add_video_reference_to_form(aiohttp.FormData(), "https://example.com/ref")
def test_image_and_inline_video_use_combined_reference_fields(mocker: MockerFixture) -> None:
"""Image plus data:video must be image_reference + video_reference, not input_references."""
import aiohttp
content = [
{"type": "image_url", "image_url": {"url": "https://example.com/ref.png"}},
{"type": "video_url", "video_url": {"url": "data:video/mp4;base64,AAAA"}},
]
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
form = aiohttp.FormData()
_add_combined_video_form_references(form, content)
field_names = [name for name, _ in captured]
assert field_names.count("image_reference") == 1
assert field_names.count("video_reference") == 1
assert "input_references" not in field_names
assert "input_reference" not in field_names
image_payload = next(value for name, value in captured if name == "image_reference")
video_payload = next(value for name, value in captured if name == "video_reference")
assert isinstance(image_payload, (str, bytes, bytearray))
assert isinstance(video_payload, (str, bytes, bytearray))
assert json.loads(image_payload) == {"image_url": "https://example.com/ref.png"}
assert json.loads(video_payload) == {"video_url": "data:video/mp4;base64,AAAA"}
def test_image_url_and_bare_data_video_string_use_json_fields(mocker: MockerFixture) -> None:
"""Image URL plus a bare data:video string must not upload input_references."""
import aiohttp
content = [{"type": "image_url", "image_url": {"url": "https://example.com/ref.png"}}]
extra_body = {"video_reference": "data:video/mp4;base64,AAAA"}
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
form = aiohttp.FormData()
_add_combined_video_form_references(form, content, extra_body)
field_names = [name for name, _ in captured]
assert field_names.count("image_reference") == 1
assert field_names.count("video_reference") == 1
assert "input_references" not in field_names
assert "input_reference" not in field_names
image_payload = next(value for name, value in captured if name == "image_reference")
video_payload = next(value for name, value in captured if name == "video_reference")
assert isinstance(image_payload, (str, bytes, bytearray))
assert isinstance(video_payload, (str, bytes, bytearray))
assert json.loads(image_payload) == {"image_url": "https://example.com/ref.png"}
assert json.loads(video_payload) == {"video_url": "data:video/mp4;base64,AAAA"}
captured.clear()
assert _add_video_reference_to_form(form, "data:video/mp4;base64,AAAA") is True
assert [name for name, _ in captured] == ["video_reference"]
def _oversized_data_video_url() -> str:
"""data:video whose JSON text part is larger than 1MB."""
return "data:video/mp4;base64," + ("A" * (1024 * 1024))
def test_oversized_data_video_uploads_as_input_references(mocker: MockerFixture) -> None:
"""A lone data:video over the 1MB text limit is uploaded, not sent as JSON."""
import aiohttp
video_url = _oversized_data_video_url()
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
assert _add_video_reference_to_form(aiohttp.FormData(), {"video_url": video_url}) is True
uploaded = next(value for name, value in captured if name == "input_references")
assert uploaded == base64.b64decode("A" * (1024 * 1024))
field_names = [name for name, _ in captured]
assert "video_reference" not in field_names
assert "image_reference" not in field_names
def test_oversized_data_video_with_image_stays_json(mocker: MockerFixture) -> None:
"""input_references cannot be combined with image_reference, even for a large video."""
import aiohttp
content = [{"type": "image_url", "image_url": {"url": "https://example.com/ref.png"}}]
extra_body = {"video_reference": _oversized_data_video_url()}
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
_add_combined_video_form_references(aiohttp.FormData(), content, extra_body)
field_names = [name for name, _ in captured]
assert "image_reference" in field_names
assert "video_reference" in field_names
assert "input_references" not in field_names
def test_image_upload_bytes_and_structured_video_use_json_fields(mocker: MockerFixture) -> None:
"""Image upload bytes plus a video URL must not use singular input_reference."""
import aiohttp
image_bytes = base64.b64decode(_MIN_PNG_B64)
extra_body = {
"image_reference": {"bytes": image_bytes, "content_type": "image/png"},
"video_reference": {"video_url": "https://example.com/ref.mp4"},
}
captured: list[tuple[str, object]] = []
real_add_field = aiohttp.FormData.add_field
def tracking_add_field(self, name, value=None, **kwargs):
captured.append((str(name), value))
return real_add_field(self, name, value, **kwargs)
mocker.patch.object(aiohttp.FormData, "add_field", tracking_add_field)
form = aiohttp.FormData()
_add_combined_video_form_references(form, None, extra_body)
field_names = [name for name, _ in captured]
assert field_names.count("image_reference") == 1
assert field_names.count("video_reference") == 1
assert "input_reference" not in field_names
assert "input_references" not in field_names
image_payload = next(value for name, value in captured if name == "image_reference")
video_payload = next(value for name, value in captured if name == "video_reference")
assert isinstance(image_payload, (str, bytes, bytearray))
assert isinstance(video_payload, (str, bytes, bytearray))
assert json.loads(image_payload) == {"image_url": f"data:image/png;base64,{_MIN_PNG_B64}"}
assert json.loads(video_payload) == {"video_url": "https://example.com/ref.mp4"}
def test_video_unsupported_image_reference_raises() -> None:
import aiohttp
form = aiohttp.FormData()
with pytest.raises(ValueError, match="Unsupported image_reference"):
with pytest.raises(ValueError, match="Unsupported reference"):
_add_video_reference_to_form(form, {"not_a_supported_key": "x"})
with pytest.raises(ValueError, match="Unsupported image_reference"):
with pytest.raises(ValueError, match="Unsupported reference"):
_add_video_reference_to_form(form, "/tmp/does-not-exist-ref.png")
def test_extract_stage_durations_from_video_and_image_shapes() -> None:
video_payload = {
"stage_durations": {
"diffuse": 1.5,
"text_encoder.forward": 0.2,
"vae.decode": 0.1,
"stage_0_gen_ms": 4000.0,
}
}
assert _extract_stage_durations_from_payload(video_payload) == video_payload["stage_durations"]
image_stage_durations = {
"diffuse": 2.0,
"vae.decode": 0.3,
}
image_payload = {
"metrics": {"stage_durations": image_stage_durations},
"data": [{"b64_json": "x"}],
}
assert _extract_stage_durations_from_payload(image_payload) == image_stage_durations
def test_video_metrics_persist_full_stage_durations() -> None:
output = MixRequestFuncOutput()
output.latency = 4.2
stage_durations = {
"diffuse": 1.5,
"vae.decode": 0.25,
"stage_0_gen_ms": 4000.0,
}
payload = {
"duration_s": 2.0,
"num_frames": 48,
"fps": 24.0,
"stage_durations": stage_durations,
}
_apply_video_metrics_from_payload(output, payload, {})
assert output.stage_durations == stage_durations
assert output.video_generation_time_ms == pytest.approx(4000.0)
def test_image_metrics_persist_stage_durations_from_metrics() -> None:
output = MixRequestFuncOutput()
stage_durations = {
"diffuse": 1.1,
"text_encoder.forward": 0.4,
"vae.decode": 0.2,
}
payload = {
"created": 1,
"data": [{"b64_json": _MIN_PNG_B64}],
"metrics": {"stage_durations": stage_durations},
}
assert _apply_image_metrics_from_payload(output, payload) == 1
assert output.stage_durations == stage_durations
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])
def test_get_samples_forwards_upstream_multimodal_backends_kwarg(mocker: MockerFixture) -> None:
"""The patched ``datasets.get_samples`` must stay call-compatible upstream.
Upstream ``vllm.benchmarks.datasets.get_samples`` takes a keyword-only
``multimodal_backends`` (``vllm/benchmarks/throughput.py`` passes it) and
``patch.py`` rebinds that symbol module-wide, so a non-omni request must
forward the keyword to the original implementation instead of raising
``TypeError`` or silently dropping it.
"""
calls: list[tuple[Namespace, object, dict]] = []
def fake_get_samples_old(args, tokenizer, **kwargs):
calls.append((args, tokenizer, kwargs))
return ["delegated"]
mocker.patch.object(patch, "get_samples_old", fake_get_samples_old)
args = Namespace(
dataset_name="random",
backend="vllm-chat",
dataset_path=None,
hf_name=None,
)
sentinel = object()
mm_backends = ("openai-chat", "openai-audio")
assert patch.get_samples(args, sentinel, multimodal_backends=mm_backends) == ["delegated"]
assert calls == [(args, sentinel, {"multimodal_backends": mm_backends})]
# No upstream kwargs: unchanged legacy delegate call.
assert patch.get_samples(args, sentinel) == ["delegated"]
assert calls[-1] == (args, sentinel, {})
+6
View File
@@ -198,6 +198,12 @@ def test_response_metrics_include_engine_tpot_and_stream_window():
"tpot_ms": {"count": 2, "mean": 15.0, "p50": 10.0, "p99": 20.0},
"ttfp_ms": {"count": 2, "mean": 300.0, "p50": 200.0, "p99": 400.0},
"rtf": {"count": 2, "mean": 2.0, "p50": 2.0, "p99": 2.0},
"stages": {
"0": {
"ttft_ms": {"count": 2, "mean": 0.0, "p50": 0.0, "p99": 0.0},
"tpot_ms": {"count": 2, "mean": 15.0, "p50": 10.0, "p99": 20.0},
}
},
"stream_ttft_ms": 200.0,
"stream_ttfp_ms": 300.0,
"stream_rtf": 8.333333,
+148
View File
@@ -42,6 +42,7 @@ from vllm_omni.clients.duplex import (
read_pcm16_wav,
reference_audio_data_url,
summarize_session_request_metrics,
summarize_stage_metrics,
write_pcm16_wav,
)
from vllm_omni.clients.inline_duplex import InlineDuplexClient
@@ -1066,6 +1067,7 @@ def test_event_collector_reports_engine_token_and_audio_intervals():
"max": 18.0,
},
}
assert timing["stages"] == {"0": timing["stage0_tokens"]}
assert timing["audio_output"] == {
"source": "client_monotonic_receive",
"chunk_count": 3,
@@ -1321,6 +1323,152 @@ def test_summarize_session_request_metrics_averages_audio_turns():
}
def test_event_collector_reports_all_engine_stages():
collector = EventCollector()
collector.add({"type": "response.created", "response": {"id": "resp-a"}}, received_at_s=10.0)
collector.add(
{
"type": "response.output_audio.delta",
"response_id": "resp-a",
"delta": base64.b64encode(b"audio").decode("ascii"),
"sample_rate_hz": 16_000,
"metadata": {
"audio_duration_ms": 80,
"vllm_omni": {
"stage_metrics": {
"0": {
"final_output_type": "text",
"num_tokens_out": 4,
"vllm_ttft_ms": 120.0,
"vllm_tpot_ms": 15.0,
"vllm_itls_ms": [10.0, 20.0],
},
"1": {
"output_unit_type": "stream",
"output_unit_count": 8,
"serving_time_to_first_output_ms": 40.0,
"time_per_output_unit_ms": 5.0,
"inter_output_latencies_ms": [5.0],
},
}
},
},
},
received_at_s=10.2,
)
collector.add(
{
"type": "response.output_audio.delta",
"response_id": "resp-a",
"delta": base64.b64encode(b"audio").decode("ascii"),
"sample_rate_hz": 16_000,
"metadata": {
"audio_duration_ms": 160,
"vllm_omni": {
"stage_metrics": {
"2": {
"final_output_type": "audio",
"output_unit_type": "audio",
"serving_time_to_first_output_ms": 80.0,
"output_unit_count": 2,
"audio_generated_frames": 2,
"audio_duration_s": 0.08,
}
}
},
},
},
received_at_s=10.3,
)
timing = collector.timing_summary(after_s=10.0, response_id="resp-a")
assert list(timing["stages"]) == ["0", "1", "2"]
assert timing["stages"]["0"] is timing["stage0_tokens"]
assert timing["stages"]["0"]["ttft_ms"] == 120.0
assert "ttft_ms" not in timing["stages"]["1"]
assert timing["stages"]["1"]["ttfc_ms"] == 40.0
assert timing["stages"]["1"]["tpop_ms"] == 5.0
assert "ttft_ms" not in timing["stages"]["2"]
assert timing["stages"]["2"]["ttfp_ms"] == 80.0
def test_audio_stage_omits_missing_or_zero_ttfp():
def _audio_stage_timing(serving_time: object) -> dict[str, object]:
collector = EventCollector()
collector.add({"type": "response.created", "response": {"id": "resp-a"}}, received_at_s=10.0)
stage_2: dict[str, object] = {
"final_output_type": "audio",
"output_unit_type": "audio",
"output_unit_count": 1,
"audio_generated_frames": 1,
"audio_duration_s": 0.08,
}
if serving_time is not None:
stage_2["serving_time_to_first_output_ms"] = serving_time
collector.add(
{
"type": "response.output_audio.delta",
"response_id": "resp-a",
"delta": base64.b64encode(b"audio").decode("ascii"),
"sample_rate_hz": 16_000,
"metadata": {
"audio_duration_ms": 80,
"vllm_omni": {"stage_metrics": {"2": stage_2}},
},
},
received_at_s=10.2,
)
return collector.timing_summary(after_s=10.0, response_id="resp-a")["stages"]["2"]
assert "ttfp_ms" not in _audio_stage_timing(None)
assert "ttfp_ms" not in _audio_stage_timing(0.0)
assert _audio_stage_timing(80.0)["ttfp_ms"] == 80.0
def test_summarize_session_request_metrics_groups_stages():
summary = summarize_session_request_metrics(
[
{
"ttft_ms": 100.0,
"stages": {
"0": {"ttft_ms": 10.0, "tpot_ms": 2.0},
"1": {"ttfc_ms": 20.0, "tpop_ms": 0.0},
},
},
{
"ttft_ms": 300.0,
"stages": {
"0": {"ttft_ms": 30.0, "tpot_ms": 6.0},
"1": {"ttfc_ms": 40.0, "tpop_ms": 8.0},
"2": {"ttfp_ms": 50.0},
},
},
],
session_id="sess-1",
)
assert summary["stages"] == {
"0": {
"ttft_ms": {"count": 2, "mean": 20.0, "p50": 10.0, "p99": 30.0},
"tpot_ms": {"count": 2, "mean": 4.0, "p50": 2.0, "p99": 6.0},
},
"1": {
"ttfc_ms": {"count": 2, "mean": 30.0, "p50": 20.0, "p99": 40.0},
"tpop_ms": {"count": 1, "mean": 8.0, "p50": 8.0, "p99": 8.0},
},
"2": {
"ttfp_ms": {"count": 1, "mean": 50.0, "p50": 50.0, "p99": 50.0},
},
}
assert summarize_stage_metrics([{"stage0_tokens": {"ttft_ms": 12.0, "tpot_ms": 3.0}}]) == {
"0": {
"ttft_ms": {"count": 1, "mean": 12.0, "p50": 12.0, "p99": 12.0},
"tpot_ms": {"count": 1, "mean": 3.0, "p50": 3.0, "p99": 3.0},
}
}
def test_pcm16_wav_round_trip(tmp_path):
path = tmp_path / "audio.wav"
pcm16 = b"\x01\x00\x02\x00"
+1 -1
View File
@@ -164,7 +164,7 @@ def test_inventory_matches_reviewed_snapshot_counts():
"""Make an inventory expansion an explicit review decision."""
category_counts = Counter(item.category for item in ENVIRONMENT_VARIABLE_INVENTORY.values())
assert category_counts == {
EnvironmentVariableCategory.PUBLIC_OMNI: 29,
EnvironmentVariableCategory.PUBLIC_OMNI: 30,
EnvironmentVariableCategory.INHERITED_VLLM: 20,
EnvironmentVariableCategory.PLATFORM_EXTERNAL: 27,
EnvironmentVariableCategory.MODEL_SPECIFIC: 59,
@@ -66,18 +66,25 @@ def _make_generation_scheduler(waiting_request, *, use_v2_model_runner=False):
scheduler.log_stats = False
scheduler.scheduler_config = SimpleNamespace(enable_chunked_prefill=True)
scheduler.num_lookahead_tokens = 0
scheduler.num_spec_tokens = 0
scheduler.dynamic_sd_lookup = None
scheduler.reset_preempted_req_ids = set()
scheduler.kv_cache_manager = SimpleNamespace(
new_step_starts=lambda: None,
allocate_slots=lambda *args, **kwargs: SimpleNamespace(get_block_ids=lambda: ([1],)),
get_num_common_prefix_blocks=lambda request_id: [0],
take_new_block_ids=lambda: None,
take_new_block_ids=lambda: [],
take_boundary_state_offloads=lambda: {},
)
scheduler.kv_cache_config = SimpleNamespace(kv_cache_groups=[object()])
scheduler.use_v2_model_runner = use_v2_model_runner
scheduler._retains_state_across_chunks = False
scheduler.needs_kv_cache_zeroing = False
scheduler.finished_req_ids = set()
scheduler.encoder_cache_manager = SimpleNamespace(get_freed_mm_hashes=lambda: [])
scheduler.encoder_cache_manager = SimpleNamespace(
get_freed_mm_hashes=lambda: [],
get_manager_metadata=lambda: None,
)
scheduler.connector = None
scheduler.ec_connector = None
scheduler.prev_step_scheduled_req_ids = set()
@@ -137,7 +137,7 @@ def _make_scheduler_stub(requests: list[_Request]) -> SimpleNamespace:
running=list(requests),
waiting=_RequestQueue(),
skipped_waiting=_RequestQueue(),
structured_output_manager=SimpleNamespace(should_advance=lambda _request: False),
structured_output_manager=SimpleNamespace(accept_tokens=lambda _request, _token_ids: True),
transfer_triggered_requests=set(),
active_kv_transfers=set(),
pending_stop_after_extraction=set(),
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Investigation tests for the num_stale_output_tokens drain (59b9e719).
@@ -89,7 +89,7 @@ def _make_drain_sched(session: Request) -> MagicMock:
sched = MagicMock()
sched.requests = {session.request_id: session}
sched.perf_metrics = None
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
sched._update_request_with_output.return_value = ([42], False)
sched._process_kv_transfer_trigger.return_value = False
sched.chunk_transfer_adapter = MagicMock()
@@ -150,7 +150,7 @@ def _run_resumable_segment_stop(
sched = MagicMock()
sched.requests = {session.request_id: session}
sched.perf_metrics = None
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
def stop_request(request: Request, _token_ids: list[int]):
request.status = RequestStatus.FINISHED_STOPPED
@@ -298,7 +298,7 @@ def test_running_decode_step_without_inter_stage_payload_does_not_raise() -> Non
sched = MagicMock()
sched.requests = {session.request_id: session}
sched.perf_metrics = None
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
sched._update_request_with_output.return_value = ([42], False)
sched._process_kv_transfer_trigger.return_value = False
sched.chunk_transfer_adapter = MagicMock()
@@ -381,7 +381,7 @@ def test_stale_async_frame_is_dropped_before_output_processing() -> None:
sched = MagicMock()
sched.requests = {session.request_id: session}
sched.perf_metrics = None
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
def discard_stale_output(request: Request, token_ids: list[int]) -> tuple[list[int], bool]:
request.async_tokens_to_discard = 0
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Unit tests for generation streaming session replacement.
These tests pin the behavior of `_update_request_as_session` against
@@ -20,7 +23,7 @@ import pytest
import vllm_omni # noqa: F401 - import for side effects (patch vLLM)
from vllm.sampling_params import SamplingParams
from vllm.v1.core.sched.output import SchedulerOutput
from vllm.v1.engine import EngineCoreEventType
from vllm.v1.engine import EngineCoreEventType, FinishReason
from vllm.v1.metrics.stats import PrefillStats
from vllm.v1.outputs import ModelRunnerOutput
from vllm.v1.request import Request, RequestStatus, StreamingUpdate
@@ -130,7 +133,7 @@ def test_resumable_generation_stop_marks_segment_boundary() -> None:
sched.running = [session]
sched.waiting = MagicMock()
sched.skipped_waiting = MagicMock()
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
# Async scheduling can observe the segment boundary in the next schedule()
# before this model output is applied and defer the same request for finish.
# update_from_output() must not re-arm the resumable request twice.
@@ -171,6 +174,77 @@ def test_resumable_generation_stop_marks_segment_boundary() -> None:
assert sched._pending_finish_reqs == []
@pytest.mark.parametrize("prompt_complete", [False, True])
def test_rejected_grammar_finishes_and_frees_generation_request(prompt_complete: bool) -> None:
session = _make_request(request_id="req-generation-segment")
session.status = RequestStatus.RUNNING
session.resumable = True
session.num_computed_tokens = len(session.prompt_token_ids) if prompt_complete else 1
# schedule() has counted this step's token as in flight; update_from_output()
# must settle it before handling the segment boundary.
session.num_in_flight_tokens = 1
sched = MagicMock()
sched.requests = {session.request_id: session}
sched.perf_metrics = None
sched.chunk_transfer_adapter = SimpleNamespace(
is_done_receiving_chunks=lambda _request_id: True,
segment_finished_requests={session.request_id},
)
sched._async_chunk_transport_enabled.return_value = False
sched._native_data_plane = False
sched._handle_stopped_request.return_value = True
sched._free_request.return_value = (None, None)
sched.chunk_transfer_adapter.cleanup = MagicMock()
sched.running = [session]
sched._remove_stopped_requests_from_queues.side_effect = lambda running, preempted: (
OmniSchedulerMixin._remove_stopped_requests_from_queues(sched, running, preempted)
)
sched.waiting = MagicMock()
sched.skipped_waiting = MagicMock()
sched.structured_output_manager.accept_tokens.return_value = False
sched._pending_finish_reqs = []
sched.recompute_kv_load_failures = False
sched.connector = None
sched.kv_cache_manager.take_events.return_value = None
sched.kv_cache_manager.estimate_cached_tokens.return_value = 0
sched.finished_req_ids_dict = {}
sched.make_stats.return_value = None
scheduler_output = MagicMock(spec=SchedulerOutput)
scheduler_output.num_scheduled_tokens = {session.request_id: 1}
scheduler_output.scheduled_spec_decode_tokens = {}
scheduler_output.num_invalid_spec_tokens = 0
model_runner_output = MagicMock(spec=ModelRunnerOutput)
model_runner_output.sampled_token_ids = [[7]]
model_runner_output.logprobs = None
model_runner_output.prompt_logprobs_dict = {}
model_runner_output.pooler_output = None
model_runner_output.num_nans_in_logits = None
model_runner_output.kv_connector_output = None
model_runner_output.cudagraph_stats = None
model_runner_output.req_id_to_index = {session.request_id: 0}
model_runner_output.routed_experts = None
outputs = OmniGenerationScheduler.update_from_output(
sched,
scheduler_output,
model_runner_output,
)
output = outputs[session.client_index].outputs[0]
assert output.finish_reason == session.get_finished_reason() == FinishReason.ERROR
assert not output.is_segment_finished
assert session.status == RequestStatus.FINISHED_ERROR
assert session.resumable is False
sched._handle_stopped_request.assert_called_once_with(session)
sched._free_request.assert_called_once_with(session)
sched.chunk_transfer_adapter.cleanup.assert_called_once()
assert session not in sched.running
def test_async_chunk_resumable_stop_rearms_connector_polling() -> None:
sched = _AsyncChunkStopSchedulerStub()
session = _make_request(request_id="req-async-chunk-next-segment")
@@ -123,7 +123,7 @@ def _make_finish_sched(request: Request) -> MagicMock:
sched.requests = {request.request_id: request}
sched.perf_metrics = None
sched.defer_block_free = False
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
sched._process_kv_transfer_trigger.return_value = False
sched._maybe_decode_pooling_output.return_value = None
sched._handle_stopped_request.return_value = True
@@ -56,7 +56,7 @@ def _make_sched(request: Request) -> MagicMock:
sched.requests = {request.request_id: request}
sched.perf_metrics = None
sched.defer_block_free = False
sched.structured_output_manager.should_advance.return_value = False
sched.structured_output_manager.accept_tokens.return_value = True
sched._process_kv_transfer_trigger.return_value = False
sched._maybe_decode_pooling_output.return_value = None
sched._handle_stopped_request.return_value = True
+43 -13
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import json
import os
import re
@@ -84,17 +87,20 @@ def resolve_pytest_marks(mark_field: Any) -> list[pytest.MarkDecorator]:
raise ValueError(f"mark must be a list; got {type(mark_field).__name__}")
def _mark_names(mark_field: Any) -> set[str]:
if isinstance(mark_field, list):
return {str(item) for item in mark_field if isinstance(item, str)}
return set()
def is_diffusion_perf_config(cfg: dict[str, Any]) -> bool:
"""True for perf JSON cases intended for ``run_diffusion_benchmark.py``."""
if cfg.get("server_type") is not None:
return True
return "diffusion" in _mark_names(cfg.get("mark"))
"""True for perf JSON cases intended for ``run_diffusion_benchmark.py``.
Schema split (not marks / endpoints):
- ``benchmark_params[].dataset`` → diffusion client (``run_diffusion_benchmark.py``)
- ``benchmark_params[].dataset_name`` → ``vllm bench serve --omni`` (``run_benchmark.py``)
"""
for params in cfg.get("benchmark_params") or []:
if not isinstance(params, dict):
continue
if "dataset" in params and "dataset_name" not in params:
return True
return False
def _marks_by_test_name(configs: list[dict[str, Any]]) -> dict[str, list[pytest.MarkDecorator]]:
@@ -654,6 +660,7 @@ def run_benchmark(
random_output_len: Any | None = None,
resource_label: str | None = None,
num_warmups: int = 2,
benchmark_params_name: str | None = None,
) -> dict[str, Any]:
"""Run one ``vllm bench serve --omni`` iteration and return parsed metrics.
@@ -662,15 +669,35 @@ def run_benchmark(
maps keep every hardware bucket, but each metric is reduced to the value for
this concurrency / request-rate step. If the benchmark exits without writing a
result file, ``result_omni_template.json`` is used as a fallback.
``benchmark_params_name`` (the ``name`` field from ``benchmark_params``) is
persisted in the result JSON and filename so multiple generation configs
under one ``test_name`` stay distinguishable (e.g. Wan USP2 832x480 vs
1280x720).
"""
current_dt = datetime.now().strftime("%Y%m%d-%H%M%S")
ri = _safe_filename_token(random_input_len)
ro = _safe_filename_token(random_output_len)
hw = resource_label_for_filename(resource_label)
name_token = _safe_filename_token(benchmark_params_name) if benchmark_params_name else ""
if name_token == "na":
name_token = ""
name_parts = [f"result_{test_name}"]
if hw:
result_filename = f"result_{test_name}_{hw}_{dataset_name}_{flow}_{num_prompt}_in{ri}_out{ro}_{current_dt}.json"
else:
result_filename = f"result_{test_name}_{dataset_name}_{flow}_{num_prompt}_in{ri}_out{ro}_{current_dt}.json"
name_parts.append(hw)
if name_token:
name_parts.append(name_token)
name_parts.extend(
[
str(dataset_name),
str(flow),
str(num_prompt),
f"in{ri}",
f"out{ro}",
current_dt,
]
)
result_filename = "_".join(name_parts) + ".json"
if "--result-filename" in args:
print(f"The result file will be overwritten by {result_filename}")
command = (
@@ -740,6 +767,9 @@ def run_benchmark(
if random_output_len is not None:
result["random_output_len"] = random_output_len
result["Hardware"] = hardware_json_value(resource_label)
result["test_name"] = test_name
if benchmark_params_name:
result["name"] = benchmark_params_name
with open(result_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
return result
+113 -8
View File
@@ -50,12 +50,19 @@ if CONFIG_FILE_PATH is None:
_all_configs = load_benchmark_configs(config_dir=_PERF_TESTS_DIR)
BENCHMARK_CONFIGS = [cfg for cfg in _all_configs if not is_diffusion_perf_config(cfg)]
print(
f"No --test-config-file: loaded {len(BENCHMARK_CONFIGS)} omni/tts case(s) from "
f"No --test-config-file: loaded {len(BENCHMARK_CONFIGS)} omni/tts/generation case(s) from "
f"{_PERF_TESTS_DIR}/*.json (skipped {len(_all_configs) - len(BENCHMARK_CONFIGS)} diffusion; "
f"use -m to filter, e.g. -m tts)"
)
else:
BENCHMARK_CONFIGS = load_benchmark_configs(CONFIG_FILE_PATH)
_loaded = load_benchmark_configs(CONFIG_FILE_PATH)
BENCHMARK_CONFIGS = [cfg for cfg in _loaded if not is_diffusion_perf_config(cfg)]
skipped = len(_loaded) - len(BENCHMARK_CONFIGS)
if skipped:
print(
f"--test-config-file: loaded {len(BENCHMARK_CONFIGS)} omni/tts/generation case(s); "
f"skipped {skipped} remaining diffusion case(s) (chat completions / custom jsonl)"
)
DEPLOY_CONFIGS_DIR = Path(__file__).parent.parent / "deploy"
server_to_benchmark_mapping = create_test_parameter_mapping(BENCHMARK_CONFIGS)
@@ -93,24 +100,107 @@ class _SingleActiveContext:
stack.close()
# OmniServer defaults for flags not already present in JSON ``serve_args`` /
# ``extra_cli_args``. Add new (flag, value) pairs here rather than special-casing.
_OMNI_DEFAULT_SERVER_ARGS: tuple[tuple[str, str], ...] = (
("--stage-init-timeout", "600"),
("--init-timeout", "900"),
)
def _cli_flag_names(cli_args: tuple[str, ...] | list[str]) -> set[str]:
"""Return long-option names present in a flat CLI argv list."""
names: set[str] = set()
for item in cli_args:
token = str(item)
if not token.startswith("--"):
continue
names.add(token.split("=", 1)[0])
return names
def _merge_omni_default_server_args(
extra_cli_args: tuple[str, ...] | list[str],
*,
use_omni: bool,
defaults: tuple[tuple[str, str], ...] = _OMNI_DEFAULT_SERVER_ARGS,
) -> list[str]:
"""Fill Omni defaults for flags not already set in JSON-derived CLI args.
JSON ``serve_args`` / ``extra_cli_args`` win; only missing flags are appended.
"""
if not use_omni:
return []
present = _cli_flag_names(extra_cli_args)
args: list[str] = []
for flag, value in defaults:
if flag not in present:
args += [flag, value]
return args
def _omni_server_env() -> dict[str, str]:
"""Writable video/image storage for ``/v1/videos`` and related generation APIs."""
result_dir = Path(os.environ.get("BENCHMARK_DIR", "tests/dfx/perf/results"))
storage_path = Path(os.environ.get("VLLM_OMNI_STORAGE_PATH", str(result_dir / "storage")))
storage_path.mkdir(parents=True, exist_ok=True)
return {"VLLM_OMNI_STORAGE_PATH": str(storage_path)}
def _resolve_offline_model(model: str) -> str:
"""Resolve HF ids / MiniMax env overrides the same way as the diffusion runner."""
import huggingface_hub
from vllm_omni.transformers_utils.repo_utils import hf_api
if not model or os.path.isdir(model):
return model
model_env_overrides = {
"MiniMaxAI/MiniMax-H3": "VLLM_TEST_MINIMAX_H3_MODEL",
"MiniMaxAI/MiniMax-H3/FL2VA": "VLLM_TEST_MINIMAX_H3_FL2VA_MODEL",
"MiniMaxAI/MiniMax-H3/Ref2VA": "VLLM_TEST_MINIMAX_H3_REF2VA_MODEL",
}
env_name = model_env_overrides.get(model)
if env_name:
env_model = os.environ.get(env_name)
if env_model:
return env_model
parts = model.split("/")
if len(parts) >= 3:
repo_id = "/".join(parts[:2])
subfolder = "/".join(parts[2:])
snapshot_root = hf_api().snapshot_download(
repo_id,
allow_patterns=[f"{subfolder}/**"],
local_files_only=huggingface_hub.constants.HF_HUB_OFFLINE,
)
return str(Path(snapshot_root) / subfolder)
if not huggingface_hub.constants.HF_HUB_OFFLINE:
return model
return hf_api().snapshot_download(model, local_files_only=True)
@contextmanager
def _start_omni_server(server_param):
test_name, model, stage_config_path, stage_overrides, extra_cli_args, use_omni = server_param
extra = tuple(extra_cli_args or ())
model = _resolve_offline_model(model)
print(f"Starting OmniServer with test: {test_name}, model: {model}")
server_args: list[str] = []
if use_omni:
server_args += ["--stage-init-timeout", "600", "--init-timeout", "900"]
server_args: list[str] = _merge_omni_default_server_args(extra, use_omni=use_omni)
# --deploy-config and --stage-overrides compose at the CLI (see vllm_omni/entrypoints/utils.py):
# deploy-config sets the base; stage-overrides are applied on top. Both can be set.
if stage_config_path:
server_args = ["--deploy-config", stage_config_path] + server_args
if stage_overrides:
server_args = ["--stage-overrides", stage_overrides] + server_args
if extra_cli_args:
server_args = list(extra_cli_args) + server_args
with OmniServer(model, server_args, use_omni=use_omni) as server:
if extra:
server_args = list(extra) + server_args
with OmniServer(model, server_args, use_omni=use_omni, env_dict=_omni_server_env()) as server:
server.test_name = test_name
print("OmniServer started successfully")
yield server
@@ -259,11 +349,24 @@ def test_performance_benchmark(omni_server, benchmark_params):
"eval_phase",
"trust_remote_code",
"expected_duplex_audio_turns_per_session",
"name",
"enable_negative_prompt",
"random_request_config",
"num_input_images",
"warmup_requests",
"warmup_concurrency",
"warmup_num_inference_steps",
}
param_keys = {str(key).replace("-", "_") for key in params}
if "model" not in param_keys:
args.extend(["--model", str(model)])
for key, value in params.items():
if key in exclude_keys or value is None:
continue
if key in {"extra_body", "extra-body"} and value == {}:
continue
arg_name = f"--{key.replace('_', '-')}"
@@ -298,6 +401,7 @@ def test_performance_benchmark(omni_server, benchmark_params):
random_output_len=params.get("random_output_len"),
resource_label=resource_label,
num_warmups=_resolve_num_warmups(params, default=2),
benchmark_params_name=params.get("name") if isinstance(params.get("name"), str) else None,
)
assert_result(result, params, num_prompt)
@@ -316,5 +420,6 @@ def test_performance_benchmark(omni_server, benchmark_params):
random_output_len=params.get("random_output_len"),
resource_label=resource_label,
num_warmups=_resolve_num_warmups(params, default=max(2, int(concurrency))),
benchmark_params_name=params.get("name") if isinstance(params.get("name"), str) else None,
)
assert_result(result, params, num_prompt)
@@ -204,11 +204,11 @@ def load_diffusion_benchmark_configs(
) -> list[dict[str, Any]]:
"""Load one diffusion benchmark JSON, or merge all ``*.json`` under *config_dir*."""
if config_path is not None:
configs = load_configs(config_path)
loaded = load_configs(config_path)
source = str(Path(config_path).resolve())
for cfg in configs:
for cfg in loaded:
cfg.setdefault(_DIFFUSION_SOURCE_CONFIG_KEY, source)
return configs
return loaded
if config_dir is None:
raise ValueError("load_diffusion_benchmark_configs requires config_path or config_dir")
configs: list[dict[str, Any]] = []
@@ -231,7 +231,15 @@ if CONFIG_FILE_PATH is None:
f"use -m to filter, e.g. -m diffusion)"
)
else:
BENCHMARK_CONFIGS = load_diffusion_benchmark_configs(CONFIG_FILE_PATH)
_loaded = load_diffusion_benchmark_configs(CONFIG_FILE_PATH)
BENCHMARK_CONFIGS = [cfg for cfg in _loaded if is_diffusion_perf_config(cfg)]
skipped = len(_loaded) - len(BENCHMARK_CONFIGS)
if skipped:
print(
f"--test-config-file: loaded {len(BENCHMARK_CONFIGS)} diffusion case(s); "
f"skipped {skipped} omni-bench generation case(s) "
f"(/v1/images/edits, /v1/images/generations, /v1/videos → run_benchmark.py)"
)
_AGGREGATED_RESULT_FILES_BY_SOURCE: dict[str, Path] = {}
+104 -85
View File
@@ -5,7 +5,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 2
}
@@ -14,8 +17,6 @@
"diffusion"
],
"description": "Cosmos3-Nano text-to-image official demo workload on 2 GPUs",
"server_type": "vllm-omni",
"benchmark_endpoint": "/v1/images/generations",
"server_params": {
"model": "nvidia/Cosmos3-Nano",
"serve_args": {
@@ -34,24 +35,28 @@
"benchmark_params": [
{
"name": "1024x1024_steps4",
"dataset": "random",
"task": "t2i",
"width": 1024,
"height": 1024,
"num-inference-steps": 4,
"num-prompts": 3,
"max-concurrency": 1,
"seed": 42,
"extra-body": {
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/images/generations",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 1024,
"height": 1024,
"num_inference_steps": 4,
"seed": 42,
"guidance_scale": 7.0,
"flow_shift": 3.0,
"guardrails": false
},
"baseline": {
"H100": {
"throughput_qps": 1.1958,
"latency_mean": 0.843,
"peak_memory_mb_mean": 80000.0
"request_throughput": 1.1958,
"mean_e2el_ms": 843.0,
"mean_peak_memory_mb": 80000.0
}
}
}
@@ -63,7 +68,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 2
}
@@ -72,8 +80,6 @@
"diffusion"
],
"description": "Cosmos3-Nano text-to-video official demo workload on 2 GPUs",
"server_type": "vllm-omni",
"benchmark_endpoint": "/v1/videos",
"server_params": {
"model": "nvidia/Cosmos3-Nano",
"serve_args": {
@@ -92,23 +98,21 @@
"benchmark_params": [
{
"name": "1280x720_frames189_steps4",
"dataset": "random",
"task": "t2v",
"num-prompts": 3,
"max-concurrency": 1,
"seed": 42,
"random-request-config": [
{
"width": 1280,
"height": 720,
"prompt": "A robot arm is cleaning a plate in the kitchen.",
"num_inference_steps": 4,
"num_frames": 189,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 1280,
"height": 720,
"num_inference_steps": 4,
"num_frames": 189,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 10.0,
"max_sequence_length": 4096,
@@ -118,9 +122,9 @@
},
"baseline": {
"H100": {
"throughput_qps": 0.0307,
"latency_mean": 32.7665,
"peak_memory_mb_mean": 28762.4762
"request_throughput": 0.0307,
"mean_e2el_ms": 32766.5,
"mean_peak_memory_mb": 28762.4762
}
}
}
@@ -132,7 +136,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 2
}
@@ -141,8 +148,6 @@
"diffusion"
],
"description": "Cosmos3-Nano image-to-video official demo workload on 2 GPUs",
"server_type": "vllm-omni",
"benchmark_endpoint": "/v1/videos",
"server_params": {
"model": "nvidia/Cosmos3-Nano",
"serve_args": {
@@ -161,24 +166,30 @@
"benchmark_params": [
{
"name": "1280x720_frames189_steps4",
"dataset": "random",
"task": "i2v",
"num-prompts": 3,
"max-concurrency": 1,
"num-input-images": 1,
"seed": 42,
"random-request-config": [
{
"width": 1280,
"height": 720,
"prompt": "The scene comes to life with smooth, natural motion.",
"num_inference_steps": 4,
"num_frames": 189,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random-mm",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"random_range_ratio": 0.0,
"percentile_metrics": "e2el",
"random_mm_base_items_per_request": 1,
"random_mm_num_mm_items_range_ratio": 0,
"random_mm_limit_mm_per_prompt": {
"image": 1
},
"random_mm_bucket_config": {
"(720, 1280, 1)": 1.0
},
"extra_body": {
"width": 1280,
"height": 720,
"num_inference_steps": 4,
"num_frames": 189,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 10.0,
"max_sequence_length": 4096,
@@ -186,9 +197,9 @@
},
"baseline": {
"H100": {
"throughput_qps": 0.0305,
"latency_mean": 33.1009,
"peak_memory_mb_mean": 29441.1429
"request_throughput": 0.0305,
"mean_e2el_ms": 33100.9,
"mean_peak_memory_mb": 29441.1429
}
}
}
@@ -200,7 +211,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 2
}
@@ -209,8 +223,6 @@
"diffusion"
],
"description": "Cosmos3-Nano video-to-video official demo workload on 2 GPUs",
"server_type": "vllm-omni",
"benchmark_endpoint": "/v1/videos",
"server_params": {
"model": "nvidia/Cosmos3-Nano",
"serve_args": {
@@ -229,23 +241,30 @@
"benchmark_params": [
{
"name": "1280x720_frames189_steps4",
"dataset": "random",
"task": "v2v",
"num-prompts": 3,
"max-concurrency": 1,
"seed": 42,
"random-request-config": [
{
"width": 1280,
"height": 720,
"prompt": "Continue the same scene with smooth natural motion.",
"num_inference_steps": 4,
"num_frames": 189,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random-mm",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"random_range_ratio": 0.0,
"percentile_metrics": "e2el",
"random_mm_base_items_per_request": 1,
"random_mm_num_mm_items_range_ratio": 0,
"random_mm_limit_mm_per_prompt": {
"video": 1
},
"random_mm_bucket_config": {
"(720, 1280, 189)": 1.0
},
"extra_body": {
"width": 1280,
"height": 720,
"num_inference_steps": 4,
"num_frames": 189,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 10.0,
"max_sequence_length": 4096,
@@ -258,9 +277,9 @@
},
"baseline": {
"H100": {
"throughput_qps": 0.0299,
"latency_mean": 33.925,
"peak_memory_mb_mean": 29443.1429
"request_throughput": 0.0299,
"mean_e2el_ms": 33925.0,
"mean_peak_memory_mb": 29443.1429
}
}
}
@@ -5,7 +5,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 1
}
@@ -14,7 +17,6 @@
"diffusion"
],
"description": "Single-device baseline",
"server_type": "vllm-omni",
"server_params": {
"model": "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v",
"serve_args": {
@@ -25,26 +27,33 @@
"benchmark_params": [
{
"name": "832x480_frames33_steps4",
"dataset": "random",
"task": "i2v",
"num-prompts": 10,
"max-concurrency": 1,
"num-input-images": 1,
"seed": 42,
"enable-negative-prompt": true,
"random-request-config": [
{
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random-mm",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 10,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"random_range_ratio": 0.0,
"percentile_metrics": "e2el",
"random_mm_base_items_per_request": 1,
"random_mm_num_mm_items_range_ratio": 0,
"random_mm_limit_mm_per_prompt": {
"image": 1
},
"random_mm_bucket_config": {
"(480, 832, 1)": 1.0
},
"extra_body": {
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 5.0
"flow_shift": 5.0,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
@@ -55,7 +64,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 2
}
@@ -64,7 +76,6 @@
"diffusion"
],
"description": "CacheDiT + TP=2 + VAE patch parallel=2 + VAE tiling",
"server_type": "vllm-omni",
"server_params": {
"model": "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v",
"serve_args": {
@@ -79,26 +90,33 @@
"benchmark_params": [
{
"name": "832x480_frames33_steps4",
"dataset": "random",
"task": "i2v",
"num-prompts": 10,
"max-concurrency": 1,
"num-input-images": 1,
"seed": 42,
"enable-negative-prompt": true,
"random-request-config": [
{
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random-mm",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 10,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"random_range_ratio": 0.0,
"percentile_metrics": "e2el",
"random_mm_base_items_per_request": 1,
"random_mm_num_mm_items_range_ratio": 0,
"random_mm_limit_mm_per_prompt": {
"image": 1
},
"random_mm_bucket_config": {
"(480, 832, 1)": 1.0
},
"extra_body": {
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 5.0
"flow_shift": 5.0,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
@@ -5,7 +5,10 @@
{
"hardware_marks": {
"res": {
"cuda": ["H100", "B200"]
"cuda": [
"H100",
"B200"
]
},
"num_cards": 1
}
@@ -14,7 +17,6 @@
"diffusion"
],
"description": "Single-device baseline",
"server_type": "vllm-omni",
"server_params": {
"model": "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
"serve_args": {
@@ -25,25 +27,24 @@
"benchmark_params": [
{
"name": "832x480_frames33_steps4",
"dataset": "random",
"task": "t2v",
"num-prompts": 10,
"max-concurrency": 1,
"seed": 42,
"enable-negative-prompt": true,
"random-request-config": [
{
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 10,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 5.0
"flow_shift": 5.0,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
@@ -64,7 +65,6 @@
"diffusion"
],
"description": "CacheDiT + TP=2 + VAE patch parallel=2 + VAE tiling",
"server_type": "vllm-omni",
"server_params": {
"model": "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
"serve_args": {
@@ -79,25 +79,24 @@
"benchmark_params": [
{
"name": "832x480_frames33_steps4",
"dataset": "random",
"task": "t2v",
"num-prompts": 10,
"max-concurrency": 1,
"seed": 42,
"enable-negative-prompt": true,
"random-request-config": [
{
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"weight": 1
}
],
"extra-body": {
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 10,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 832,
"height": 480,
"num_inference_steps": 4,
"num_frames": 33,
"fps": 24,
"seed": 42,
"guidance_scale": 6.0,
"flow_shift": 5.0
"flow_shift": 5.0,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
@@ -1,33 +1,50 @@
[
{
"test_name": "test_lingbot_video_single_device",
"description": "Single-device LingBot dense T2V baseline at 320x192, 9 frames, 2 steps.",
"server_type": "vllm-omni",
"server_params": {
"model": "robbyant/lingbot-video-dense-1.3b",
"serve_args": {
"model-class-name": "LingBotVideoPipeline",
"enable-diffusion-pipeline-profiler": true
}
},
"benchmark_params": [
{
"name": "320x192_frames9_steps2",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 320,
"height": 192,
"num-frames": 9,
"fps": 24,
"num-inference-steps": 2,
"num-prompts": 3,
"max-concurrency": 1,
"warmup-requests": 1,
"warmup-concurrency": 1,
"warmup-num-inference-steps": 2,
"enable-negative-prompt": true
}
]
}
{
"test_name": "test_lingbot_video_single_device",
"mark": [
{
"hardware_marks": {
"res": {
"cuda": [
"H100",
"B200"
]
},
"num_cards": 1
}
},
"full_model",
"diffusion"
],
"description": "Single-device LingBot dense T2V baseline at 320x192, 9 frames, 2 steps.",
"server_params": {
"model": "robbyant/lingbot-video-dense-1.3b",
"serve_args": {
"model-class-name": "LingBotVideoPipeline",
"enable-diffusion-pipeline-profiler": true
}
},
"benchmark_params": [
{
"name": "320x192_frames9_steps2",
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"num_warmups": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 320,
"height": 192,
"num_frames": 9,
"fps": 24,
"num_inference_steps": 2,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
}
]
+160 -115
View File
@@ -14,7 +14,6 @@
"diffusion"
],
"description": "Single-device baseline with enforce-eager (no torch.compile)",
"server_type": "vllm-omni",
"server_params": {
"model": "Lightricks/LTX-2",
"serve_args": {
@@ -25,31 +24,41 @@
"benchmark_params": [
{
"name": "256x256_145f_steps6",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 256,
"height": 256,
"num-frames": 145,
"fps": 24,
"num-inference-steps": 6,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 256,
"height": 256,
"num_frames": 145,
"fps": 24,
"num_inference_steps": 6,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
},
{
"name": "480x768_41f_steps20",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 768,
"height": 480,
"num-frames": 41,
"fps": 24,
"num-inference-steps": 20,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 768,
"height": 480,
"num_frames": 41,
"fps": 24,
"num_inference_steps": 20,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
},
@@ -68,7 +77,6 @@
"diffusion"
],
"description": "Single-device with torch.compile (default, no enforce-eager)",
"server_type": "vllm-omni",
"server_params": {
"model": "Lightricks/LTX-2",
"serve_args": {
@@ -78,31 +86,41 @@
"benchmark_params": [
{
"name": "256x256_145f_steps6",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 256,
"height": 256,
"num-frames": 145,
"fps": 24,
"num-inference-steps": 6,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 256,
"height": 256,
"num_frames": 145,
"fps": 24,
"num_inference_steps": 6,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
},
{
"name": "480x768_41f_steps20",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 768,
"height": 480,
"num-frames": 41,
"fps": 24,
"num-inference-steps": 20,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 768,
"height": 480,
"num_frames": 41,
"fps": 24,
"num_inference_steps": 20,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
},
@@ -121,7 +139,6 @@
"diffusion"
],
"description": "CFG-parallel=2 with enforce-eager",
"server_type": "vllm-omni",
"server_params": {
"model": "Lightricks/LTX-2",
"serve_args": {
@@ -133,31 +150,41 @@
"benchmark_params": [
{
"name": "256x256_145f_steps6",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 256,
"height": 256,
"num-frames": 145,
"fps": 24,
"num-inference-steps": 6,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 256,
"height": 256,
"num_frames": 145,
"fps": 24,
"num_inference_steps": 6,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
},
{
"name": "480x768_41f_steps20",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 768,
"height": 480,
"num-frames": 41,
"fps": 24,
"num-inference-steps": 20,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 768,
"height": 480,
"num_frames": 41,
"fps": 24,
"num_inference_steps": 20,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
},
@@ -176,7 +203,6 @@
"diffusion"
],
"description": "CFG-parallel=2 with torch.compile",
"server_type": "vllm-omni",
"server_params": {
"model": "Lightricks/LTX-2",
"serve_args": {
@@ -187,31 +213,41 @@
"benchmark_params": [
{
"name": "256x256_145f_steps6",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 256,
"height": 256,
"num-frames": 145,
"fps": 24,
"num-inference-steps": 6,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 256,
"height": 256,
"num_frames": 145,
"fps": 24,
"num_inference_steps": 6,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
},
{
"name": "480x768_41f_steps20",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 768,
"height": 480,
"num-frames": 41,
"fps": 24,
"num-inference-steps": 20,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 768,
"height": 480,
"num_frames": 41,
"fps": 24,
"num_inference_steps": 20,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
},
@@ -230,7 +266,6 @@
"diffusion"
],
"description": "CacheDiT with enforce-eager",
"server_type": "vllm-omni",
"server_params": {
"model": "Lightricks/LTX-2",
"serve_args": {
@@ -242,31 +277,41 @@
"benchmark_params": [
{
"name": "256x256_145f_steps6",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 256,
"height": 256,
"num-frames": 145,
"fps": 24,
"num-inference-steps": 6,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 256,
"height": 256,
"num_frames": 145,
"fps": 24,
"num_inference_steps": 6,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
},
{
"name": "480x768_41f_steps20",
"dataset": "random",
"task": "t2v",
"backend": "v1/videos",
"width": 768,
"height": 480,
"num-frames": 41,
"fps": 24,
"num-inference-steps": 20,
"num-prompts": 3,
"max-concurrency": 1,
"enable-negative-prompt": true
"dataset_name": "random",
"tokenizer": "gpt2",
"endpoint": "/v1/videos",
"num_prompts": 3,
"max_concurrency": 1,
"random_input_len": 8,
"random_output_len": 1,
"percentile_metrics": "e2el",
"extra_body": {
"width": 768,
"height": 480,
"num_frames": 41,
"fps": 24,
"num_inference_steps": 20,
"negative_prompt": "Negative prompt for benchmarking diffusion models"
}
}
]
}
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more