lidarverse / diffusion /training /utils /diffusion_forcing.py
multimodalart's picture
multimodalart HF Staff
LidarVerse Stage-3 world generation demo
af8bc9f verified
Raw History Blame Contribute Delete
13.8 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Small dependency-free helpers for blockwise Diffusion Forcing."""
import torch
from torch import Tensor
def temporal_chunk_slices(
num_frames: int,
block_size: int,
prefix_frames: int = 0,
) -> list[tuple[int, int]]:
"""Partition time into an optional prefix and complete causal blocks."""
if num_frames <= 0:
raise ValueError(f"num_frames must be positive, got {num_frames}")
if block_size <= 0:
raise ValueError(f"block_size must be positive, got {block_size}")
if prefix_frames < 0 or prefix_frames > num_frames:
raise ValueError(
"prefix_frames must be in [0, num_frames], got "
f"{prefix_frames}/{num_frames}"
)
body_frames = num_frames - prefix_frames
if body_frames % block_size != 0:
raise ValueError(
"temporal body must contain complete causal blocks: "
f"({num_frames}-{prefix_frames}) % {block_size} != 0"
)
chunks = []
if prefix_frames:
chunks.append((0, prefix_frames))
chunks.extend(
(start, start + block_size)
for start in range(prefix_frames, num_frames, block_size)
)
return chunks
def temporal_stream_chunk_bounds(
chunk_index: int,
block_size: int,
prefix_frames: int = 0,
) -> tuple[int, int]:
"""Return one cached-call interval for a prefix-plus-block stream."""
if chunk_index < 0:
raise ValueError("chunk_index must be non-negative")
if block_size <= 0:
raise ValueError("block_size must be positive")
if prefix_frames < 0:
raise ValueError("prefix_frames must be non-negative")
if prefix_frames and chunk_index == 0:
return 0, prefix_frames
body_index = chunk_index - int(prefix_frames > 0)
start = prefix_frames + body_index * block_size
return start, start + block_size
def pad_temporal_stream_tokens(
num_tokens: int,
block_size: int,
prefix_frames: int = 0,
) -> int:
"""Pad a stream to a complete prefix-plus-block boundary."""
if num_tokens < prefix_frames:
raise ValueError(
f"num_tokens={num_tokens} is smaller than prefix={prefix_frames}"
)
if block_size <= 0:
raise ValueError("block_size must be positive")
body_tokens = num_tokens - prefix_frames
padded_body = (
(body_tokens + block_size - 1) // block_size
) * block_size
return prefix_frames + padded_body
def is_temporal_stream_boundary(
num_tokens: int,
block_size: int,
prefix_frames: int = 0,
) -> bool:
"""Whether ``num_tokens`` ends after the prefix or a complete block."""
if block_size <= 0 or prefix_frames < 0 or num_tokens < prefix_frames:
return False
return (num_tokens - prefix_frames) % block_size == 0
def temporal_block_allows(
query_frame: Tensor | int,
key_frame: Tensor | int,
block_size: int,
):
"""Whether a query block may attend to a key block."""
if block_size <= 0:
raise ValueError(f"block_size must be positive, got {block_size}")
return (key_frame // block_size) <= (query_frame // block_size)
def temporal_prefix_block_allows(
query_frame: Tensor | int,
key_frame: Tensor | int,
block_size: int,
prefix_frames: int,
):
"""Prefix-bidirectional, then block-causal temporal attention.
Prefix queries see only the complete prefix. Future queries see the prefix,
their own future block, and earlier future blocks. ``prefix_frames=0`` is
exactly ``temporal_block_allows``.
"""
if block_size <= 0:
raise ValueError(f"block_size must be positive, got {block_size}")
if prefix_frames < 0:
raise ValueError(
f"prefix_frames must be non-negative, got {prefix_frames}"
)
query_is_prefix = query_frame < prefix_frames
key_is_prefix = key_frame < prefix_frames
query_is_future = query_frame >= prefix_frames
key_is_future = key_frame >= prefix_frames
query_block = (query_frame - prefix_frames) // block_size
key_block = (key_frame - prefix_frames) // block_size
future_allows = key_is_prefix | (
key_is_future & (key_block <= query_block)
)
return (query_is_prefix & key_is_prefix) | (
query_is_future & future_allows
)
def teacher_forcing_clean_noisy_allows(
query_frame: Tensor | int,
key_frame: Tensor | int,
clean_frames: int,
block_size: int,
prefix_frames: int,
):
"""Visibility for a packed ``[clean | noisy]`` teacher-forcing pass.
Clean queries use frame-causal inclusive visibility. A noisy query sees
clean chunks strictly before its own chunk and noisy keys from its own
chunk. This matches Imaginaire's frame-causal clean-history pass while
preserving its chunkwise noisy-query layout; text remains cross-attention
in the LiDAR DiT and is therefore not part of this packed mask.
"""
if clean_frames <= 0:
raise ValueError(
f"clean_frames must be positive, got {clean_frames}"
)
if block_size <= 0:
raise ValueError(f"block_size must be positive, got {block_size}")
if prefix_frames < 0 or prefix_frames > clean_frames:
raise ValueError(
"prefix_frames must be in [0, clean_frames], got "
f"{prefix_frames}/{clean_frames}"
)
query_is_clean = query_frame < clean_frames
key_is_clean = key_frame < clean_frames
query_is_noisy = query_frame >= clean_frames
key_is_noisy = key_frame >= clean_frames
query_local = query_frame - query_is_noisy * clean_frames
key_local = key_frame - key_is_noisy * clean_frames
clean_query_allows = (
query_is_clean & key_is_clean & (key_local <= query_local)
)
query_is_prefix = query_local < prefix_frames
key_is_prefix = key_local < prefix_frames
query_is_future = query_local >= prefix_frames
key_is_future = key_local >= prefix_frames
query_block = (query_local - prefix_frames) // block_size
key_block = (key_local - prefix_frames) // block_size
clean_strictly_precedes = query_is_future & (
key_is_prefix
| (key_is_future & (key_block < query_block))
)
same_chunk = (query_is_prefix & key_is_prefix) | (
query_is_future
& key_is_future
& (query_block == key_block)
)
noisy_query_allows = query_is_noisy & (
(key_is_clean & clean_strictly_precedes)
| (key_is_noisy & same_chunk)
)
return clean_query_allows | noisy_query_allows
def expand_blockwise(
block_values_B_K: Tensor,
num_frames: int,
block_size: int,
) -> Tensor:
"""Broadcast ``(B,K)`` values onto ``(B,T)`` consecutive frame blocks."""
if block_values_B_K.ndim != 2:
raise ValueError(
f"block_values must be (B,K), got {tuple(block_values_B_K.shape)}"
)
if num_frames <= 0 or block_size <= 0:
raise ValueError(
f"num_frames and block_size must be positive, got "
f"{num_frames}, {block_size}"
)
num_blocks = (num_frames + block_size - 1) // block_size
if block_values_B_K.shape[1] != num_blocks:
raise ValueError(
f"need K={num_blocks} blocks for T={num_frames}, "
f"block_size={block_size}; got K={block_values_B_K.shape[1]}"
)
block_ids = (
torch.arange(num_frames, device=block_values_B_K.device)
// block_size
)
return block_values_B_K[:, block_ids]
def expand_chunkwise(
chunk_values_B_K: Tensor,
num_frames: int,
block_size: int,
prefix_frames: int = 0,
) -> Tensor:
"""Broadcast values over an optional prefix and complete causal blocks."""
if chunk_values_B_K.ndim != 2:
raise ValueError(
f"chunk values must be (B,K), got {tuple(chunk_values_B_K.shape)}"
)
chunks = temporal_chunk_slices(
num_frames,
block_size,
prefix_frames,
)
if chunk_values_B_K.shape[1] != len(chunks):
raise ValueError(
f"need K={len(chunks)} chunks for T={num_frames}, "
f"prefix={prefix_frames}, block_size={block_size}; got "
f"K={chunk_values_B_K.shape[1]}"
)
return torch.cat(
[
chunk_values_B_K[:, index : index + 1].expand(-1, end - start)
for index, (start, end) in enumerate(chunks)
],
dim=1,
)
def artifixer_flow_schedule(
timestep_ids: Tensor,
*,
num_train_timesteps: int = 1000,
shift: float = 5.0,
) -> tuple[Tensor, Tensor, Tensor]:
"""Map discrete ArtiFixer IDs to timestep, sigma, and training weight."""
if num_train_timesteps <= 0:
raise ValueError(
"num_train_timesteps must be positive, got "
f"{num_train_timesteps}"
)
if shift <= 0.0:
raise ValueError(f"shift must be positive, got {shift}")
if timestep_ids.dtype not in (
torch.int8,
torch.int16,
torch.int32,
torch.int64,
torch.uint8,
):
raise TypeError(
f"timestep_ids must be integer, got {timestep_ids.dtype}"
)
# IDs are sampled in-range on the training hot path. Keep validation for
# CPU callers without introducing a device synchronization every update.
if timestep_ids.device.type == "cpu" and bool(
((timestep_ids < 0) | (timestep_ids >= num_train_timesteps)).any()
):
raise ValueError(
f"timestep_ids must be in [0,{num_train_timesteps})"
)
base_ladder = torch.linspace(
1.0,
0.0,
num_train_timesteps + 1,
device=timestep_ids.device,
dtype=torch.float32,
)[:-1]
sigma_ladder = (
shift
* base_ladder
/ (1.0 + (shift - 1.0) * base_ladder)
)
timestep_ladder = sigma_ladder * num_train_timesteps
bell = torch.exp(
-2.0
* (
(
timestep_ladder - num_train_timesteps / 2.0
)
/ num_train_timesteps
)
** 2
)
bell = bell - bell.min()
weight_ladder = bell * num_train_timesteps / bell.sum()
return (
timestep_ladder[timestep_ids],
sigma_ladder[timestep_ids],
weight_ladder[timestep_ids],
)
def expand_after_prefix(
block_values_B_K: Tensor,
num_frames: int,
block_size: int,
prefix_frames: int,
prefix_value: float = 0.0,
) -> Tensor:
"""Broadcast block values after a fixed prefix.
Returns ``(B,T)`` with ``prefix_value`` in ``[:prefix_frames]`` and
consecutive ``block_size`` groups thereafter.
"""
if not 0 <= prefix_frames < num_frames:
raise ValueError(
f"prefix_frames={prefix_frames} must be in [0,{num_frames})"
)
future_frames = num_frames - prefix_frames
future = expand_blockwise(
block_values_B_K,
num_frames=future_frames,
block_size=block_size,
)
prefix = future.new_full(
(future.shape[0], prefix_frames),
float(prefix_value),
)
return torch.cat([prefix, future], dim=1)
def mean_after_prefix(values_B_T: Tensor, prefix_frames: int) -> Tensor:
"""Per-sample mean over future frames only."""
if values_B_T.ndim != 2:
raise ValueError(
f"values must be (B,T), got {tuple(values_B_T.shape)}"
)
if not 0 <= prefix_frames < values_B_T.shape[1]:
raise ValueError(
f"prefix_frames={prefix_frames} must be in "
f"[0,{values_B_T.shape[1]})"
)
return values_B_T[:, prefix_frames:].mean(dim=1)
def build_clean_context_timesteps(
active_timesteps_B: Tensor,
known_mask_B_T: Tensor,
) -> Tensor:
"""Set committed prefix frames to t=0 and active frames to the sampler t."""
if active_timesteps_B.ndim != 1:
raise ValueError(
f"active timesteps must be (B,), got {tuple(active_timesteps_B.shape)}"
)
if known_mask_B_T.ndim != 2:
raise ValueError(
f"known mask must be (B,T), got {tuple(known_mask_B_T.shape)}"
)
if active_timesteps_B.shape[0] != known_mask_B_T.shape[0]:
raise ValueError(
f"timestep batch {active_timesteps_B.shape[0]} does not match "
f"mask batch {known_mask_B_T.shape[0]}"
)
active_B_T = active_timesteps_B[:, None].expand_as(known_mask_B_T)
return torch.where(
known_mask_B_T.to(torch.bool),
torch.zeros_like(active_B_T),
active_B_T,
)
def validate_prefix_mask(
known_mask_B_T: Tensor,
block_size: int,
) -> tuple[Tensor, Tensor]:
"""Validate block-aligned known-prefix masks and return known/active counts."""
if known_mask_B_T.ndim != 2:
raise ValueError(
f"known mask must be (B,T), got {tuple(known_mask_B_T.shape)}"
)
mask = known_mask_B_T.to(torch.bool)
_, num_frames = mask.shape
n_known = mask.sum(dim=1)
expected = (
torch.arange(num_frames, device=mask.device)[None, :]
< n_known[:, None]
)
if not torch.equal(mask, expected):
raise ValueError(
"known mask must be a contiguous prefix followed by active frames"
)
if bool((n_known % block_size != 0).any()):
raise ValueError(
f"known prefix lengths {n_known.tolist()} must align to "
f"block_size={block_size}"
)
n_active = num_frames - n_known
if bool((n_active <= 0).any()) or bool((n_active > block_size).any()):
raise ValueError(
f"active lengths must be in [1,{block_size}], got "
f"{n_active.tolist()}"
)
return n_known, n_active