[Perf][AuK] Compile the DiT step, use cuDNN attention and warm up at startup (#8300)
Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
This commit is contained in:
1 parent
9097fa6547
commit
146ccc133a
8 files changed
+722
-297
No files matched your search
+20
-10
@@ -12,7 +12,7 @@
|
|||||||
both with `Qwen/Qwen2.5-Omni-3B` as the frozen encoder
|
both with `Qwen/Qwen2.5-Omni-3B` as the frozen encoder
|
||||||
- Task: zero-shot TTS, instruct TTS, content and acoustic editing,
|
- Task: zero-shot TTS, instruct TTS, content and acoustic editing,
|
||||||
paralinguistic editing, speech enhancement, speaker and music separation
|
paralinguistic editing, speech enhancement, speaker and music separation
|
||||||
- Mode: offline inference (online chat serving is not yet qualified)
|
- Mode: offline inference and online `/v1/audio/speech` (online chat serving is not yet qualified)
|
||||||
- Hardware: 1x H100 80GB
|
- Hardware: 1x H100 80GB
|
||||||
- Maintainer: Community
|
- Maintainer: Community
|
||||||
|
|
||||||
@@ -130,10 +130,16 @@ Measured on one H100 against the upstream implementation with the same seeds
|
|||||||
and mean VAE latents (four cookbook cases, base checkpoint): identical
|
and mean VAE latents (four cookbook cases, base checkpoint): identical
|
||||||
transcripts, speaker similarity to the upstream output 0.993 to 0.999, log-mel
|
transcripts, speaker similarity to the upstream output 0.993 to 0.999, log-mel
|
||||||
L1 0.11 to 0.47 (the upstream VAE-resampling noise floor is 0.39). Flash:
|
L1 0.11 to 0.47 (the upstream VAE-resampling noise floor is 0.39). Flash:
|
||||||
0.989 to 0.999 and 0.24 to 0.51. Wall per request on the second call: base
|
0.989 to 0.999 and 0.24 to 0.51.
|
||||||
1.0 to 2.0 s for 3.5 to 10.9 s of audio, Flash 0.17 to 0.30 s; engine start
|
|
||||||
about 47 s with a warm page cache, plus the codec decode compile on the
|
Latency with the DiT and codec CUDA graphs, measured end to end on one
|
||||||
diffusion stage (about a minute on a cold Inductor cache, ~30 s warm).
|
H200-class GPU with both stages on it, concurrency 1, median of 10 warm
|
||||||
|
requests: base (32 steps, CFG 2) zero-shot 0.25 / 0.31 / 0.40 s for 3 / 6 /
|
||||||
|
12 s of audio, instruct TTS 0.17 / 0.23 / 0.32 s; Flash zero-shot 0.08 /
|
||||||
|
0.10 / 0.14 s. The first request after start is 1.2 to 1.6 s, because the
|
||||||
|
common shapes are compiled at startup. Engine start adds the codec decode
|
||||||
|
compile and the DiT warmup to model loading (a few minutes on a cold
|
||||||
|
Inductor cache, about 30 s less warm).
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
@@ -144,11 +150,15 @@ diffusion stage (about a minute on a cold Inductor cache, ~30 s warm).
|
|||||||
- Key flags: `enforce_eager` on the encoder stage (it walks the decoder
|
- Key flags: `enforce_eager` on the encoder stage (it walks the decoder
|
||||||
layers itself for the layer fusion); the diffusion stage runs with
|
layers itself for the layer fusion); the diffusion stage runs with
|
||||||
`enforce_eager: false` so the codec decode is compiled into bucketed CUDA
|
`enforce_eager: false` so the codec decode is compiled into bucketed CUDA
|
||||||
graphs at startup (128/256/512 latent frames, i.e. up to 10.24 s; longer
|
graphs at startup (the deploy config sets 160/320/640 latent frames, i.e.
|
||||||
clips are decoded in overlapping 512-frame tiles of the same graph;
|
up to 12.8 s; longer clips are decoded in overlapping 640-frame tiles of
|
||||||
|
the same graph;
|
||||||
override with `model_config.auk_vae_compile_shapes` and
|
override with `model_config.auk_vae_compile_shapes` and
|
||||||
`auk_vae_tile_frames`). The DiT itself stays eager unless
|
`auk_vae_tile_frames`). Each DiT denoise step replays a per-shape CUDA
|
||||||
`diffusion_compile_granularity: full`. `enable_prefix_caching` must stay off
|
graph of the regionally compiled double- and single-stream blocks; the
|
||||||
|
per-request conditioning (text projection, reference embedding, padding
|
||||||
|
biases, rotary tables) is prepared once per request outside the graph.
|
||||||
|
`enable_prefix_caching` must stay off
|
||||||
for the encoder: a cache hit skips prompt positions that the fused
|
for the encoder: a cache hit skips prompt positions that the fused
|
||||||
condition needs. `enable_chunked_prefill` is off by default: forcing it
|
condition needs. `enable_chunked_prefill` is off by default: forcing it
|
||||||
(128-token chunks, so two to three chunks per prompt) reproduces the
|
(128-token chunks, so two to three chunks per prompt) reproduces the
|
||||||
@@ -169,7 +179,7 @@ diffusion stage (about a minute on a cold Inductor cache, ~30 s warm).
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Offline `Omni.generate` | supported (base and Flash) | `docs/getting_started/quickstart.md` |
|
| Offline `Omni.generate` | supported (base and Flash) | `docs/getting_started/quickstart.md` |
|
||||||
| Online `/v1/chat/completions` with audio | not yet qualified | `docs/serving/` |
|
| Online `/v1/chat/completions` with audio | not yet qualified | `docs/serving/` |
|
||||||
| `/v1/audio/speech` | not supported (needs a TTS adapter) | `docs/contributing/model/adding_tts_model.md` |
|
| `/v1/audio/speech` | supported (base and Flash, `instructions` or `task_type`) | `docs/user_guide/examples/online_serving/text_to_speech.md` |
|
||||||
| Streaming / async chunk | not supported | `docs/design/feature/async_chunk.md` |
|
| Streaming / async chunk | not supported | `docs/design/feature/async_chunk.md` |
|
||||||
| Batching across requests | one request per DiT forward | `docs/user_guide/diffusion/` |
|
| Batching across requests | one request per DiT forward | `docs/user_guide/diffusion/` |
|
||||||
| Tensor / sequence parallelism | not supported | `docs/configuration/composable_parallel.md` |
|
| Tensor / sequence parallelism | not supported | `docs/configuration/composable_parallel.md` |
|
||||||
@@ -29,13 +29,14 @@ def _make_dit(device: str) -> AuKTransformer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _sample_inputs(device: str, offset: float = 0.0) -> dict[str, torch.Tensor]:
|
def _sample_inputs(device: str, offset: float = 0.0, ref_frames: int = 4) -> dict[str, torch.Tensor]:
|
||||||
|
"""``ref_frames=0`` is the text-only (instruct TTS) request shape."""
|
||||||
generator = torch.Generator(device=device).manual_seed(42)
|
generator = torch.Generator(device=device).manual_seed(42)
|
||||||
return {
|
return {
|
||||||
"text": torch.randn(1, 7, 8, generator=generator, device=device) + offset,
|
"text": torch.randn(1, 7, 8, generator=generator, device=device) + offset,
|
||||||
"c_mask": torch.ones(1, 7, dtype=torch.bool, device=device),
|
"c_mask": torch.ones(1, 7, dtype=torch.bool, device=device),
|
||||||
"ref": torch.randn(1, 4, 4, generator=generator, device=device) - offset,
|
"ref": (torch.randn(1, 4, 4, generator=generator, device=device) - offset)[:, :ref_frames],
|
||||||
"ref_mask": torch.ones(1, 4, dtype=torch.bool, device=device),
|
"ref_mask": torch.ones(1, ref_frames, dtype=torch.bool, device=device),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -44,8 +45,8 @@ def _sample_inputs(device: str, offset: float = 0.0) -> dict[str, torch.Tensor]:
|
|||||||
def test_single_request_graph_wrapper_cpu_falls_back_to_eager(cfg_strength: float, mocker) -> None:
|
def test_single_request_graph_wrapper_cpu_falls_back_to_eager(cfg_strength: float, mocker) -> None:
|
||||||
dit = _make_dit("cpu")
|
dit = _make_dit("cpu")
|
||||||
wrapper = AuKCUDAGraphWrapper(dit)
|
wrapper = AuKCUDAGraphWrapper(dit)
|
||||||
run_spy = mocker.spy(wrapper, "_run")
|
prepare_spy = mocker.spy(wrapper, "_prepare")
|
||||||
run_cfg_spy = mocker.spy(wrapper, "_run_cfg")
|
step_spy = mocker.spy(wrapper, "_step")
|
||||||
capture_spy = mocker.spy(wrapper, "_capture")
|
capture_spy = mocker.spy(wrapper, "_capture")
|
||||||
inputs = _sample_inputs("cpu")
|
inputs = _sample_inputs("cpu")
|
||||||
common = dict(
|
common = dict(
|
||||||
@@ -58,13 +59,10 @@ def test_single_request_graph_wrapper_cpu_falls_back_to_eager(cfg_strength: floa
|
|||||||
graph = sample_latents(dit, **common, generator=torch.Generator().manual_seed(7), sampler=wrapper)
|
graph = sample_latents(dit, **common, generator=torch.Generator().manual_seed(7), sampler=wrapper)
|
||||||
torch.testing.assert_close(graph, eager)
|
torch.testing.assert_close(graph, eager)
|
||||||
|
|
||||||
if cfg_strength >= 1e-5:
|
# The conditioning is prepared once per request and reused by every step.
|
||||||
run_spy.assert_not_called()
|
assert prepare_spy.call_count == 1
|
||||||
assert run_cfg_spy.call_count == 2
|
assert prepare_spy.call_args.args[-1] is (cfg_strength >= 1e-5)
|
||||||
else:
|
assert step_spy.call_count == 2
|
||||||
assert run_spy.call_count == 2
|
|
||||||
run_cfg_spy.assert_not_called()
|
|
||||||
|
|
||||||
capture_spy.assert_not_called()
|
capture_spy.assert_not_called()
|
||||||
assert not wrapper._cache
|
assert not wrapper._cache
|
||||||
|
|
||||||
@@ -79,14 +77,14 @@ def test_graph_inputs_use_bounded_length_buckets() -> None:
|
|||||||
|
|
||||||
padded = wrapper._bucket_inputs(x, text, c_mask, ref, ref_mask)
|
padded = wrapper._bucket_inputs(x, text, c_mask, ref, ref_mask)
|
||||||
|
|
||||||
assert padded[0].shape == (1, 128, 4)
|
assert padded[0].shape == (1, 96, 4)
|
||||||
assert padded[1].shape == (1, 128)
|
assert padded[1].shape == (1, 96)
|
||||||
assert padded[2].shape == (1, 128, 8)
|
assert padded[2].shape == (1, 96, 8)
|
||||||
assert padded[3].shape == (1, 128)
|
assert padded[3].shape == (1, 96)
|
||||||
assert padded[4].shape == (1, 100, 4)
|
assert padded[4].shape == (1, 100, 4)
|
||||||
assert padded[5].shape == (1, 100)
|
assert padded[5].shape == (1, 100)
|
||||||
assert [mask.sum().item() for mask in (padded[1], padded[3], padded[5])] == [65, 65, 51]
|
assert [mask.sum().item() for mask in (padded[1], padded[3], padded[5])] == [65, 65, 51]
|
||||||
assert wrapper._key(padded[0], padded[2], padded[4], False) == (128, 128, 100, False)
|
assert wrapper._key(padded[0], padded[2], padded[4], False) == (96, 96, 100, False)
|
||||||
assert wrapper.max_graphs == 32
|
assert wrapper.max_graphs == 32
|
||||||
|
|
||||||
|
|
||||||
@@ -105,43 +103,24 @@ def test_full_graph_cache_retires_as_one_generation() -> None:
|
|||||||
|
|
||||||
|
|
||||||
@torch.inference_mode()
|
@torch.inference_mode()
|
||||||
|
@pytest.mark.parametrize("ref_frames", [0, 4])
|
||||||
@pytest.mark.parametrize("cfg_strength", [0.0, 2.0])
|
@pytest.mark.parametrize("cfg_strength", [0.0, 2.0])
|
||||||
def test_bucket_padding_preserves_real_frame_outputs(cfg_strength: float) -> None:
|
def test_bucket_padding_preserves_real_frame_outputs(cfg_strength: float, ref_frames: int) -> None:
|
||||||
dit = _make_dit("cpu")
|
dit = _make_dit("cpu")
|
||||||
wrapper = AuKCUDAGraphWrapper(dit)
|
wrapper = AuKCUDAGraphWrapper(dit)
|
||||||
inputs = _sample_inputs("cpu")
|
inputs = _sample_inputs("cpu", ref_frames=ref_frames)
|
||||||
x = torch.randn(1, 9, 4)
|
x = torch.randn(1, 9, 4)
|
||||||
timestep = torch.tensor(0.4)
|
timestep = torch.tensor(0.4)
|
||||||
cfg = torch.tensor(cfg_strength)
|
cfg = torch.tensor(cfg_strength)
|
||||||
if cfg_strength >= 1e-5:
|
uses_cfg = cfg_strength >= 1e-5
|
||||||
eager = wrapper._run_cfg(
|
text, c_mask, ref, ref_mask = inputs["text"], inputs["c_mask"], inputs["ref"], inputs["ref_mask"]
|
||||||
x,
|
|
||||||
None,
|
|
||||||
inputs["text"],
|
|
||||||
inputs["c_mask"],
|
|
||||||
inputs["ref"],
|
|
||||||
inputs["ref_mask"],
|
|
||||||
timestep,
|
|
||||||
cfg_strength=cfg,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
eager = wrapper._run(
|
|
||||||
x,
|
|
||||||
None,
|
|
||||||
inputs["text"],
|
|
||||||
inputs["c_mask"],
|
|
||||||
inputs["ref"],
|
|
||||||
inputs["ref_mask"],
|
|
||||||
timestep,
|
|
||||||
)
|
|
||||||
dit.clear_cache()
|
|
||||||
|
|
||||||
bucketed = wrapper._bucket_inputs(x, inputs["text"], inputs["c_mask"], inputs["ref"], inputs["ref_mask"])
|
ctx = wrapper._prepare(x, None, text, c_mask, ref, ref_mask, uses_cfg)
|
||||||
if cfg_strength >= 1e-5:
|
eager = wrapper._step(x, timestep, ctx, cfg)
|
||||||
padded = wrapper._run_cfg(*bucketed, timestep, cfg_strength=cfg)
|
|
||||||
else:
|
bucketed = wrapper._bucket_inputs(x, text, c_mask, ref, ref_mask)
|
||||||
padded = wrapper._run(*bucketed, timestep)
|
padded_ctx = wrapper._prepare(*bucketed, uses_cfg)
|
||||||
dit.clear_cache()
|
padded = wrapper._step(bucketed[0], timestep, padded_ctx, cfg)
|
||||||
|
|
||||||
torch.testing.assert_close(padded[:, : x.shape[1]], eager)
|
torch.testing.assert_close(padded[:, : x.shape[1]], eager)
|
||||||
|
|
||||||
@@ -149,13 +128,14 @@ def test_bucket_padding_preserves_real_frame_outputs(cfg_strength: float) -> Non
|
|||||||
@pytest.mark.cuda
|
@pytest.mark.cuda
|
||||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA graph replay requires CUDA")
|
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA graph replay requires CUDA")
|
||||||
@torch.inference_mode()
|
@torch.inference_mode()
|
||||||
|
@pytest.mark.parametrize("ref_frames", [0, 4])
|
||||||
@pytest.mark.parametrize("cfg_strength", [0.0, 2.0])
|
@pytest.mark.parametrize("cfg_strength", [0.0, 2.0])
|
||||||
def test_single_request_graph_replay_matches_eager_and_updates_inputs(cfg_strength: float) -> None:
|
def test_single_request_graph_replay_matches_eager_and_updates_inputs(cfg_strength: float, ref_frames: int) -> None:
|
||||||
dit = _make_dit("cuda")
|
dit = _make_dit("cuda")
|
||||||
wrapper = AuKCUDAGraphWrapper(dit)
|
wrapper = AuKCUDAGraphWrapper(dit)
|
||||||
|
|
||||||
for offset in (0.0, 0.25):
|
for offset in (0.0, 0.25):
|
||||||
inputs = _sample_inputs("cuda", offset)
|
inputs = _sample_inputs("cuda", offset, ref_frames=ref_frames)
|
||||||
common = dict(
|
common = dict(
|
||||||
**inputs,
|
**inputs,
|
||||||
gen_frames=9,
|
gen_frames=9,
|
||||||
@@ -176,12 +156,14 @@ def test_single_request_graph_replay_matches_eager_and_updates_inputs(cfg_streng
|
|||||||
key = wrapper._key(bucketed[0], bucketed[2], bucketed[4], cfg_strength >= 1e-5)
|
key = wrapper._key(bucketed[0], bucketed[2], bucketed[4], cfg_strength >= 1e-5)
|
||||||
assert key in wrapper._cache
|
assert key in wrapper._cache
|
||||||
|
|
||||||
|
# The static context was refreshed for this request's conditioning.
|
||||||
entry = wrapper._cache[key]
|
entry = wrapper._cache[key]
|
||||||
torch.testing.assert_close(entry.static_x_mask, bucketed[1])
|
fresh = wrapper._prepare(*bucketed, cfg_strength >= 1e-5)
|
||||||
torch.testing.assert_close(entry.static_text, bucketed[2])
|
for static, want in zip(entry.static_ctx.tensors(), fresh.tensors(), strict=True):
|
||||||
torch.testing.assert_close(entry.static_c_mask, bucketed[3])
|
if want is None:
|
||||||
torch.testing.assert_close(entry.static_ref, bucketed[4])
|
assert static is None
|
||||||
torch.testing.assert_close(entry.static_ref_mask, bucketed[5])
|
else:
|
||||||
|
torch.testing.assert_close(static, want)
|
||||||
torch.testing.assert_close(entry.static_timestep, torch.tensor(0.4, device="cuda"))
|
torch.testing.assert_close(entry.static_timestep, torch.tensor(0.4, device="cuda"))
|
||||||
|
|
||||||
assert len(wrapper._cache) == 1
|
assert len(wrapper._cache) == 1
|
||||||
@@ -194,7 +176,7 @@ def test_graph_capture_failure_is_propagated(mocker) -> None:
|
|||||||
dit = _make_dit("cuda")
|
dit = _make_dit("cuda")
|
||||||
wrapper = AuKCUDAGraphWrapper(dit)
|
wrapper = AuKCUDAGraphWrapper(dit)
|
||||||
capture = mocker.patch.object(wrapper, "_capture", side_effect=RuntimeError("capture failed"))
|
capture = mocker.patch.object(wrapper, "_capture", side_effect=RuntimeError("capture failed"))
|
||||||
eager = mocker.spy(wrapper, "_run")
|
eager = mocker.spy(wrapper, "_step")
|
||||||
inputs = _sample_inputs("cuda")
|
inputs = _sample_inputs("cuda")
|
||||||
common = dict(
|
common = dict(
|
||||||
**inputs,
|
**inputs,
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||||
|
"""The AuK DiT's per-request prepare() / per-step step() split.
|
||||||
|
|
||||||
|
The Euler loop prepares the conditioning once and then only steps; these
|
||||||
|
tests pin that the split, the additive key-padding bias and the shared CFG
|
||||||
|
target embedding reproduce the one-shot forward exactly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
from vllm_omni.diffusion.models.auk.auk_transformer import (
|
||||||
|
AuKTransformer,
|
||||||
|
_key_padding_bias,
|
||||||
|
_sdpa,
|
||||||
|
build_time_grid,
|
||||||
|
sample_latents,
|
||||||
|
)
|
||||||
|
|
||||||
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
|
|
||||||
|
def _make_dit(attn_mask_enabled: bool = True) -> AuKTransformer:
|
||||||
|
torch.manual_seed(3)
|
||||||
|
return AuKTransformer(
|
||||||
|
dim=32,
|
||||||
|
heads=2,
|
||||||
|
dim_head=16,
|
||||||
|
ff_mult=2,
|
||||||
|
latent_dim=4,
|
||||||
|
text_hidden_dim=8,
|
||||||
|
num_layers=2,
|
||||||
|
num_single_layers=2,
|
||||||
|
attn_mask_enabled=attn_mask_enabled,
|
||||||
|
).eval()
|
||||||
|
|
||||||
|
|
||||||
|
def _inputs(ref_frames: int, pad: bool) -> dict[str, torch.Tensor | None]:
|
||||||
|
generator = torch.Generator().manual_seed(5)
|
||||||
|
text = torch.randn(1, 7, 8, generator=generator)
|
||||||
|
c_mask = torch.ones(1, 7, dtype=torch.bool)
|
||||||
|
mask = None
|
||||||
|
ref = torch.randn(1, ref_frames, 4, generator=generator)
|
||||||
|
ref_mask = torch.ones(1, ref_frames, dtype=torch.bool)
|
||||||
|
if pad:
|
||||||
|
# Padded tails in every stream, as the CUDA graph buckets produce.
|
||||||
|
c_mask[:, 5:] = False
|
||||||
|
text[:, 5:] = 0.0
|
||||||
|
mask = torch.ones(1, 9, dtype=torch.bool)
|
||||||
|
mask[:, 7:] = False
|
||||||
|
if ref_frames:
|
||||||
|
ref_mask[:, ref_frames - 1 :] = False
|
||||||
|
return {"text": text, "c_mask": c_mask, "mask": mask, "ref": ref, "ref_mask": ref_mask}
|
||||||
|
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def test_key_padding_bias_matches_boolean_mask() -> None:
|
||||||
|
generator = torch.Generator().manual_seed(0)
|
||||||
|
q, k, v = (torch.randn(2, 3, 6, 8, generator=generator) for _ in range(3))
|
||||||
|
keep = torch.tensor([[True] * 6, [True, True, True, True, False, False]])
|
||||||
|
expanded = keep[:, None, None, :].expand(2, 3, 6, 6)
|
||||||
|
want = F.scaled_dot_product_attention(q, k, v, attn_mask=expanded)
|
||||||
|
torch.testing.assert_close(_sdpa(q, k, v, _key_padding_bias(keep, q.dtype)), want)
|
||||||
|
assert _key_padding_bias(None, q.dtype) is None
|
||||||
|
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
@pytest.mark.parametrize("attn_mask_enabled", [True, False])
|
||||||
|
@pytest.mark.parametrize("cfg_infer", [False, True])
|
||||||
|
@pytest.mark.parametrize("ref_frames", [0, 4])
|
||||||
|
@pytest.mark.parametrize("pad", [False, True])
|
||||||
|
def test_prepare_then_step_matches_forward(attn_mask_enabled: bool, cfg_infer: bool, ref_frames: int, pad: bool):
|
||||||
|
dit = _make_dit(attn_mask_enabled)
|
||||||
|
inputs = _inputs(ref_frames, pad)
|
||||||
|
x = torch.randn(1, 9, 4, generator=torch.Generator().manual_seed(9))
|
||||||
|
time = torch.tensor(0.3)
|
||||||
|
|
||||||
|
want = dit(x, time=time, cfg_infer=cfg_infer, **inputs)
|
||||||
|
ctx = dit.prepare(inputs.pop("text"), target_len=x.shape[1], cfg_infer=cfg_infer, **inputs)
|
||||||
|
got = dit.step(x, time, ctx)
|
||||||
|
|
||||||
|
torch.testing.assert_close(got, want, rtol=0, atol=0)
|
||||||
|
assert got.shape == (2 if cfg_infer else 1, 9, 4)
|
||||||
|
assert ctx.branches == (2 if cfg_infer else 1)
|
||||||
|
if not attn_mask_enabled:
|
||||||
|
assert ctx.joint_bias is None and ctx.single_bias is None
|
||||||
|
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def test_cfg_branches_match_separate_cond_and_uncond_forwards() -> None:
|
||||||
|
"""The shared target embedding keeps each CFG branch equal to its own forward."""
|
||||||
|
dit = _make_dit()
|
||||||
|
inputs = _inputs(4, pad=True)
|
||||||
|
x = torch.randn(1, 9, 4, generator=torch.Generator().manual_seed(9))
|
||||||
|
time = torch.tensor(0.6)
|
||||||
|
|
||||||
|
both = dit(x, time=time, cfg_infer=True, **inputs)
|
||||||
|
cond = dit(x, time=time, **inputs)
|
||||||
|
uncond = dit(x, time=time, drop_audio_cond=True, drop_text=True, **inputs)
|
||||||
|
|
||||||
|
torch.testing.assert_close(both[:1], cond)
|
||||||
|
torch.testing.assert_close(both[1:], uncond)
|
||||||
|
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
@pytest.mark.parametrize("cfg_strength", [0.0, 2.0])
|
||||||
|
def test_hoisted_euler_loop_matches_per_step_forward(cfg_strength: float) -> None:
|
||||||
|
"""sample_latents prepares once; stepping the full forward every time agrees."""
|
||||||
|
dit = _make_dit()
|
||||||
|
inputs = _inputs(4, pad=False)
|
||||||
|
inputs.pop("mask")
|
||||||
|
grid = [0.0, 0.3, 0.7, 1.0]
|
||||||
|
|
||||||
|
got = sample_latents(
|
||||||
|
dit, **inputs, gen_frames=9, t_grid=grid, cfg_strength=cfg_strength, generator=torch.Generator().manual_seed(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
x = torch.randn(9, 4, generator=torch.Generator().manual_seed(1)).unsqueeze(0)
|
||||||
|
timesteps = build_time_grid(nfe=len(grid) - 1, sway_sampling_coef=None, t_grid=grid, device="cpu")
|
||||||
|
for i in range(len(grid) - 1):
|
||||||
|
if cfg_strength >= 1e-5:
|
||||||
|
cond, uncond = dit(x, time=timesteps[i], cfg_infer=True, **inputs).chunk(2, dim=0)
|
||||||
|
v = cond + (cond - uncond) * cfg_strength
|
||||||
|
else:
|
||||||
|
v = dit(x, time=timesteps[i], **inputs)
|
||||||
|
x = x + (timesteps[i + 1] - timesteps[i]) * v
|
||||||
|
|
||||||
|
torch.testing.assert_close(got, x, rtol=0, atol=0)
|
||||||
|
assert dit.text_cond is None and dit.text_uncond is None
|
||||||
|
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
def test_context_copy_rejects_structural_mismatch() -> None:
|
||||||
|
dit = _make_dit()
|
||||||
|
inputs = _inputs(4, pad=True)
|
||||||
|
text = inputs.pop("text")
|
||||||
|
cfg = dit.prepare(text, target_len=9, cfg_infer=True, **inputs)
|
||||||
|
plain = dit.prepare(text, target_len=9, **inputs)
|
||||||
|
with pytest.raises(ValueError, match="branches"):
|
||||||
|
cfg.copy_(plain)
|
||||||
|
|
||||||
|
clone = cfg.clone()
|
||||||
|
other = dit.prepare(text * 2.0, target_len=9, cfg_infer=True, **inputs)
|
||||||
|
clone.copy_(other)
|
||||||
|
torch.testing.assert_close(clone.c, other.c)
|
||||||
|
assert clone.c.data_ptr() != other.c.data_ptr()
|
||||||
@@ -424,24 +424,56 @@ class TestRequestParsing:
|
|||||||
def test_setup_compile_warms_the_decode_buckets_on_the_pipeline_device(self, build_pipeline, mocker):
|
def test_setup_compile_warms_the_decode_buckets_on_the_pipeline_device(self, build_pipeline, mocker):
|
||||||
pipeline, _ = build_pipeline()
|
pipeline, _ = build_pipeline()
|
||||||
warmup = mocker.patch.object(pipeline.vae_decode, "warmup")
|
warmup = mocker.patch.object(pipeline.vae_decode, "warmup")
|
||||||
compile_dit = mocker.patch.object(pipeline.dit, "compile")
|
regional = mocker.patch.object(pipeline_auk, "regionally_compile")
|
||||||
|
mocker.patch.object(pipeline, "_warmup_dit")
|
||||||
|
|
||||||
pipeline.setup_compile()
|
pipeline.setup_compile()
|
||||||
|
|
||||||
warmup.assert_called_once_with(pipeline.device)
|
warmup.assert_called_once_with(pipeline.device)
|
||||||
# Regional is the default and a no-op for the DiT, which has no repeated blocks.
|
# Regional is the default: the DiT's repeated blocks are compiled.
|
||||||
compile_dit.assert_not_called()
|
regional.assert_called_once_with(pipeline.dit, dynamic=pipeline.od_config.diffusion_compile_dynamic)
|
||||||
|
|
||||||
def test_setup_compile_honours_full_dit_granularity(self, build_pipeline, mocker):
|
def test_setup_compile_honours_full_dit_granularity(self, build_pipeline, mocker):
|
||||||
pipeline, _ = build_pipeline()
|
pipeline, _ = build_pipeline()
|
||||||
pipeline.od_config.diffusion_compile_granularity = "full"
|
pipeline.od_config.diffusion_compile_granularity = "full"
|
||||||
pipeline.od_config.diffusion_compile_dynamic = False
|
pipeline.od_config.diffusion_compile_dynamic = False
|
||||||
mocker.patch.object(pipeline.vae_decode, "warmup")
|
mocker.patch.object(pipeline.vae_decode, "warmup")
|
||||||
compile_dit = mocker.patch.object(pipeline.dit, "compile")
|
mocker.patch.object(pipeline, "_warmup_dit")
|
||||||
|
step = mocker.Mock()
|
||||||
|
pipeline.dit.step = step
|
||||||
|
compile_fn = mocker.patch.object(pipeline_auk.torch, "compile", side_effect=lambda fn, **_: fn)
|
||||||
|
|
||||||
pipeline.setup_compile()
|
pipeline.setup_compile()
|
||||||
|
|
||||||
compile_dit.assert_called_once_with(dynamic=False)
|
# The samplers call step() directly, so that is what gets compiled.
|
||||||
|
compile_fn.assert_called_once_with(step, dynamic=False)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("variant, cfg", [("base", 2.0), ("flash", 0.0)])
|
||||||
|
def test_setup_compile_warms_the_dit_graph_buckets(self, build_pipeline, mocker, variant, cfg):
|
||||||
|
pipeline, _ = build_pipeline(variant)
|
||||||
|
mocker.patch.object(pipeline.vae_decode, "warmup")
|
||||||
|
mocker.patch.object(pipeline_auk, "regionally_compile")
|
||||||
|
wrapper = mocker.Mock(enabled=True)
|
||||||
|
pipeline.cudagraph_wrapper = wrapper
|
||||||
|
|
||||||
|
pipeline.setup_compile()
|
||||||
|
|
||||||
|
shapes = [(call.kwargs["x"].shape[1], call.kwargs["ref"].shape[1]) for call in wrapper.call_args_list]
|
||||||
|
assert shapes == [(150, 0), (300, 0), (600, 0), (150, 150), (300, 150), (600, 150)]
|
||||||
|
for call in wrapper.call_args_list:
|
||||||
|
assert call.kwargs["cfg_strength"] == cfg
|
||||||
|
assert call.kwargs["new_request"] is True
|
||||||
|
assert call.kwargs["text"].shape == (1, 96, TEXT_HIDDEN_DIM)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("model_config, enabled", [({"auk_dit_warmup_frames": []}, True), ({}, False)])
|
||||||
|
def test_dit_warmup_can_be_skipped(self, build_pipeline, mocker, model_config, enabled):
|
||||||
|
pipeline, _ = build_pipeline(model_config=model_config)
|
||||||
|
wrapper = mocker.Mock(enabled=enabled)
|
||||||
|
pipeline.cudagraph_wrapper = wrapper
|
||||||
|
|
||||||
|
pipeline._warmup_dit()
|
||||||
|
|
||||||
|
wrapper.assert_not_called()
|
||||||
|
|
||||||
def test_latent_output_type_skips_the_decoder(self, build_pipeline):
|
def test_latent_output_type_skips_the_decoder(self, build_pipeline):
|
||||||
pipeline, _ = build_pipeline()
|
pipeline, _ = build_pipeline()
|
||||||
|
|||||||
@@ -43,16 +43,22 @@ stages:
|
|||||||
# Not eager: the codec decode is compiled into bucketed CUDA graphs at
|
# Not eager: the codec decode is compiled into bucketed CUDA graphs at
|
||||||
# startup (about a minute more on a cold Inductor cache, ~30 s warm, and
|
# startup (about a minute more on a cold Inductor cache, ~30 s warm, and
|
||||||
# about 1 GB of graph pools); clips longer than the largest bucket are
|
# about 1 GB of graph pools); clips longer than the largest bucket are
|
||||||
# decoded in overlapping tiles of it. Each DiT denoise step replays a lazily
|
# decoded in overlapping tiles of it. Each DiT denoise step replays a
|
||||||
# captured per-shape CUDA graph. Buckets, tile size and the
|
# per-shape CUDA graph of the regionally compiled blocks; the common 3/6/12 s
|
||||||
|
# shapes are compiled and captured at startup (auk_dit_warmup_frames), other
|
||||||
|
# shapes on first use. Buckets, tile size and the
|
||||||
# plain-graph cache are tunable through model_config:
|
# plain-graph cache are tunable through model_config:
|
||||||
# model_config: {auk_vae_compile_shapes: [128, 256, 512], auk_vae_tile_frames: 512, auk_vae_max_graphs: 32}
|
# model_config: {auk_vae_compile_shapes: [...], auk_vae_tile_frames: <frames>, auk_vae_max_graphs: 32}
|
||||||
- stage_id: 1
|
- stage_id: 1
|
||||||
devices: "0"
|
devices: "0"
|
||||||
max_num_seqs: 1
|
max_num_seqs: 1
|
||||||
gpu_memory_utilization: 0.35
|
gpu_memory_utilization: 0.35
|
||||||
enforce_eager: false
|
enforce_eager: false
|
||||||
model_config:
|
model_config:
|
||||||
|
# Codec decode buckets in latent frames (3.2 / 6.4 / 12.8 s). Tighter
|
||||||
|
# than the 128/256/512 defaults for the common 3-12 s requests: a
|
||||||
|
# 3 s clip decodes in the 160 bucket instead of padding to 256.
|
||||||
|
auk_vae_compile_shapes: [160, 320, 640]
|
||||||
# Maximum number of cached DiT graphs. When the cache reaches this limit,
|
# Maximum number of cached DiT graphs. When the cache reaches this limit,
|
||||||
# capturing a new shape retires all existing graphs before capture.
|
# capturing a new shape retires all existing graphs before capture.
|
||||||
max_dit_graphs: 32
|
max_dit_graphs: 32
|
||||||
|
|||||||
@@ -29,25 +29,50 @@ Deliberate differences from the reference, all inference-only:
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
from collections.abc import Callable, Iterable, Mapping
|
from collections.abc import Callable, Iterable, Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
from torch import nn
|
from torch import nn
|
||||||
|
from torch.nn.attention import SDPBackend, sdpa_kernel
|
||||||
|
|
||||||
__all__ = ["AuKTransformer", "build_time_grid", "dit_state_dict", "sample_latents"]
|
__all__ = ["AuKStepContext", "AuKTransformer", "build_time_grid", "dit_state_dict", "sample_latents"]
|
||||||
|
|
||||||
|
|
||||||
def _sdpa(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, mask: torch.Tensor | None) -> torch.Tensor:
|
def _sdpa(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, bias: torch.Tensor | None) -> torch.Tensor:
|
||||||
"""The single attention call site, so the backend can be swapped in one place.
|
"""The single attention call site, so the backend can be swapped in one place.
|
||||||
|
|
||||||
``q``/``k``/``v`` are ``[B, H, N, D]``. ``mask`` is a boolean key-padding
|
``q``/``k``/``v`` are ``[B, H, N, D]``. ``bias`` is an additive key-padding
|
||||||
mask ``[B, K]`` in which ``True`` marks a valid key; it is broadcast to
|
bias ``[B, 1, 1, K]`` (see :func:`_key_padding_bias`) or ``None``. Passing
|
||||||
``[B, H, Q, K]`` only when given.
|
it unexpanded lets SDPA broadcast it inside a fused kernel (cuDNN on
|
||||||
|
Hopper) instead of materializing a ``[B, H, Q, K]`` mask per layer.
|
||||||
"""
|
"""
|
||||||
attn_mask = None
|
if bias is None or not q.is_cuda:
|
||||||
if mask is not None:
|
return F.scaled_dot_product_attention(q, k, v, attn_mask=bias, dropout_p=0.0, is_causal=False)
|
||||||
attn_mask = mask[:, None, None, :].expand(q.shape[0], q.shape[1], q.shape[-2], k.shape[-2])
|
# With a bias, SDPA's default choice here is the memory-efficient kernel,
|
||||||
return F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
|
# about 3x slower than cuDNN's at these lengths on Hopper.
|
||||||
|
with sdpa_kernel(_MASKED_SDPA_PRIORITY, set_priority=True):
|
||||||
|
return F.scaled_dot_product_attention(q, k, v, attn_mask=bias, dropout_p=0.0, is_causal=False)
|
||||||
|
|
||||||
|
|
||||||
|
_MASKED_SDPA_PRIORITY = [
|
||||||
|
SDPBackend.CUDNN_ATTENTION,
|
||||||
|
SDPBackend.FLASH_ATTENTION,
|
||||||
|
SDPBackend.EFFICIENT_ATTENTION,
|
||||||
|
SDPBackend.MATH,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _key_padding_bias(mask: torch.Tensor | None, dtype: torch.dtype) -> torch.Tensor | None:
|
||||||
|
"""Additive attention bias ``[B, 1, 1, K]`` from a boolean key mask ``[B, K]``.
|
||||||
|
|
||||||
|
Valid keys add 0 and padded keys add ``-inf``, which is exactly what SDPA
|
||||||
|
does with a boolean mask internally.
|
||||||
|
"""
|
||||||
|
if mask is None:
|
||||||
|
return None
|
||||||
|
bias = torch.zeros(mask.shape, dtype=dtype, device=mask.device).masked_fill(~mask, float("-inf"))
|
||||||
|
return bias[:, None, None, :]
|
||||||
|
|
||||||
|
|
||||||
def _rotate_half(x: torch.Tensor) -> torch.Tensor:
|
def _rotate_half(x: torch.Tensor) -> torch.Tensor:
|
||||||
@@ -57,14 +82,26 @@ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
|
|||||||
return torch.stack((-x2, x1), dim=-1).flatten(-2)
|
return torch.stack((-x2, x1), dim=-1).flatten(-2)
|
||||||
|
|
||||||
|
|
||||||
def _apply_rope(x: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
|
def _apply_rope_cos_sin(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
|
||||||
"""Apply rotary frequencies ``[1, 1, N, D]`` to ``x`` ``[B, H, N, D]`` in fp32."""
|
"""Rotate ``x`` ``[B, H, N, D]`` in fp32 with precomputed ``cos``/``sin`` of the frequencies."""
|
||||||
out_dtype = x.dtype
|
out_dtype = x.dtype
|
||||||
x32 = x.float()
|
x32 = x.float()
|
||||||
out = x32 * freqs.cos() + _rotate_half(x32) * freqs.sin()
|
out = x32 * cos + _rotate_half(x32) * sin
|
||||||
return out.to(out_dtype)
|
return out.to(out_dtype)
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_rope(x: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
|
||||||
|
"""Apply rotary frequencies ``[1, 1, N, D]`` to ``x`` ``[B, H, N, D]`` in fp32."""
|
||||||
|
return _apply_rope_cos_sin(x, freqs.cos(), freqs.sin())
|
||||||
|
|
||||||
|
|
||||||
|
RopeCache = tuple[torch.Tensor, torch.Tensor]
|
||||||
|
|
||||||
|
|
||||||
|
def _rope_cos_sin(freqs: torch.Tensor) -> RopeCache:
|
||||||
|
return freqs.cos(), freqs.sin()
|
||||||
|
|
||||||
|
|
||||||
class Rotary(nn.Module):
|
class Rotary(nn.Module):
|
||||||
"""Rotary position frequencies for one stream, evaluated in fp32.
|
"""Rotary position frequencies for one stream, evaluated in fp32.
|
||||||
|
|
||||||
@@ -230,32 +267,50 @@ class Attention(nn.Module):
|
|||||||
packed: torch.Tensor,
|
packed: torch.Tensor,
|
||||||
q_norm: nn.Module,
|
q_norm: nn.Module,
|
||||||
k_norm: nn.Module,
|
k_norm: nn.Module,
|
||||||
rope: torch.Tensor | None,
|
rope: RopeCache | None,
|
||||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||||
"""Split a packed QKV projection into heads, RMSNorm Q/K, then rotate."""
|
"""Split a packed QKV projection into heads, RMSNorm Q/K, then rotate.
|
||||||
|
|
||||||
|
``rope`` is the ``(cos, sin)`` pair of the rotary frequencies.
|
||||||
|
"""
|
||||||
q, k, v = (t.view(t.shape[0], -1, self.heads, self.dim_head).transpose(1, 2) for t in packed.chunk(3, dim=-1))
|
q, k, v = (t.view(t.shape[0], -1, self.heads, self.dim_head).transpose(1, 2) for t in packed.chunk(3, dim=-1))
|
||||||
q = q_norm(q)
|
q = q_norm(q)
|
||||||
k = k_norm(k)
|
k = k_norm(k)
|
||||||
if rope is not None:
|
if rope is not None:
|
||||||
q = _apply_rope(q, rope)
|
q = _apply_rope_cos_sin(q, *rope)
|
||||||
k = _apply_rope(k, rope)
|
k = _apply_rope_cos_sin(k, *rope)
|
||||||
return q, k, v
|
return q, k, v
|
||||||
|
|
||||||
def _project(self, x: torch.Tensor, rope: torch.Tensor | None) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
def _project(self, x: torch.Tensor, rope: RopeCache | None) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||||
return self._qkv(self.to_qkv(x), self.q_norm, self.k_norm, rope)
|
return self._qkv(self.to_qkv(x), self.q_norm, self.k_norm, rope)
|
||||||
|
|
||||||
def forward(
|
def forward(
|
||||||
self, x: torch.Tensor, mask: torch.Tensor | None = None, rope: torch.Tensor | None = None
|
self,
|
||||||
|
x: torch.Tensor,
|
||||||
|
mask: torch.Tensor | None = None,
|
||||||
|
rope: RopeCache | None = None,
|
||||||
|
bias: torch.Tensor | None = None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
|
"""``bias`` is the precomputed key-padding bias for ``mask``; built here when absent."""
|
||||||
q, k, v = self._project(x, rope)
|
q, k, v = self._project(x, rope)
|
||||||
attn_mask = mask if self.attn_mask_enabled else None
|
if bias is None and self.attn_mask_enabled:
|
||||||
out = self._merge_heads(_sdpa(q, k, v, attn_mask).to(q.dtype))
|
bias = _key_padding_bias(mask, q.dtype)
|
||||||
|
out = self._merge_heads(_sdpa(q, k, v, bias).to(q.dtype))
|
||||||
out = self.to_out[0](out)
|
out = self.to_out[0](out)
|
||||||
if mask is not None:
|
if mask is not None:
|
||||||
out = out.masked_fill(~mask.unsqueeze(-1), 0.0)
|
out = out.masked_fill(~mask.unsqueeze(-1), 0.0)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def joint_key_mask(mask: torch.Tensor | None, c_mask: torch.Tensor | None, text_len: int) -> torch.Tensor | None:
|
||||||
|
"""Key mask of joint attention over ``[audio | text]``; ``None`` when the audio has no mask."""
|
||||||
|
if mask is None:
|
||||||
|
return None
|
||||||
|
if c_mask is not None:
|
||||||
|
return torch.cat([mask, c_mask], dim=1)
|
||||||
|
return F.pad(mask, (0, text_len), value=True)
|
||||||
|
|
||||||
|
|
||||||
class JointAttention(Attention):
|
class JointAttention(Attention):
|
||||||
"""Attention over the audio and text streams jointly, with separate projections."""
|
"""Attention over the audio and text streams jointly, with separate projections."""
|
||||||
|
|
||||||
@@ -267,7 +322,7 @@ class JointAttention(Attention):
|
|||||||
self.c_k_norm = nn.RMSNorm(dim_head, elementwise_affine=True)
|
self.c_k_norm = nn.RMSNorm(dim_head, elementwise_affine=True)
|
||||||
self.to_out_c = nn.Linear(inner_dim, context_dim)
|
self.to_out_c = nn.Linear(inner_dim, context_dim)
|
||||||
|
|
||||||
def _project_context(self, c: torch.Tensor, c_rope: torch.Tensor | None) -> tuple[torch.Tensor, ...]:
|
def _project_context(self, c: torch.Tensor, c_rope: RopeCache | None) -> tuple[torch.Tensor, ...]:
|
||||||
return self._qkv(self.to_qkv_c(c), self.c_q_norm, self.c_k_norm, c_rope)
|
return self._qkv(self.to_qkv_c(c), self.c_q_norm, self.c_k_norm, c_rope)
|
||||||
|
|
||||||
def forward( # type: ignore[override] # joint attention takes the context stream too
|
def forward( # type: ignore[override] # joint attention takes the context stream too
|
||||||
@@ -275,10 +330,12 @@ class JointAttention(Attention):
|
|||||||
x: torch.Tensor,
|
x: torch.Tensor,
|
||||||
c: torch.Tensor,
|
c: torch.Tensor,
|
||||||
mask: torch.Tensor | None = None,
|
mask: torch.Tensor | None = None,
|
||||||
rope: torch.Tensor | None = None,
|
rope: RopeCache | None = None,
|
||||||
c_rope: torch.Tensor | None = None,
|
c_rope: RopeCache | None = None,
|
||||||
c_mask: torch.Tensor | None = None,
|
c_mask: torch.Tensor | None = None,
|
||||||
|
bias: torch.Tensor | None = None,
|
||||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
|
"""``bias`` is the precomputed :func:`joint_key_mask` bias; built here when absent."""
|
||||||
audio_len = x.shape[1]
|
audio_len = x.shape[1]
|
||||||
q, k, v = self._project(x, rope)
|
q, k, v = self._project(x, rope)
|
||||||
c_q, c_k, c_v = self._project_context(c, c_rope)
|
c_q, c_k, c_v = self._project_context(c, c_rope)
|
||||||
@@ -287,14 +344,10 @@ class JointAttention(Attention):
|
|||||||
k = torch.cat([k, c_k], dim=2)
|
k = torch.cat([k, c_k], dim=2)
|
||||||
v = torch.cat([v, c_v], dim=2)
|
v = torch.cat([v, c_v], dim=2)
|
||||||
|
|
||||||
joint_mask = None
|
if bias is None and self.attn_mask_enabled:
|
||||||
if self.attn_mask_enabled and mask is not None:
|
bias = _key_padding_bias(joint_key_mask(mask, c_mask, c.shape[1]), q.dtype)
|
||||||
if c_mask is not None:
|
|
||||||
joint_mask = torch.cat([mask, c_mask], dim=1)
|
|
||||||
else:
|
|
||||||
joint_mask = F.pad(mask, (0, c.shape[1]), value=True)
|
|
||||||
|
|
||||||
out = self._merge_heads(_sdpa(q, k, v, joint_mask).to(q.dtype))
|
out = self._merge_heads(_sdpa(q, k, v, bias).to(q.dtype))
|
||||||
x_out = self.to_out[0](out[:, :audio_len])
|
x_out = self.to_out[0](out[:, :audio_len])
|
||||||
c_out = self.to_out_c(out[:, audio_len:])
|
c_out = self.to_out_c(out[:, audio_len:])
|
||||||
|
|
||||||
@@ -326,14 +379,15 @@ class DoubleBlock(nn.Module):
|
|||||||
c: torch.Tensor,
|
c: torch.Tensor,
|
||||||
t: torch.Tensor,
|
t: torch.Tensor,
|
||||||
mask: torch.Tensor | None,
|
mask: torch.Tensor | None,
|
||||||
rope: torch.Tensor,
|
rope: RopeCache,
|
||||||
c_rope: torch.Tensor,
|
c_rope: RopeCache,
|
||||||
c_mask: torch.Tensor | None,
|
c_mask: torch.Tensor | None,
|
||||||
|
bias: torch.Tensor | None = None,
|
||||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, t)
|
norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, t)
|
||||||
norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, t)
|
norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, t)
|
||||||
|
|
||||||
x_attn, c_attn = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope, c_mask=c_mask)
|
x_attn, c_attn = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope, c_mask=c_mask, bias=bias)
|
||||||
|
|
||||||
c = c + c_gate_msa[:, None] * c_attn
|
c = c + c_gate_msa[:, None] * c_attn
|
||||||
norm_c = self.ff_norm_c(c) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
|
norm_c = self.ff_norm_c(c) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
|
||||||
@@ -355,9 +409,16 @@ class SingleBlock(nn.Module):
|
|||||||
self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
||||||
self.ff = FeedForward(dim=dim, mult=ff_mult)
|
self.ff = FeedForward(dim=dim, mult=ff_mult)
|
||||||
|
|
||||||
def forward(self, x: torch.Tensor, t: torch.Tensor, mask: torch.Tensor | None, rope: torch.Tensor) -> torch.Tensor:
|
def forward(
|
||||||
|
self,
|
||||||
|
x: torch.Tensor,
|
||||||
|
t: torch.Tensor,
|
||||||
|
mask: torch.Tensor | None,
|
||||||
|
rope: RopeCache,
|
||||||
|
bias: torch.Tensor | None = None,
|
||||||
|
) -> torch.Tensor:
|
||||||
norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, t)
|
norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, t)
|
||||||
x = x + gate_msa[:, None] * self.attn(x=norm, mask=mask, rope=rope)
|
x = x + gate_msa[:, None] * self.attn(x=norm, mask=mask, rope=rope, bias=bias)
|
||||||
norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
||||||
return x + gate_mlp[:, None] * self.ff(norm)
|
return x + gate_mlp[:, None] * self.ff(norm)
|
||||||
|
|
||||||
@@ -365,6 +426,9 @@ class SingleBlock(nn.Module):
|
|||||||
class AuKTransformer(nn.Module):
|
class AuKTransformer(nn.Module):
|
||||||
"""Flow-matching velocity predictor for AuK audio latents.
|
"""Flow-matching velocity predictor for AuK audio latents.
|
||||||
|
|
||||||
|
The forward pass is :meth:`prepare` (per request) followed by
|
||||||
|
:meth:`step` (per timestep); an Euler loop calls the two separately.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dim: Model width.
|
dim: Model width.
|
||||||
heads: Attention heads.
|
heads: Attention heads.
|
||||||
@@ -379,6 +443,9 @@ class AuKTransformer(nn.Module):
|
|||||||
which is what the reference does with the flag off.
|
which is what the reference does with the flag off.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Regional torch.compile targets (see vllm_omni.diffusion.compile).
|
||||||
|
_repeated_blocks = ["DoubleBlock", "SingleBlock"]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -395,6 +462,7 @@ class AuKTransformer(nn.Module):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.dim = dim
|
self.dim = dim
|
||||||
self.latent_dim = latent_dim
|
self.latent_dim = latent_dim
|
||||||
|
self.attn_mask_enabled = attn_mask_enabled
|
||||||
self.text_cond: torch.Tensor | None = None
|
self.text_cond: torch.Tensor | None = None
|
||||||
self.text_uncond: torch.Tensor | None = None
|
self.text_uncond: torch.Tensor | None = None
|
||||||
|
|
||||||
@@ -433,40 +501,134 @@ class AuKTransformer(nn.Module):
|
|||||||
self.text_cond = None
|
self.text_cond = None
|
||||||
self.text_uncond = None
|
self.text_uncond = None
|
||||||
|
|
||||||
def _embed_audio(
|
def _embed_prompt(self, ref: torch.Tensor, ref_mask: torch.Tensor | None, drop: bool) -> torch.Tensor:
|
||||||
self,
|
"""Embed the reference prompt, zeroed for the audio-dropped CFG branch."""
|
||||||
x: torch.Tensor,
|
if drop:
|
||||||
ref: torch.Tensor | None,
|
ref = torch.zeros_like(ref)
|
||||||
drop_audio_cond: bool,
|
return self.audio_embed(ref, mask=ref_mask)
|
||||||
mask: torch.Tensor | None,
|
|
||||||
ref_mask: torch.Tensor | None,
|
|
||||||
) -> tuple[torch.Tensor, torch.Tensor | None, int]:
|
|
||||||
"""Embed the target latents, prepending the reference prompt when present.
|
|
||||||
|
|
||||||
Returns the ``[ref | target]`` sequence, its padding mask and the
|
def prepare(
|
||||||
reference length, which the caller uses to slice the target back out.
|
self,
|
||||||
|
text: torch.Tensor,
|
||||||
|
*,
|
||||||
|
target_len: int,
|
||||||
|
mask: torch.Tensor | None = None,
|
||||||
|
c_mask: torch.Tensor | None = None,
|
||||||
|
ref: torch.Tensor | None = None,
|
||||||
|
ref_mask: torch.Tensor | None = None,
|
||||||
|
drop_audio_cond: bool = False,
|
||||||
|
drop_text: bool = False,
|
||||||
|
cfg_infer: bool = False,
|
||||||
|
cache: bool = False,
|
||||||
|
) -> "AuKStepContext":
|
||||||
|
"""Everything a denoise step needs that does not depend on ``x`` or the time.
|
||||||
|
|
||||||
|
An Euler loop over fixed conditioning calls this once and then
|
||||||
|
:meth:`step` per timestep, so the text projection, the reference
|
||||||
|
prompt embedding, the padding masks and biases and the rotary tables
|
||||||
|
are not recomputed on every step. Arguments are those of
|
||||||
|
:meth:`forward`; ``target_len`` is the target frame count ``n``.
|
||||||
"""
|
"""
|
||||||
|
batch = text.shape[0]
|
||||||
|
if c_mask is None:
|
||||||
|
# Padding rows are all-zero in the text encoder's output.
|
||||||
|
c_mask = text.abs().sum(-1) > 0
|
||||||
if ref is not None and ref.shape[1] == 0:
|
if ref is not None and ref.shape[1] == 0:
|
||||||
ref = None
|
ref = None
|
||||||
|
|
||||||
target = self.audio_embed(x, mask=mask)
|
if cfg_infer:
|
||||||
if ref is None:
|
if cache and self.text_cond is not None:
|
||||||
return target, mask, 0
|
c_cond, c_uncond = self.text_cond, self.text_uncond
|
||||||
|
else:
|
||||||
|
c_cond = self.project_text(text)
|
||||||
|
# The uncond branch drops the text, which is exactly a zeroed projection.
|
||||||
|
c_uncond = torch.zeros_like(c_cond)
|
||||||
|
if cache:
|
||||||
|
self.text_cond, self.text_uncond = c_cond, c_uncond
|
||||||
|
c = torch.cat((c_cond, c_uncond), dim=0)
|
||||||
|
prompt = None
|
||||||
|
if ref is not None:
|
||||||
|
prompt = torch.cat(
|
||||||
|
(self._embed_prompt(ref, ref_mask, False), self._embed_prompt(ref, ref_mask, True)), dim=0
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
c = self.project_text(text)
|
||||||
|
if drop_text:
|
||||||
|
c = torch.zeros_like(c)
|
||||||
|
prompt = None if ref is None else self._embed_prompt(ref, ref_mask, drop_audio_cond)
|
||||||
|
|
||||||
if drop_audio_cond:
|
audio_mask = mask
|
||||||
ref = torch.zeros_like(ref)
|
if ref is not None and (mask is not None or ref_mask is not None):
|
||||||
prompt = self.audio_embed(ref, mask=ref_mask)
|
|
||||||
audio = torch.cat([prompt, target], dim=1)
|
|
||||||
prompt_len = prompt.shape[1]
|
|
||||||
|
|
||||||
audio_mask = None
|
|
||||||
if mask is not None or ref_mask is not None:
|
|
||||||
if mask is None:
|
if mask is None:
|
||||||
mask = x.new_ones(x.shape[:2], dtype=torch.bool)
|
mask = torch.ones((batch, target_len), dtype=torch.bool, device=ref.device)
|
||||||
if ref_mask is None:
|
if ref_mask is None:
|
||||||
ref_mask = ref.new_ones(ref.shape[:2], dtype=torch.bool)
|
ref_mask = torch.ones(ref.shape[:2], dtype=torch.bool, device=ref.device)
|
||||||
audio_mask = torch.cat([ref_mask, mask], dim=1)
|
audio_mask = torch.cat([ref_mask, mask], dim=1)
|
||||||
return audio, audio_mask, prompt_len
|
|
||||||
|
branches = 2 if cfg_infer else 1
|
||||||
|
if branches == 2:
|
||||||
|
c_mask = torch.cat((c_mask, c_mask), dim=0)
|
||||||
|
if audio_mask is not None:
|
||||||
|
audio_mask = torch.cat((audio_mask, audio_mask), dim=0)
|
||||||
|
|
||||||
|
prompt_len = 0 if prompt is None else prompt.shape[1]
|
||||||
|
text_len = c.shape[1]
|
||||||
|
audio_len = prompt_len + target_len
|
||||||
|
single_mask = None if audio_mask is None else torch.cat([c_mask, audio_mask], dim=1)
|
||||||
|
joint_bias = single_bias = None
|
||||||
|
if self.attn_mask_enabled:
|
||||||
|
joint_bias = _key_padding_bias(joint_key_mask(audio_mask, c_mask, text_len), c.dtype)
|
||||||
|
single_bias = _key_padding_bias(single_mask, c.dtype)
|
||||||
|
|
||||||
|
return AuKStepContext(
|
||||||
|
c=c,
|
||||||
|
prompt=prompt,
|
||||||
|
target_mask=mask,
|
||||||
|
c_mask=c_mask,
|
||||||
|
audio_mask=audio_mask,
|
||||||
|
single_mask=single_mask,
|
||||||
|
joint_bias=joint_bias,
|
||||||
|
single_bias=single_bias,
|
||||||
|
rope_audio=_rope_cos_sin(self.rotary_embed(audio_len, audio_mask)),
|
||||||
|
rope_text=_rope_cos_sin(self.rotary_embed(text_len, c_mask)),
|
||||||
|
rope_single=_rope_cos_sin(self.rotary_embed(text_len + audio_len, single_mask)),
|
||||||
|
branches=branches,
|
||||||
|
)
|
||||||
|
|
||||||
|
def step(self, x: torch.Tensor, time: torch.Tensor, ctx: "AuKStepContext") -> torch.Tensor:
|
||||||
|
"""Predict the velocity of ``x`` at ``time`` under a :meth:`prepare` context.
|
||||||
|
|
||||||
|
The target embedding does not depend on the CFG branch, so it is
|
||||||
|
computed once and shared by both branches.
|
||||||
|
"""
|
||||||
|
if time.ndim == 0:
|
||||||
|
time = time.repeat(x.shape[0])
|
||||||
|
t = self.time_embed(time)
|
||||||
|
target = self.audio_embed(x, mask=ctx.target_mask)
|
||||||
|
if ctx.branches == 2:
|
||||||
|
t = torch.cat((t, t), dim=0)
|
||||||
|
target = torch.cat((target, target), dim=0)
|
||||||
|
audio = target if ctx.prompt is None else torch.cat([ctx.prompt, target], dim=1)
|
||||||
|
c = ctx.c
|
||||||
|
|
||||||
|
for block in self.transformer_blocks:
|
||||||
|
c, audio = block(
|
||||||
|
audio,
|
||||||
|
c,
|
||||||
|
t,
|
||||||
|
mask=ctx.audio_mask,
|
||||||
|
rope=ctx.rope_audio,
|
||||||
|
c_rope=ctx.rope_text,
|
||||||
|
c_mask=ctx.c_mask,
|
||||||
|
bias=ctx.joint_bias,
|
||||||
|
)
|
||||||
|
|
||||||
|
h = torch.cat([c, audio], dim=1)
|
||||||
|
for block in self.single_transformer_blocks:
|
||||||
|
h = block(h, t, mask=ctx.single_mask, rope=ctx.rope_single, bias=ctx.single_bias)
|
||||||
|
|
||||||
|
h = h[:, h.shape[1] - x.shape[1] :]
|
||||||
|
return self.proj_out(self.norm_out(h, t))
|
||||||
|
|
||||||
def forward(
|
def forward(
|
||||||
self,
|
self,
|
||||||
@@ -508,57 +670,89 @@ class AuKTransformer(nn.Module):
|
|||||||
Velocity ``[B, n, latent_dim]``, or ``[2B, n, latent_dim]`` under
|
Velocity ``[B, n, latent_dim]``, or ``[2B, n, latent_dim]`` under
|
||||||
``cfg_infer``.
|
``cfg_infer``.
|
||||||
"""
|
"""
|
||||||
batch = x.shape[0]
|
ctx = self.prepare(
|
||||||
if time.ndim == 0:
|
text,
|
||||||
time = time.repeat(batch)
|
target_len=x.shape[1],
|
||||||
t = self.time_embed(time)
|
mask=mask,
|
||||||
|
c_mask=c_mask,
|
||||||
|
ref=ref,
|
||||||
|
ref_mask=ref_mask,
|
||||||
|
drop_audio_cond=drop_audio_cond,
|
||||||
|
drop_text=drop_text,
|
||||||
|
cfg_infer=cfg_infer,
|
||||||
|
cache=cache and cfg_infer,
|
||||||
|
)
|
||||||
|
return self.step(x, time, ctx)
|
||||||
|
|
||||||
if c_mask is None:
|
|
||||||
# Padding rows are all-zero in the text encoder's output.
|
|
||||||
c_mask = text.abs().sum(-1) > 0
|
|
||||||
|
|
||||||
if cfg_infer:
|
@dataclass
|
||||||
if cache and self.text_cond is not None:
|
class AuKStepContext:
|
||||||
c_cond, c_uncond = self.text_cond, self.text_uncond
|
"""Per-request state of the denoise step, built by :meth:`AuKTransformer.prepare`.
|
||||||
else:
|
|
||||||
c_cond = self.project_text(text)
|
|
||||||
# The uncond branch drops the text, which is exactly a zeroed projection.
|
|
||||||
c_uncond = torch.zeros_like(c_cond)
|
|
||||||
if cache:
|
|
||||||
self.text_cond, self.text_uncond = c_cond, c_uncond
|
|
||||||
x_cond, mask_cond, prompt_len = self._embed_audio(x, ref, False, mask, ref_mask)
|
|
||||||
x_uncond, mask_uncond, _ = self._embed_audio(x, ref, True, mask, ref_mask)
|
|
||||||
|
|
||||||
x = torch.cat((x_cond, x_uncond), dim=0)
|
Batch rows are ``branches`` copies of the request (cond first under CFG).
|
||||||
c = torch.cat((c_cond, c_uncond), dim=0)
|
``target_mask`` covers the target frames only and has the request's batch
|
||||||
t = torch.cat((t, t), dim=0)
|
size; the other masks cover their full sequences and every branch.
|
||||||
c_mask = torch.cat((c_mask, c_mask), dim=0)
|
"""
|
||||||
audio_mask = None
|
|
||||||
if mask_cond is not None and mask_uncond is not None:
|
|
||||||
audio_mask = torch.cat((mask_cond, mask_uncond), dim=0)
|
|
||||||
else:
|
|
||||||
c = self.project_text(text)
|
|
||||||
if drop_text:
|
|
||||||
c = torch.zeros_like(c)
|
|
||||||
x, audio_mask, prompt_len = self._embed_audio(x, ref, drop_audio_cond, mask, ref_mask)
|
|
||||||
|
|
||||||
seq_len = x.shape[1]
|
c: torch.Tensor
|
||||||
text_len = c.shape[1]
|
prompt: torch.Tensor | None
|
||||||
rope_audio = self.rotary_embed(seq_len, audio_mask)
|
target_mask: torch.Tensor | None
|
||||||
rope_text = self.rotary_embed(text_len, c_mask)
|
c_mask: torch.Tensor | None
|
||||||
|
audio_mask: torch.Tensor | None
|
||||||
|
single_mask: torch.Tensor | None
|
||||||
|
joint_bias: torch.Tensor | None
|
||||||
|
single_bias: torch.Tensor | None
|
||||||
|
rope_audio: RopeCache
|
||||||
|
rope_text: RopeCache
|
||||||
|
rope_single: RopeCache
|
||||||
|
branches: int
|
||||||
|
|
||||||
for block in self.transformer_blocks:
|
def tensors(self) -> list[torch.Tensor | None]:
|
||||||
c, x = block(x, c, t, mask=audio_mask, rope=rope_audio, c_rope=rope_text, c_mask=c_mask)
|
"""Every tensor field in a fixed order, rope pairs flattened."""
|
||||||
|
return [
|
||||||
|
self.c,
|
||||||
|
self.prompt,
|
||||||
|
self.target_mask,
|
||||||
|
self.c_mask,
|
||||||
|
self.audio_mask,
|
||||||
|
self.single_mask,
|
||||||
|
self.joint_bias,
|
||||||
|
self.single_bias,
|
||||||
|
*self.rope_audio,
|
||||||
|
*self.rope_text,
|
||||||
|
*self.rope_single,
|
||||||
|
]
|
||||||
|
|
||||||
x = torch.cat([c, x], dim=1)
|
def copy_(self, other: "AuKStepContext") -> None:
|
||||||
single_mask = None if audio_mask is None else torch.cat([c_mask, audio_mask], dim=1)
|
"""Overwrite these tensors in place with ``other``'s, for CUDA graph static inputs."""
|
||||||
rope = self.rotary_embed(text_len + seq_len, single_mask)
|
if other.branches != self.branches:
|
||||||
|
raise ValueError(f"AuK step context mismatch: {other.branches} vs {self.branches} branches")
|
||||||
|
for dst, src in zip(self.tensors(), other.tensors(), strict=True):
|
||||||
|
if (dst is None) != (src is None):
|
||||||
|
raise ValueError("AuK step context mismatch: a mask is present in only one context")
|
||||||
|
if dst is not None:
|
||||||
|
dst.copy_(src)
|
||||||
|
|
||||||
for block in self.single_transformer_blocks:
|
def clone(self) -> "AuKStepContext":
|
||||||
x = block(x, t, mask=single_mask, rope=rope)
|
"""A copy whose tensors own fresh storage."""
|
||||||
|
|
||||||
x = x[:, text_len + prompt_len :]
|
def _clone(value: torch.Tensor | None) -> torch.Tensor | None:
|
||||||
return self.proj_out(self.norm_out(x, t))
|
return None if value is None else value.clone()
|
||||||
|
|
||||||
|
return AuKStepContext(
|
||||||
|
c=self.c.clone(),
|
||||||
|
prompt=_clone(self.prompt),
|
||||||
|
target_mask=_clone(self.target_mask),
|
||||||
|
c_mask=_clone(self.c_mask),
|
||||||
|
audio_mask=_clone(self.audio_mask),
|
||||||
|
single_mask=_clone(self.single_mask),
|
||||||
|
joint_bias=_clone(self.joint_bias),
|
||||||
|
single_bias=_clone(self.single_bias),
|
||||||
|
rope_audio=(self.rope_audio[0].clone(), self.rope_audio[1].clone()),
|
||||||
|
rope_text=(self.rope_text[0].clone(), self.rope_text[1].clone()),
|
||||||
|
rope_single=(self.rope_single[0].clone(), self.rope_single[1].clone()),
|
||||||
|
branches=self.branches,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def dit_state_dict(
|
def dit_state_dict(
|
||||||
@@ -686,6 +880,7 @@ def sample_latents(
|
|||||||
ref_mask=ref_mask,
|
ref_mask=ref_mask,
|
||||||
timestep=t[i],
|
timestep=t[i],
|
||||||
cfg_strength=cfg_strength,
|
cfg_strength=cfg_strength,
|
||||||
|
new_request=i == 0,
|
||||||
)
|
)
|
||||||
x = x + (t[i + 1] - t[i]) * velocity
|
x = x + (t[i + 1] - t[i]) * velocity
|
||||||
finally:
|
finally:
|
||||||
@@ -718,22 +913,21 @@ def _sample_latents(
|
|||||||
x = initial_latents
|
x = initial_latents
|
||||||
guided = cfg_strength >= 1e-5
|
guided = cfg_strength >= 1e-5
|
||||||
try:
|
try:
|
||||||
|
# The conditioning is fixed across the loop: prepare it once.
|
||||||
|
ctx = dit.prepare(
|
||||||
|
text,
|
||||||
|
target_len=x.shape[1],
|
||||||
|
c_mask=c_mask,
|
||||||
|
ref=ref,
|
||||||
|
ref_mask=ref_mask,
|
||||||
|
cfg_infer=guided,
|
||||||
|
cache=guided,
|
||||||
|
)
|
||||||
for i in range(timesteps.shape[0] - 1):
|
for i in range(timesteps.shape[0] - 1):
|
||||||
|
v = dit.step(x, timesteps[i], ctx)
|
||||||
if guided:
|
if guided:
|
||||||
pred = dit(
|
v_cond, v_uncond = v.chunk(2, dim=0)
|
||||||
x,
|
|
||||||
text,
|
|
||||||
timesteps[i],
|
|
||||||
c_mask=c_mask,
|
|
||||||
ref=ref,
|
|
||||||
ref_mask=ref_mask,
|
|
||||||
cfg_infer=True,
|
|
||||||
cache=True,
|
|
||||||
)
|
|
||||||
v_cond, v_uncond = pred.chunk(2, dim=0)
|
|
||||||
v = v_cond + (v_cond - v_uncond) * cfg_strength
|
v = v_cond + (v_cond - v_uncond) * cfg_strength
|
||||||
else:
|
|
||||||
v = dit(x, text, timesteps[i], c_mask=c_mask, ref=ref, ref_mask=ref_mask)
|
|
||||||
x = x + (timesteps[i + 1] - timesteps[i]) * v
|
x = x + (timesteps[i + 1] - timesteps[i]) * v
|
||||||
finally:
|
finally:
|
||||||
# The cached text projections belong to this request only; a failed
|
# The cached text projections belong to this request only; a failed
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import torch.nn.functional as F
|
|||||||
from vllm.logger import init_logger
|
from vllm.logger import init_logger
|
||||||
from vllm.utils.math_utils import round_up
|
from vllm.utils.math_utils import round_up
|
||||||
|
|
||||||
from vllm_omni.diffusion.models.auk.auk_transformer import AuKTransformer
|
from vllm_omni.diffusion.models.auk.auk_transformer import AuKStepContext, AuKTransformer
|
||||||
|
|
||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
@@ -21,11 +21,7 @@ logger = init_logger(__name__)
|
|||||||
class _GraphEntry:
|
class _GraphEntry:
|
||||||
graph: torch.cuda.CUDAGraph
|
graph: torch.cuda.CUDAGraph
|
||||||
static_x: torch.Tensor
|
static_x: torch.Tensor
|
||||||
static_x_mask: torch.Tensor
|
static_ctx: AuKStepContext
|
||||||
static_text: torch.Tensor
|
|
||||||
static_c_mask: torch.Tensor
|
|
||||||
static_ref: torch.Tensor
|
|
||||||
static_ref_mask: torch.Tensor
|
|
||||||
static_timestep: torch.Tensor
|
static_timestep: torch.Tensor
|
||||||
static_cfg: torch.Tensor | None
|
static_cfg: torch.Tensor | None
|
||||||
static_out: torch.Tensor
|
static_out: torch.Tensor
|
||||||
@@ -38,10 +34,15 @@ class AuKCUDAGraphWrapper:
|
|||||||
whether the CFG branch is enabled. Timestep and the CFG strength are
|
whether the CFG branch is enabled. Timestep and the CFG strength are
|
||||||
mutable scalar buffers, so all Euler steps and CFG values within one path
|
mutable scalar buffers, so all Euler steps and CFG values within one path
|
||||||
reuse one graph.
|
reuse one graph.
|
||||||
|
|
||||||
|
Only :meth:`AuKTransformer.step` is captured. The per-request context
|
||||||
|
(text projection, reference embedding, padding biases, rotary tables) is
|
||||||
|
built by :meth:`AuKTransformer.prepare` on the first step of a request and
|
||||||
|
copied into the graph's static context, so the replayed steps skip it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_TARGET_ALIGNMENT = 64
|
_TARGET_ALIGNMENT = 32
|
||||||
_TEXT_ALIGNMENT = 64
|
_TEXT_ALIGNMENT = 32
|
||||||
_REF_ALIGNMENT = 50
|
_REF_ALIGNMENT = 50
|
||||||
|
|
||||||
def __init__(self, dit: AuKTransformer, *, enabled: bool = True, max_graphs: int = 32) -> None:
|
def __init__(self, dit: AuKTransformer, *, enabled: bool = True, max_graphs: int = 32) -> None:
|
||||||
@@ -50,6 +51,10 @@ class AuKCUDAGraphWrapper:
|
|||||||
self.max_graphs = max(1, int(max_graphs))
|
self.max_graphs = max(1, int(max_graphs))
|
||||||
self._cache: OrderedDict[tuple, _GraphEntry] = OrderedDict()
|
self._cache: OrderedDict[tuple, _GraphEntry] = OrderedDict()
|
||||||
self._pool_handle: int | None = None
|
self._pool_handle: int | None = None
|
||||||
|
# Key whose static context holds the current request, and the context
|
||||||
|
# the eager fallback reuses across the steps of one request.
|
||||||
|
self._active_key: tuple | None = None
|
||||||
|
self._eager_ctx: AuKStepContext | None = None
|
||||||
logger.info("Initialized AuK DiT lazy CUDA graph cache: enabled=%s slots=%d", self.enabled, self.max_graphs)
|
logger.info("Initialized AuK DiT lazy CUDA graph cache: enabled=%s slots=%d", self.enabled, self.max_graphs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -87,6 +92,41 @@ class AuKCUDAGraphWrapper:
|
|||||||
"""Retire all graphs together so none outlive shared workspaces."""
|
"""Retire all graphs together so none outlive shared workspaces."""
|
||||||
if len(self._cache) >= self.max_graphs:
|
if len(self._cache) >= self.max_graphs:
|
||||||
self._cache.clear()
|
self._cache.clear()
|
||||||
|
self._active_key = None
|
||||||
|
|
||||||
|
def _prepare(
|
||||||
|
self,
|
||||||
|
x: torch.Tensor,
|
||||||
|
x_mask: torch.Tensor | None,
|
||||||
|
text: torch.Tensor,
|
||||||
|
c_mask: torch.Tensor,
|
||||||
|
ref: torch.Tensor,
|
||||||
|
ref_mask: torch.Tensor,
|
||||||
|
uses_cfg: bool,
|
||||||
|
) -> AuKStepContext:
|
||||||
|
return self.dit.prepare(
|
||||||
|
text,
|
||||||
|
target_len=x.shape[1],
|
||||||
|
mask=x_mask,
|
||||||
|
c_mask=c_mask,
|
||||||
|
ref=ref,
|
||||||
|
ref_mask=ref_mask,
|
||||||
|
cfg_infer=uses_cfg,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _step(
|
||||||
|
self,
|
||||||
|
x: torch.Tensor,
|
||||||
|
timestep: torch.Tensor,
|
||||||
|
ctx: AuKStepContext,
|
||||||
|
cfg_strength: torch.Tensor | float,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""One guided (or unguided) velocity; the CFG combine is part of the graph."""
|
||||||
|
velocity = self.dit.step(x, timestep, ctx)
|
||||||
|
if ctx.branches == 1:
|
||||||
|
return velocity
|
||||||
|
conditional, unconditional = velocity.chunk(2, dim=0)
|
||||||
|
return conditional + (conditional - unconditional) * cfg_strength
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def __call__(
|
def __call__(
|
||||||
@@ -99,25 +139,37 @@ class AuKCUDAGraphWrapper:
|
|||||||
ref_mask: torch.Tensor,
|
ref_mask: torch.Tensor,
|
||||||
timestep: torch.Tensor,
|
timestep: torch.Tensor,
|
||||||
cfg_strength: float,
|
cfg_strength: float,
|
||||||
|
new_request: bool = True,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
|
"""Velocity of ``x`` at ``timestep``.
|
||||||
|
|
||||||
|
``new_request`` marks the first step of a request, whose conditioning
|
||||||
|
(``text``, ``c_mask``, ``ref``, ``ref_mask``) is then prepared anew; the
|
||||||
|
later steps of the request pass ``False`` and reuse it. The default
|
||||||
|
prepares on every call, which is always correct.
|
||||||
|
"""
|
||||||
uses_cfg = cfg_strength >= 1e-5
|
uses_cfg = cfg_strength >= 1e-5
|
||||||
if not self.enabled or x.device.type != "cuda" or torch.cuda.is_current_stream_capturing():
|
if not self.enabled or x.device.type != "cuda" or torch.cuda.is_current_stream_capturing():
|
||||||
if uses_cfg:
|
if new_request or self._eager_ctx is None:
|
||||||
return self._run_cfg(x, None, text, c_mask, ref, ref_mask, timestep, cfg_strength=cfg_strength)
|
self._eager_ctx = self._prepare(x, None, text, c_mask, ref, ref_mask, uses_cfg)
|
||||||
return self._run(x, None, text, c_mask, ref, ref_mask, timestep)
|
return self._step(x, timestep, self._eager_ctx, cfg_strength)
|
||||||
|
|
||||||
target_frames = x.shape[1]
|
target_frames = x.shape[1]
|
||||||
x, x_mask, text, c_mask, ref, ref_mask = self._bucket_inputs(x, text, c_mask, ref, ref_mask)
|
x, x_mask, text, c_mask, ref, ref_mask = self._bucket_inputs(x, text, c_mask, ref, ref_mask)
|
||||||
inputs = (x, x_mask, text, c_mask, ref, ref_mask, timestep)
|
|
||||||
key = self._key(x, text, ref, uses_cfg)
|
key = self._key(x, text, ref, uses_cfg)
|
||||||
entry = self._cache.get(key)
|
entry = self._cache.get(key)
|
||||||
|
ctx = None
|
||||||
|
if entry is None or new_request or key != self._active_key:
|
||||||
|
ctx = self._prepare(x, x_mask, text, c_mask, ref, ref_mask, uses_cfg)
|
||||||
if entry is None:
|
if entry is None:
|
||||||
# Same Retirement as #6587, details discussed in #7469
|
# Same Retirement as #6587, details discussed in #7469
|
||||||
self._retire_graph_generation_if_full()
|
self._retire_graph_generation_if_full()
|
||||||
try:
|
try:
|
||||||
entry = self._capture(*inputs, cfg_strength=cfg_strength, uses_cfg=uses_cfg)
|
assert ctx is not None
|
||||||
|
entry = self._capture(x, ctx, timestep, cfg_strength=cfg_strength)
|
||||||
except Exception:
|
except Exception:
|
||||||
self._cache.clear()
|
self._cache.clear()
|
||||||
|
self._active_key = None
|
||||||
self.enabled = False
|
self.enabled = False
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Disabling AuK DiT CUDA graphs after capture failure for key=%s; "
|
"Disabling AuK DiT CUDA graphs after capture failure for key=%s; "
|
||||||
@@ -128,112 +180,55 @@ class AuKCUDAGraphWrapper:
|
|||||||
self._cache[key] = entry
|
self._cache[key] = entry
|
||||||
else:
|
else:
|
||||||
self._cache.move_to_end(key)
|
self._cache.move_to_end(key)
|
||||||
|
if ctx is not None:
|
||||||
|
entry.static_ctx.copy_(ctx)
|
||||||
|
self._active_key = key
|
||||||
|
|
||||||
entry.static_x.copy_(x)
|
entry.static_x.copy_(x)
|
||||||
entry.static_x_mask.copy_(x_mask)
|
|
||||||
entry.static_text.copy_(text)
|
|
||||||
entry.static_c_mask.copy_(c_mask)
|
|
||||||
entry.static_ref.copy_(ref)
|
|
||||||
entry.static_ref_mask.copy_(ref_mask)
|
|
||||||
entry.static_timestep.copy_(timestep)
|
entry.static_timestep.copy_(timestep)
|
||||||
if entry.static_cfg is not None:
|
if entry.static_cfg is not None:
|
||||||
entry.static_cfg.fill_(cfg_strength)
|
entry.static_cfg.fill_(cfg_strength)
|
||||||
entry.graph.replay()
|
entry.graph.replay()
|
||||||
return entry.static_out[:, :target_frames].clone()
|
return entry.static_out[:, :target_frames].clone()
|
||||||
|
|
||||||
def _run(
|
|
||||||
self,
|
|
||||||
x: torch.Tensor,
|
|
||||||
x_mask: torch.Tensor | None,
|
|
||||||
text: torch.Tensor,
|
|
||||||
c_mask: torch.Tensor,
|
|
||||||
ref: torch.Tensor,
|
|
||||||
ref_mask: torch.Tensor,
|
|
||||||
timestep: torch.Tensor,
|
|
||||||
) -> torch.Tensor:
|
|
||||||
return self.dit(x, text, timestep, mask=x_mask, c_mask=c_mask, ref=ref, ref_mask=ref_mask)
|
|
||||||
|
|
||||||
def _run_cfg(
|
|
||||||
self,
|
|
||||||
x: torch.Tensor,
|
|
||||||
x_mask: torch.Tensor | None,
|
|
||||||
text: torch.Tensor,
|
|
||||||
c_mask: torch.Tensor,
|
|
||||||
ref: torch.Tensor,
|
|
||||||
ref_mask: torch.Tensor,
|
|
||||||
timestep: torch.Tensor,
|
|
||||||
*,
|
|
||||||
cfg_strength: torch.Tensor | float,
|
|
||||||
) -> torch.Tensor:
|
|
||||||
pred = self.dit(
|
|
||||||
x,
|
|
||||||
text,
|
|
||||||
timestep,
|
|
||||||
mask=x_mask,
|
|
||||||
c_mask=c_mask,
|
|
||||||
ref=ref,
|
|
||||||
ref_mask=ref_mask,
|
|
||||||
cfg_infer=True,
|
|
||||||
cache=True,
|
|
||||||
)
|
|
||||||
conditional, unconditional = pred.chunk(2, dim=0)
|
|
||||||
return conditional + (conditional - unconditional) * cfg_strength
|
|
||||||
|
|
||||||
def _capture(
|
def _capture(
|
||||||
self,
|
self,
|
||||||
*inputs: torch.Tensor,
|
x: torch.Tensor,
|
||||||
|
ctx: AuKStepContext,
|
||||||
|
timestep: torch.Tensor,
|
||||||
|
*,
|
||||||
cfg_strength: float,
|
cfg_strength: float,
|
||||||
uses_cfg: bool,
|
|
||||||
) -> _GraphEntry:
|
) -> _GraphEntry:
|
||||||
static_inputs = tuple(value.clone() for value in inputs)
|
static_x = x.clone()
|
||||||
|
static_ctx = ctx.clone()
|
||||||
|
static_timestep = timestep.clone()
|
||||||
static_cfg = None
|
static_cfg = None
|
||||||
if uses_cfg:
|
if ctx.branches == 2:
|
||||||
static_cfg = torch.empty((), device=static_inputs[0].device, dtype=torch.float32)
|
static_cfg = torch.empty((), device=static_x.device, dtype=torch.float32)
|
||||||
static_cfg.fill_(cfg_strength)
|
static_cfg.fill_(cfg_strength)
|
||||||
try:
|
scale = static_cfg if static_cfg is not None else cfg_strength
|
||||||
for _ in range(3):
|
# Warm-up runs any lazy torch.compile outside the capture.
|
||||||
if uses_cfg:
|
for _ in range(3):
|
||||||
assert static_cfg is not None
|
self._step(static_x, static_timestep, static_ctx, scale)
|
||||||
self._run_cfg(*static_inputs, cfg_strength=static_cfg)
|
if self._pool_handle is None:
|
||||||
else:
|
self._pool_handle = torch.cuda.graph_pool_handle()
|
||||||
self._run(*static_inputs)
|
graph = torch.cuda.CUDAGraph()
|
||||||
# CFG warm-up populates AuKTransformer's Python-side projected-text
|
with torch.cuda.graph(graph, pool=self._pool_handle):
|
||||||
# cache. Clear it before capture so the graph includes
|
# The CFG scalar stays a mutable buffer, so cfg=2 and cfg=3 share this graph.
|
||||||
# project_text(static_text), rather than closing over the first
|
static_out = self._step(static_x, static_timestep, static_ctx, scale)
|
||||||
# request's projection and ignoring later static_text updates.
|
|
||||||
self.dit.clear_cache()
|
|
||||||
if static_cfg is not None:
|
|
||||||
static_cfg.fill_(cfg_strength)
|
|
||||||
if self._pool_handle is None:
|
|
||||||
self._pool_handle = torch.cuda.graph_pool_handle()
|
|
||||||
graph = torch.cuda.CUDAGraph()
|
|
||||||
with torch.cuda.graph(graph, pool=self._pool_handle):
|
|
||||||
# Capture the actual CFG path. The scalar value remains a
|
|
||||||
# mutable buffer, so cfg=2 and cfg=3 share this graph.
|
|
||||||
if uses_cfg:
|
|
||||||
assert static_cfg is not None
|
|
||||||
static_out = self._run_cfg(*static_inputs, cfg_strength=static_cfg)
|
|
||||||
else:
|
|
||||||
static_out = self._run(*static_inputs)
|
|
||||||
finally:
|
|
||||||
self.dit.clear_cache()
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Captured AuK DiT single-step CUDA graph: target_frames=%d text_tokens=%d ref_frames=%d cfg=%s",
|
"Captured AuK DiT single-step CUDA graph: target_frames=%d text_tokens=%d ref_frames=%d cfg=%s",
|
||||||
inputs[0].shape[1],
|
x.shape[1],
|
||||||
inputs[2].shape[1],
|
ctx.c.shape[1],
|
||||||
inputs[4].shape[1],
|
0 if ctx.prompt is None else ctx.prompt.shape[1],
|
||||||
cfg_strength,
|
cfg_strength,
|
||||||
)
|
)
|
||||||
return _GraphEntry(
|
return _GraphEntry(
|
||||||
graph=graph,
|
graph=graph,
|
||||||
static_x=static_inputs[0],
|
static_x=static_x,
|
||||||
static_x_mask=static_inputs[1],
|
static_ctx=static_ctx,
|
||||||
static_text=static_inputs[2],
|
static_timestep=static_timestep,
|
||||||
static_c_mask=static_inputs[3],
|
|
||||||
static_ref=static_inputs[4],
|
|
||||||
static_ref_mask=static_inputs[5],
|
|
||||||
static_timestep=static_inputs[6],
|
|
||||||
static_cfg=static_cfg,
|
static_cfg=static_cfg,
|
||||||
static_out=static_out,
|
static_out=static_out,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ from safetensors import safe_open
|
|||||||
from torch import nn
|
from torch import nn
|
||||||
from vllm.logger import init_logger
|
from vllm.logger import init_logger
|
||||||
|
|
||||||
|
from vllm_omni.diffusion.compile import regionally_compile
|
||||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||||
from vllm_omni.diffusion.distributed.utils import get_local_device
|
from vllm_omni.diffusion.distributed.utils import get_local_device
|
||||||
from vllm_omni.diffusion.models.auk.auk_transformer import AuKTransformer, dit_state_dict, sample_latents
|
from vllm_omni.diffusion.models.auk.auk_transformer import AuKTransformer, dit_state_dict, sample_latents
|
||||||
@@ -50,6 +52,13 @@ _FUSION_KEYS = frozenset({"layer_weights", "layer_scale"})
|
|||||||
# The distilled variant only reproduces its training recipe at these settings.
|
# The distilled variant only reproduces its training recipe at these settings.
|
||||||
_FLASH_NFE = 4
|
_FLASH_NFE = 4
|
||||||
_FLASH_CFG = 0.0
|
_FLASH_CFG = 0.0
|
||||||
|
# Startup DiT warm-up shapes: target frames of 3 / 6 / 12 s requests, with no
|
||||||
|
# reference (instruct TTS) and a 3 s one (zero-shot). The first call compiles
|
||||||
|
# the blocks; each shape then captures its graph bucket. Other shapes still
|
||||||
|
# capture lazily, at about a second each.
|
||||||
|
_WARMUP_TARGET_FRAMES = (150, 300, 600)
|
||||||
|
_WARMUP_REF_FRAMES = (0, 150)
|
||||||
|
_WARMUP_TEXT_TOKENS = 96
|
||||||
# Decorrelates the VAE posterior draw from the initial latent for the same request seed.
|
# Decorrelates the VAE posterior draw from the initial latent for the same request seed.
|
||||||
_VAE_SEED_OFFSET = 0x5EED_0A0C
|
_VAE_SEED_OFFSET = 0x5EED_0A0C
|
||||||
|
|
||||||
@@ -182,6 +191,7 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
|
|||||||
|
|
||||||
# The transformer owns every key the checkpoint tool writes into `dit`,
|
# The transformer owns every key the checkpoint tool writes into `dit`,
|
||||||
# `attn_mask_enabled` included, so the section passes straight through.
|
# `attn_mask_enabled` included, so the section passes straight through.
|
||||||
|
self.text_hidden_dim = int(config["dit"]["text_hidden_dim"])
|
||||||
self.dit = AuKTransformer(latent_dim=self.latent_dim, **config["dit"])
|
self.dit = AuKTransformer(latent_dim=self.latent_dim, **config["dit"])
|
||||||
self.dit = self.dit.to(dtype=self.dtype)
|
self.dit = self.dit.to(dtype=self.dtype)
|
||||||
self.dit.load_state_dict(_read_dit_weights(model_dir, self.dtype), strict=True)
|
self.dit.load_state_dict(_read_dit_weights(model_dir, self.dtype), strict=True)
|
||||||
@@ -218,22 +228,70 @@ class AuKPipeline(nn.Module, SupportAudioInput, SupportAudioOutput, SupportsComp
|
|||||||
|
|
||||||
Defining this hook replaces the runner's generic transformer compile,
|
Defining this hook replaces the runner's generic transformer compile,
|
||||||
so the DiT's ``diffusion_compile_granularity`` is honoured here: full
|
so the DiT's ``diffusion_compile_granularity`` is honoured here: full
|
||||||
compiles the whole transformer lazily, exactly as the runner would;
|
compiles the whole denoise step;
|
||||||
regional is a no-op because the DiT declares no repeated blocks. The
|
regional compiles the double- and single-stream blocks. Compilation is
|
||||||
startup cost worth paying is the VAE decode, whose buckets are
|
lazy and happens in the DiT CUDA graph's warm-up, before capture, so
|
||||||
compiled and captured before the first request.
|
each graph replays the compiled kernels. The startup cost worth paying
|
||||||
|
is the VAE decode, whose buckets are compiled and captured before the
|
||||||
|
first request.
|
||||||
"""
|
"""
|
||||||
granularity = self.od_config.diffusion_compile_granularity
|
granularity = self.od_config.diffusion_compile_granularity
|
||||||
|
dynamic = self.od_config.diffusion_compile_dynamic
|
||||||
if granularity == "full":
|
if granularity == "full":
|
||||||
self.dit.compile(dynamic=self.od_config.diffusion_compile_dynamic)
|
# The samplers drive prepare() and step() directly rather than
|
||||||
logger.info(
|
# __call__, so the per-step body is what gets compiled.
|
||||||
"AuK DiT configured for lazy full torch.compile with dynamic=%s",
|
self.dit.step = torch.compile(self.dit.step, dynamic=dynamic)
|
||||||
self.od_config.diffusion_compile_dynamic,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.info("AuK DiT declares no repeated blocks; regional torch.compile is a no-op, running eager")
|
regionally_compile(self.dit, dynamic=dynamic)
|
||||||
|
logger.info("AuK DiT configured for lazy %s torch.compile with dynamic=%s", granularity, dynamic)
|
||||||
|
self._warmup_dit()
|
||||||
self.vae_decode.warmup(self.device)
|
self.vae_decode.warmup(self.device)
|
||||||
|
|
||||||
|
def _warmup_dit(self) -> None:
|
||||||
|
"""Compile the DiT step and capture the common graph buckets before serving.
|
||||||
|
|
||||||
|
The engine's synthetic warm-up request cannot reach this pipeline
|
||||||
|
(``dummy_run_num_frames = 0``), so without this the first real request
|
||||||
|
pays the whole torch.compile. Runs the variant's default guidance path
|
||||||
|
on zero conditioning; ``model_config.auk_dit_warmup_frames`` overrides
|
||||||
|
the target lengths and ``[]`` disables the warm-up.
|
||||||
|
"""
|
||||||
|
if not self.cudagraph_wrapper.enabled:
|
||||||
|
return
|
||||||
|
model_config = getattr(self.od_config, "model_config", None) or {}
|
||||||
|
frames = model_config.get("auk_dit_warmup_frames", _WARMUP_TARGET_FRAMES)
|
||||||
|
if not frames:
|
||||||
|
logger.info("AuK DiT startup warm-up disabled")
|
||||||
|
return
|
||||||
|
cfg = _FLASH_CFG if self.is_flash else self.default_cfg
|
||||||
|
text = torch.zeros(1, _WARMUP_TEXT_TOKENS, self.text_hidden_dim, device=self.device, dtype=self.dtype)
|
||||||
|
c_mask = torch.ones(text.shape[:2], dtype=torch.bool, device=self.device)
|
||||||
|
timestep = torch.zeros((), device=self.device, dtype=torch.float32)
|
||||||
|
start = time.perf_counter()
|
||||||
|
with torch.inference_mode(), self._dit_autocast():
|
||||||
|
for ref_frames in _WARMUP_REF_FRAMES:
|
||||||
|
ref = torch.zeros(1, ref_frames, self.latent_dim, device=self.device, dtype=torch.float32)
|
||||||
|
ref_mask = torch.ones(ref.shape[:2], dtype=torch.bool, device=self.device)
|
||||||
|
for target_frames in frames:
|
||||||
|
x = torch.zeros(1, int(target_frames), self.latent_dim, device=self.device, dtype=torch.float32)
|
||||||
|
self.cudagraph_wrapper(
|
||||||
|
x=x,
|
||||||
|
text=text,
|
||||||
|
c_mask=c_mask,
|
||||||
|
ref=ref,
|
||||||
|
ref_mask=ref_mask,
|
||||||
|
timestep=timestep,
|
||||||
|
cfg_strength=cfg,
|
||||||
|
new_request=True,
|
||||||
|
)
|
||||||
|
if self.device.type != "cpu":
|
||||||
|
torch.accelerator.synchronize(self.device)
|
||||||
|
logger.info(
|
||||||
|
"AuK DiT warm-up: compiled and captured %d shapes in %.1f s",
|
||||||
|
len(frames) * len(_WARMUP_REF_FRAMES),
|
||||||
|
time.perf_counter() - start,
|
||||||
|
)
|
||||||
|
|
||||||
# The assembled checkpoint is not a diffusers layout: __init__ reads
|
# The assembled checkpoint is not a diffusers layout: __init__ reads
|
||||||
# auk.safetensors and vae.safetensors directly, so the loader has no
|
# auk.safetensors and vae.safetensors directly, so the loader has no
|
||||||
# component sources to stream and load_weights only reports what is loaded.
|
# component sources to stream and load_weights only reports what is loaded.
|
||||||
|
|||||||
Reference in new issue
Block a user