Skip to content

perf(hy_worldplay): longer video bench + HY model card#231

Closed
wenqingw-nv wants to merge 0 commit into
NVIDIA:mainfrom
wenqingw-nv:wenqing/hy-worldplay-perf-handoff
Closed

perf(hy_worldplay): longer video bench + HY model card#231
wenqingw-nv wants to merge 0 commit into
NVIDIA:mainfrom
wenqingw-nv:wenqing/hy-worldplay-perf-handoff

Conversation

@wenqingw-nv

@wenqingw-nv wenqingw-nv commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #155 for #203: GB300 perf re-bench, the hy_worldplay model card, and two num_chunk≥4 fixes the larger GPU surfaced.

Changes

  • _action.py: unwrap the torch.compile OptimizedModule in prefill_memory_kv_cache; run memory-engaged chunks eager under CUDA graphs (the data-dependent memory torch.cat can't replay in a graph captured pre-memory). Both reachable only at num_chunk≥4.
  • bench.sh: default HY_VENDOR_SDPA=1 (vendor on cuDNN SDPA, matching native). run.sh: add torchvision to the vendor deps.
  • models/hy_worldplay.rst (LingBot-style) + perf-0530.md + native sample videos; registered in the toctree; clean under sphinx-build -W.

Bench

Native vs wan/generate.py: num_chunk=8 / pose=w-31 / seed=0 / 704×1280, warmup-discard 5, DiT+VAE scope, both legs cuDNN SDPA + torch.compile, single GB300. Post-warmup medians (chunks 5–7):

stage native vendor speedup
DiT (diffuse) 632 ms 1206 ms 1.91×
VAE decode 383 ms 372 ms ~parity
DiT + VAE / chunk 1015 ms 1578 ms 1.55×

Per data_local/ input:

image DiT nat/ven VAE nat/ven ratio mean |Δ|
1.png 630 / 1208 382 / 374 1.56× 40.8
2.png 632 / 1210 382 / 374 1.56× 31.1
6.jpeg 628 / 1208 381 / 374 1.57× 21.1
cat_surf.jpg 632 / 1207 381 / 375 1.56× 51.8
median 631 / 1208 382 / 374 1.56×

Native ~1.91× on DiT, parity on VAE, input-independent (native DiT 628–632 ms across inputs). Parity |Δ| is cumulative bf16 AR drift (chunk-0 ≈14 matches #155's 12.91, ramps to ~49 by chunk-7, no jump at memory engagement); highest on the off-aspect cat_surf (625×350 upscaled).

Native vs vendor video pairs

imagenativevendor
1.png
hy-worldplay-wan-i2v-5b.mp4

hy-worldplay-wan-i2v-5b.mp4

2.png
hy-worldplay-wan-i2v-5b.mp4

hy-worldplay-wan-i2v-5b.mp4

6.jpeg
hy-worldplay-wan-i2v-5b.mp4

hy-worldplay-wan-i2v-5b.mp4

cat_surf.jpg
hy-worldplay-wan-i2v-5b.mp4

hy-worldplay-wan-i2v-5b.mp4

@copy-pr-bot

copy-pr-bot Bot commented May 30, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@wenqingw-nv wenqingw-nv changed the title perf(hy_worldplay): GB300 num_chunk=8 re-bench + model card + num_chunk≥4 fixes perf(hy_worldplay): longer video bench + HY model card May 30, 2026
@wenqingw-nv

Copy link
Copy Markdown
Collaborator Author

/ok to test 1e7a2a3

@wenqingw-nv
wenqingw-nv enabled auto-merge May 30, 2026 12:38
@liruilong940607

Copy link
Copy Markdown
Collaborator

The visual results here in the native column is choppy -- this seems to indicate something is wrong with the current implementation. Needs revisit and fix.

Let's not add this to the public-facing doc until we are sure it is correctly implemented.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Lets not check in videos into this repo. you can send me the videos on slack and I will host them.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — removed the checked-in .mp4s from the repo; I'll send the clips over Slack for hosting.

Comment on lines +971 to +974
# Run the reconstituted-context prefill once at the first
# denoising step of each chunk past the first; the
# ``prefill_completed_for_chunk`` latch suppresses re-runs on
# subsequent scheduler steps within the chunk.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This slipped through my previous review. Why would we need prefill_memory_kv? The BlockKVCache in flashdreams should naturally work with CUDAGraph.

I traced it and it seems to lead to HyWorldPlayMemoryKVCache, which stored roped K and roped V. What's the reason we can't use BlockKVCache for that? And what's the reason it can't be compatible with cudagraph like other models?

@wenqingw-nv wenqingw-nv Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

HyWorldPlayMemoryKVCache is separate because memory isn't a rolling window, it's a per-chunk re-selection of non-contiguous frames, so BlockKVCache doesn't map directly. The non-graphability is just a stopgap; since the selected set is always exactly 16, a fixed-size in-place buffer is graph-compatible. This path also causes the choppy output, where the memory keys sit at collapsed RoPE positions [0..16) regardless of identity, so on the first window slide a frame's encoding shifts and the scene jolts.

I'll rework it into a fixed-size, consistently-positioned in-place buffer to fix both, and numerically diff vs vendor's prefill first. Do you think we should: gather K/V from retained history or re-prefill each chunk (vendor re-prefills)? @liruilong940607

@wenqingw-nv
wenqingw-nv marked this pull request as draft June 2, 2026 05:31
auto-merge was automatically disabled June 2, 2026 05:31

Pull request was converted to draft

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR lands two correctness fixes for num_chunk≥4 HY-WorldPlay runs, adds the hy_worldplay model card, and re-benchmarks on GB300. The code changes are small and targeted, the documentation follows existing patterns, and the shell/lock changes are well-explained.

  • _action.py: Extracts memory_engaged as a standalone flag (set on the instance before super().predict_flow() is called) so that the new _select_network override can force CUDAGraphWrapper.drain for any denoising step in a memory-engaged chunk, and unwraps _orig_mod in prefill_memory_kv_cache so the prefill reaches the raw HyWorldPlayWanDiTNetwork instead of the compiled proxy.
  • bench.sh / run.sh: Defaults HY_VENDOR_SDPA=1 for an apples-to-apples attention-backend comparison and moves torchvision from the native package's lock to a vendor-only uv pip install step, with a comment explaining the rationale.
  • Docs: Registers hy_worldplay.rst in the toctree, ships benchmark data (perf-0530.md), and links externally-hosted sample videos; passes sphinx-build -W.

Confidence Score: 4/5

Safe to merge; the two runtime fixes are narrowly scoped to the memory-engaged path and are guarded by the same condition they test against.

The _action.py changes are well-reasoned: _hy_memory_engaged is always written by predict_flow before _select_network reads it (via super().predict_flow()), the _orig_mod unwrap falls back gracefully when the network isn't compiled, and the drain call is gated behind _use_cuda_graph so the non-graph debug path is unaffected. The only open item is the duplicate "Project page" / "Official code" link in the model card.

docs/source/models/hy_worldplay.rst — both header link buttons point to the same GitHub URL and should be reconciled once a dedicated project page is available.

Important Files Changed

Filename Overview
integrations/hy_worldplay/hy_worldplay/_action.py Two targeted num_chunk≥4 fixes: unwrap _orig_mod for prefill under compile_network=True, and override _select_network to drain memory-engaged chunks eagerly to avoid CUDA graph replay faults. Logic is sound; _hy_memory_engaged is set immediately before super().predict_flow() so _select_network always sees a fresh value.
docs/source/models/hy_worldplay.rst New LingBot-style model card for HY-WorldPlay; benchmark chart, installation guide, and sample videos all present. Both header link buttons currently share the same GitHub URL (see comment).
integrations/hy_worldplay/tests/parity_check/bench.sh Defaults HY_VENDOR_SDPA=1 so the vendor leg uses cuDNN SDPA to match the native leg for an apples-to-apples comparison; existing override semantics preserved via ${VAR:-default}.
integrations/hy_worldplay/tests/parity_check/run.sh Adds torchvision==0.26.* to the vendor-only uv pip install block; justified because the upstream hyvideo import requires it and the native plugin does not, so it was intentionally removed from pyproject.toml.
integrations/hy_worldplay/tests/parity_check/uv.lock Removes torchvision from the native package's lock (now installed externally for vendor-only use), relaxes torch spec from >=2.11 to >=2.9 (resolved pin stays 2.11.0), bumps tyro floor from >=0.8 to >=1.0, and adds >=4.5 specifier to opencv-python-headless.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["predict_flow()"] --> B["Evaluate memory_engaged\n(cache populated + memory indices present)"]
    B --> C["self._hy_memory_engaged = memory_engaged"]
    C --> D{memory_engaged AND\nprefill not done\nfor this chunk?}
    D -- yes --> E["prefill_memory_kv_cache()\n(unwrap _orig_mod to reach raw network)"]
    E --> F["cache.prefill_completed_for_chunk = ar_idx"]
    D -- no --> G["super().predict_flow()"]
    F --> G
    G --> H["_select_network(uncond)"]
    H --> I{_use_cuda_graph AND\n_hy_memory_engaged?}
    I -- yes --> J["return network_call.drain\n(eager, no CUDA graph replay)"]
    I -- no --> K["super()._select_network()"]
    K --> L{filling phase?\nar_idx < capture_idx}
    L -- yes --> M["return wrapper.drain\n(filling / autotune)"]
    L -- no --> N["return wrapper\n(CUDA graph capture/replay)"]
Loading

Reviews (1): Last reviewed commit: "docs(hy_worldplay): un-check-in sample v..." | Re-trigger Greptile

Comment on lines +22 to +24
<a class="model-link-button" href="https://github.com/Tencent-Hunyuan/HY-WorldPlay" target="_blank" rel="noopener noreferrer">Project page</a>
<a class="model-link-button" href="https://github.com/Tencent-Hunyuan/HY-WorldPlay" target="_blank" rel="noopener noreferrer">Official code</a>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Duplicate "Project page" and "Official code" URLs

Both link buttons resolve to the same https://github.com/Tencent-Hunyuan/HY-WorldPlay URL. If there is no separate landing/demo page then the "Project page" label is misleading; the redundant button should either be removed or its href updated to the canonical project page (e.g., a Hugging Face space or a paper link) once one is available. As-is, readers who click both buttons land on identical content with no way to reach a distinct artifact.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

wenqingw-nv added a commit that referenced this pull request Jun 9, 2026
… native output)

The reconstituted-context memory prefill modulated the selected memory
frames at collapsed RoPE positions [0, K) — i.e. the buffer slot index —
regardless of which frames were chosen. Because the memory set is
re-selected each chunk, a given frame's slot (and thus its RoPE position)
changed between chunks, so its key's positional phase shifted and the
scene jolted on each window slide. That's the choppiness Ruilong flagged
on PR #231 (native choppy, vendor smooth).

Fix: build the memory RoPE freqs at each frame's true temporal position
(its clean-latent-history index), which is the same absolute coordinate
frame the main path uses via `shift_t` (offset = ar_idx * len_t). A
frame's encoding is now stable across chunks regardless of its slot.
Renames `_build_collapsed_rope_freqs` -> `_build_memory_rope_freqs` and
updates the now-inaccurate "collapsed position" docs.

Adds a CPU regression test asserting the prefill driver passes the
selected frame indices (not arange(K)) and that a frame keeps its
position across re-selection. Full HY CPU suite passes; ruff clean.
Numerical parity vs the vendor prefill still to be confirmed on GPU.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wenqingw-nv pushed a commit that referenced this pull request Jun 13, 2026
Per-stage and per-input perf + parity (PR #231 format) for num_chunk=8,
pose=w-31, prompt "a person walking", over HY-WorldPlay/assets/img/
{1,2,5,6,10} on GB300. Native (use_cuda_graph=False after the memory-prefill
corruption fix) is 5.5x faster DiT diffuse and ~4x faster DiT+VAE per chunk
than vendor; median parity 36.0/255 with all chunks rendering cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Jun 13, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

wenqingw-nv added a commit that referenced this pull request Jun 29, 2026
… native output)

The reconstituted-context memory prefill modulated the selected memory
frames at collapsed RoPE positions [0, K) — i.e. the buffer slot index —
regardless of which frames were chosen. Because the memory set is
re-selected each chunk, a given frame's slot (and thus its RoPE position)
changed between chunks, so its key's positional phase shifted and the
scene jolted on each window slide. That's the choppiness Ruilong flagged
on PR #231 (native choppy, vendor smooth).

Fix: build the memory RoPE freqs at each frame's true temporal position
(its clean-latent-history index), which is the same absolute coordinate
frame the main path uses via `shift_t` (offset = ar_idx * len_t). A
frame's encoding is now stable across chunks regardless of its slot.
Renames `_build_collapsed_rope_freqs` -> `_build_memory_rope_freqs` and
updates the now-inaccurate "collapsed position" docs.

Adds a CPU regression test asserting the prefill driver passes the
selected frame indices (not arange(K)) and that a frame keeps its
position across re-selection. Full HY CPU suite passes; ruff clean.
Numerical parity vs the vendor prefill still to be confirmed on GPU.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wenqingw-nv pushed a commit that referenced this pull request Jun 29, 2026
Per-stage and per-input perf + parity (PR #231 format) for num_chunk=8,
pose=w-31, prompt "a person walking", over HY-WorldPlay/assets/img/
{1,2,5,6,10} on GB300. Native (use_cuda_graph=False after the memory-prefill
corruption fix) is 5.5x faster DiT diffuse and ~4x faster DiT+VAE per chunk
than vendor; median parity 36.0/255 with all chunks rendering cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3a1b2c3 pushed a commit to 3a1b2c3/flashdreams that referenced this pull request Jul 3, 2026
…choppy native output) + Re-evaluated perfs (NVIDIA#318)

* fix(hy_worldplay): RoPE memory frames at true positions (fixes choppy native output)

The reconstituted-context memory prefill modulated the selected memory
frames at collapsed RoPE positions [0, K) — i.e. the buffer slot index —
regardless of which frames were chosen. Because the memory set is
re-selected each chunk, a given frame's slot (and thus its RoPE position)
changed between chunks, so its key's positional phase shifted and the
scene jolted on each window slide. That's the choppiness Ruilong flagged
on PR NVIDIA#231 (native choppy, vendor smooth).

Fix: build the memory RoPE freqs at each frame's true temporal position
(its clean-latent-history index), which is the same absolute coordinate
frame the main path uses via `shift_t` (offset = ar_idx * len_t). A
frame's encoding is now stable across chunks regardless of its slot.
Renames `_build_collapsed_rope_freqs` -> `_build_memory_rope_freqs` and
updates the now-inaccurate "collapsed position" docs.

Adds a CPU regression test asserting the prefill driver passes the
selected frame indices (not arange(K)) and that a frame keeps its
position across re-selection. Full HY CPU suite passes; ruff clean.
Numerical parity vs the vendor prefill still to be confirmed on GPU.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hy_worldplay): disable CUDA-graph capture on memory-prefill path

The CUDAGraphWrapper captures pointers into the KV cache, but HY-WorldPlay
re-runs prefill_memory_kv_cache every chunk: it resets and repopulates each
PRoPE block's memory KV from a different FOV-selected frame set
(select_mem_frames_wan), reallocating the underlying storage. A graph
captured on one chunk then replays against another chunk's stale/freed
memory-KV slots, decoding to a deterministic "shatter" of speckle corruption
on the captured/replayed chunks (independent of seed and prompt). Set
use_cuda_graph=False so the rollout keeps torch.compile (Inductor) -- still
~5x faster diffuse than vendor -- without the unsafe replay.

Also unwrap torch.compile's OptimizedModule (_orig_mod) before the
HyWorldPlayWanDiTNetwork isinstance assert in prefill_memory_kv_cache; with
compile_network=True the wrapper otherwise trips the assert at the first
memory-prefill step (AR 1).

Parity vs vendor (8-chunk, matched prompt) improves 80.4 -> 44.9 / 255 as the
corruption is removed.

Test harness: keep HF_HOME local in bench.sh (avoids ~/.cache permission
errors on shared machines) and add torchvision to the vendor heavy-deps in
run.sh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(hy_worldplay): 8-chunk native-vs-vendor perf across 5 inputs

Per-stage and per-input perf + parity (PR NVIDIA#231 format) for num_chunk=8,
pose=w-31, prompt "a person walking", over HY-WorldPlay/assets/img/
{1,2,5,6,10} on GB300. Native (use_cuda_graph=False after the memory-prefill
corruption fix) is 5.5x faster DiT diffuse and ~4x faster DiT+VAE per chunk
than vendor; median parity 36.0/255 with all chunks rendering cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(hy_worldplay): refresh parity after Wan 2.2 VAE fix (NVIDIA#338)

Re-measured native-vs-vendor mean |Δ| with the VAE patchify channel-order
fix on the native leg (native regenerated, vendor reused unchanged). The
~2px decode checkerboard removal drops median parity 36.0 -> 24.0 / 255
(per-image 36->28, 28->23, 36->26, 21->17, 47->24). Perf/speedup table is
unchanged — the fix is a zero-cost einops axis swap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(hy_worldplay): drop unused imports and dead test var (lint)

Surfaced after rebasing onto current main: an unused ``typing.Any``
import in ``_camera.py`` and an unused ``HyWorldPlayPRoPESelfAttention``
instantiation + ``HyWorldPlayCtrl`` import in ``test_prefill.py``.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jmccaffrey <jmccaffrey@nvidia.com>
Co-authored-by: Ruilong Li(李瑞龙) <ruilongl@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants