From f7e6c9750f8c3a386b2697b67ee2cd20b20f3671 Mon Sep 17 00:00:00 2001 From: Aryan Kumar Date: Thu, 29 Oct 2026 18:51:52 -0700 Subject: [PATCH] [docs]: document FA4, bucket, duration, and offload choices for Pika H3 Flex The runbook now gives the FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN FASTVIDEO_FA4=1 launch prefix and the SDPA fallback, the tier override, and measured GB200 timings and memory. The example selects the bucket with --resolution/--aspect, the frame count with --duration, and --offload resident|lazy. --- docs/inference/pika_h3_flex.md | 94 +++++++++++++++- .../inference/basic/basic_pika_h3_flex.py | 103 +++++++++++++++--- 2 files changed, 177 insertions(+), 20 deletions(-) diff --git a/docs/inference/pika_h3_flex.md b/docs/inference/pika_h3_flex.md index c43dbd8e7..94ecef730 100644 --- a/docs/inference/pika_h3_flex.md +++ b/docs/inference/pika_h3_flex.md @@ -31,11 +31,21 @@ VSA sparsity are not used. ## Tasks +The commands below assume a FlashAttention-4 host; see +[Attention backend](#attention-backend) before running them elsewhere. + ```bash +export FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN FASTVIDEO_FA4=1 + # text to video + audio python examples/inference/basic/basic_pika_h3_flex.py --task t2va \ --prompt "a paper boat drifting down a rainy street" +# 480p portrait, ten seconds +python examples/inference/basic/basic_pika_h3_flex.py --task t2va \ + --resolution 480p --aspect 9:16 --duration 10 \ + --prompt "a lighthouse at dusk, waves breaking below" + # first frame, last frame, or both python examples/inference/basic/basic_pika_h3_flex.py --task i2va \ --image first.png --prompt "the camera slowly pushes in" @@ -74,9 +84,88 @@ kept exactly; otherwise FastVideo picks the tier from the requested short edge Frame counts follow Pika's `17n + 5` grid at 24 fps (5–15 s, 120–362 aligned frames). The default 16:9 p768 canvas is 1344×768 and 124 frames. +| Duration | 5 s | 10 s | 15 s | +|---|---|---|---| +| `num_frames` | 124 | 243 | 362 | + +The example script picks the bucket with `--resolution {480p,768p}` and +`--aspect` (`21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, +`9:16`, `9:21`), and the frame count with `--duration` seconds. `--height`, +`--width`, and `--num-frames` override them. + +Through the API, a request that omits `height` and `width` uses the 16:9 +bucket of the configured tier: + +```python +PipelineSelection(experimental={"pika_resolution_tier": "p480"}) # 832x480 when the size is omitted +``` + +An explicit `height`/`width` always wins and selects its own tier. "Omitted" +means absent from a request parsed from a mapping or config file, or from a +`generate_video(...)` call without `height`/`width`; a `GenerationRequest` +constructed in Python counts `SamplingConfig`'s 1280×720 default as set, which +snaps to the 768p 16:9 bucket. Pass the size explicitly in that case. + Pika center-crops both keyframes to the bucket. MiniMax-H3 stretches its first frame instead; do not reuse that fit for Pika. +## Attention backend + +Run Pika with FlashAttention-4 on Hopper or Blackwell: + +```bash +FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN FASTVIDEO_FA4=1 \ + python examples/inference/basic/basic_pika_h3_flex.py --task t2va --prompt "..." +``` + +FA4 is opt-in. Installing `flash_attn.cute` is not enough: +`fastvideo/attention/utils/flash_attn_default.py` only selects it when +`FASTVIDEO_FA4=1`, and neither the example's defaults nor `--lossless-accel` +set that variable. With `FASTVIDEO_FA4=1` and an unusable FA4 install the run +fails at import instead of silently falling back. + +Without FA4, the `FLASH_ATTN` backend uses FA3 or FA2 if one is installed. On a +host with neither (common on Blackwell), backend selection falls back to +`TORCH_SDPA`. SDPA at 768p is much slower and can run out of memory, so do not +use it for 768p benchmarks or production runs. + +## Performance and memory + +All measurements: one GB200, 768p 16:9 (1344×768), 5 s (124 frames), six +forwards, warm requests, FA4, every component resident (`--offload resident`). + +| Configuration (T2VA) | Generation time | +|---|---| +| Official `pika-h3-pro` CLI | 59.7 s | +| FastVideo, BF16 | 26.2 s | +| FastVideo, NVFP4 `quality-36L` decoder | 18.0 s | +| FastVideo, NVFP4 `fast-8L` decoder | 17.4 s | + +The 36L decoder measured 40.5 dB PSNR against the BF16 decoder on the same +latents. The 8L decoder was timed but not scored here. + +Task coverage, seed 42, NVFP4 text encoder, BF16 decoder, also with +`FASTVIDEO_MINIMAX_H3_FUSIONS=all`: + +| Task | Wall | Denoise | Decode | Peak memory | Check | +|---|---|---|---|---|---| +| T2VA | 25.49 s | 16.70 s | 8.04 s | 104.6 GiB | on prompt; 21.7 dB PSNR vs the BF16-encoder output | +| I2VA | 27.37 s | 18.02 s | 8.23 s | 104.9 GiB | first frame vs input 26.5 dB, SSIM 0.715 | +| FL2VA | 28.95 s | 19.49 s | 8.12 s | 105.2 GiB | first frame 39.6 dB / 0.971, last frame 29.1 dB / 0.819 | +| Ref2VA, two image references | 51.17 s | 38.42 s | 7.74 s | 108.5 GiB | 64,241 packed tokens; both references used | + +The NVFP4 text encoder's first request in a fresh process paid a one-time +first-use cost of about 183 s; later requests did not. + +Peak resident memory for the BF16 FastVideo T2VA run was about 129 GiB. The +official CLI's sampling peak was 71.7 GiB; the two figures measure different +things (process-resident memory versus the sampling phase). + +The example's default `--offload lazy` loads components on demand and offloads +the encoders and VAE. It fits smaller devices but reloads the DiT on every +request: the same T2VA request took 48.06 s (28.31 s denoise) on GB200 in that +mode. Use `--offload resident` when the device holds every component. + ## Training-free acceleration The 6 forwards already use FastVideo's normal inference path. Available @@ -91,10 +180,11 @@ lossless switches: * `OffloadConfig(lazy_module_load=True)` and `dit_layerwise=True` for smaller GPUs; * `CompileConfig(enabled=True)` for `torch.compile`; -* the standard FlashAttention/SDPA backend selection. +* FlashAttention-4 through `FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN + FASTVIDEO_FA4=1` (see [Attention backend](#attention-backend)). `--lossless-accel` in the example enables the first four before the generator -starts. Kernel work beyond the existing exact kernels is intentionally not part +starts. It does not enable FA4; set the two attention variables at launch. Kernel work beyond the existing exact kernels is intentionally not part of this integration. ## NVFP4 on Blackwell diff --git a/examples/inference/basic/basic_pika_h3_flex.py b/examples/inference/basic/basic_pika_h3_flex.py index f0383d62c..771d1b19e 100644 --- a/examples/inference/basic/basic_pika_h3_flex.py +++ b/examples/inference/basic/basic_pika_h3_flex.py @@ -16,9 +16,23 @@ parallel VAE tiles) before the generator starts. ``--nvfp4-decoder`` and ``--nvfp4-text-encoder`` opt into the existing Blackwell FP4 weight paths; they change weights, not the six-forward sampler. Kernel/VSA work is separate. +``--resolution`` (480p/768p), ``--aspect`` (one of Pika's eleven buckets), and +``--duration`` (5-15 s) pick the bucket and the ``17n + 5`` frame count; +``--height``/``--width``/``--num-frames`` override them with explicit values. +Launch with ``FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN FASTVIDEO_FA4=1`` on a +FlashAttention-4 host (Hopper/Blackwell); without FA4 attention falls back to +FA3/FA2 or, if neither is installed, to SDPA. + +``--offload resident`` keeps every component on the GPU, which is how the +GB200 timings in docs/inference/pika_h3_flex.md were measured; the default +``lazy`` mode loads components on demand for smaller devices and is slower. + Examples: - python examples/inference/basic/basic_pika_h3_flex.py --task t2va \ + FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN FASTVIDEO_FA4=1 \ + python examples/inference/basic/basic_pika_h3_flex.py --task t2va --offload resident \ --prompt "a paper boat drifting down a rainy street" + python examples/inference/basic/basic_pika_h3_flex.py --task t2va \ + --resolution 480p --aspect 9:16 --duration 10 --prompt "a lighthouse at dusk" python examples/inference/basic/basic_pika_h3_flex.py --task fl2va \ --image first.png --last-image last.png --prompt "the flower opens" python examples/inference/basic/basic_pika_h3_flex.py --task ref2va \ @@ -49,6 +63,14 @@ from fastvideo.api import ( ) from fastvideo.models.vision_utils import load_image from fastvideo.pipelines.basic.minimax_h3 import MiniMaxH3Reference +from fastvideo.pipelines.basic.pika_h3_flex.buckets import ( + PIKA_ASPECTS, + PIKA_FPS, + PIKA_MAX_DURATION, + PIKA_MIN_DURATION, + canvas_for_aspect, + resolve_frames, +) # Training-free switches FastVideo already exposes. Workers inherit the # environment, so they must be set before VideoGenerator starts. @@ -60,6 +82,7 @@ LOSSLESS_ACCEL_ENV = { } TASKS = ("t2va", "i2va", "l2va", "fl2va", "ref2va") +RESOLUTIONS = {"480p": "p480", "768p": "p768"} def _reference(value: str) -> MiniMaxH3Reference: @@ -84,14 +107,25 @@ def parse_args() -> argparse.Namespace: type=_reference, default=[], help="ordered reference (repeatable; ref2va)") - parser.add_argument("--height", type=int, default=768) - parser.add_argument("--width", type=int, default=1344) - parser.add_argument("--num-frames", type=int, default=124) + parser.add_argument("--resolution", choices=tuple(RESOLUTIONS), default="768p", help="Pika resolution tier") + parser.add_argument("--aspect", choices=PIKA_ASPECTS, default="16:9", help="bucket aspect ratio (W:H)") + parser.add_argument("--duration", + type=float, + default=5.0, + help=f"seconds ({PIKA_MIN_DURATION:g}-{PIKA_MAX_DURATION:g}); rounded up to the 17n+5 grid") + parser.add_argument("--height", type=int, help="explicit canvas height (with --width; overrides the bucket)") + parser.add_argument("--width", type=int, help="explicit canvas width (with --height; overrides the bucket)") + parser.add_argument("--num-frames", type=int, help="explicit frame count (overrides --duration)") parser.add_argument("--seed", type=int, default=0) parser.add_argument("--num-gpus", type=int, default=1) parser.add_argument("--compile", action="store_true", help="torch.compile the DiT") parser.add_argument("--lossless-accel", action="store_true", help="enable bit-identical speed-ups") - parser.add_argument("--low-vram", action="store_true", help="stream the DiT layerwise") + parser.add_argument("--offload", + choices=("lazy", "resident"), + default="lazy", + help="lazy: load components on demand and offload the encoders/VAE (fits smaller devices); " + "resident: keep everything on the GPU (fastest, about 105-130 GiB on GB200)") + parser.add_argument("--low-vram", action="store_true", help="stream the DiT layerwise (lazy offload only)") parser.add_argument("--nvfp4-decoder", help="Blackwell-only NVFP4 H3 decoder (quality-36L or fast-8L decoder-nvfp4.safetensors)") parser.add_argument("--nvfp4-text-encoder", @@ -107,16 +141,53 @@ def parse_args() -> argparse.Namespace: parser.error("--task ref2va needs at least one --reference") if args.task != "ref2va" and args.reference: parser.error("--reference is only valid with --task ref2va") + if (args.height is None) != (args.width is None): + parser.error("--height and --width must be passed together") + if args.low_vram and args.offload == "resident": + parser.error("--low-vram streams the DiT and cannot be combined with --offload resident") return args +def _offload(args: argparse.Namespace) -> OffloadConfig: + if args.offload == "resident": + return OffloadConfig(dit=False, + dit_layerwise=False, + text_encoder=False, + vae=False, + pin_cpu_memory=False, + lazy_module_load=False) + return OffloadConfig( + dit=False, + dit_layerwise=args.low_vram, + text_encoder=True, + vae=True, + pin_cpu_memory=not args.low_vram, + lazy_module_load=True, + ) + + +def _canvas(args: argparse.Namespace) -> tuple[int, int]: + if args.height is not None: + return args.height, args.width + return canvas_for_aspect(args.aspect, RESOLUTIONS[args.resolution]) + + +def _num_frames(args: argparse.Namespace) -> int: + if args.num_frames is not None: + return args.num_frames + return resolve_frames(round(args.duration * PIKA_FPS)) + + def _inputs(args: argparse.Namespace) -> InputConfig: if args.task == "ref2va": return InputConfig(references=args.reference) + # Keep only the keyframes the task uses, so switching --task never turns + # an I2VA run into FL2VA because a --last-image was also passed. + first = args.image if args.task in ("i2va", "fl2va") else None + last = args.last_image if args.task in ("l2va", "fl2va") else None # The request adapter copies image fields verbatim; the stage loads # ``image_path`` itself but requires ``last_image`` to already be a PIL image. - last_image = load_image(args.last_image) if args.last_image else None - return InputConfig(image_path=args.image, last_image=last_image) + return InputConfig(image_path=first, last_image=load_image(last) if last else None) def _pipeline(args: argparse.Namespace) -> PipelineSelection: @@ -140,16 +211,12 @@ def main() -> None: for key, value in LOSSLESS_ACCEL_ENV.items(): os.environ.setdefault(key, value) + height, width = _canvas(args) + num_frames = _num_frames(args) + print(f"Pika H3 Flex {args.task}: {width}x{height}, {num_frames} frames ({num_frames / PIKA_FPS:.2f} s)") output_path = Path(args.output) / f"pika_h3_flex_{args.task}.mp4" output_path.parent.mkdir(parents=True, exist_ok=True) - offload = OffloadConfig( - dit=False, - dit_layerwise=args.low_vram, - text_encoder=True, - vae=True, - pin_cpu_memory=not args.low_vram, - lazy_module_load=True, - ) + offload = _offload(args) generator = VideoGenerator.from_config( GeneratorConfig( model_path=args.model_path, @@ -169,9 +236,9 @@ def main() -> None: negative_prompt="", inputs=_inputs(args), sampling=SamplingConfig( - height=args.height, - width=args.width, - num_frames=args.num_frames, + height=height, + width=width, + num_frames=num_frames, fps=24, num_inference_steps=6, guidance_scale=1.0,