Files
hao-ai-lab-FastVideo/fastvideo-kernel/wan22_vae_decode/solution/prefix.cu
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

57 lines
2.7 KiB
Plaintext

// Prefix ownership transpose: no arithmetic, only bf16 NDHWC packets.
#include "prefix.h"
#include "kernels.h"
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <cuda_runtime.h>
namespace {
// Blocks for destination bands are disjoint; each packet is [T,H_peer,W,C].
__global__ void pack_prefix_rows(const uint4* x,uint4* send,int T,int H,int WC8,int world) {
const int peer=blockIdx.y;
const int lo=H*peer/world, hi=H*(peer+1)/world;
const int64_t n=(int64_t)T*(hi-lo)*WC8;
const int64_t offset=(int64_t)T*lo*WC8;
for(int64_t i=(int64_t)blockIdx.x*blockDim.x+threadIdx.x;i<n;i+=(int64_t)gridDim.x*blockDim.x) {
const int64_t t=i/((int64_t)(hi-lo)*WC8);
const int64_t rowcol=i%((int64_t)(hi-lo)*WC8);
send[offset+i]=x[(t*H+lo)*WC8+rowcol];
}
}
}
at::Tensor prefix_temporal_to_rows(const at::Tensor& local,int64_t total_frames,
int rank,int world,const c10::intrusive_ptr<c10d::ProcessGroup>& pg) {
const int C=local.size(1),T=local.size(2),H=local.size(3),W=local.size(4);
TORCH_CHECK(C%8==0 && local.is_contiguous(at::MemoryFormat::ChannelsLast3d),"Prefix needs aligned NDHWC");
TORCH_CHECK(T==total_frames*(rank+1)/world-total_frames*rank/world,"Prefix ownership mismatch");
auto send=at::empty({local.numel()},local.options());
const int band_height=H*(rank+1)/world-H*rank/world;
auto output=nhwc_empty(C,total_frames,band_height,W,local);
// ProcessGroup splits count bf16 elements, not bytes. Receive into the
// physical NDHWC order: the source ranks own chronological frame intervals.
auto physical=output.permute({0,2,3,4,1});
TORCH_CHECK(physical.is_contiguous(),"Prefix receive must be dense NDHWC");
auto recv=physical.view({-1});
if(T) {
dim3 grid(256,world);
pack_prefix_rows<<<grid,256,0,at::cuda::getCurrentCUDAStream()>>>(
(const uint4*)local.data_ptr(),(uint4*)send.data_ptr(),T,H,W*(C/8),world);
C10_CUDA_KERNEL_LAUNCH_CHECK();
}
std::vector<int64_t> in_split(world),out_split(world);
int64_t sent=0,received=0;
for(int p=0;p<world;++p) {
in_split[p]=(int64_t)T*(H*(p+1)/world-H*p/world)*W*C;
out_split[p]=(total_frames*(p+1)/world-total_frames*p/world)*band_height*W*C;
sent+=in_split[p]; received+=out_split[p];
}
TORCH_CHECK(sent==send.numel() && received==recv.numel(),"Prefix packet size mismatch");
// WorkNCCL::wait orders the caller's stream after the collective; it need
// not block the host. ProcessGroup's tensor/allocator stream tracking keeps
// send/recv storage live through that dependency. Do not drop this wait
// when tail kernels consume output on the caller's current stream.
pg->alltoall_base(recv,send,out_split,in_split)->wait();
return output;
}