832 lines
30 KiB
Python
832 lines
30 KiB
Python
# Copyright 2024 NVIDIA CORPORATION & AFFILIATES
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Triton-kernel dispatch wrappers for streaming chunk-causal inference.
|
|
|
|
These are pure helpers that take an attention-block ``layer`` instance and the
|
|
current chunk's tensors and run the fused kernels in
|
|
:mod:`diffusion.model.ops.fused_gdn`,
|
|
:mod:`diffusion.model.ops.fused_gdn_chunkwise`, and
|
|
:mod:`diffusion.model.ops.fused_cam_gdn`. The ``nn.Module`` wrappers live in
|
|
:mod:`diffusion.model.nets.sana_gdn_blocks` (``CachedChunkCausalGDN`` /
|
|
``CachedChunkCausalSoftmaxAttn``) and
|
|
:mod:`diffusion.model.nets.sana_gdn_camctrl_blocks`
|
|
(``CachedChunkCausalGDNUCPESinglePathLiteLA`` /
|
|
``CachedSoftmaxUCPESinglePathLiteLA``) and call into here.
|
|
|
|
Cache slot layout (10 slots per attention block, shared with the
|
|
scheduler). Slot 6 distinguishes GDN (state-based) from softmax
|
|
(concat-based) blocks.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
|
|
* - Slot
|
|
- GDN blocks
|
|
- Softmax blocks
|
|
* - 0
|
|
- S_kv state (B, H, D, D)
|
|
- k post-RoPE (B, H, N, D)
|
|
* - 1
|
|
- S_z state (B, H, D, 1)
|
|
- v (B, H, N, D)
|
|
* - 2
|
|
- cam_S_kv state (B, H_c, D_c, D_c)
|
|
- cam_k post-UCPE (B, H_c, N, D_c)
|
|
* - 3
|
|
- camera K ShortConv state (B*S, K-1, C)
|
|
- cam_v post-UCPE (B, H_c, N, D_c)
|
|
* - 4
|
|
- ShortConv K state (B*S, K-1, C)
|
|
- None (no conv for softmax)
|
|
* - 5
|
|
- reserved
|
|
- reserved
|
|
* - 6
|
|
- type flag: 1.0
|
|
- type flag: 0.0
|
|
* - 7-8
|
|
- reserved
|
|
- reserved
|
|
* - 9
|
|
- tconv state (handled by CachedGLUMBConvTemp)
|
|
- tconv state
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
import triton
|
|
from fla.modules import ShortConvolution
|
|
|
|
from diffusion.model.nets.sana_camctrl_blocks import _prepare_ray_apply_fns
|
|
from diffusion.model.ops.fused_cam_gdn import (
|
|
_invert_SE3,
|
|
_prepare_ucpe_rope_tables,
|
|
_process_camera_conditions_raymats_only,
|
|
_torch_cam_scan_reference,
|
|
_torch_cam_scan_single_chunk,
|
|
cam_prep_func,
|
|
cam_prep_func_with_grad,
|
|
)
|
|
from diffusion.model.ops.fused_gdn import fused_qk_inv_rms, prepare_rope_tables
|
|
from diffusion.model.ops.fused_gdn_chunkwise import (
|
|
_default_dot_prec,
|
|
cam_scan_chunkwise,
|
|
phase_a,
|
|
phase_b_triton,
|
|
phase_c,
|
|
)
|
|
from diffusion.model.ops.fused_gdn_chunkwise_cuda import cam_scan_chunkwise_cuda
|
|
from diffusion.model.ops.fused_gdn_chunkwise_stateful_raw import fused_gdn_chunkwise_stateful_raw_autograd
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cache slot indices (must match scheduler constants)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_SLOT_FWD_KV = 0
|
|
_SLOT_FWD_Z = 1
|
|
_SLOT_CAM = 2
|
|
_SLOT_CAM_AUX = 3
|
|
_SLOT_SHORTCONV = 4
|
|
_SLOT_TCONV = 5 # NOTE: CachedGLUMBConvTemp actually writes to kv_cache[-1] (slot 9), not slot 5!
|
|
_SLOT_TYPE_FLAG = 6
|
|
|
|
_TYPE_STATE = 1.0 # GDN: state-based cache
|
|
_TYPE_CONCAT = 0.0 # Softmax: concat-based cache
|
|
|
|
|
|
def _slice_rope_to_current_chunk(rotary_emb: torch.Tensor, current_n: int) -> torch.Tensor:
|
|
"""Slice rotary embedding freqs to the trailing ``current_n`` token positions.
|
|
|
|
When ``sink_token=true``, upstream rope is built for sink + current chunk
|
|
positions (covers ``frame_index.numel()`` frames). But q/k inside the
|
|
cached chunk-causal attention only cover the current chunk — sink K is
|
|
either pre-rotated in S_kv (linear attn) or pre-rotated in kv_cache K
|
|
(softmax attn). Slicing the trailing portion of ``rotary_emb`` aligns it
|
|
with current-chunk q/k. If sizes already match (e.g. rolling_rope path
|
|
that generates rope only for the current chunk's frame range), this is a
|
|
no-op.
|
|
"""
|
|
rope_n = rotary_emb.shape[-2]
|
|
if rope_n == current_n:
|
|
return rotary_emb
|
|
if rope_n < current_n:
|
|
raise RuntimeError(
|
|
f"rotary_emb has {rope_n} positions, smaller than current chunk's " f"{current_n}; cannot slice."
|
|
)
|
|
return rotary_emb[..., -current_n:, :]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cached temporal short convolution
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _cached_temporal_short_conv(
|
|
x: torch.Tensor,
|
|
conv: ShortConvolution,
|
|
HW: tuple[int, int, int],
|
|
conv_cache: torch.Tensor | None,
|
|
save_cache: bool,
|
|
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
|
"""Short conv with cached left context: forward-cached + backward-isolated.
|
|
|
|
Mirrors ``ChunkCausalGDN._apply_temporal_short_conv`` but replaces the
|
|
global forward causal conv with a cache-aware version.
|
|
|
|
Uses the same ``ShortConvolution.forward()`` (Triton/CUDA backend) as the
|
|
training path for bit-exact numerical parity. The only difference is that
|
|
the forward causal pass prepends cached left context instead of starting
|
|
from zeros.
|
|
|
|
Args:
|
|
x: ``(B, N, C)`` where ``N = T * S``.
|
|
conv: FLA ``ShortConvolution`` (depthwise causal Conv1d).
|
|
HW: ``(T, H, W)``.
|
|
conv_cache: ``(B*S, K-1, C)`` from previous chunk, or None.
|
|
save_cache: Whether to return a new cache for the next chunk.
|
|
|
|
Returns:
|
|
(output, new_cache): output ``(B, N, C)``, new_cache ``(B*S, K-1, C)`` or None.
|
|
"""
|
|
T, H, W = HW
|
|
S = H * W
|
|
B_orig, N, C = x.shape
|
|
dtype_in = x.dtype
|
|
K = conv.weight.shape[-1]
|
|
|
|
# Reshape to temporal: (B*S, T, C).
|
|
x_t = x.reshape(B_orig, T, S, C).permute(0, 2, 1, 3).contiguous().reshape(B_orig * S, T, C)
|
|
|
|
# --- Forward causal conv with cache ---
|
|
# Use ShortConvolution.forward() (Triton/CUDA kernel) for exact numerical
|
|
# parity with ChunkCausalGDN._apply_temporal_short_conv.
|
|
if conv_cache is not None:
|
|
# Prepend cached left context and run full causal conv, then slice.
|
|
x_fwd_in = torch.cat([conv_cache.to(x_t.dtype), x_t], dim=1)
|
|
y_fwd_full, _ = conv(x_fwd_in)
|
|
y_fwd = y_fwd_full[:, K - 1 :, :] # drop positions from cached prefix
|
|
else:
|
|
y_fwd, _ = conv(x_t)
|
|
|
|
# --- Backward conv (isolated within current chunk) ---
|
|
# Same as ChunkCausalGDN._backward_causal_conv_per_chunk for a single chunk:
|
|
# flip → causal conv → flip back.
|
|
y_bwd_flipped, _ = conv(x_t.flip(1))
|
|
y_bwd = y_bwd_flipped.flip(1) # (B*S, T, C)
|
|
|
|
# --- Center tap ---
|
|
w_center = conv.weight[:, 0, -1] # (C,)
|
|
center_term = x_t * w_center.unsqueeze(0).unsqueeze(0)
|
|
|
|
y = y_fwd + y_bwd - center_term
|
|
|
|
# Save cache: last K-1 timesteps of the conv INPUT (for next chunk's left context).
|
|
new_cache: torch.Tensor | None = None
|
|
if save_cache and K > 1:
|
|
new_cache = x_t[:, -(K - 1) :, :].detach().clone()
|
|
|
|
# Reshape back to (B, N, C).
|
|
y = y.reshape(B_orig, S, T, C).permute(0, 2, 1, 3).reshape(B_orig, N, C)
|
|
if y.dtype != dtype_in:
|
|
y = y.to(dtype_in)
|
|
return y, new_cache
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fused Triton scan (chunk-causal main GDN branch)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _gdn_main_triton(
|
|
layer,
|
|
qkv: torch.Tensor,
|
|
beta: torch.Tensor,
|
|
decay: torch.Tensor,
|
|
rotary_emb: torch.Tensor | None,
|
|
HW: tuple[int, int, int],
|
|
S_kv_prev: torch.Tensor | None,
|
|
S_z_prev: torch.Tensor | None,
|
|
save_kv_cache: bool,
|
|
) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
|
|
"""Run the chunk-causal main GDN scan through the fused Triton chunkwise
|
|
kernels.
|
|
|
|
Uses two fused Triton calls (forward-with-state + per-chunk reverse) on
|
|
a shared ``qkv`` prep.
|
|
|
|
The chunk-causal layout (forward seeded from cached state, backward
|
|
isolated per chunk) is NOT what the bidir convenience wrapper does
|
|
(``fused_bigdn_bidi_chunkwise`` sums both directions in-kernel and
|
|
assumes neither has state). We call ``phase_a`` once, ``phase_b_triton``
|
|
twice (direction=1 stateful, direction=2 stateless), accumulate in
|
|
``phase_c`` via ``accumulate=True``, then divide.
|
|
|
|
Args:
|
|
layer: A :class:`CachedChunkCausalGDN` instance — for q_norm /
|
|
k_norm weights, eps, kernel_func, etc.
|
|
qkv: ``(B, N, 3, H, D)`` raw QKV (post short-conv K). Triton applies
|
|
RMSNorm + ReLU + K-scale + RoPE inside the kernel.
|
|
beta: ``(B, H, F)`` or ``(B, H, F, S)`` per-frame gates (input dtype).
|
|
decay: ``(B, H, F)`` per-frame gates.
|
|
rotary_emb: complex rotary frequencies for the current chunk.
|
|
HW: ``(T=F, H, W)``; with ``S = H * W``.
|
|
S_kv_prev: cached forward-scan ``(B, H, D, D)`` state from the prior
|
|
chunk, or ``None`` for the first chunk.
|
|
S_z_prev: cached forward-scan ``(B, H, D, 1)`` state, or ``None``.
|
|
save_kv_cache: when ``True``, return ``(out, S_kv_new, S_z_new)``;
|
|
otherwise return ``(out, None, None)``.
|
|
|
|
Returns:
|
|
``(out, S_kv_new, S_z_new)`` where ``out`` is ``(B, N, H, D)``
|
|
post-divide in the kernel's dot_precision dtype (fp32 or bf16).
|
|
"""
|
|
B, N, three, H, D = qkv.shape
|
|
assert three == 3, f"qkv last-3 dim must be 3 (q,k,v); got shape {qkv.shape}"
|
|
T, H_sp, W_sp = HW
|
|
S = H_sp * W_sp
|
|
assert N == T * S, f"N={N} != T*S={T * S} for HW={HW}"
|
|
C = H * D
|
|
|
|
# ---- RMS norm parameters ----
|
|
if isinstance(layer.q_norm, nn.Identity):
|
|
q_nw = torch.ones(C, device=qkv.device, dtype=torch.float32)
|
|
k_nw = torch.ones(C, device=qkv.device, dtype=torch.float32)
|
|
norm_eps = 1e-5
|
|
else:
|
|
q_nw = layer.q_norm.weight.float().contiguous()
|
|
k_nw = layer.k_norm.weight.float().contiguous()
|
|
norm_eps = float(getattr(layer.q_norm, "eps", 1e-5))
|
|
|
|
# ---- RoPE tables for the current chunk ----
|
|
if rotary_emb is None:
|
|
rope_cos = torch.ones(N, D, device=qkv.device, dtype=torch.float32)
|
|
rope_sin = torch.zeros(N, D, device=qkv.device, dtype=torch.float32)
|
|
else:
|
|
rope_cur = _slice_rope_to_current_chunk(rotary_emb, N)
|
|
rope_cos, rope_sin = prepare_rope_tables(rope_cur, N, D, qkv.device)
|
|
|
|
# ---- K scale (same convention as torch path: D^-1/2 * S^-1/2) ----
|
|
k_scale = (D**-0.5) * (S**-0.5)
|
|
|
|
# ---- Beta broadcast convention: kernels accept (B,H,F) or (B,H,F,S) ----
|
|
beta_c = beta.contiguous()
|
|
decay_c = decay.contiguous()
|
|
|
|
dot_prec = _default_dot_prec()
|
|
|
|
if torch.is_grad_enabled() and qkv.requires_grad:
|
|
forward = fused_gdn_chunkwise_stateful_raw_autograd(
|
|
qkv,
|
|
beta_c,
|
|
decay_c,
|
|
q_nw,
|
|
k_nw,
|
|
rope_cos,
|
|
rope_sin,
|
|
T,
|
|
S,
|
|
init_state_kv=S_kv_prev,
|
|
init_state_z=S_z_prev,
|
|
k_scale=k_scale,
|
|
norm_eps=norm_eps,
|
|
dot_precision=dot_prec,
|
|
save_final_state=save_kv_cache,
|
|
direction=1,
|
|
)
|
|
reverse = fused_gdn_chunkwise_stateful_raw_autograd(
|
|
qkv,
|
|
beta_c,
|
|
decay_c,
|
|
q_nw,
|
|
k_nw,
|
|
rope_cos,
|
|
rope_sin,
|
|
T,
|
|
S,
|
|
k_scale=k_scale,
|
|
norm_eps=norm_eps,
|
|
dot_precision=dot_prec,
|
|
direction=2,
|
|
)
|
|
num_fwd, den_fwd = forward[:2]
|
|
num_rev, den_rev = reverse
|
|
denominator = (den_fwd.float() + den_rev.float()).permute(0, 2, 1).unsqueeze(-1)
|
|
out = ((num_fwd.float() + num_rev.float()) / (denominator + float(layer.eps))).to(qkv.dtype)
|
|
if save_kv_cache:
|
|
return out, forward[2], forward[3]
|
|
return out, None, None
|
|
|
|
# ---- inv RMS (single fused launch over Q and K halves of qkv) ----
|
|
q_inv_rms, k_inv_rms = fused_qk_inv_rms(qkv, eps=norm_eps)
|
|
|
|
# ---- Phase A: shared prep for both directions ----
|
|
I_P_kv, A_buf, I_P_z, B_z = phase_a(
|
|
qkv,
|
|
beta_c,
|
|
q_inv_rms,
|
|
k_inv_rms,
|
|
q_nw,
|
|
k_nw,
|
|
rope_cos,
|
|
rope_sin,
|
|
F=T,
|
|
S=S,
|
|
k_scale=k_scale,
|
|
norm_eps=norm_eps,
|
|
dot_precision=dot_prec,
|
|
)
|
|
|
|
# ---- Pad caller-supplied (B,H,D,D)/(B,H,D,1) state to padded layout ----
|
|
BLOCK_D = I_P_kv.shape[-1]
|
|
init_kv_padded = None
|
|
init_z_padded = None
|
|
if S_kv_prev is not None:
|
|
sk = S_kv_prev
|
|
sk = sk.to(torch.float32) if sk.dtype != torch.float32 else sk
|
|
B_s, H_s, D_in, D_out = sk.shape
|
|
if D_in != BLOCK_D or D_out != BLOCK_D:
|
|
init_kv_padded = F.pad(
|
|
sk.transpose(-1, -2).reshape(B_s * H_s, D_out, D_in),
|
|
(0, BLOCK_D - D_in, 0, BLOCK_D - D_out),
|
|
).contiguous()
|
|
else:
|
|
init_kv_padded = sk.transpose(-1, -2).reshape(B_s * H_s, BLOCK_D, BLOCK_D).contiguous()
|
|
sz = S_z_prev.squeeze(-1) if S_z_prev.dim() == 4 else S_z_prev
|
|
sz = sz.to(torch.float32) if sz.dtype != torch.float32 else sz
|
|
Bz, Hz, Dz = sz.shape
|
|
if Dz != BLOCK_D:
|
|
init_z_padded = F.pad(sz.reshape(Bz * Hz, Dz), (0, BLOCK_D - Dz)).contiguous()
|
|
else:
|
|
init_z_padded = sz.reshape(Bz * Hz, BLOCK_D).contiguous()
|
|
|
|
# ---- Phase B forward (direction=1) with state ----
|
|
if save_kv_cache:
|
|
M_fwd, z_fwd, _, _, final_kv, final_z = phase_b_triton(
|
|
I_P_kv,
|
|
A_buf,
|
|
I_P_z,
|
|
B_z,
|
|
decay_c,
|
|
F=T,
|
|
dot_precision=dot_prec,
|
|
direction=1,
|
|
init_state_kv=init_kv_padded,
|
|
init_state_z=init_z_padded,
|
|
return_final_state=True,
|
|
)
|
|
else:
|
|
M_fwd, z_fwd, _, _ = phase_b_triton(
|
|
I_P_kv,
|
|
A_buf,
|
|
I_P_z,
|
|
B_z,
|
|
decay_c,
|
|
F=T,
|
|
dot_precision=dot_prec,
|
|
direction=1,
|
|
init_state_kv=init_kv_padded,
|
|
init_state_z=init_z_padded,
|
|
)
|
|
|
|
# ---- Phase B reverse (direction=2) — per-chunk, no state ----
|
|
_, _, M_rev, z_rev = phase_b_triton(
|
|
I_P_kv,
|
|
A_buf,
|
|
I_P_z,
|
|
B_z,
|
|
decay_c,
|
|
F=T,
|
|
dot_precision=dot_prec,
|
|
direction=2,
|
|
)
|
|
del I_P_kv, A_buf, I_P_z, B_z
|
|
|
|
# ---- Phase C: fwd output, then accumulate rev output into same buffers ----
|
|
num_out, den_out = phase_c(
|
|
qkv,
|
|
q_inv_rms,
|
|
q_nw,
|
|
rope_cos,
|
|
rope_sin,
|
|
M_fwd,
|
|
z_fwd,
|
|
F=T,
|
|
S=S,
|
|
dot_precision=dot_prec,
|
|
)
|
|
phase_c(
|
|
qkv,
|
|
q_inv_rms,
|
|
q_nw,
|
|
rope_cos,
|
|
rope_sin,
|
|
M_rev,
|
|
z_rev,
|
|
F=T,
|
|
S=S,
|
|
dot_precision=dot_prec,
|
|
num_out=num_out,
|
|
den_out=den_out,
|
|
accumulate=True,
|
|
)
|
|
del M_fwd, z_fwd, M_rev, z_rev
|
|
|
|
# ---- Divide: (B, H, N) -> (B, N, H, 1) for broadcast over D ----
|
|
eps = float(layer.eps)
|
|
total_den = den_out.float().permute(0, 2, 1).unsqueeze(-1) # (B, N, H, 1)
|
|
out = (num_out.float() / (total_den + eps)).to(qkv.dtype) # (B, N, H, D)
|
|
del num_out, den_out, total_den
|
|
|
|
# ---- Unpad final state back to (B, H, D, D) / (B, H, D, 1) ----
|
|
if save_kv_cache:
|
|
S_kv_new = final_kv.view(B, H, BLOCK_D, BLOCK_D)[:, :, :D, :D].transpose(-1, -2).contiguous()
|
|
S_z_new = final_z.view(B, H, BLOCK_D)[:, :, :D].unsqueeze(-1).contiguous()
|
|
return out, S_kv_new, S_z_new
|
|
return out, None, None
|
|
|
|
|
|
def _cam_main_triton(
|
|
q_cam_trans: torch.Tensor,
|
|
k_cam_trans: torch.Tensor,
|
|
v_cam_trans: torch.Tensor,
|
|
beta: torch.Tensor,
|
|
decay: torch.Tensor,
|
|
cam_S_kv_prev: torch.Tensor | None,
|
|
save_kv_cache: bool,
|
|
T: int,
|
|
S: int,
|
|
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
|
"""Run the camera-branch single-path delta-rule chunk-causal scan with
|
|
two ``cam_scan_chunkwise`` calls (forward seeded from cached state +
|
|
per-chunk reverse).
|
|
|
|
The kernel expects fp32 q/k/v in ``(B, H, D, N)`` layout (already
|
|
cam-prep'd: RMSNorm + ReLU + UCPE + RoPE). State is stored as
|
|
``(B*H, BLOCK_D, BLOCK_D)`` fp32 internally; we accept/return the
|
|
``(B, H, D, D)`` torch-format used by the kv_cache slot.
|
|
|
|
Args:
|
|
q_cam_trans, k_cam_trans, v_cam_trans: ``(B, H, D, N)`` —
|
|
cam-prep'd inputs. Cast to fp32 if not already.
|
|
beta: ``(B, H, F)`` or ``(B, H, F, S)``.
|
|
decay: ``(B, H, F)``.
|
|
cam_S_kv_prev: ``(B, H, D, D)`` fp32 cached state, or ``None``.
|
|
save_kv_cache: when ``True``, return ``(out, cam_S_kv_new)``.
|
|
T, S: frames and spatial-tokens-per-frame (``N = T * S``).
|
|
|
|
Returns:
|
|
``(out, cam_S_kv_new)`` with ``out`` shaped ``(B, H, D, N)`` fp32
|
|
(fwd + per-chunk bwd combined) and ``cam_S_kv_new`` shaped
|
|
``(B, H, D, D)`` fp32 (or ``None`` when not saving).
|
|
"""
|
|
if torch.is_grad_enabled() and any(tensor.requires_grad for tensor in (q_cam_trans, k_cam_trans, v_cam_trans)):
|
|
beta_f = beta.float().contiguous()
|
|
if beta_f.ndim == 3:
|
|
beta_f = beta_f.unsqueeze(-1).expand(-1, -1, -1, S).contiguous()
|
|
decay_f = decay.float().contiguous()
|
|
# The fused cache stores M as (K feature, V feature), while the
|
|
# torch recurrence uses state @ k and therefore keeps (V, K).
|
|
init_state = cam_S_kv_prev.transpose(-1, -2) if cam_S_kv_prev is not None else None
|
|
forward, final_state = _torch_cam_scan_single_chunk(
|
|
q_cam_trans.float().contiguous(),
|
|
k_cam_trans.float().contiguous(),
|
|
v_cam_trans.float().contiguous(),
|
|
beta_f,
|
|
decay_f,
|
|
init_state=init_state,
|
|
return_final_state=True,
|
|
)
|
|
reverse = _torch_cam_scan_reference(
|
|
q_cam_trans.float().contiguous(),
|
|
k_cam_trans.float().contiguous(),
|
|
v_cam_trans.float().contiguous(),
|
|
beta_f,
|
|
decay_f,
|
|
reverse=True,
|
|
)
|
|
final_state = final_state.transpose(-1, -2).contiguous() if save_kv_cache else None
|
|
return forward + reverse, final_state
|
|
|
|
# CUDA cam scan on by default; set SANA_GDN_CUDA=0 to force Triton.
|
|
scan = cam_scan_chunkwise_cuda if os.environ.get("SANA_GDN_CUDA", "1") != "0" else None
|
|
scan = scan or cam_scan_chunkwise
|
|
|
|
B, H, D, N = q_cam_trans.shape
|
|
assert N == T * S, f"N={N} != T*S={T * S}"
|
|
BLOCK_D = triton.next_power_of_2(D)
|
|
|
|
# ---- Inputs: fp32 contiguous (kernel hard-requires this). ----
|
|
q32 = q_cam_trans.float().contiguous() if q_cam_trans.dtype != torch.float32 else q_cam_trans.contiguous()
|
|
k32 = k_cam_trans.float().contiguous() if k_cam_trans.dtype != torch.float32 else k_cam_trans.contiguous()
|
|
v32 = v_cam_trans.float().contiguous() if v_cam_trans.dtype != torch.float32 else v_cam_trans.contiguous()
|
|
|
|
# ---- Beta: kernel expects (B, H, F, S) per docstring. ----
|
|
if beta.ndim == 3:
|
|
beta_c = beta.unsqueeze(-1).expand(B, H, T, S).contiguous().float()
|
|
else:
|
|
beta_c = beta.contiguous().float()
|
|
decay_c = decay.contiguous().float()
|
|
|
|
# ---- Pad caller-supplied (B, H, D, D) to (B*H, BLOCK_D, BLOCK_D) fp32. ----
|
|
init_state = None
|
|
if cam_S_kv_prev is not None:
|
|
sk = cam_S_kv_prev.to(torch.float32) if cam_S_kv_prev.dtype != torch.float32 else cam_S_kv_prev
|
|
if D != BLOCK_D:
|
|
init_state = F.pad(
|
|
sk.reshape(B * H, D, D),
|
|
(0, BLOCK_D - D, 0, BLOCK_D - D),
|
|
).contiguous()
|
|
else:
|
|
init_state = sk.reshape(B * H, BLOCK_D, BLOCK_D).contiguous()
|
|
|
|
# ---- Forward scan with state ----
|
|
if save_kv_cache:
|
|
out_fwd, final_state = scan(
|
|
q32,
|
|
k32,
|
|
v32,
|
|
beta_c,
|
|
decay_c,
|
|
reverse=False,
|
|
init_state=init_state,
|
|
save_final_state=True,
|
|
)
|
|
else:
|
|
out_fwd = scan(
|
|
q32,
|
|
k32,
|
|
v32,
|
|
beta_c,
|
|
decay_c,
|
|
reverse=False,
|
|
init_state=init_state,
|
|
save_final_state=False,
|
|
)
|
|
final_state = None
|
|
|
|
# ---- Backward scan (per-chunk isolated; no state) ----
|
|
out_bwd = scan(
|
|
q32,
|
|
k32,
|
|
v32,
|
|
beta_c,
|
|
decay_c,
|
|
reverse=True,
|
|
init_state=None,
|
|
save_final_state=False,
|
|
)
|
|
|
|
out = out_fwd + out_bwd # (B, H, D, N) fp32
|
|
|
|
if final_state is None:
|
|
return out, None
|
|
# Cam state is stored as M[K_feat, V_feat] (row-major D_K, D_V) — NO
|
|
# transpose unlike main GDN (which transposes on save/load). Unpad to
|
|
# the (B, H, D, D) shape callers expect for the kv_cache slot.
|
|
cam_S_kv_new = final_state.view(B, H, BLOCK_D, BLOCK_D)[:, :, :D, :D].contiguous()
|
|
return out, cam_S_kv_new
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fused Triton cam-prep (RMSNorm + ReLU + K-scale + UCPE 4x4 + RoPE)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _cam_prep_triton(
|
|
layer,
|
|
x: torch.Tensor,
|
|
HW: tuple[int, int, int],
|
|
camera_conditions: torch.Tensor,
|
|
rotary_emb: torch.Tensor | None,
|
|
conv_cache: torch.Tensor | None,
|
|
save_cache: bool,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, callable, torch.Tensor | None]:
|
|
"""Streaming cam-branch QKV prep through the bidir's fused Triton kernel.
|
|
|
|
Mirrors the prep section of
|
|
:meth:`BidirectionalGDNUCPESinglePathLiteLABothTriton._forward_cam_branch`
|
|
(QKV linear + cam-K short conv + ``cam_prep_func`` Triton kernel +
|
|
``inflation_sq`` reshape) but applies the K conv with a per-chunk
|
|
cached left context on the forward half and chunk-local context on the
|
|
backward half.
|
|
|
|
Args:
|
|
layer: A :class:`CachedChunkCausalGDNUCPESinglePathLiteLA` instance.
|
|
x: ``(B, N, C)`` input activations for the current chunk.
|
|
HW: ``(T, H, W)`` token layout.
|
|
camera_conditions: ``(B, T, ...)`` camera-pose tensor.
|
|
rotary_emb: complex RoPE frequencies for the current chunk.
|
|
conv_cache: Previous camera-K short-convolution context.
|
|
save_cache: Whether to return context for the next chunk.
|
|
|
|
Returns:
|
|
``(q_cam_trans, k_cam_trans, v_cam_trans, inflation_sq, apply_fn_o, new_conv_cache)``
|
|
with ``q/k/v_cam_trans`` shaped ``(B, H_cam, D_cam, N)`` in the input
|
|
dtype, ``inflation_sq`` shaped ``(B, H_cam, 1, N)`` fp32, and
|
|
``apply_fn_o`` a torch closure that applies the inverse UCPE+RoPE to
|
|
the scan output, and the new short-convolution cache (or ``None``).
|
|
"""
|
|
if layer.conv_q_cam is not None or layer.conv_v_cam is not None:
|
|
raise NotImplementedError(
|
|
"Triton cam-prep requires k_conv_only=True on the camera branch " "(conv_q_cam / conv_v_cam must be None)."
|
|
)
|
|
|
|
B, N, _ = x.shape
|
|
T, H_sp, W_sp = HW
|
|
S = H_sp * W_sp
|
|
H_heads = layer.cam_heads
|
|
D_head = layer.cam_head_dim
|
|
|
|
# ---- 1. QKV linear (fused via cat) + cam-K short conv ----
|
|
qkv_w = torch.cat([layer.q_proj_cam.weight, layer.k_proj_cam.weight, layer.v_proj_cam.weight])
|
|
qkv_b = torch.cat([layer.q_proj_cam.bias, layer.k_proj_cam.bias, layer.v_proj_cam.bias])
|
|
qkv_cam = F.linear(x, qkv_w, qkv_b)
|
|
q_raw, k_raw, v_raw = qkv_cam.chunk(3, dim=-1)
|
|
|
|
new_conv_cache = None
|
|
if layer.conv_k_cam is not None:
|
|
k_raw, new_conv_cache = _cached_temporal_short_conv(
|
|
k_raw,
|
|
layer.conv_k_cam,
|
|
HW,
|
|
conv_cache,
|
|
save_cache,
|
|
)
|
|
|
|
q_raw = q_raw.contiguous().view(B, N, H_heads, D_head).contiguous()
|
|
k_raw = k_raw.contiguous().view(B, N, H_heads, D_head).contiguous()
|
|
v_raw = v_raw.contiguous().view(B, N, H_heads, D_head).contiguous()
|
|
|
|
# ---- 2. UCPE projection matrices (P / P_T / P_inv) ----
|
|
raymats = _process_camera_conditions_raymats_only(camera_conditions, B, HW, layer.patch_size)
|
|
raymats = raymats.reshape(B, -1, 4, 4)
|
|
P = raymats
|
|
P_T = P.transpose(-1, -2).contiguous()
|
|
P_inv = _invert_SE3(P).contiguous()
|
|
|
|
# ---- 3. Sliced cam-branch RoPE (D/2 dims; T/H/W split halved) ----
|
|
if rotary_emb is not None:
|
|
# Mirror the WAN-RoPE slicing used by the bidir kernel call site.
|
|
head_dim = D_head
|
|
orig_t_size = head_dim // 2 - 2 * (head_dim // 6)
|
|
orig_h_size = head_dim // 6
|
|
new_head_dim = head_dim // 2
|
|
new_t_size = new_head_dim // 2 - 2 * (new_head_dim // 6)
|
|
new_h_size = new_head_dim // 6
|
|
new_w_size = new_head_dim // 6
|
|
t_part = rotary_emb[..., :new_t_size]
|
|
h_part = rotary_emb[..., orig_t_size : orig_t_size + new_h_size]
|
|
w_part = rotary_emb[..., orig_t_size + orig_h_size : orig_t_size + orig_h_size + new_w_size]
|
|
rotary_emb_cam = torch.cat([t_part, h_part, w_part], dim=-1)
|
|
# Slice trailing N positions when upstream RoPE covers sink+current.
|
|
rotary_emb_cam = _slice_rope_to_current_chunk(rotary_emb_cam, N)
|
|
rope_cos, rope_sin = _prepare_ucpe_rope_tables(rotary_emb_cam, N, D_head // 2, x.device)
|
|
else:
|
|
rotary_emb_cam = None
|
|
rope_cos = torch.ones(N, D_head // 2, device=x.device, dtype=torch.float32)
|
|
rope_sin = torch.zeros(N, D_head // 2, device=x.device, dtype=torch.float32)
|
|
|
|
# ---- 4. Fused Triton prep kernel ----
|
|
q_norm_w = layer.q_norm_cam.weight.float().contiguous()
|
|
k_norm_w = layer.k_norm_cam.weight.float().contiguous()
|
|
k_scale = (D_head**-0.5) * (S**-0.5)
|
|
norm_eps_val = float(getattr(layer.q_norm_cam, "eps", getattr(layer.q_norm_cam, "variance_epsilon", 1e-6)))
|
|
prep = cam_prep_func_with_grad if torch.is_grad_enabled() and q_raw.requires_grad else cam_prep_func
|
|
q_cam_trans, k_cam_trans, v_cam_trans, inflation_sq = prep(
|
|
q_raw,
|
|
k_raw,
|
|
v_raw,
|
|
q_norm_weight=q_norm_w,
|
|
k_norm_weight=k_norm_w,
|
|
proj_q=P_T,
|
|
proj_kv=P_inv,
|
|
rope_cos=rope_cos,
|
|
rope_sin=rope_sin,
|
|
k_scale=k_scale,
|
|
norm_eps=norm_eps_val,
|
|
)
|
|
inflation_sq = inflation_sq.view(B, H_heads, 1, N)
|
|
|
|
# ---- 5. Inverse-UCPE closure for the scan output ----
|
|
_, _, apply_fn_o = _prepare_ray_apply_fns(head_dim=D_head, P=P, P_T=P_T, P_inv=P_inv, rotary_emb=rotary_emb_cam)
|
|
|
|
return q_cam_trans, k_cam_trans, v_cam_trans, inflation_sq, apply_fn_o, new_conv_cache
|
|
|
|
|
|
def _cached_gdn_forward_triton(
|
|
layer,
|
|
x: torch.Tensor,
|
|
HW: tuple[int, int, int] | None,
|
|
rotary_emb: torch.Tensor | None,
|
|
apply_output_gate: bool,
|
|
**kwargs: object,
|
|
) -> tuple[torch.Tensor, list]:
|
|
"""Cached chunk-causal forward through the fused Triton scan.
|
|
|
|
Same return contract as the torch ``CachedChunkCausalGDN.forward``
|
|
cached path (``(out, kv_cache)``). Recurrent state on slots
|
|
``[_SLOT_FWD_KV, _SLOT_FWD_Z]`` and the shortconv slot
|
|
``[_SLOT_SHORTCONV]`` are updated in place exactly like the torch
|
|
path so the scheduler can swap between implementations chunk-by-chunk
|
|
without seeing a difference.
|
|
|
|
Takes ``layer`` as an explicit argument (not ``self``) so it works
|
|
whether the dispatch comes from ``CachedChunkCausalGDN.forward`` called
|
|
directly or from the camctrl wrapper's
|
|
``CachedChunkCausalGDNUCPESinglePathLiteLA.forward`` which invokes
|
|
``CachedChunkCausalGDN.forward(self, ...)`` against the wrapper
|
|
instance (wrapper instances don't have this helper on themselves).
|
|
|
|
Guards: ``conv_q``/``conv_v`` are unsupported by the fused kernel —
|
|
the streaming production checkpoint uses ``k_conv_only=True`` so this
|
|
is fine in practice, but raise here if anyone tries to load a
|
|
non-k_conv_only configuration through this path.
|
|
"""
|
|
if HW is None:
|
|
raise ValueError("HW (T, H, W) must be provided.")
|
|
if layer.conv_q is not None or layer.conv_v is not None:
|
|
raise NotImplementedError(
|
|
"Triton chunk-causal scan requires k_conv_only=True; " "got conv_q / conv_v not None."
|
|
)
|
|
|
|
kv_cache = kwargs["kv_cache"]
|
|
save_kv_cache = kwargs.get("save_kv_cache", False)
|
|
B, N, C = x.shape
|
|
T, H_sp, W_sp = HW
|
|
S = H_sp * W_sp
|
|
if N != T * S:
|
|
raise ValueError(f"N={N} != T*S={T * S} for HW={HW}")
|
|
H, D = layer.heads, layer.dim
|
|
|
|
# 1. QKV projection -> (B, N, 3, H, D), made contiguous so the fused
|
|
# kernel can stride-iterate over it.
|
|
qkv = layer.qkv(x).reshape(B, N, 3, H, D)
|
|
|
|
# 2. Short conv on K (with cache). Write the post-conv K back into
|
|
# qkv[:, :, 1] so the kernel sees it as the K stream.
|
|
if layer.conv_k is not None:
|
|
k_flat = qkv[:, :, 1].reshape(B, N, C)
|
|
k_flat, new_conv_cache = _cached_temporal_short_conv(
|
|
k_flat, layer.conv_k, HW, kv_cache[_SLOT_SHORTCONV], save_kv_cache
|
|
)
|
|
qkv = qkv.clone() if torch.is_grad_enabled() and qkv.requires_grad else qkv.contiguous()
|
|
qkv[:, :, 1].copy_(k_flat.reshape(B, N, H, D))
|
|
if save_kv_cache:
|
|
kv_cache[_SLOT_SHORTCONV] = new_conv_cache
|
|
else:
|
|
qkv = qkv.contiguous()
|
|
|
|
# 3. Frame gates — Triton accepts (B,H,F) or (B,H,F,S); same as torch.
|
|
precomputed_gates = kwargs.get("precomputed_gates", None)
|
|
if precomputed_gates is not None:
|
|
beta, decay = precomputed_gates
|
|
else:
|
|
beta, decay = layer._compute_frame_gates(x, HW)
|
|
|
|
# 4. Fused Triton fwd-with-state + per-chunk rev scan.
|
|
S_kv_prev = kv_cache[_SLOT_FWD_KV]
|
|
S_z_prev = kv_cache[_SLOT_FWD_Z]
|
|
out_4d, S_kv_new, S_z_new = _gdn_main_triton(
|
|
layer,
|
|
qkv,
|
|
beta,
|
|
decay,
|
|
rotary_emb,
|
|
HW,
|
|
S_kv_prev,
|
|
S_z_prev,
|
|
save_kv_cache,
|
|
)
|
|
|
|
if save_kv_cache:
|
|
kv_cache[_SLOT_FWD_KV] = S_kv_new.detach().clone()
|
|
kv_cache[_SLOT_FWD_Z] = S_z_new.detach().clone()
|
|
kv_cache[_SLOT_TYPE_FLAG] = _TYPE_STATE
|
|
|
|
# 5. Output gate + projection, matching the torch path's tail.
|
|
out = out_4d.reshape(B, N, C)
|
|
if apply_output_gate:
|
|
out = layer._apply_output_gate(out, x)
|
|
out = layer.proj(out.to(layer.proj.weight.dtype))
|
|
return out, kv_cache
|
|
return out, kv_cache
|