Files
hao-ai-lab-FastVideo/fastvideo-kernel/wan22_vae_decode/solution/comm.h
T
William Lin 86ef6bfadf [perf] PAN2 sampling: Wan2.2 VAE decode kernel, split decode, fused DiT kernels
- fastvideo-kernel/wan22_vae_decode: the Wan2.2 (TI2V-5B) VAE decoder on sm_100a / sm_103a at 704x1248, built on
  first use against fastvideo-kernel's CUTLASS submodule. It frees intermediates early and carries 4 latent frames
  per pass: a 5 s video peaks at 20.3 GiB, down from 94.6 GiB in the kernel's first version, with the same output.
- WanVAEConfig.decode_kernel routes AutoencoderKLWan.decode through it where it applies (bf16 with fp32
  accumulation, about 7x faster than the fp32 decode on a B300, PSNR 61-63 dB, SSIM 0.998 against it);
  decode_kernel_parallel splits the decode over the sequence-parallel group (2, 4 or 8 GPUs) and broadcasts the
  video, with the ranks agreeing on whether every one of them could build the kernel.
- PAN2's DiT runs fused Triton kernels for Q/K RMSNorm + RoPE and LayerNorm + modulation (pan2_fusions,
  set_fused_kernels).
- PAN2PipelineConfig turns all three on (vae_decode_kernel, vae_decode_kernel_parallel, dit_fused_kernels).
- VideoGenerator copies a decoded RGB video's frames to the host once instead of once per frame.
- The PAN2 presets sample at guidance 2.5, the reference launcher's default since 2026-10-23.
2026-11-03 20:31:55 +00:00

84 lines
5.0 KiB
C++

// Halo edge transport for the row-band split: the two boundary rows a 3x3 convolution needs from its
// neighbours, moved with the cheapest mechanism available. Representation-agnostic by design: it carries
// whatever `halo_pack` packs (today the normalized NDHWC bf16 edge rows; the fusion work may change that),
// and it leaves the packed layout and `halo_unpack` untouched, so the decoded video is bit-identical to the
// all-gather it replaces.
//
// Two ways in:
// halo_exchange(...) one call: allocates the recv buffer, transports and closes. What
// Decoder::conv uses today.
// post_edges(...) / wait_edges(...) the split seam for overlapping the transport with interior compute:
// post enqueues the exchange and returns a handle without a host block,
// wait closes it. `halo_pack` may run on one stream and the transport on
// another: post_edges takes the stream the transport is ordered on.
#pragma once
#include <ATen/ATen.h>
#include <c10/cuda/CUDAStream.h>
#include <torch/csrc/distributed/c10d/ProcessGroup.hpp>
#include <torch/csrc/distributed/c10d/Work.hpp>
#include <c10/util/intrusive_ptr.h>
#include <cuda_runtime.h>
#include <vector>
using PG = c10::intrusive_ptr<c10d::ProcessGroup>;
// Which transport the halo exchange uses, from WAN_HALO once per process. With nothing set the world-size rule
// in comm.cu decides; the variable is for forcing an arm in a measurement (both must be the same on every rank
// of a world: one rank in a collective and one in a p2p group would never meet).
// unset: the rule -- the ring all-gather only at world 2, where both arms have been measured equal, and the
// neighbour path at world 4 and at every world above it. (World 1 has no exchange at all: ph is 0
// there, so nothing calls this.)
// "ag" or "allgather": the ring collective, whatever the world size.
// "p2p" or "neighbour": the neighbour path, whatever the world size. This is what the rule selects above
// world 4, and the neighbour path across trays has never been measured: nothing at that world size
// has, on either arm.
enum HaloTransport { kPolicy = 0, kAllGather = 1, kP2P = 2 };
int halo_transport();
// The exchange sites and the bytes they moved. Debug instrumentation only: nothing on the run path reads them,
// and with WAN_HALO_STATS unset they cost one increment per exchange.
struct HaloStats {
int64_t exchanges = 0, bytes_sent = 0, bytes_received = 0;
};
HaloStats& halo_stats();
// Gather the two edges a rank must send: the first and the last real row of the padded NDHWC tensor
// [1,C,T,H,W] (H includes exactly one pad row per side), as [2,T,W,C] contiguous, edge-major: edge 0 is the
// first real row (H index 1), edge 1 the last (H index H-2). Same layout as the seed's pack_edges, eight
// channels per thread.
at::Tensor halo_pack(const at::Tensor& padded);
// Scatter the received edges into the pad rows: row 0 takes rank-1's edge 1, row H-1 takes rank+1's edge 0,
// and a rank with no neighbour on that side gets zeros. `edges` is the transport's `recv` buffer, and rank+1
// is the band *below* (higher rows), since the split gives rank 0 the rows [0, H/world).
void halo_unpack(at::Tensor& padded, const at::Tensor& edges, int rank, int world);
// The transport, split in two. `send` is halo_pack's [2,T,W,C]; `recv` is the caller's buffer of
// world*2*T*W*C elements in the all-gather's layout (slot r is rank r's two edges), which is what
// halo_unpack reads. The handle owns the works and keeps every send and recv view alive until it is waited.
struct HaloHandle {
std::vector<c10::intrusive_ptr<c10d::Work>> works;
std::vector<at::Tensor> keep;
at::Tensor send, recv; // flat views the p2p slices index
bool started = false;
HaloHandle() = default;
HaloHandle(HaloHandle&&) = default;
HaloHandle& operator=(HaloHandle&&) = default;
HaloHandle(const HaloHandle&) = delete;
HaloHandle& operator=(const HaloHandle&) = delete;
};
// Enqueue only: no host block, and nothing added to the compute stream. `comm` is the stream the transport is
// ordered on; it must already carry whatever ordering the caller needs (a wait on the pack, say), and the
// unpack must not run on a different stream before wait_edges has been called on it.
HaloHandle post_edges(const PG& pg, const at::Tensor& send, at::Tensor& recv, int rank, int world,
cudaStream_t comm);
// Close it: this orders the CALLER'S CURRENT STREAM after the transfer and returns at once. WorkNCCL::wait is
// an event wait on the stream that is current at the call, not a CPU block. So the next kernel enqueued on
// that stream sees the edges -- and a kernel enqueued on any other stream does not.
void wait_edges(HaloHandle& h);
// Allocate the recv buffer, post, close and return the edges in halo_unpack's layout.
at::Tensor halo_exchange(const at::Tensor& send, const PG& pg, int rank, int world);