Skip to content

Commit 389fff5

Browse files
Merge pull request #480 from Ravi-VK:svg-head-local-pr-clean
PiperOrigin-RevId: 987128543
2 parents 3b6ee39 + db8c0cd commit 389fff5

24 files changed

Lines changed: 4000 additions & 20 deletions

‎src/maxdiffusion/aot_cache.py‎

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,77 @@ def transformer_forward_pass(...):
7373
_FORMAT_VERSION = 1
7474

7575

76+
def _is_graphdef(x: Any) -> bool:
77+
return hasattr(x, "attributes") and hasattr(x, "nodes")
78+
79+
80+
def _graphdef_desc(gd: Any) -> str:
81+
"""Extracts a process-deterministic digest of static attributes in an nnx.GraphDef."""
82+
items = []
83+
for k, v in getattr(gd, "attributes", ()):
84+
if str(k).startswith("_pytree__"):
85+
continue
86+
if hasattr(v, "value"):
87+
try:
88+
s = json.dumps(
89+
v.value,
90+
sort_keys=True,
91+
default=lambda o: re.sub(r"0x[0-9a-fA-F]+", "@", repr(o)),
92+
)
93+
except Exception: # noqa: BLE001
94+
s = re.sub(r"0x[0-9a-fA-F]+", "@", repr(v.value))
95+
items.append(f"{k}:{s}")
96+
return "GraphDef(" + ",".join(items) + ")"
97+
98+
99+
def extract_svg_meta(config: Any, pipeline: Any = None) -> dict[str, Any]:
100+
"""Extracts all graph-affecting SVG configuration settings for AOT cache identity."""
101+
svg_keys = (
102+
"use_svg_attention",
103+
"svg_implementation",
104+
"svg_spatial_density",
105+
"svg_high_noise_density",
106+
"svg_low_noise_density",
107+
"svg_sample_max_row",
108+
"svg_profile_query_count",
109+
"svg_profile_seed",
110+
"svg_dense_layer_fraction",
111+
"svg_dense_timestep_fraction",
112+
"svg_active_start_step",
113+
"svg_active_end_step",
114+
"svg_active_start_layer",
115+
"svg_active_end_layer",
116+
"svg_num_train_timesteps",
117+
"svg_num_layers",
118+
"svg_include_first_frame",
119+
"svg_global_stride",
120+
"svg_global_offset",
121+
"svg_flash_block_sizes",
122+
)
123+
meta: dict[str, Any] = {}
124+
if config is not None:
125+
for k in svg_keys:
126+
val = getattr(config, k, None) if not isinstance(config, dict) else config.get(k, None)
127+
if val is not None:
128+
meta[k] = str(val)
129+
if pipeline is not None:
130+
for attr in ("transformer", "high_noise_transformer", "low_noise_transformer"):
131+
t = getattr(pipeline, attr, None)
132+
if t is not None:
133+
t_cfg = getattr(t, "config", None)
134+
attn_cfg = (
135+
getattr(t_cfg, "attention_config", None)
136+
or (t_cfg.get("attention_config") if isinstance(t_cfg, dict) else None)
137+
)
138+
if isinstance(attn_cfg, dict):
139+
meta[f"{attr}_svg_config"] = json.dumps(
140+
{k: v for k, v in attn_cfg.items() if "svg" in str(k)},
141+
sort_keys=True,
142+
default=str,
143+
)
144+
return meta
145+
146+
76147
def _metadata_fingerprint(meta: dict[str, Any]) -> str:
77148
"""Returns the stable filename fingerprint for install-time metadata."""
78149
serialized = json.dumps(meta, sort_keys=True, default=str)
@@ -87,15 +158,18 @@ def _dynamic_signature(args: tuple, kwargs: dict) -> str:
87158
addresses and hash-order-dependent content that differ per process and
88159
made signatures never match across restarts (measured: every array
89160
part stable, only the treedef part unstable). Static graph metadata
90-
not visible in key paths (attention kernel, dtypes, model path) is
91-
covered by the install-time config fingerprint in the filename.
161+
in GraphDef leaves is extracted deterministically via ``_graphdef_desc``.
92162
Array leaves contribute shape/dtype; non-array leaves (python scalars,
93163
None flags) contribute an address-stripped repr.
94164
"""
95-
leaves_with_paths = jax.tree_util.tree_flatten_with_path((args, kwargs))[0]
165+
leaves_with_paths = jax.tree_util.tree_flatten_with_path(
166+
(args, kwargs), is_leaf=_is_graphdef
167+
)[0]
96168
parts = []
97169
for path, leaf in leaves_with_paths:
98-
if hasattr(leaf, "shape") and hasattr(leaf, "dtype"):
170+
if _is_graphdef(leaf):
171+
desc = _graphdef_desc(leaf)
172+
elif hasattr(leaf, "shape") and hasattr(leaf, "dtype"):
99173
desc = f"{tuple(leaf.shape)}:{leaf.dtype}"
100174
else:
101175
desc = re.sub(r"0x[0-9a-fA-F]+", "@", repr(leaf))

‎src/maxdiffusion/configs/base_wan_14b.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,26 @@ jit_initializers: True
8383
# Set true to load weights from pytorch
8484
from_pt: True
8585
split_head_dim: True
86+
# Sparse VideoGen (SVG) Attention configuration
87+
use_svg_attention: False
88+
svg_spatial_density: 0.25
89+
svg_sample_max_row: 10000
90+
svg_profile_query_count: 64
91+
svg_profile_seed: 0
92+
svg_active_start_step: -1
93+
svg_active_end_step: -1
94+
svg_active_start_layer: -1
95+
svg_active_end_layer: -1
96+
svg_include_first_frame: True
97+
svg_high_noise_density: -1.0
98+
svg_low_noise_density: -1.0
99+
# Tiling for the sparse SVG kernel only. Empty means "reuse flash_block_sizes",
100+
# which is rarely what you want: the dense ring kernel is tuned for large kv
101+
# compute blocks and the sparse kernel for small ones. Must stay a dict so the
102+
# command line can override it with JSON.
103+
svg_flash_block_sizes: {}
86104
attention: 'flash' # Supported attention: dot_product, flash, tokamax_flash, cudnn_flash_te, ring, tokamax_ring, ulysses, ulysses_custom, ulysses_ring
105+
87106
use_base2_exp: True
88107
use_experimental_scheduler: True
89108
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.

‎src/maxdiffusion/configs/base_wan_1_3b.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,26 @@ jit_initializers: True
8080
# Set true to load weights from pytorch
8181
from_pt: True
8282
split_head_dim: True
83+
# Sparse VideoGen (SVG) Attention configuration
84+
use_svg_attention: False
85+
svg_spatial_density: 0.25
86+
svg_sample_max_row: 10000
87+
svg_profile_query_count: 64
88+
svg_profile_seed: 0
89+
svg_active_start_step: -1
90+
svg_active_end_step: -1
91+
svg_active_start_layer: -1
92+
svg_active_end_layer: -1
93+
svg_include_first_frame: True
94+
svg_high_noise_density: -1.0
95+
svg_low_noise_density: -1.0
96+
# Tiling for the sparse SVG kernel only. Empty means "reuse flash_block_sizes",
97+
# which is rarely what you want: the dense ring kernel is tuned for large kv
98+
# compute blocks and the sparse kernel for small ones. Must stay a dict so the
99+
# command line can override it with JSON.
100+
svg_flash_block_sizes: {}
83101
attention: 'flash' # Supported attention: dot_product, flash, tokamax_flash, cudnn_flash_te, ring, tokamax_ring, ulysses, ulysses_custom, ulysses_ring
102+
84103
use_base2_exp: True
85104
use_experimental_scheduler: True
86105
# For attention=ulysses_ring, hidden Ulysses shard count; ring shards are context / this.

‎src/maxdiffusion/configs/base_wan_27b.yml‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,29 @@ jit_initializers: True
8383
# Set true to load weights from pytorch
8484
from_pt: True
8585
split_head_dim: True
86+
# Sparse VideoGen (SVG) Attention configuration
87+
use_svg_attention: False
88+
svg_spatial_density: 0.35
89+
svg_sample_max_row: 10000
90+
svg_profile_query_count: 64
91+
svg_profile_seed: 0
92+
svg_active_start_step: -1
93+
svg_active_end_step: -1
94+
svg_active_start_layer: -1
95+
svg_active_end_layer: -1
96+
svg_include_first_frame: True
97+
svg_high_noise_density: -1.0
98+
svg_low_noise_density: -1.0
99+
# Tiling for the sparse SVG kernel only. Empty means "reuse flash_block_sizes",
100+
# which is rarely what you want: the dense ring kernel is tuned for large kv
101+
# compute blocks and the sparse kernel for small ones. Must stay a dict so the
102+
# command line can override it with JSON. The tuned v6e-8 720p values are
103+
# {"block_q":3328,"block_kv":2816,"block_kv_compute":256,
104+
# "block_kv_compute_in":256,"heads_per_tile":1,"vmem_limit_bytes":67108864}.
105+
svg_flash_block_sizes: {}
86106
attention: 'flash' # Supported attention: dot_product, flash, tokamax_flash, cudnn_flash_te, ring, tokamax_ring, tokamax_ring_custom, ulysses, ulysses_custom, ulysses_ring, ulysses_ring_custom, ulysses_ring_custom_bidir
107+
108+
87109
#
88110
# Best 2D-ring / USP (Ulysses x ring) configs for WAN2.2-T2V-A14B (720x1280, 81 frames)
89111
# Set attention=ulysses_ring_custom and ulysses_shards=U (ring degree R=CP/U):
@@ -314,6 +336,7 @@ max_train_steps: 1500
314336
num_train_epochs: 1
315337
seed: 0
316338
output_dir: 'sdxl-model-finetuned'
339+
tensorboard_dir: ''
317340
per_device_batch_size: 1.0
318341
# If global_batch_size % jax.device_count is not 0, use FSDP sharding.
319342
global_batch_size: 0

‎src/maxdiffusion/generate_wan.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ def run(config, pipeline=None, filename_prefix="", commit_hash=None):
314314
"activations_dtype": str(config.activations_dtype),
315315
"scan_layers": str(config.scan_layers),
316316
"jax": jax.__version__,
317+
**aot_cache.extract_svg_meta(config, pipeline),
317318
},
318319
mesh=pipeline.mesh,
319320
)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
Copyright 2026 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
Production entry points for SVG attention.
17+
18+
This module is the top layer of the SVG kernel stack and exists to keep that
19+
stack acyclic:
20+
21+
custom_splash_attention (dense primitives)
22+
-> custom_svg_static_range_attention (tiling, partial kernels,
23+
exact reference builder)
24+
-> custom_svg_balanced_rounding_attention (boundary selection, production
25+
builder)
26+
-> custom_svg_attention_dispatch (this module)
27+
28+
Each arrow points from a dependency to its dependent, and no arrow runs the
29+
other way. Callers that want "the SVG kernel we ship" use this module; callers
30+
that want a specific implementation import the corresponding layer directly.
31+
"""
32+
33+
from __future__ import annotations
34+
35+
from typing import Any
36+
37+
import jax
38+
import jax.numpy as jnp
39+
import numpy as np
40+
41+
from maxdiffusion.kernels import custom_svg_balanced_rounding_attention as balanced
42+
from maxdiffusion.kernels import custom_svg_static_range_attention as static_range
43+
44+
SVGBlockSizes = static_range.SVGBlockSizes
45+
46+
47+
def make_svg_static_range_mha(
48+
*,
49+
block_sizes,
50+
orig_q_seq_len,
51+
orig_kv_seq_len,
52+
band_width,
53+
frame_size,
54+
include_first_frame=True,
55+
bkv_compute_in=None,
56+
use_base2_exp=True,
57+
use_experimental_scheduler=False,
58+
vmem_limit_bytes=None,
59+
):
60+
"""Build the production balanced-rounding SVG implementation.
61+
62+
The upstream path intentionally has no environment-variable policy switch.
63+
Production uses the validated `global_balanced`, scale=1.0 policy. The exact
64+
reference remains available explicitly via
65+
`custom_svg_static_range_attention.make_svg_exact_static_range_mha`.
66+
"""
67+
return balanced.make_svg_balanced_rounding_mha(
68+
policy="global_balanced",
69+
budget_scale=1.0,
70+
block_sizes=block_sizes,
71+
orig_q_seq_len=orig_q_seq_len,
72+
orig_kv_seq_len=orig_kv_seq_len,
73+
band_width=band_width,
74+
frame_size=frame_size,
75+
include_first_frame=include_first_frame,
76+
bkv_compute_in=bkv_compute_in,
77+
use_base2_exp=use_base2_exp,
78+
use_experimental_scheduler=use_experimental_scheduler,
79+
vmem_limit_bytes=vmem_limit_bytes,
80+
)
81+
82+
83+
def custom_svg_static_range_attention(
84+
query,
85+
key,
86+
value,
87+
band_width,
88+
anchor_width=0,
89+
global_stride=0,
90+
global_offset=0,
91+
mesh: Any = None,
92+
axis_names_q=None,
93+
axis_names_kv=None,
94+
dtype=jnp.bfloat16,
95+
block_sizes=None,
96+
use_base2_exp=True,
97+
use_experimental_scheduler=False,
98+
):
99+
del global_stride, global_offset, mesh, axis_names_q, axis_names_kv, dtype
100+
if block_sizes is None:
101+
block_sizes = SVGBlockSizes()
102+
qlen = query.shape[2]
103+
klen = key.shape[2]
104+
bw = int(np.asarray(band_width).max()) if isinstance(band_width, (jax.Array, np.ndarray)) else int(band_width)
105+
kernel = make_svg_static_range_mha(
106+
block_sizes=block_sizes,
107+
orig_q_seq_len=qlen,
108+
orig_kv_seq_len=klen,
109+
band_width=bw,
110+
frame_size=int(anchor_width) if anchor_width > 0 else 0,
111+
include_first_frame=anchor_width > 0,
112+
use_base2_exp=use_base2_exp,
113+
use_experimental_scheduler=use_experimental_scheduler,
114+
)
115+
out = jax.vmap(kernel, in_axes=(0, 0, 0))(query, key, value)
116+
return jnp.swapaxes(out, 2, 3)

0 commit comments

Comments
 (0)