Skip to content

Commit a793373

Browse files
ltx2/svg: address #498 review (profiling note, consistent SVG check, AOT key)
Add svg_attention_enabled() so model construction, the CFG cache/MagCache check and the AOT cache key agree that SVG is off at svg_spatial_density >= 1.0. Key the LTX-2 AOT cache on svg_* settings, including those recorded in the transformer attention_config, only when SVG is active. Note in docs/svg.md that the default 5-step profile never reaches an SVG window that starts at step 10.
1 parent 93c4518 commit a793373

5 files changed

Lines changed: 112 additions & 25 deletions

File tree

‎docs/svg.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,5 @@ The tests cover routing and layout, configuration propagation, schedules, shardi
163163
For CPU semantics checks, set `JAX_PLATFORMS=cpu` and `XLA_FLAGS=--xla_force_host_platform_device_count=8`. Run the suite separately on an eight-device TPU host to exercise the compiled kernels. Tests restricted to one platform are skipped on the other.
164164

165165
For profiling, enable `enable_jax_named_scopes=True` and capture a short warm denoising interval. Routing, placement, main attention, padding cleanup, merging, and restoration have named scopes, including `svg_route_profile`, `svg_layout_place`, `svg_union_main`, `svg_tail_cleanup`, `svg_lse_merge`, and `svg_layout_restore`. The `svg_kernel_c_tiles…` scope reports the fraction of physical tiles executed, which differs from attention-pair density and total transformer FLOP savings.
166+
167+
The LTX-2 profiling pass runs a separate `profiler_steps`-step denoise (steps `0` through `profiler_steps - 1`), and `profiler_steps` defaults to `5`. With the LTX-2 example above (`svg_active_start_step: 10`), that profile contains only dense steps. Set `profiler_steps` above `svg_active_start_step`, or lower `svg_active_start_step` while profiling, so the trace includes sparse SVG steps.

‎src/maxdiffusion/generate_ltx2.py‎

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from maxdiffusion.utils.export_utils import export_to_video_with_audio
2929
from maxdiffusion.loaders.ltx2_lora_nnx_loader import LTX2NNXLoraLoader
30+
from maxdiffusion.pipelines.ltx2.ltx2_pipeline import svg_attention_enabled
3031

3132

3233
def get_git_commit_hash():
@@ -201,6 +202,13 @@ def ltx2_aot_metadata(config, pipeline, source_revision=None):
201202
):
202203
transformer_config.pop(key, None)
203204

205+
# SVG settings only shape the compiled graph when SVG is active. When it is
206+
# off, leave them out so editing an unused svg_* value keeps the cache.
207+
svg_enabled = svg_attention_enabled(config)
208+
attention_config = transformer_config.get("attention_config")
209+
if not svg_enabled and isinstance(attention_config, dict):
210+
transformer_config["attention_config"] = {k: v for k, v in attention_config.items() if not str(k).startswith("svg_")}
211+
204212
config_keys = [
205213
"attention",
206214
"a2v_attention_kernel",
@@ -223,26 +231,29 @@ def ltx2_aot_metadata(config, pipeline, source_revision=None):
223231
"sharding",
224232
"weights_dtype",
225233
"activations_dtype",
226-
"use_svg_attention",
227-
"svg_implementation",
228-
"svg_spatial_density",
229-
"svg_sample_max_row",
230-
"svg_profile_query_count",
231-
"svg_profile_seed",
232-
"svg_dense_layer_fraction",
233-
"svg_dense_timestep_fraction",
234-
"svg_active_start_step",
235-
"svg_active_end_step",
236-
"svg_active_start_layer",
237-
"svg_active_end_layer",
238-
"svg_num_train_timesteps",
239-
"svg_num_layers",
240-
"svg_include_first_frame",
241-
"svg_global_stride",
242-
"svg_global_offset",
243-
"svg_flash_block_sizes",
244234
]
235+
if svg_enabled:
236+
config_keys += [
237+
"svg_implementation",
238+
"svg_spatial_density",
239+
"svg_sample_max_row",
240+
"svg_profile_query_count",
241+
"svg_profile_seed",
242+
"svg_dense_layer_fraction",
243+
"svg_dense_timestep_fraction",
244+
"svg_active_start_step",
245+
"svg_active_end_step",
246+
"svg_active_start_layer",
247+
"svg_active_end_layer",
248+
"svg_num_train_timesteps",
249+
"svg_num_layers",
250+
"svg_include_first_frame",
251+
"svg_global_stride",
252+
"svg_global_offset",
253+
"svg_flash_block_sizes",
254+
]
245255
config_dict = {k: getattr(config, k, None) for k in config_keys}
256+
config_dict["use_svg_attention"] = svg_enabled
246257

