ostris commited on
Commit
bee1996
·
verified ·
1 Parent(s): b269527

Primal VAE 2x1 and 2x2

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/img01.png filter=lfs diff=lfs merge=lfs -text
37
+ assets/img02.png filter=lfs diff=lfs merge=lfs -text
38
+ assets/img03.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,3 +1,157 @@
1
  ---
2
- license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: diffusers
3
+ tags:
4
+ - vae
5
+ - autoencoder
6
+ - latent-diffusion
7
  ---
8
+
9
+ # Primal VAE
10
+
11
+ Primal VAE is a new VAE concept I am working on. It only does 2x spatial down to 1 channel (or 2). If you
12
+ patch 4 the latent, you end up with 8x at 16 channels, the same as most modern VAEs, without the need for deep
13
+ compression. The DiT does the patching, like it already does, and the VAE stays small and nearly lossless.
14
+
15
+ The latent is trained to keep its norm in line with noise, so there is no scaling factor, no shift factor, and
16
+ no latent normalization to carry around. Encode, patch 4, diffuse.
17
+
18
+ | model | latent | patch 4 | same shape as |
19
+ |---|---|---|---|
20
+ | `2x1` | 1 ch @ 2x | 16 ch @ 8x | Flux.1 VAE (16 ch @ 8x) |
21
+ | `2x2` | 2 ch @ 2x | 32 ch @ 8x | Flux.2 VAE (32 ch @ 8x) |
22
+
23
+ Both are ~80M parameters. Image sides need to be multiples of 16.
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ import torch
29
+ from diffusers import AutoModel
30
+
31
+ vae = AutoModel.from_pretrained("ostris/primal_vae", variant="2x1", trust_remote_code=True, torch_dtype=torch.bfloat16).to("cuda")
32
+
33
+ x = ... # [B, 3, H, W] in [-1, 1], H and W multiples of 16
34
+ z = vae.encode(x) # [B, 1, H/2, W/2], ~N(0, 1). No .latent_dist, no sampling.
35
+ x_hat = vae.decode(z) # [B, 3, H, W]
36
+ ```
37
+
38
+ ### Matching the Flux.1 latent (2x1)
39
+
40
+ ```python
41
+ vae = AutoModel.from_pretrained("ostris/primal_vae", variant="2x1", trust_remote_code=True, torch_dtype=torch.bfloat16).to("cuda")
42
+
43
+ tokens = vae.patchify(vae.encode(x)) # [B, 16, H/8, W/8]: the Flux.1 VAE latent shape
44
+ # drop `tokens` in wherever the Flux.1 VAE latent went. No scaling_factor / shift_factor, it is already unit normal.
45
+ # e.g. FluxPipeline._pack_latents(tokens, B, 16, H // 8, W // 8) -> [B, (H/16) * (W/16), 64]
46
+
47
+ x_hat = vae.decode(vae.unpatchify(tokens))
48
+ ```
49
+
50
+ ### Matching the Flux.2 latent (2x2)
51
+
52
+ ```python
53
+ vae = AutoModel.from_pretrained("ostris/primal_vae", variant="2x2", trust_remote_code=True, torch_dtype=torch.bfloat16).to("cuda")
54
+
55
+ tokens = vae.patchify(vae.encode(x)) # [B, 32, H/8, W/8]: the Flux.2 VAE latent shape
56
+ # drop `tokens` in where the Flux.2 VAE latent went, before its own 2x2 patchify to 128-channel tokens.
57
+ # Skip the Flux.2 batch-norm latent normalization, it is already unit normal per image and channel.
58
+
59
+ x_hat = vae.decode(vae.unpatchify(tokens))
60
+ ```
61
+
62
+ `variant` picks the weight file, `primal_vae_<variant>.safetensors`. The architecture is read from the tensor
63
+ shapes, so `weights_name="my_finetune.safetensors"` loads any Primal VAE checkpoint out of ai-toolkit as-is.
64
+ `patchify` / `unpatchify` are just `pixel_unshuffle` / `pixel_shuffle` with `token_patch = 4`.
65
+ `vae.token_channels`, `vae.token_compression` and `vae.image_multiple` give you the numbers above.
66
+
67
+ ## Architecture
68
+
69
+ Both models are the same network, they only differ in output channels.
70
+
71
+ - Input is `pixel_unshuffle(2)`: RGB becomes 12 channels at 1/2 resolution. Everything runs at latent
72
+ resolution or below, so the latent is a spatially honest map of the image.
73
+ - Encoder and decoder are mirrored conv U-Nets, 4 levels at 1/2, 1/4, 1/8 and 1/16 of the image, widths
74
+ 96 / 192 / 384 / 384, residual block depths 1 / 1 / 2 / 4 down and 1 / 1 / 2 up. Downs and ups are
75
+ `pixel_unshuffle` / `pixel_shuffle` plus a 3x3 conv. Residual blocks cycle dilations 1, 2, 4.
76
+ - Normalization is RMSNorm over channels at each position. No spatial statistics, no attention, so tiles
77
+ encode and decode the same as the full image given some overlap.
78
+ - The encoder ends in a plain 3x3 conv to 1 or 2 channels. No posterior, no KL, no quant convs.
79
+
80
+ ## Keeping the latent in line with noise
81
+
82
+ A diffusion model is trained against Gaussian noise, so the latent should look like Gaussian noise. Instead of a
83
+ KL term and a scaling factor someone measures after the fact, I put the statistics directly in the loss on the
84
+ encoder output.
85
+
86
+ - **Moment loss** (both): `mean^2 + (std - 1)^2`, weight 0.1. On `2x1` the mean and std are pooled over the
87
+ batch. On `2x2` they are per image and per channel, so every single image comes out mean 0 / std 1 in each
88
+ channel, not just the dataset on average.
89
+ - **Gaussian order-statistic loss** (`2x2`): sort the latent values of each image and channel and MSE them
90
+ against the expected order statistics of a standard normal of the same size (Blom's approximation). This
91
+ pins the whole distribution shape, not just the first two moments, so a flat gray image still encodes to
92
+ something that looks like noise instead of a constant.
93
+
94
+ ## Examples
95
+
96
+ Each row is input, reconstruction, latent, |error| × 4. Under each Primal VAE row is the Flux VAE with the
97
+ same latent layout, on the same image. Latents are shown at ±3σ. The 1-channel latent is gray; the 2-channel
98
+ one has channel 0 on red and channel 1 on blue. The Flux latents show their first 3 of 16 / 32 channels after
99
+ the scaling the DiT actually sees (Flux.1: shift and scale factor; Flux.2: batch-norm statistics). Mean and std
100
+ are over the full latent tensor.
101
+
102
+ ![2x1 vs Flux.1](assets/compare_2x1.svg)
103
+
104
+ ![2x2 vs Flux.2](assets/compare_2x2.svg)
105
+
106
+ PSNR on these three 512 px images (dB):
107
+
108
+ | image | Primal 2x1 | Flux.1 VAE | Primal 2x2 | Flux.2 VAE |
109
+ |---|---|---|---|---|
110
+ | img01 (people, studio) | 32.1 | 35.7 | 37.5 | 37.2 |
111
+ | img02 (picnic, foliage) | 22.2 | 24.2 | 27.1 | 26.5 |
112
+ | img03 (UI screenshot) | 21.0 | 24.7 | 27.4 | 25.9 |
113
+
114
+ The 2x2 matches or beats the Flux.2 VAE here at the same information budget (both are 32 values per 8×8 pixel
115
+ block). Three images and PSNR alone are not a benchmark, and a KL VAE pays a reconstruction tax for its posterior
116
+ noise that a deterministic one does not. The 2x1 trails the Flux.1 VAE on textured content. One channel at 2x
117
+ is a 12:1 reduction with no slack, and it has about a third of the training steps of the 2x2. Its per-image
118
+ latent std also drifts (0.9 to 1.4 here) because its moment loss was batch pooled. The per-image normalization
119
+ on the 2x2 holds std within a few percent of 1.
120
+
121
+ ## Training
122
+
123
+ I am training these in [ai-toolkit](https://github.com/ostris/ai-toolkit) with a custom extension on
124
+ aspect-bucketed images. The weight files here are the training checkpoints, unchanged.
125
+
126
+ Reconstruction is MAE (0.5) and LPIPS (1.0), plus an L1 on the log-magnitude 2D spectrum (0.1) that pushes
127
+ high-frequency energy without demanding exact phase.
128
+
129
+ The tricks that should make the latent space easier to diffuse:
130
+
131
+ - **Latent flips and rotations (from EQ-VAE).** On 50% of steps I flip or rotate the latent by 90 degrees
132
+ and decode it against the same flipped or rotated image. The latent has to be a picture of the image, not an
133
+ arbitrary code. I tried downscale equivariance too and dropped it, it slowed down color learning.
134
+ - **Noise-perturbed decode.** On 20% of steps the decoder gets `z + sigma * noise`, `sigma` drawn per image
135
+ from `U(0, 0.5)`, and still has to reconstruct the clean image. The decoder learns to tolerate diffusion
136
+ model error and the latent gets smooth: nearby codes decode to nearby images.
137
+ - **Solid colors** (`2x2`): 5% of images are swapped for a random flat color. Flat regions are the hard case
138
+ for a per-image normalized latent.
139
+ - **Critic** (`2x1`): a frozen SigLIP2 NaFlex ViT gives patch features from layers 4, 8 and 12 of the real and
140
+ reconstructed image at native resolution. Small spectral-normalized conv heads on those features train with
141
+ a hinge loss plus R1, and the decoder gets the generator term at weight 0.1, ramped in over the first 1000
142
+ steps. It sharpens the texture that MAE and LPIPS leave soft. The 2x2 was trained without a critic, it
143
+ seemed to converge fine without one.
144
+
145
+ Every step is one encode and one decode. On a flip/rotate step the transformed pair replaces the clean pair
146
+ instead of adding a second decoder pass.
147
+
148
+ ## Files
149
+
150
+ ```
151
+ primal_vae_2x1.safetensors 1 ch @ 2x (fp32)
152
+ primal_vae_2x2.safetensors 2 ch @ 2x (fp32)
153
+ modeling_primal_vae.py the diffusers model (remote code)
154
+ config.json points AutoModel at it
155
+ ```
156
+
157
+ Load with `torch_dtype=torch.bfloat16` for inference, it costs ~0.05 dB.
assets/compare_2x1.svg ADDED
assets/compare_2x2.svg ADDED
assets/img01.png ADDED

Git LFS Details

  • SHA256: e52eb66759fa93e82556fe28ea7e6058ccccf483cb292165f08ed2421c0d88c8
  • Pointer size: 131 Bytes
  • Size of remote file: 476 kB
assets/img02.png ADDED

Git LFS Details

  • SHA256: 91d44f76cf0a74973e317a183285bca57b83b2a8242186f7cfcda4c44f18375d
  • Pointer size: 131 Bytes
  • Size of remote file: 674 kB
assets/img03.png ADDED

Git LFS Details

  • SHA256: ceca452dab225f3fc62f2a785d57acd39e4205a4d5ae033b621dc203b585e892
  • Pointer size: 131 Bytes
  • Size of remote file: 191 kB
config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "PrimalVAE",
3
+ "_diffusers_version": "0.36.0",
4
+ "auto_map": {
5
+ "AutoModel": "modeling_primal_vae.PrimalVAE"
6
+ },
7
+ "weights": [
8
+ "primal_vae_2x1.safetensors",
9
+ "primal_vae_2x2.safetensors"
10
+ ],
11
+ "token_patch": 4
12
+ }
modeling_primal_vae.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Primal VAE for diffusers (AutoModel remote code). Deterministic autoencoder: RGB in [-1, 1] ->
2
+ latent_channels at `compression`x spatial compression, latent ~ N(0, 1) by training, no scaling or
3
+ shift factor. `patchify(z)` regroups the latent into the token grid a DiT consumes (4x4 by default:
4
+ 1ch @ 2x -> 16ch @ 8x, 2ch @ 2x -> 32ch @ 8x).
5
+
6
+ Loads the ai-toolkit training checkpoints directly: `from_pretrained(repo, variant="2x1")` reads
7
+ `primal_vae_2x1.safetensors` and derives the architecture from the tensor shapes; `weights_name=` picks
8
+ any other file. Standalone: torch + diffusers + safetensors.
9
+ """
10
+
11
+ import math
12
+ import os
13
+ from typing import List, Optional
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
19
+ from diffusers.models.modeling_utils import ModelMixin
20
+ from huggingface_hub import hf_hub_download
21
+ from safetensors import safe_open
22
+
23
+ # dilation of block i's first conv; widens the receptive field without downsampling
24
+ DILATION_CYCLE = (1, 2, 4)
25
+
26
+
27
+ class ChannelRMSNorm(nn.Module):
28
+ """RMSNorm over channels at each position: no spatial statistics, so tiles encode/decode like the whole image."""
29
+
30
+ def __init__(self, ch: int, eps: float = 1e-6):
31
+ super().__init__()
32
+ self.eps = eps
33
+ self.weight = nn.Parameter(torch.ones(ch))
34
+
35
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
36
+ return F.rms_norm(x.movedim(1, -1), (x.shape[1],), self.weight.to(x.dtype), self.eps).movedim(-1, 1)
37
+
38
+
39
+ class ResBlock(nn.Module):
40
+ def __init__(self, width: int, dilation: int):
41
+ super().__init__()
42
+ self.norm1 = ChannelRMSNorm(width)
43
+ self.conv1 = nn.Conv2d(width, width, 3, padding=dilation, dilation=dilation)
44
+ self.norm2 = ChannelRMSNorm(width)
45
+ self.conv2 = nn.Conv2d(width, width, 3, padding=1)
46
+
47
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
48
+ h = self.conv1(F.silu(self.norm1(x)))
49
+ h = self.conv2(F.silu(self.norm2(h)))
50
+ return x + h
51
+
52
+
53
+ class UStack(nn.Module):
54
+ """conv_in -> [blocks, pixel-unshuffle down] per level -> bottom blocks -> [up, + skip, blocks] per level
55
+ -> norm/act -> conv_out."""
56
+
57
+ def __init__(self, in_ch: int, widths: List[int], depths: List[int], up_depths: List[int], out_ch: int):
58
+ super().__init__()
59
+ L = len(widths)
60
+ if len(depths) != L or len(up_depths) != L - 1:
61
+ raise ValueError("level_depths must match level_widths; up_depths has one entry less")
62
+ self.conv_in = nn.Conv2d(in_ch, widths[0], 3, padding=1)
63
+ self.down_blocks = nn.ModuleList([nn.ModuleList([ResBlock(widths[i], DILATION_CYCLE[j % len(DILATION_CYCLE)]) for j in range(depths[i])]) for i in range(L)])
64
+ self.downs = nn.ModuleList([nn.Conv2d(4 * widths[i], widths[i + 1], 3, padding=1) for i in range(L - 1)])
65
+ self.ups = nn.ModuleList([nn.Conv2d(widths[i + 1], 4 * widths[i], 3, padding=1) for i in range(L - 1)])
66
+ self.up_blocks = nn.ModuleList([nn.ModuleList([ResBlock(widths[i], DILATION_CYCLE[j % len(DILATION_CYCLE)]) for j in range(up_depths[i])]) for i in range(L - 1)])
67
+ self.norm_out = ChannelRMSNorm(widths[0])
68
+ self.conv_out = nn.Conv2d(widths[0], out_ch, 3, padding=1)
69
+
70
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
71
+ x = self.conv_in(x)
72
+ skips = []
73
+ L = len(self.down_blocks)
74
+ for i in range(L):
75
+ for blk in self.down_blocks[i]:
76
+ x = blk(x)
77
+ if i < L - 1:
78
+ skips.append(x)
79
+ x = self.downs[i](F.pixel_unshuffle(x, 2))
80
+ for i in reversed(range(L - 1)):
81
+ x = F.pixel_shuffle(self.ups[i](x), 2) + skips[i]
82
+ for blk in self.up_blocks[i]:
83
+ x = blk(x)
84
+ return self.conv_out(F.silu(self.norm_out(x)))
85
+
86
+
87
+ class PrimalVAE(ModelMixin, ConfigMixin):
88
+ """Primal VAE: `encode` -> [B, latent_channels, H/compression, W/compression] (~N(0,1)), `decode` back to
89
+ [B, 3, H, W] in [-1, 1]. No posterior, no scaling/shift factor."""
90
+
91
+ @register_to_config
92
+ def __init__(
93
+ self,
94
+ compression: int = 2,
95
+ latent_channels: int = 1,
96
+ level_widths: List[int] = (96, 192, 384, 384),
97
+ level_depths: List[int] = (1, 1, 2, 4),
98
+ up_depths: Optional[List[int]] = (1, 1, 2),
99
+ token_patch: int = 4,
100
+ ):
101
+ super().__init__()
102
+ widths, depths = [int(v) for v in level_widths], [int(v) for v in level_depths]
103
+ ups = [int(v) for v in up_depths] if up_depths is not None else depths[:-1]
104
+ self.compression = int(compression)
105
+ self.latent_channels = int(latent_channels)
106
+ self.token_patch = int(token_patch)
107
+ # the latent must tile into the U's 2^(levels-1) and the DiT's token patch
108
+ self.latent_multiple = max(self.token_patch, 2 ** (len(widths) - 1))
109
+ pix = 3 * self.compression ** 2
110
+ self.encoder = UStack(pix, widths, depths, ups, self.latent_channels)
111
+ self.decoder = UStack(self.latent_channels, widths, depths, ups, pix)
112
+
113
+ @property
114
+ def image_multiple(self) -> int:
115
+ """Image sides must be a multiple of this (16 for the released models)."""
116
+ return self.compression * self.latent_multiple
117
+
118
+ @property
119
+ def token_channels(self) -> int:
120
+ """Channels of the patchified latent: 16 for 2x1 (Flux.1 layout), 32 for 2x2 (Flux.2 layout)."""
121
+ return self.latent_channels * self.token_patch ** 2
122
+
123
+ @property
124
+ def token_compression(self) -> int:
125
+ """Spatial compression of the patchified latent (8 for the released models)."""
126
+ return self.compression * self.token_patch
127
+
128
+ def _check_image(self, x: torch.Tensor):
129
+ H, W = x.shape[-2:]
130
+ m = self.image_multiple
131
+ if H % m or W % m:
132
+ raise ValueError(f"image sides must be multiples of {m}, got {H}x{W}")
133
+
134
+ def encode(self, x: torch.Tensor) -> torch.Tensor:
135
+ """x: [B, 3, H, W] in [-1, 1] -> z: [B, latent_channels, H/compression, W/compression]."""
136
+ self._check_image(x)
137
+ return self.encoder(F.pixel_unshuffle(x.to(self.dtype), self.compression))
138
+
139
+ def decode(self, z: torch.Tensor) -> torch.Tensor:
140
+ """z -> x_hat: [B, 3, H, W], nominally in [-1, 1] (not clamped)."""
141
+ return F.pixel_shuffle(self.decoder(z.to(self.dtype)), self.compression)
142
+
143
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
144
+ return self.decode(self.encode(x))
145
+
146
+ def patchify(self, z: torch.Tensor) -> torch.Tensor:
147
+ """Latent -> DiT token grid: [B, C, h, w] -> [B, C * p^2, h/p, w/p] (space-to-depth, p = token_patch)."""
148
+ return F.pixel_unshuffle(z, self.token_patch)
149
+
150
+ def unpatchify(self, tokens: torch.Tensor) -> torch.Tensor:
151
+ """Inverse of `patchify`."""
152
+ return F.pixel_shuffle(tokens, self.token_patch)
153
+
154
+ # ---- loading -----------------------------------------------------------
155
+ HUB_KWARGS = ("cache_dir", "force_download", "proxies", "local_files_only", "token", "revision", "subfolder")
156
+
157
+ @staticmethod
158
+ def config_from_shapes(shapes: dict) -> dict:
159
+ """Architecture from the tensor shapes alone; the file's header metadata is not consulted."""
160
+ widths = [shapes["encoder.conv_in.weight"][0]]
161
+ while f"encoder.downs.{len(widths) - 1}.weight" in shapes:
162
+ widths.append(shapes[f"encoder.downs.{len(widths) - 1}.weight"][0])
163
+ count = lambda pre, i: len({k.split(".")[3] for k in shapes if k.startswith(f"{pre}.{i}.")})
164
+ return {
165
+ "compression": math.isqrt(shapes["encoder.conv_in.weight"][1] // 3),
166
+ "latent_channels": shapes["encoder.conv_out.weight"][0],
167
+ "level_widths": widths,
168
+ "level_depths": [count("encoder.down_blocks", i) for i in range(len(widths))],
169
+ "up_depths": [count("encoder.up_blocks", i) for i in range(len(widths) - 1)],
170
+ }
171
+
172
+ @classmethod
173
+ def from_pretrained(cls, pretrained_model_name_or_path, variant: str = "2x1", weights_name: Optional[str] = None, torch_dtype=None, token_patch: int = 4, **kwargs):
174
+ """`variant` picks `primal_vae_<variant>.safetensors` in the repo ("2x1" or "2x2"); `weights_name` any file."""
175
+ name = weights_name or f"primal_vae_{variant}.safetensors"
176
+ hub = {k: v for k, v in kwargs.items() if k in cls.HUB_KWARGS and v is not None}
177
+ path = str(pretrained_model_name_or_path)
178
+ if os.path.isdir(path):
179
+ file = os.path.join(path, hub.get("subfolder") or "", name)
180
+ else:
181
+ file = hf_hub_download(path, name, **hub)
182
+ with safe_open(file, framework="pt", device="cpu") as f:
183
+ keys = [k for k in f.keys() if k != "arch_spec"] # arch_spec: ai-toolkit's own derived buffer
184
+ cfg = cls.config_from_shapes({k: f.get_slice(k).get_shape() for k in keys})
185
+ with torch.device("meta"):
186
+ model = cls(**cfg, token_patch=token_patch)
187
+ state = {k: f.get_tensor(k) for k in keys}
188
+ if torch_dtype is not None:
189
+ state = {k: v.to(torch_dtype) for k, v in state.items()}
190
+ model.load_state_dict(state, assign=True)
191
+ model.register_to_config(_name_or_path=path)
192
+ return model.eval()
primal_vae_2x1.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9cf37dd0ff80edde481ee2cae858107a71855e6ed5fb495aa095a09b33b9189a
3
+ size 321415508
primal_vae_2x2.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f90813d9a7b2aa4f9334a93dc22c28867f5204b850357fe224029431727be3e8
3
+ size 321422432