Instructions to use Efficient-Large-Model/Sol-H3-VAE with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use Efficient-Large-Model/Sol-H3-VAE with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("Efficient-Large-Model/Sol-H3-VAE", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
File size: 13,933 Bytes
775b8fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | """General (non-causal) fast H3 VAE decoder: the released H3 VAE ViT decoder pruned to 24 layers, decoding whole
native 5-latent blocks with window attention and no tiling / temporal-overlap recomputation.
* time: native 5-latent H3 blocks (17 video frames). Attention is bidirectional inside a temporal window of 5
latent frames. Odd layers shift the temporal windows by 2 latent frames (Video-Swin style), so neighbouring
blocks exchange information inside the network; nothing is recomputed and nothing is cross-faded.
* space: 16x16-token windows (256 px, the released tile size); odd layers shift them by half a window.
* global tokens: every native 5-latent block owns 5 global tokens (4 register + 1 class, from the released decoder).
They attend to every token of their block; every window attends to the global tokens of the blocks it overlaps.
* rotary positions: spatial ids are window-local (as in the released tile decode); time ids are window-local too,
so nothing depends on the absolute position in the video and any length decodes the same way.
* output: per token a 3 x 4 x 16 x 16 pixel patch; the first latent of every block keeps 1 of its 4 frames,
exactly as the released decoder, so 5 latent frames give 17 video frames.
Input z: normalized latents (B, T, 24, H, W), t = 0 is the start of a native block. Output: decode_base-space
pixels (B, 3, F, 16H, 16W); apply vae.processor.revert_tensor for RGB.
"""
from __future__ import annotations
import copy
import torch
from torch import nn
from torch.nn import functional as F
from spark_compat import _apply_qk_norm, _apply_rotary_pos_emb_impl
PATCH, PATCH_T, OUT_CH = 16, 4, 3
KEEP_24 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 31, 32, 35]
# greedy single-layer removal on the released 36-layer decoder (vae_prune/layer_scan.py)
KEEP_16 = [0, 1, 2, 3, 5, 6, 8, 9, 10, 11, 14, 15, 20, 31, 32, 35]
KEEP_12 = [0, 1, 2, 3, 9, 10, 14, 15, 20, 31, 32, 35]
KEEP_8 = [0, 1, 2, 15, 20, 31, 32, 35]
KEEP_6 = [0, 1, 2, 15, 20, 35] # KEEP_8 minus the two layers with the smallest single-removal PSNR drop
KEEP = {24: KEEP_24, 16: KEEP_16, 12: KEEP_12, 8: KEEP_8, 6: KEEP_6}
def shrink_ffn(ff, idx):
"""Keep the SwiGLU hidden channels idx: gate rows idx, up rows I+idx of w1 and columns idx of w2."""
I = ff.w2.weight.shape[1]
rows = torch.cat([idx, idx + I])
w1 = nn.Linear(ff.w1.in_features, 2 * len(idx), bias=ff.w1.bias is not None).to(ff.w1.weight)
w2 = nn.Linear(len(idx), ff.w2.out_features, bias=ff.w2.bias is not None).to(ff.w2.weight)
with torch.no_grad():
w1.weight.copy_(ff.w1.weight[rows]); w2.weight.copy_(ff.w2.weight[:, idx])
if ff.w1.bias is not None: w1.bias.copy_(ff.w1.bias[rows])
if ff.w2.bias is not None: w2.bias.copy_(ff.w2.bias)
ff.w1, ff.w2 = w1, w2
class NonCausalViTDecoder(nn.Module):
def __init__(self, vae, keep_layers=KEEP_24, *, window: int = 16, block: int = 5, t_shift: int = 2,
frames_to_trim: int = 3, time_norm: float | None = None, ffn_keep: int | None = None):
super().__init__()
dec = vae.decoder
self.keep_layers = list(keep_layers)
self.window, self.block, self.t_shift, self.frames_to_trim = window, block, t_shift, frames_to_trim
# time ids: (window-local index + 0.5) / time_norm * 2 - 1. The released decoder decodes 7-latent chunks
# (5 + 2 overlap) with length-normalized ids, i.e. time_norm = 7; default = block keeps the v1 behaviour.
self.time_norm = float(time_norm if time_norm is not None else block)
self.post_quant_conv = copy.deepcopy(vae.post_quant_conv)
self.x_embedder = copy.deepcopy(dec.x_embedder)
self.register_tokens = nn.Parameter(dec.register_tokens.detach().clone())
self.blocks = nn.ModuleList([copy.deepcopy(dec.transformer_blocks[i]) for i in self.keep_layers])
self.norm_out = copy.deepcopy(dec.norm_out)
self.proj_out = copy.deepcopy(dec.proj_out)
self.pos_embed = copy.deepcopy(dec.pos_embed)
self.heads = self.blocks[0].attn.heads
self.dh = self.blocks[0].attn.dim_head
self.register_buffer("latents_mean", vae.latents_mean.detach().clone().float(), persistent=False)
self.register_buffer("latents_std", vae.latents_std.detach().clone().float(), persistent=False)
for p in self.pos_embed.parameters():
p.requires_grad_(False)
self.grad_checkpointing = False # per-layer activation checkpointing (training only)
if ffn_keep is not None: # placeholder channels; prune_ffn picks the real ones before training
for blk in self.blocks:
shrink_ffn(blk.ff, torch.arange(ffn_keep, device=blk.ff.w2.weight.device))
def prune_ffn(self, vae, scores, keep: int, src_model=None):
"""Rebuild every FFN with the `keep` highest-scoring hidden channels of the source FFN.
Source: the full-width layers of src_model (e.g. a trained decoder), else the released VAE layers.
scores[li]: (I,) importance of the source layer's hidden channels."""
for li, blk in enumerate(self.blocks):
ff = src_model.blocks[li].ff if src_model is not None else vae.decoder.transformer_blocks[self.keep_layers[li]].ff
src = copy.deepcopy(ff).to(blk.ff.w2.weight.device)
idx = torch.topk(scores[li].to(src.w2.weight.device), keep).indices.sort().values
shrink_ffn(src, idx)
blk.ff = src
# ------------------------------------------------------------------ geometry
def _shifts(self, li):
odd = li % 2 == 1
return (self.window // 2 if odd else 0), (self.t_shift if odd else 0)
def _rope(self, T, H, W, s, st, device):
"""cos/sin for tokens in (T, H, W) order with window-local time and space ids for shifts (st, s)."""
lt = ((torch.arange(T, device=device) + st) % self.block).float()
ly = ((torch.arange(H, device=device) + s) % self.window).float()
lx = ((torch.arange(W, device=device) + s) % self.window).float()
t = (lt + 0.5) / self.time_norm * 2 - 1
hy = (ly + 0.5) / self.window * 2 - 1
wx = (lx + 0.5) / self.window * 2 - 1
grid = torch.stack(torch.meshgrid(t, hy, wx, indexing="ij"), -1).reshape(1, -1, 3)
return self.pos_embed(grid.float())
def _partition(self, x, s, st):
"""x: (B, T, H, W, ...) -> windows (B*nW, Tw*ws*ws, ...), valid (nW, Tw*ws*ws), info.
Window order is (time window, row, col)."""
B, T, H, W = x.shape[:4]
ws, tb = self.window, self.block
rest = x.shape[4:]
Tp = ((T + st + tb - 1) // tb) * tb
Hp = ((H + s + ws - 1) // ws) * ws
Wp = ((W + s + ws - 1) // ws) * ws
xp = x.new_zeros(B, Tp, Hp, Wp, *rest)
xp[:, st:st + T, s:s + H, s:s + W] = x
valid = torch.zeros(Tp, Hp, Wp, dtype=torch.bool, device=x.device)
valid[st:st + T, s:s + H, s:s + W] = True
nt, nh, nw = Tp // tb, Hp // ws, Wp // ws
r = len(rest)
xw = xp.view(B, nt, tb, nh, ws, nw, ws, *rest).permute(0, 1, 3, 5, 2, 4, 6, *range(7, 7 + r))
xw = xw.reshape(B * nt * nh * nw, tb * ws * ws, *rest)
vw = valid.view(nt, tb, nh, ws, nw, ws).permute(0, 2, 4, 1, 3, 5).reshape(nt * nh * nw, tb * ws * ws)
return xw, vw, (B, T, H, W, Tp, Hp, Wp, nt, nh, nw)
def _unpartition(self, xw, info, s, st):
B, T, H, W, Tp, Hp, Wp, nt, nh, nw = info
ws, tb = self.window, self.block
rest = xw.shape[2:]
r = len(rest)
x = xw.view(B, nt, nh, nw, tb, ws, ws, *rest).permute(0, 1, 4, 2, 5, 3, 6, *range(7, 7 + r))
x = x.reshape(B, Tp, Hp, Wp, *rest)
return x[:, st:st + T, s:s + H, s:s + W]
def _layer(self, li, blk, x, g):
"""One transformer layer on the tokens x (B,T,H,W,D) and the per-block global tokens g (B,nb,S,D)."""
B, T, H, W, nb, S = self._geom
dev = x.device
tb = self.block
s, st = self._shifts(li)
h = blk.norm1(x.float()).to(x.dtype)
hg = blk.norm1(g.float()).to(g.dtype)
q, k, v = blk.attn.to_qkv(h).view(B, T, H, W, self.heads, 3 * self.dh).chunk(3, -1)
qg, kg, vg = blk.attn.to_qkv(hg).view(B, nb, S, self.heads, 3 * self.dh).chunk(3, -1)
if blk.attn.norm_q is not None:
q, qg = _apply_qk_norm(blk.attn.norm_q, q), _apply_qk_norm(blk.attn.norm_q, qg)
k, kg = _apply_qk_norm(blk.attn.norm_k, k), _apply_qk_norm(blk.attn.norm_k, kg)
cos, sin = self._rope(T, H, W, s, st, dev)
q = _apply_rotary_pos_emb_impl(q.reshape(B, T * H * W, self.heads, self.dh), (cos, sin)).view(B, T, H, W, self.heads, self.dh)
k = _apply_rotary_pos_emb_impl(k.reshape(B, T * H * W, self.heads, self.dh), (cos, sin)).view(B, T, H, W, self.heads, self.dh)
# ---- window attention: tokens of a window + global tokens of the native blocks the window overlaps
qw, vmask, info = self._partition(q, s, st)
kw, _, _ = self._partition(k, s, st)
vw, _, _ = self._partition(v, s, st)
nt, nh, nw = info[7:]
nW = nt * nh * nw
# native blocks overlapped by temporal window j: latent range [j*tb - st, (j+1)*tb - st)
tw_lo = (torch.arange(nt, device=dev) * tb - st).clamp(min=0)
tw_hi = ((torch.arange(nt, device=dev) + 1) * tb - st - 1).clamp(max=T - 1)
b0, b1 = tw_lo // tb, tw_hi // tb # at most 2 blocks per window
gk_idx = torch.stack([b0, b1], 1) # nt, 2
gk_valid = torch.stack([torch.ones_like(b0, dtype=torch.bool), b1 != b0], 1) # nt, 2
kgw = kg[:, gk_idx].reshape(B, nt, 2 * S, self.heads, self.dh)
vgw = vg[:, gk_idx].reshape(B, nt, 2 * S, self.heads, self.dh)
kgw = kgw[:, :, None, None].expand(B, nt, nh, nw, 2 * S, self.heads, self.dh).reshape(B * nW, 2 * S, self.heads, self.dh)
vgw = vgw[:, :, None, None].expand(B, nt, nh, nw, 2 * S, self.heads, self.dh).reshape(B * nW, 2 * S, self.heads, self.dh)
gmask = gk_valid[:, :, None].expand(nt, 2, S).reshape(nt, 2 * S)
gmask = gmask[:, None, None].expand(nt, nh, nw, 2 * S).reshape(nW, 2 * S)
Kc = torch.cat([kw, kgw], 1).transpose(1, 2)
Vc = torch.cat([vw, vgw], 1).transpose(1, 2)
kmask = torch.cat([vmask, gmask], 1).repeat(B, 1)[:, None, None, :]
ow = F.scaled_dot_product_attention(qw.transpose(1, 2), Kc, Vc, attn_mask=kmask).transpose(1, 2)
o = self._unpartition(ow, info, s, st).reshape(B, T, H, W, self.heads * self.dh)
# ---- global tokens: attend to every token of their own native block + the block's global tokens
Tp = nb * tb
kb = k.new_zeros(B, Tp, H, W, self.heads, self.dh); kb[:, :T] = k
vb = v.new_zeros(B, Tp, H, W, self.heads, self.dh); vb[:, :T] = v
kb = kb.view(B, nb, tb * H * W, self.heads, self.dh)
vb = vb.view(B, nb, tb * H * W, self.heads, self.dh)
tvalid = torch.arange(Tp, device=dev) < T
bmask = tvalid.view(nb, tb, 1).expand(nb, tb, H * W).reshape(nb, tb * H * W)
Kg = torch.cat([kb, kg], 2).reshape(B * nb, tb * H * W + S, self.heads, self.dh).transpose(1, 2)
Vg = torch.cat([vb, vg], 2).reshape(B * nb, tb * H * W + S, self.heads, self.dh).transpose(1, 2)
gm = torch.cat([bmask, torch.ones(nb, S, dtype=torch.bool, device=dev)], 1).repeat(B, 1)[:, None, None, :]
og = F.scaled_dot_product_attention(qg.reshape(B * nb, S, self.heads, self.dh).transpose(1, 2), Kg, Vg,
attn_mask=gm).transpose(1, 2).reshape(B, nb, S, -1)
x = x + blk.attn.to_out(o) * blk.scale1
g = g + blk.attn.to_out(og) * blk.scale1
x = x + blk.ff(blk.norm2(x.float()).to(x.dtype)) * blk.scale2
g = g + blk.ff(blk.norm2(g.float()).to(g.dtype)) * blk.scale2
return x, g
# ------------------------------------------------------------------ forward
def forward(self, z):
B, T, _, H, W = z.shape
dev = z.device
tb, ws = self.block, self.window
nb = (T + tb - 1) // tb # native blocks
compute_dtype = torch.get_autocast_dtype("cuda") if torch.is_autocast_enabled("cuda") else z.dtype
raw = z.float() * self.latents_std.view(1, 1, -1, 1, 1) + self.latents_mean.view(1, 1, -1, 1, 1)
raw = self.post_quant_conv(raw.transpose(1, 2))
with torch.autocast("cuda", enabled=False):
x = self.x_embedder(raw.permute(0, 2, 3, 4, 1).float()) # B,T,H,W,D
x = x.to(compute_dtype)
D = x.shape[-1]
S = self.register_tokens.shape[1] + 1
g = torch.cat([self.register_tokens.expand(B, -1, -1), x.new_zeros(B, 1, D)], 1).to(compute_dtype)
g = g.unsqueeze(1).expand(B, nb, S, D).contiguous() # B, nb, S, D (one set per native block)
self._geom = (B, T, H, W, nb, S)
use_ckpt = self.grad_checkpointing and torch.is_grad_enabled() and self.training
for li, blk in enumerate(self.blocks):
if use_ckpt:
x, g = torch.utils.checkpoint.checkpoint(self._layer, li, blk, x, g, use_reentrant=False)
else:
x, g = self._layer(li, blk, x, g)
x = self.norm_out(x)
with torch.autocast("cuda", enabled=False):
out = self.proj_out(x.float()) # B,T,H,W,3*4*16*16
out = out.view(B, T, H, W, OUT_CH, PATCH_T, PATCH, PATCH).permute(0, 4, 1, 5, 2, 6, 3, 7)
out = out.reshape(B, OUT_CH, T * PATCH_T, H * PATCH, W * PATCH)
keep = torch.ones(T * PATCH_T, dtype=torch.bool, device=dev)
for t in range(0, T, tb):
keep[t * PATCH_T: t * PATCH_T + self.frames_to_trim] = False
return out[:, :, keep]
|