Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
757e024dc7 | ||
|
|
d3ff912ac0 |
@@ -39,6 +39,16 @@ export FASTVIDEO_GENERATION_SEGMENT_CAP="${FASTVIDEO_GENERATION_SEGMENT_CAP:-6}"
|
|||||||
export FASTVIDEO_PROMPT_AUTO_SLEEP_MS="${FASTVIDEO_PROMPT_AUTO_SLEEP_MS:-120}"
|
export FASTVIDEO_PROMPT_AUTO_SLEEP_MS="${FASTVIDEO_PROMPT_AUTO_SLEEP_MS:-120}"
|
||||||
export FASTVIDEO_PROMPT_AUTO_TIMEOUT_MS="${FASTVIDEO_PROMPT_AUTO_TIMEOUT_MS:-1800}"
|
export FASTVIDEO_PROMPT_AUTO_TIMEOUT_MS="${FASTVIDEO_PROMPT_AUTO_TIMEOUT_MS:-1800}"
|
||||||
|
|
||||||
|
# Persistent torch.compile cache so warmup pays the full autotune/codegen cost only on the first launch.
|
||||||
|
# Later launches on the same box reload the Inductor FX-graph + AOTAutograd + Triton artifacts from disk.
|
||||||
|
export DREAMVERSE_TORCH_COMPILE_CACHE_ROOT="${DREAMVERSE_TORCH_COMPILE_CACHE_ROOT:-${HOME}/.cache/dreamverse/torch_compile}"
|
||||||
|
export TORCHINDUCTOR_CACHE_DIR="${TORCHINDUCTOR_CACHE_DIR:-${DREAMVERSE_TORCH_COMPILE_CACHE_ROOT}/inductor}"
|
||||||
|
export TRITON_CACHE_DIR="${TRITON_CACHE_DIR:-${DREAMVERSE_TORCH_COMPILE_CACHE_ROOT}/triton}"
|
||||||
|
export TORCHINDUCTOR_FX_GRAPH_CACHE="${TORCHINDUCTOR_FX_GRAPH_CACHE:-1}"
|
||||||
|
export TORCHINDUCTOR_AUTOGRAD_CACHE="${TORCHINDUCTOR_AUTOGRAD_CACHE:-1}"
|
||||||
|
mkdir -p "${TORCHINDUCTOR_CACHE_DIR}" "${TRITON_CACHE_DIR}"
|
||||||
|
echo "[launch-demo] torch.compile cache: ${DREAMVERSE_TORCH_COMPILE_CACHE_ROOT} (first launch builds it, later launches reload it)"
|
||||||
|
|
||||||
cd "${DREAMVERSE_ROOT}"
|
cd "${DREAMVERSE_ROOT}"
|
||||||
|
|
||||||
if ! command -v dreamverse-server >/dev/null 2>&1; then
|
if ! command -v dreamverse-server >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -180,8 +180,11 @@ class FlashAttentionImpl(AttentionImpl):
|
|||||||
# SP). Cast through bf16 and restore, matching TORCH_SDPA's tolerance.
|
# SP). Cast through bf16 and restore, matching TORCH_SDPA's tolerance.
|
||||||
orig_dtype = query.dtype
|
orig_dtype = query.dtype
|
||||||
if orig_dtype not in (torch.float16, torch.bfloat16):
|
if orig_dtype not in (torch.float16, torch.bfloat16):
|
||||||
logger.warning_once(f"FLASH_ATTN received {orig_dtype} inputs; casting to "
|
# Keep logging (and its Python lru_cache) out of compiled traces
|
||||||
f"bfloat16 for the kernel and restoring on output.")
|
# so that the AOTAutograd cache can serialize.
|
||||||
|
if not torch.compiler.is_compiling():
|
||||||
|
logger.warning_once(f"FLASH_ATTN received {orig_dtype} inputs; casting to "
|
||||||
|
f"bfloat16 for the kernel and restoring on output.")
|
||||||
query = query.to(torch.bfloat16)
|
query = query.to(torch.bfloat16)
|
||||||
key = key.to(torch.bfloat16)
|
key = key.to(torch.bfloat16)
|
||||||
value = value.to(torch.bfloat16)
|
value = value.to(torch.bfloat16)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import functools
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
@@ -43,15 +42,17 @@ except ImportError:
|
|||||||
_flash_attn_2_func = None
|
_flash_attn_2_func = None
|
||||||
_flash_attn_2_varlen_func = None
|
_flash_attn_2_varlen_func = None
|
||||||
|
|
||||||
|
_IS_SM90_OR_NEWER = current_platform.has_device_capability(90)
|
||||||
|
|
||||||
|
|
||||||
def _check_dropout(dropout_p: float) -> None:
|
def _check_dropout(dropout_p: float) -> None:
|
||||||
if dropout_p != 0.0:
|
if dropout_p != 0.0:
|
||||||
raise NotImplementedError(f"flash_attn.cute does not support dropout (got dropout_p={dropout_p})")
|
raise NotImplementedError(f"flash_attn.cute does not support dropout (got dropout_p={dropout_p})")
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def _sm90_or_newer() -> bool:
|
def _sm90_or_newer() -> bool:
|
||||||
return current_platform.has_device_capability(90)
|
# Compiled callers only read a boolean instead of tracing through a memoized platform query.
|
||||||
|
return _IS_SM90_OR_NEWER
|
||||||
|
|
||||||
|
|
||||||
def _use_fa2(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> bool:
|
def _use_fa2(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> bool:
|
||||||
|
|||||||
@@ -5,14 +5,11 @@ LTX-2 transformer implementation
|
|||||||
|
|
||||||
from dataclasses import dataclass, replace
|
from dataclasses import dataclass, replace
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import functools
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Tuple, Callable
|
from typing import Any, Optional, Tuple, Callable
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
from einops import rearrange, repeat
|
from einops import rearrange, repeat
|
||||||
@@ -770,28 +767,30 @@ def _apply_ltx_split_rotary_emb(
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=5)
|
def generate_ltx_freq_grid_float64(
|
||||||
def generate_ltx_freq_grid_np(
|
|
||||||
positional_embedding_theta: float, positional_embedding_max_pos_count: int, inner_dim: int
|
positional_embedding_theta: float, positional_embedding_max_pos_count: int, inner_dim: int
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Generate LTX-2 rotary frequencies with high-precision numpy."""
|
"""Generate LTX-2 rotary frequencies with float64 precision.
|
||||||
|
|
||||||
|
Implemented in pure torch and free of Python memoization so it is safe
|
||||||
|
for callers that may be captured and reused by torch.compile.
|
||||||
|
"""
|
||||||
theta = positional_embedding_theta
|
theta = positional_embedding_theta
|
||||||
start = 1
|
start = 1
|
||||||
end = theta
|
end = theta
|
||||||
n_elem = 2 * positional_embedding_max_pos_count
|
n_elem = 2 * positional_embedding_max_pos_count
|
||||||
pow_indices = np.power(
|
pow_indices = torch.pow(
|
||||||
theta,
|
theta,
|
||||||
np.linspace(
|
torch.linspace(
|
||||||
np.log(start) / np.log(theta),
|
math.log(start) / math.log(theta),
|
||||||
np.log(end) / np.log(theta),
|
math.log(end) / math.log(theta),
|
||||||
inner_dim // n_elem,
|
inner_dim // n_elem,
|
||||||
dtype=np.float64,
|
dtype=torch.float64,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return torch.tensor(pow_indices * math.pi / 2, dtype=torch.float32)
|
return (pow_indices * math.pi / 2).to(dtype=torch.float32)
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=5)
|
|
||||||
def generate_ltx_freq_grid_pytorch(
|
def generate_ltx_freq_grid_pytorch(
|
||||||
positional_embedding_theta: float, positional_embedding_max_pos_count: int, inner_dim: int
|
positional_embedding_theta: float, positional_embedding_max_pos_count: int, inner_dim: int
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
@@ -1012,7 +1011,7 @@ class TransformerArgsPreprocessor:
|
|||||||
x_dtype: torch.dtype,
|
x_dtype: torch.dtype,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
if self.double_precision_rope:
|
if self.double_precision_rope:
|
||||||
freq_grid_generator = generate_ltx_freq_grid_np
|
freq_grid_generator = generate_ltx_freq_grid_float64
|
||||||
else:
|
else:
|
||||||
freq_grid_generator = generate_ltx_freq_grid_pytorch
|
freq_grid_generator = generate_ltx_freq_grid_pytorch
|
||||||
return precompute_ltx_freqs_cis(
|
return precompute_ltx_freqs_cis(
|
||||||
@@ -2058,27 +2057,35 @@ class BasicAVTransformerBlock(torch.nn.Module):
|
|||||||
"""STG keep-mask for self-attention.
|
"""STG keep-mask for self-attention.
|
||||||
|
|
||||||
Returns a per-sample multiplier (1 keep, 0 perturb) broadcastable
|
Returns a per-sample multiplier (1 keep, 0 perturb) broadcastable
|
||||||
over ``values``. Only the configured ``stg_block_idx`` is perturbed.
|
over ``values``. Applied unconditionally: the eager caller
|
||||||
|
(``_process_transformer_blocks``) enforces that only the
|
||||||
|
configured ``stg_block_idx`` block receives a truthy flag.
|
||||||
Supports both the LTX-2.0 bool flag and an LTX-2.3 per-sample
|
Supports both the LTX-2.0 bool flag and an LTX-2.3 per-sample
|
||||||
tensor ``[B]`` (1/True == perturb). When skip_flag is False/0 the
|
tensor ``[B]`` (1/True == perturb). When skip_flag is False/0 the
|
||||||
multiplier is all ones, reproducing the un-skipped LTX-2.0 path.
|
multiplier is all ones, reproducing the un-skipped LTX-2.0 path.
|
||||||
"""
|
"""
|
||||||
bsz = values.shape[0]
|
bsz = values.shape[0]
|
||||||
keep = torch.ones((bsz, ), device=values.device, dtype=values.dtype)
|
keep = torch.ones((bsz, ), device=values.device, dtype=values.dtype)
|
||||||
if self.idx == self.stg_block_idx:
|
# The ``idx == stg_block_idx`` gate lives in the eager
|
||||||
if torch.is_tensor(skip_flag):
|
# ``_process_transformer_blocks`` caller, not here: reading the
|
||||||
if skip_flag.ndim == 0:
|
# per-instance ``self.idx`` inside this compiled forward forces
|
||||||
perturb = skip_flag.reshape(1).expand(bsz)
|
# torch.compile to specialize a distinct graph per block (48x),
|
||||||
else:
|
# blowing the Dynamo recompile limit and defeating regional
|
||||||
if skip_flag.shape[0] != bsz:
|
# compilation. The caller only passes a truthy ``skip_flag`` to the
|
||||||
raise ValueError(
|
# configured STG block, so applying it unconditionally here is
|
||||||
"Per-sample STG mask batch size mismatch: "
|
# equivalent while keeping the graph identical across all 48 blocks.
|
||||||
f"got {skip_flag.shape[0]}, expected {bsz}")
|
if torch.is_tensor(skip_flag):
|
||||||
perturb = skip_flag.reshape(bsz)
|
if skip_flag.ndim == 0:
|
||||||
keep = 1.0 - perturb.to(device=values.device,
|
perturb = skip_flag.reshape(1).expand(bsz)
|
||||||
dtype=values.dtype)
|
else:
|
||||||
elif bool(skip_flag):
|
if skip_flag.shape[0] != bsz:
|
||||||
keep.zero_()
|
raise ValueError(
|
||||||
|
"Per-sample STG mask batch size mismatch: "
|
||||||
|
f"got {skip_flag.shape[0]}, expected {bsz}")
|
||||||
|
perturb = skip_flag.reshape(bsz)
|
||||||
|
keep = 1.0 - perturb.to(device=values.device, dtype=values.dtype)
|
||||||
|
elif bool(skip_flag):
|
||||||
|
keep.zero_()
|
||||||
return keep.view(bsz, *([1] * (values.ndim - 1)))
|
return keep.view(bsz, *([1] * (values.ndim - 1)))
|
||||||
|
|
||||||
if run_vx:
|
if run_vx:
|
||||||
@@ -2277,6 +2284,9 @@ class BasicAVTransformerBlock(torch.nn.Module):
|
|||||||
1 + ascale_mlp) + ashift_mlp
|
1 + ascale_mlp) + ashift_mlp
|
||||||
ax = ax + self.audio_ff(ax_scaled) * agate_mlp
|
ax = ax + self.audio_ff(ax_scaled) * agate_mlp
|
||||||
|
|
||||||
|
# Debug-only: reading ``self.idx`` (and .item() syncs) here means
|
||||||
|
# enabling this env var re-specializes the compiled graph per block,
|
||||||
|
# defeating the shared-graph regional compilation.
|
||||||
if os.getenv("LTX2_PIPELINE_DEBUG_LOG", "0") == "1":
|
if os.getenv("LTX2_PIPELINE_DEBUG_LOG", "0") == "1":
|
||||||
video_sum = vx.float().sum().item() if vx is not None else 0.0
|
video_sum = vx.float().sum().item() if vx is not None else 0.0
|
||||||
audio_sum = ax.float().sum().item() if ax is not None else 0.0
|
audio_sum = ax.float().sum().item() if ax is not None else 0.0
|
||||||
@@ -2656,8 +2666,10 @@ class LTXModel(torch.nn.Module):
|
|||||||
skip_audio_self_attn_block_set = set(skip_audio_self_attn_blocks or [])
|
skip_audio_self_attn_block_set = set(skip_audio_self_attn_blocks or [])
|
||||||
|
|
||||||
for idx, block in enumerate(self.transformer_blocks):
|
for idx, block in enumerate(self.transformer_blocks):
|
||||||
skip_v_sa = idx in skip_video_self_attn_block_set
|
# Preserve the original STG block condition in eager code so it does not introduce a block index guard in the compiled forward
|
||||||
skip_a_sa = idx in skip_audio_self_attn_block_set
|
is_stg_block = idx == block.stg_block_idx
|
||||||
|
skip_v_sa = is_stg_block and idx in skip_video_self_attn_block_set
|
||||||
|
skip_a_sa = is_stg_block and idx in skip_audio_self_attn_block_set
|
||||||
video, audio = block(
|
video, audio = block(
|
||||||
video=video,
|
video=video,
|
||||||
audio=audio,
|
audio=audio,
|
||||||
@@ -2768,6 +2780,7 @@ class LTX2Transformer3DModel(BaseDiT):
|
|||||||
reverse_param_names_mapping = LTX2VideoConfig().reverse_param_names_mapping
|
reverse_param_names_mapping = LTX2VideoConfig().reverse_param_names_mapping
|
||||||
lora_param_names_mapping = LTX2VideoConfig().lora_param_names_mapping
|
lora_param_names_mapping = LTX2VideoConfig().lora_param_names_mapping
|
||||||
_fsdp_shard_conditions = LTX2VideoConfig()._fsdp_shard_conditions
|
_fsdp_shard_conditions = LTX2VideoConfig()._fsdp_shard_conditions
|
||||||
|
_compile_conditions = LTX2VideoConfig()._compile_conditions
|
||||||
|
|
||||||
def __init__(self, config: LTX2VideoConfig, hf_config: dict[str, Any]):
|
def __init__(self, config: LTX2VideoConfig, hf_config: dict[str, Any]):
|
||||||
super().__init__(config=config, hf_config=hf_config)
|
super().__init__(config=config, hf_config=hf_config)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from fastvideo.models.dits.ltx2 import (
|
|||||||
FeedForward,
|
FeedForward,
|
||||||
LTXRopeType,
|
LTXRopeType,
|
||||||
apply_ltx_rotary_emb,
|
apply_ltx_rotary_emb,
|
||||||
generate_ltx_freq_grid_np,
|
generate_ltx_freq_grid_float64,
|
||||||
generate_ltx_freq_grid_pytorch,
|
generate_ltx_freq_grid_pytorch,
|
||||||
precompute_ltx_freqs_cis,
|
precompute_ltx_freqs_cis,
|
||||||
)
|
)
|
||||||
@@ -330,7 +330,7 @@ class Embeddings1DConnector(nn.Module):
|
|||||||
)
|
)
|
||||||
indices_grid = indices_grid[None, None, :]
|
indices_grid = indices_grid[None, None, :]
|
||||||
freq_grid_generator = (
|
freq_grid_generator = (
|
||||||
generate_ltx_freq_grid_np
|
generate_ltx_freq_grid_float64
|
||||||
if self.double_precision_rope
|
if self.double_precision_rope
|
||||||
else generate_ltx_freq_grid_pytorch
|
else generate_ltx_freq_grid_pytorch
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
"""Contract test: LTX2's per-block torch.compile wiring stays intact.
|
||||||
|
|
||||||
|
``ComposedPipelineBase._compile_with_conditions`` compiles the submodules
|
||||||
|
matched by ``model._compile_conditions`` and falls back to compiling the
|
||||||
|
whole model as one fullgraph when the list is empty. LTX2 shipped with the
|
||||||
|
conditions declared on its arch config but never exposed on the model
|
||||||
|
class, so it silently took the whole-model fallback (enormous unportable
|
||||||
|
graphs, 10+ minute cold compiles). These assertions pin the class-level
|
||||||
|
wiring and the block matcher so a refactor can't reintroduce the fallback
|
||||||
|
unnoticed. CPU-only; no model instantiation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastvideo.configs.models.dits.ltx2 import LTX2VideoConfig, is_ltx2_blocks
|
||||||
|
|
||||||
|
|
||||||
|
def test_ltx2_model_exposes_compile_conditions():
|
||||||
|
from fastvideo.models.dits.ltx2 import LTX2Transformer3DModel
|
||||||
|
|
||||||
|
conditions = LTX2Transformer3DModel._compile_conditions
|
||||||
|
assert conditions, (
|
||||||
|
"LTX2Transformer3DModel._compile_conditions is empty: "
|
||||||
|
"torch.compile falls back to a whole-model fullgraph")
|
||||||
|
assert is_ltx2_blocks in conditions
|
||||||
|
assert LTX2VideoConfig()._compile_conditions == conditions
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_ltx2_blocks_matches_only_top_level_blocks():
|
||||||
|
assert is_ltx2_blocks("transformer_blocks.0", None)
|
||||||
|
assert is_ltx2_blocks("model.transformer_blocks.47", None)
|
||||||
|
# Submodules and lookalike names must not match — compiling them would
|
||||||
|
# nest compiled regions inside the per-block graphs.
|
||||||
|
assert not is_ltx2_blocks("transformer_blocks", None)
|
||||||
|
assert not is_ltx2_blocks("transformer_blocks.0.attn1", None)
|
||||||
|
assert not is_ltx2_blocks("audio_transformer_blocks.0", None)
|
||||||
Reference in New Issue
Block a user