1from huggingface_hub import snapshot_download
2import mlx.core as mx
3from mlx_video.models.wan_2.vae22 import (
4 Wan22VAEEncoder, Wan22VAEDecoder, denormalize_latents,
5)
6
7repo = snapshot_download("mlx-community/Wan2.2-VAE-Lance-bf16")
8weights = mx.load(f"{repo}/vae.safetensors")
9
10enc = Wan22VAEEncoder(z_dim=48, dim=160)
11enc.load_weights([
12 (k, v) for k, v in weights.items()
13 if k.startswith("encoder.") or k.startswith("conv1.")
14])
15mx.eval(enc.parameters())
16
17dec = Wan22VAEDecoder(z_dim=48, dim=160, dec_dim=256)
18dec.load_weights([
19 (k, v) for k, v in weights.items()
20 if k.startswith("decoder.") or k.startswith("conv2.")
21])
22mx.eval(dec.parameters())
1import numpy as np
2from PIL import Image
3
4img = Image.open("photo.jpg").convert("RGB").resize((768, 768))
5arr = np.asarray(img, dtype=np.float32) / 127.5 - 1.0 # [-1, 1]
6x = mx.array(arr[None, None, ...]) # (1, 1, H, W, 3)
7z = enc(x) # (1, 1, 48, 48, 48)
8print("latent shape:", z.shape)
9# mean ≈ -0.07, std ≈ 0.60 (per-channel normalized)
1z_denorm = denormalize_latents(z) # apply per-channel std/mean
2decoded = dec(z_denorm) # (1, T'>=1, H', W', 3) in [-1, 1]
3out_img = ((np.array(decoded[0, 0]) + 1.0) * 127.5).clip(0, 255).astype(np.uint8)
4Image.fromarray(out_img).save("roundtrip.png")
Loaded in ~0.3 s on M5 Max 128 GB; encode 0.55 s, decode 1.65 s.
Source:
bytedance-research/Lance/Wan2.2_VAE.pth (PyTorch, 704.7 M params, 196 tensors after splitting nested modules).
Converted via
scripts/06_convert_wan_vae.py which:
Both Lance's image and video pipelines need this VAE. Publishing it once decouples versioning: a fix or upgrade to the VAE doesn't force a re-download of either ~12 GB LLM. The companion repos (
mlx-community/Lance-3B-bf16 and
mlx-community/Lance-3B-Video-bf16) bundle a copy for convenience, but power users should pin this one and use it across both.
Apache 2.0. The original Wan2.2 VAE weights are © Alibaba; this MLX port is © the lance-mlx contributors. See
NOTICE in the lance-mlx repo for attribution.