247258
device = jax.devices()[0]
248259
return {

‎src/maxdiffusion/pipelines/ltx2/ltx2_pipeline.py‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,18 @@ def _add_sharding_rule(vs: nnx.Variable, logical_axis_rules) -> nnx.Variable:
287287
return vs
288288

289289

290+
def svg_attention_enabled(config) -> bool:
291+
"""Returns whether SVG sparse attention is actually active for `config`.
292+
293+
A density of 1.0 or more is dense attention, so SVG stays off even when
294+
`use_svg_attention` is set.
295+
"""
296+
if not bool(getattr(config, "use_svg_attention", False)):
297+
return False
298+
density = getattr(config, "svg_spatial_density", None)
299+
return float(0.25 if density is None else density) < 1.0
300+
301+
290302
def create_sharded_logical_transformer(
291303
devices_array: np.array,
292304
mesh: Mesh,
@@ -341,18 +353,14 @@ def _cfg(name, default):
341353
value = getattr(config, name, None)
342354
return default if value is None else value
343355

344-
expert_density = float(_cfg("svg_spatial_density", 0.25))
345-
346-
use_svg = bool(getattr(config, "use_svg_attention", False)) and (expert_density < 1.0)
347-
348356
ltx2_config["attention_config"] = {
349357
"use_base2_exp": getattr(config, "use_base2_exp", False),
350358
"use_experimental_scheduler": getattr(config, "use_experimental_scheduler", False),
351359
"ulysses_shards": getattr(config, "ulysses_shards", -1),
352360
"ulysses_attention_chunks": getattr(config, "ulysses_attention_chunks", 1),
353-
"use_svg_attention": use_svg,
361+
"use_svg_attention": svg_attention_enabled(config),
354362
"svg_implementation": _cfg("svg_implementation", "official_svg"),
355-
"svg_spatial_density": expert_density,
363+
"svg_spatial_density": float(_cfg("svg_spatial_density", 0.25)),
356364
"svg_sample_max_row": _cfg("svg_sample_max_row", 10000),
357365
"svg_profile_query_count": _cfg("svg_profile_query_count", 64),
358366
"svg_profile_seed": _cfg("svg_profile_seed", 0),
@@ -1866,7 +1874,7 @@ def __call__(
18661874
if stg_scale > 0.0 and guidance_scale <= 1.0:
18671875
raise ValueError("Spatio-temporal guidance requires guidance_scale > 1.0.")
18681876

1869-
if self.config and bool(getattr(self.config, "use_svg_attention", False)):
1877+
if self.config and svg_attention_enabled(self.config):
18701878
if getattr(self.config, "use_cfg_cache", False) or getattr(self.config, "use_magcache", False):
18711879
raise ValueError("SVG sparse attention cannot be combined with CFG cache or MagCache.")
18721880

‎src/maxdiffusion/tests/ltx2/test_generate_ltx2_aot.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,45 @@ def test_codegen_and_remat_inputs_change_cache_fingerprint(self):
8888
self.assertNotEqual(fingerprint_b, fingerprint_c)
8989
self.assertNotEqual(fingerprint_c, fingerprint_d)
9090

91+
def _set_svg(self, **svg):
92+
for name, value in svg.items():
93+
setattr(self.config, name, value)
94+
attention_config = {"use_base2_exp": False}
95+
attention_config.update({k: v for k, v in svg.items() if k.startswith("svg_")})
96+
# Like create_sharded_logical_transformer, record the effective SVG flag.
97+
attention_config["use_svg_attention"] = generate_ltx2.svg_attention_enabled(self.config)
98+
self.pipeline.transformer.config = {"attention_config": attention_config}
99+
100+
def test_unused_svg_settings_do_not_change_cache_fingerprint(self):
101+
self._set_svg(use_svg_attention=False, svg_spatial_density=0.25, svg_active_start_step=10)
102+
_, fingerprint_a = self._fingerprint("same-revision")
103+
self._set_svg(use_svg_attention=False, svg_spatial_density=0.5, svg_active_start_step=20)
104+
_, fingerprint_b = self._fingerprint("same-revision")
105+
106+
self.assertEqual(fingerprint_a, fingerprint_b)
107+
108+
def test_svg_settings_change_cache_fingerprint_when_svg_is_active(self):
109+
self._set_svg(use_svg_attention=True, svg_spatial_density=0.25, svg_active_start_step=10)
110+
_, fingerprint_a = self._fingerprint("same-revision")
111+
self._set_svg(use_svg_attention=True, svg_spatial_density=0.5, svg_active_start_step=10)
112+
_, fingerprint_b = self._fingerprint("same-revision")
113+
self._set_svg(use_svg_attention=True, svg_spatial_density=0.5, svg_active_start_step=20)
114+
_, fingerprint_c = self._fingerprint("same-revision")
115+
116+
self.assertNotEqual(fingerprint_a, fingerprint_b)
117+
self.assertNotEqual(fingerprint_b, fingerprint_c)
118+
119+
def test_svg_at_density_one_shares_the_dense_cache_fingerprint(self):
120+
self._set_svg(use_svg_attention=False, svg_spatial_density=0.25)
121+
_, dense = self._fingerprint("same-revision")
122+
self._set_svg(use_svg_attention=True, svg_spatial_density=1.0)
123+
_, density_one = self._fingerprint("same-revision")
124+
self._set_svg(use_svg_attention=True, svg_spatial_density=0.25)
125+
_, sparse = self._fingerprint("same-revision")
126+
127+
self.assertEqual(dense, density_one)
128+
self.assertNotEqual(dense, sparse)
129+
91130
def test_tile_search_rejects_a_prebuilt_pipeline(self):
92131
config = SimpleNamespace(get_keys=lambda: {"enable_tile_search": True})
93132
with self.assertRaisesRegex(ValueError, "cannot be used with a prebuilt pipeline"):

‎src/maxdiffusion/tests/ltx2/test_svg_config_propagation_ltx2.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,33 @@ def test_cfg_and_magcache_incompatibility_validation(self):
277277
with self.assertRaisesRegex(ValueError, "SVG sparse attention cannot be combined with CFG cache or MagCache"):
278278
pipeline(prompt="test prompt")
279279

280+
def test_cfg_and_magcache_allowed_when_svg_density_is_one(self):
281+
class ReachedEncodeError(Exception):
282+
pass
283+
284+
pipeline = LTX2Pipeline.__new__(LTX2Pipeline)
285+
pipeline.check_inputs = MagicMock()
286+
pipeline.encode_prompt = MagicMock(side_effect=ReachedEncodeError)
287+
for cache_flags in ({"use_cfg_cache": True, "use_magcache": False}, {"use_cfg_cache": False, "use_magcache": True}):
288+
pipeline.config = SimpleNamespace(use_svg_attention=True, svg_spatial_density=1.0, **cache_flags)
289+
# SVG is dense at density 1.0, so the call must get past the SVG check.
290+
with self.assertRaises(ReachedEncodeError):
291+
pipeline(prompt="test prompt")
292+
293+
def test_svg_attention_enabled(self):
294+
tests = [
295+
("flag off", SimpleNamespace(use_svg_attention=False, svg_spatial_density=0.25), False),
296+
("flag missing", SimpleNamespace(), False),
297+
("sparse density", SimpleNamespace(use_svg_attention=True, svg_spatial_density=0.25), True),
298+
("density one", SimpleNamespace(use_svg_attention=True, svg_spatial_density=1.0), False),
299+
("density above one", SimpleNamespace(use_svg_attention=True, svg_spatial_density=1.5), False),
300+
("density missing uses default", SimpleNamespace(use_svg_attention=True), True),
301+
("density none uses default", SimpleNamespace(use_svg_attention=True, svg_spatial_density=None), True),
302+
]
303+
for name, config, want in tests:
304+
with self.subTest(name):
305+
self.assertEqual(ltx2_pipeline.svg_attention_enabled(config), want)
306+
280307

281308
if __name__ == "__main__":
282309
unittest.main()

0 commit comments

Comments
 (0)