Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a helper function _spatial_sharding to dynamically determine the sharding of a 5D array over the vae_spatial axis, replacing hardcoded sharding logic in __call__, _encode, and _decode. The review feedback recommends defensively checking that the input shape is 5D in _spatial_sharding to prevent unpacking errors, and addresses a potential NameError in _decode when spatial_sharding is None.
| if self.mesh is not None and "vae_spatial" in self.mesh.shape | ||
| else None | ||
| ) | ||
| spatial_sharding = _spatial_sharding(self.mesh, x.shape) |
There was a problem hiding this comment.
There is a critical bug in _decode where if spatial_sharding is None (e.g., when running on a single device without a mesh), chunk_in_0 (line 1298) and chunk_in_1 (line 1307) are never defined. This will raise a NameError when they are passed to self.decoder on lines 1299 and 1308.
To fix this, you should define chunk_in_0 and chunk_in_1 unconditionally and apply the sharding constraint conditionally. For example:
# First chunk (i=0)
with jax.named_scope("AutoencoderKLWan_decode_chunk_0"):
chunk_in_0 = x[:, 0:1, ...]
if spatial_sharding is not None:
chunk_in_0 = jax.lax.with_sharding_constraint(chunk_in_0, spatial_sharding)
out_0, dec_feat_map, _ = self.decoder(chunk_in_0, feat_cache=dec_feat_map, feat_idx=0)And similarly for chunk_in_1.
| if mesh is None or "vae_spatial" not in mesh.shape: | ||
| return None | ||
| _, _, height, width, _ = shape |
There was a problem hiding this comment.
To prevent potential ValueError during unpacking, we should defensively check that the input shape is indeed 5D (i.e., has a length of 5) before attempting to unpack it. If it is not 5D, we can safely return None to skip applying the sharding constraint.
| if mesh is None or "vae_spatial" not in mesh.shape: | |
| return None | |
| _, _, height, width, _ = shape | |
| if mesh is None or "vae_spatial" not in mesh.shape or len(shape) != 5: | |
| return None | |
| _, _, height, width, _ = shape |
The WAN VAE always sharded the width of its activations over the vae_spatial mesh axis. This works for the default 1280x720 landscape size, but users may also want to run inference on portrait videos 720x1280. The VAE downsamples their width to 720, 360, 180 and 90, which isn't always divisible by vae_spatial (e.g., 90 for 4). This change adds a _spatial_sharding(mesh, shape) helper that shards height if width **is not divisible** by vae_spatial, but height is divisible. Videos with a divisible width keep the current behavior.
85944f7 to
49cb0ce
Compare
entrpn
left a comment
There was a problem hiding this comment.
Thanks @ninatu! Supporting portrait 720x1280 without XLA padding on vae_spatial makes a lot of sense.
One important sharding/perf catch when tracing 720x1280 (H=1280, W=720) through the 4 spatial pyramid levels, plus a few consistency items:
-
W <-> Hsharding thrashing (all-to-alls) across pyramid levels andscan_fncarries on720x1280(vae_spatial=4or8):- For
720x1280(H=1280, W=720) withvae_spatial=4, levels 0–2 (1280x720,640x360,320x180) all satisfywidth % 4 == 0, while only the latent bottleneck (160x90) has90 % 4 != 0and160 % 4 == 0(similarly, forvae_spatial=8,1280x720and640x360satisfywidth % 8 == 0while320x180and160x90flip to height). - Because
WanCausalConv3d.__call__calls_spatial_sharding(self.mesh, x.shape)on its local activation shape while_encode(fromout_0.shape) and_decode(frompost_quant_conv(z).shape) compute a single height-shardedspatial_sharding:- Every
WanCausalConv3dat1280x720,640x360, and320x180constrainsxto width-sharded, - Meanwhile
scan_fnin_encode(lines 1248, 1252–1257) and_decode(lines 1350–1357) constrainschunk,out_chunk, and every 5D cache tensor innext_feat_mapacross all pyramid levels back to height-sharded, triggering repeatedall-to-alltransposes between H and W on every temporal scan chunk.
- Every
- Could we choose the spatial sharding axis once from the bottleneck/latent
(H_lat, W_lat)(i.e.(H // 8, W // 8)in_encodeand(z.shape[2], z.shape[3])in_decode) and apply that same axis across all pyramid levels inencoder/decoder/WanCausalConv3dso720x1280stays height-sharded end-to-end with zeroall-to-alls?
- For
-
AutoencoderKLWan2p2(src/maxdiffusion/models/wan/autoencoder_kl_wan_2p2.py:1344, 1397):AutoencoderKLWan2p2importsWanCausalConv3dfromautoencoder_kl_wan.py, so its internal convs will now flip to height sharding at160x90while its_encode/_decodestill hardcodeP(None, None, None, "vae_spatial", None). Worth updatingautoencoder_kl_wan_2p2.pyto match (noting itsNonevs"redundant"leading axis).
-
Pipeline divisibility warning (
wan_pipeline.py:559,wan_vace_pipeline_2_1.py:471,wan_pipeline_animate.py:423):- These still warn whenever
(width // 8) % vae_spatial != 0; updating the condition to warn only when both(width // 8) % vae_spatial != 0and(height // 8) % vae_spatial != 0will avoid a false warning on720x1280.
- These still warn whenever
-
Unit test (
src/maxdiffusion/tests/wan/wan_vae_test.py):- Could we add a small portrait encode/decode test case where
(width // 8) % vae_spatial != 0and(height // 8) % vae_spatial == 0(e.g.H=32, W=24on 4 devices)?
- Could we add a small portrait encode/decode test case where
The WAN VAE always sharded the width of its activations over the vae_spatial mesh axis. This works for the default 1280x720 landscape size, but users may also want to run inference on portrait videos 720x1280. The VAE downsamples their width to 720, 360, 180 and 90, which isn't always divisible by vae_spatial (e.g., 90 for 4).
This change adds a _spatial_sharding(mesh, shape) helper that shards height if width is not divisible by vae_spatial, but height is divisible. Videos with a divisible width keep the current behavior.