Skip to content

Model Card for creative-graphic-design/pixel-vae-crello

arXiv venue license base dataset vendor-parity hub

This package ports the image encoder and decoder from CanvasVAE, the ICCV 2021 vector-document VAE, into a 🤗transformers-style package. Its MobileNetV2 encoder maps 256 × 256 RGBA previews to the 256-dimensional posterior mean used by CanvasVAE document records. The package is a separate workspace member so it can be converted, serialized, and parity-checked independently before the document model consumes it.

CPU parity passed on one deterministic, untrained TensorFlow state copied to PyTorch: all 17 held-out metrics met their frozen per-metric limits, and state mapping, decode, and encoder save/load checks passed. This verifies conversion only; no trained-checkpoint or embedding-quality result is claimed. See TRAINING.md for the evidence and EMBEDDING_CONTRACT.md for the shared Crello input and output contract.

Model Details

Model Description

PixelVAE receives four-channel RGBA values at their original 256 × 256 size. A MobileNetV2 encoder produces posterior mean and log-variance vectors with 256 dimensions; PixelVAEEncoder.encode exports the posterior mean without sampling. The decoder produces 16 categorical logits for each RGBA channel after removing the lowest four bits of source pixels. Its objective combines alpha-masked RGB reconstruction loss, alpha reconstruction loss, a KL term weighted by 100, and L2 regularization on the variational heads.

  • Developed by: Kota Yamaguchi.
  • Shared by: creative-graphic-design.
  • Model type: content-aware; task: evaluation; conditioning: evaluation.
  • Language(s) (NLP): not applicable.
  • License: Apache-2.0.

Model Sources

  • Repository: CanvasVAE source, pinned at bc1e2072ba3a253f1b099e8b0c604f6051e787da for conversion and parity.
  • Paper: CanvasVAE, ICCV 2021.
  • Released weights: none; the conversion script can convert an original TensorFlow checkpoint or a deterministic untrained initialization.

Supported Checkpoints

Checkpoint Hub ID Status
Crello v1 creative-graphic-design/pixel-vae-crello not-published

No trained checkpoint is included or published. The seed-0 untrained conversion is only a deterministic parity fixture and is not suitable for semantic retrieval or design decisions.

Uses

Direct Use

Use PixelVAEEncoder to reproduce original posterior-mean image embeddings or to supply those embeddings to a compatible CanvasVAE document pipeline. The document encoder accepts every element's PNG bytes, regardless of element type. PixelVAE training examples use the original three-type filter (imageElement, maskElement, and svgElement).

Downstream Use

CanvasVAE may consume embeddings in the order recorded by its document manifest. Use the shared embedding contract for exact PNG decoding, ID ownership, output dtype, and row ordering.

Out-of-Scope Use

The seed-0 untrained encoder does not provide meaningful visual similarity, semantic understanding, or a quality-ranked embedding space. The package does not render vector documents or clear Crello designs for redistribution.

Bias, Risks, and Limitations

This port targets the original 256 × 256 RGBA path and rejects other dimensions instead of resizing them. The crello-dataset-v1.zip archive and all derived embeddings remain private. Crello design previews may contain third-party artwork; do not redistribute the archive, previews, or embeddings.

Recommendations

Use an encoder converted from a trained original checkpoint for research applications, preserve the original document element order, and evaluate downstream behavior on the intended data. CPU agreement on an untrained state verifies the conversion path, not trained embedding quality.

How to Get Started with the Model

Install the package directly from this repository. laygen is included because PixelVAE uses the shared vendor-path helper during conversion.

pip install \
  "laygen @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=lib/laygen" \
  "pixel-vae @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=models/pixel-vae"

For conversion and CPU reproduction, clone the repository and follow REPRODUCING.md. The commands there save an encoder under .cache/pixel-vae/converted/encoder.

git clone https://github.com/creative-graphic-design/design-generators.git
cd design-generators
uv sync --package pixel-vae
uv run --package pixel-vae python
from io import BytesIO

import torch
from PIL import Image

from pixel_vae import PixelVAEEncoder, PixelVAEImageProcessor

stream = BytesIO()
Image.new("RGBA", (256, 256)).save(stream, format="PNG")
# After Hub publication: from_pretrained("creative-graphic-design/pixel-vae-crello")
encoder = PixelVAEEncoder.from_pretrained(".cache/pixel-vae/converted/encoder").eval()
inputs = PixelVAEImageProcessor()(stream.getvalue())
with torch.inference_mode():
    embedding = encoder.encode(inputs.pixel_values)

print(embedding.shape, embedding.dtype)
Captured output will be added after running the example against the converted CPU checkpoint.

Training Details

Training Data

Dataset Dataset ID Notes
Crello cyberagent/crello Original-code parity uses a private v1 archive; the archive and derived embeddings stay private.

Original-code parity uses crello-dataset-v1.zip, 2,989,732,284 bytes with SHA-256 f6cab2d0c4d888f5082e3b19cfa841c6f483cecdfcbc02a30bc87bd3393cf91e. Keep the archive and derived embeddings private.

Training Procedure

This package does not include a package-local training loop or a trained checkpoint. It supports checkpoint conversion and CPU parity; GPU training, full-run evaluation, and S5 are out of scope.

Preprocessing

Decode every document-stage image_bytes PNG to RGBA at its original 256 × 256 dimensions, then map byte values to float32 [0, 1]. The encoder consumes all element types for document construction. Original PixelVAE training records select only imageElement, maskElement, and svgElement.

Training Hyperparameters

  • Training regime: not retrained; the original configuration uses batch size 64, 250 epochs, Adam at 1e-4, KL weight 100, and L2 weight 1e-6.

Evaluation

Testing Data, Factors & Metrics

Testing Data

CPU parity starts with a report-only diagnostic, then uses three fixed, distinct canonical train input pairs for calibration and the canonical test inputs for a fresh held-out check. The full archive audit checks PNG validity and dimensions by element type without writing image data into the repository.

Factors

The source and package receive identical PNG bytes decoded to RGBA uint8 values, scaled to float32 [0, 1], and transposed to channels-first order for PyTorch. The test records the source commit, TensorFlow version, seed, encoder-state digest, and stable input IDs.

Metrics

Compare maximum absolute error for posterior means, posterior log variances, decoder logits, reconstruction/KL/total loss, gradients, Keras Adam moments, updated parameters, and a three-step short trace. Calibration uses three independent CPU processes on distinct train image groups and freezes max(L, ceil2(1.5 × max)) per metric, rounding upward to two significant figures while preserving the per-metric floor L from parity-limit-floors.json. The held-out run uses a fresh normal-mode CPU process; image decode and structural S0/S4 gates require exact equality.

Parity Results

The CPU acceptance passed against one deterministic, untrained TensorFlow state copied to PyTorch. All 17 held-out metrics met their frozen per-metric limits; state mapping and exact decode checks passed. The example limit below illustrates the metric-specific thresholds; PARITY_PROTOCOL.md lists every frozen limit alongside the original worker-run held-out maximum absolute error.

Check Cases Criterion Result
S0 state mapping 1 untrained TensorFlow state; all mapped variables exact config, one-to-one keys, and bitwise transformed tensors pass; all checks exact
S1 forward and losses 6 held-out metrics each metric within its frozen absolute bound; posterior mean atol=1.4e-10 pass; 6/6 metrics
S2 gradients and optimizer 6 held-out metrics each metric within its frozen absolute bound pass; 6/6 metrics
S3 three-step trace 4 held-out metrics each metric within its frozen absolute bound pass; 4/4 metrics
S4 archive, decode, and serialization 23,361 documents; 5 element types; 1 encoder round trip; 1 held-out metric all archive PNGs valid at 256 × 256; exact RGBA decode and round trip; metric within frozen bound pass; full audit and exact checks; 1/1 metric

Reproducibility

Reproduce agreement with the original TensorFlow implementation using REPRODUCING.md, which covers the pinned source, private Crello archive, three-run CPU calibration, held-out validation, conversion, and local loading. No trained-checkpoint reproduction is claimed.

Technical Specifications

Model Architecture and Objective

The encoder uses Keras MobileNetV2 topology with a four-channel first convolution, global average pooling, and two dense variational heads. The decoder projects 256 latent values to an 8 × 8 × 32 grid, applies five transposed-convolution upsampling layers and one output convolution, then reshapes logits to [batch, 256, 256, 4, 16].

Compute Infrastructure

Hardware

The only verified runtime in this package is CPU; GPU training and S5 evaluation have no result and are not claimed.

Software

The model uses PyTorch and 🤗transformers. Conversion and source comparison require TensorFlow CPU 2.15.1. See the package pyproject.toml for workspace dependencies.

Citation

@inproceedings{yamaguchi2021canvasvae,
  title={CanvasVAE: Learning to Generate Vector Graphic Documents},
  author={Yamaguchi, Kota},
  booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision},
  year={2021}
}

API Reference

Transformers-style PixelVAE model package.

PixelVAEConfig

Bases: PretrainedConfig

Configuration for the 256 × 256 RGBA MobileNetV2 VAE.

Parameters:

Name Type Description Default
latent_dim int

Dimension of each posterior vector.

256
quantize_factor int

Number of low bits removed from each target channel.

4
kl_weight float

Multiplier on the mean KL divergence.

100.0
l2_weight float

L2 coefficient for the two variational-head dense layers.

1e-06
batch_norm_epsilon float

Variance epsilon used by batch normalization.

0.001
backbone_batch_norm_momentum float

Moving-average momentum in MobileNetV2.

0.999
decoder_batch_norm_momentum float

Moving-average momentum in the decoder.

0.99
**kwargs str | int | float | bool | None

Extra Transformers configuration fields.

{}

Examples:

>>> PixelVAEConfig().latent_dim
256
Source code in models/pixel-vae/src/pixel_vae/configuration_pixel_vae.py
 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
class PixelVAEConfig(PretrainedConfig):
    """Configuration for the 256 × 256 RGBA MobileNetV2 VAE.

    Args:
        latent_dim: Dimension of each posterior vector.
        quantize_factor: Number of low bits removed from each target channel.
        kl_weight: Multiplier on the mean KL divergence.
        l2_weight: L2 coefficient for the two variational-head dense layers.
        batch_norm_epsilon: Variance epsilon used by batch normalization.
        backbone_batch_norm_momentum: Moving-average momentum in MobileNetV2.
        decoder_batch_norm_momentum: Moving-average momentum in the decoder.
        **kwargs: Extra Transformers configuration fields.

    Examples:
        >>> PixelVAEConfig().latent_dim
        256
    """

    model_type = "pixel-vae"

    def __init__(
        self,
        *,
        latent_dim: int = 256,
        quantize_factor: int = 4,
        kl_weight: float = 100.0,
        l2_weight: float = 1e-6,
        batch_norm_epsilon: float = 1e-3,
        backbone_batch_norm_momentum: float = 0.999,
        decoder_batch_norm_momentum: float = 0.99,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize the configuration with the production model defaults."""
        if latent_dim <= 0:
            raise ValueError("latent_dim must be positive")

        if quantize_factor < 0 or quantize_factor > 8:
            raise ValueError("quantize_factor must be between 0 and 8")

        if kl_weight < 0 or l2_weight < 0:
            raise ValueError("loss weights must be non-negative")

        if batch_norm_epsilon <= 0:
            raise ValueError("batch_norm_epsilon must be positive")

        if not 0 <= backbone_batch_norm_momentum < 1:
            raise ValueError("backbone_batch_norm_momentum must be in [0, 1)")

        if not 0 <= decoder_batch_norm_momentum < 1:
            raise ValueError("decoder_batch_norm_momentum must be in [0, 1)")

        super().__init__(**kwargs)  # ty: ignore[invalid-argument-type]
        self.latent_dim = latent_dim
        self.quantize_factor = quantize_factor
        self.kl_weight = kl_weight
        self.l2_weight = l2_weight
        self.batch_norm_epsilon = batch_norm_epsilon
        self.backbone_batch_norm_momentum = backbone_batch_norm_momentum
        self.decoder_batch_norm_momentum = decoder_batch_norm_momentum

__init__

__init__(
    *,
    latent_dim: int = 256,
    quantize_factor: int = 4,
    kl_weight: float = 100.0,
    l2_weight: float = 1e-06,
    batch_norm_epsilon: float = 0.001,
    backbone_batch_norm_momentum: float = 0.999,
    decoder_batch_norm_momentum: float = 0.99,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize the configuration with the production model defaults.

Source code in models/pixel-vae/src/pixel_vae/configuration_pixel_vae.py
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
def __init__(
    self,
    *,
    latent_dim: int = 256,
    quantize_factor: int = 4,
    kl_weight: float = 100.0,
    l2_weight: float = 1e-6,
    batch_norm_epsilon: float = 1e-3,
    backbone_batch_norm_momentum: float = 0.999,
    decoder_batch_norm_momentum: float = 0.99,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize the configuration with the production model defaults."""
    if latent_dim <= 0:
        raise ValueError("latent_dim must be positive")

    if quantize_factor < 0 or quantize_factor > 8:
        raise ValueError("quantize_factor must be between 0 and 8")

    if kl_weight < 0 or l2_weight < 0:
        raise ValueError("loss weights must be non-negative")

    if batch_norm_epsilon <= 0:
        raise ValueError("batch_norm_epsilon must be positive")

    if not 0 <= backbone_batch_norm_momentum < 1:
        raise ValueError("backbone_batch_norm_momentum must be in [0, 1)")

    if not 0 <= decoder_batch_norm_momentum < 1:
        raise ValueError("decoder_batch_norm_momentum must be in [0, 1)")

    super().__init__(**kwargs)  # ty: ignore[invalid-argument-type]
    self.latent_dim = latent_dim
    self.quantize_factor = quantize_factor
    self.kl_weight = kl_weight
    self.l2_weight = l2_weight
    self.batch_norm_epsilon = batch_norm_epsilon
    self.backbone_batch_norm_momentum = backbone_batch_norm_momentum
    self.decoder_batch_norm_momentum = decoder_batch_norm_momentum

PixelVAEImageProcessor

Bases: BaseImageProcessor

Decode PNG bytes to unmodified 256 × 256 RGBA model inputs.

Examples:

>>> from io import BytesIO
>>> from PIL import Image
>>> stream = BytesIO()
>>> Image.new("RGBA", (256, 256)).save(stream, format="PNG")
>>> batch = PixelVAEImageProcessor()(stream.getvalue())
>>> tuple(batch.pixel_values.shape)
(1, 4, 256, 256)
Source code in models/pixel-vae/src/pixel_vae/image_processing_pixel_vae.py
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
class PixelVAEImageProcessor(BaseImageProcessor):
    """Decode PNG bytes to unmodified 256 × 256 RGBA model inputs.

    Examples:
        >>> from io import BytesIO
        >>> from PIL import Image
        >>> stream = BytesIO()
        >>> Image.new("RGBA", (256, 256)).save(stream, format="PNG")
        >>> batch = PixelVAEImageProcessor()(stream.getvalue())
        >>> tuple(batch.pixel_values.shape)
        (1, 4, 256, 256)
    """

    model_input_names = ["pixel_values"]

    def __init__(self, **kwargs: str | int | float | bool | None) -> None:
        """Initialize the processor without adding transform settings."""
        super().__init__(**kwargs)

    def preprocess(
        self,
        images: bytes | Sequence[bytes],
        *,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchFeature:
        """Decode PNG bytes and scale the exact RGBA pixels to ``[0, 1]``.

        Args:
            images: One PNG byte string or a sequence of PNG byte strings.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            A batch feature with float32 ``pixel_values`` shaped ``[B, 4, 256, 256]``.

        Raises:
            ValueError: If a PNG is malformed, is not 256 × 256, or another
                tensor framework is requested.
        """
        if return_tensors != "pt":
            raise ValueError("PixelVAEImageProcessor only supports return_tensors='pt'")

        image_batch = [images] if isinstance(images, bytes) else list(images)
        if not image_batch:
            raise ValueError("at least one PNG image is required")

        decoded = np.stack([decode_pixelvae_png(image) for image in image_batch])
        pixel_values: Float[torch.Tensor, "batch 4 256 256"] = (
            torch.from_numpy(decoded)
            .permute(0, 3, 1, 2)
            .to(torch.float32)
            .mul(1.0 / 255.0)
        )
        return BatchFeature({"pixel_values": pixel_values})

__init__

__init__(**kwargs: str | int | float | bool | None) -> None

Initialize the processor without adding transform settings.

Source code in models/pixel-vae/src/pixel_vae/image_processing_pixel_vae.py
35
36
37
def __init__(self, **kwargs: str | int | float | bool | None) -> None:
    """Initialize the processor without adding transform settings."""
    super().__init__(**kwargs)

preprocess

preprocess(
    images: bytes | Sequence[bytes],
    *,
    return_tensors: Literal["pt"] = "pt",
) -> BatchFeature

Decode PNG bytes and scale the exact RGBA pixels to [0, 1].

Parameters:

Name Type Description Default
images bytes | Sequence[bytes]

One PNG byte string or a sequence of PNG byte strings.

required
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchFeature

A batch feature with float32 pixel_values shaped [B, 4, 256, 256].

Raises:

Type Description
ValueError

If a PNG is malformed, is not 256 × 256, or another tensor framework is requested.

Source code in models/pixel-vae/src/pixel_vae/image_processing_pixel_vae.py
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
def preprocess(
    self,
    images: bytes | Sequence[bytes],
    *,
    return_tensors: Literal["pt"] = "pt",
) -> BatchFeature:
    """Decode PNG bytes and scale the exact RGBA pixels to ``[0, 1]``.

    Args:
        images: One PNG byte string or a sequence of PNG byte strings.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        A batch feature with float32 ``pixel_values`` shaped ``[B, 4, 256, 256]``.

    Raises:
        ValueError: If a PNG is malformed, is not 256 × 256, or another
            tensor framework is requested.
    """
    if return_tensors != "pt":
        raise ValueError("PixelVAEImageProcessor only supports return_tensors='pt'")

    image_batch = [images] if isinstance(images, bytes) else list(images)
    if not image_batch:
        raise ValueError("at least one PNG image is required")

    decoded = np.stack([decode_pixelvae_png(image) for image in image_batch])
    pixel_values: Float[torch.Tensor, "batch 4 256 256"] = (
        torch.from_numpy(decoded)
        .permute(0, 3, 1, 2)
        .to(torch.float32)
        .mul(1.0 / 255.0)
    )
    return BatchFeature({"pixel_values": pixel_values})

PixelVAEEncoder

Bases: PreTrainedModel

MobileNetV2 encoder that exports posterior-mean embeddings.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
class PixelVAEEncoder(PreTrainedModel):
    """MobileNetV2 encoder that exports posterior-mean embeddings."""

    config_class = PixelVAEConfig
    base_model_prefix = "pixel_vae_encoder"
    main_input_name = "pixel_values"

    def __init__(self, config: PixelVAEConfig) -> None:
        """Create the feature extractor and posterior heads."""
        super().__init__(config)
        self.backbone = PixelVAEMobileNetV2(config)
        self.z_mean = nn.Linear(self.backbone.output_channels, config.latent_dim)
        self.z_log_variance = nn.Linear(
            self.backbone.output_channels, config.latent_dim
        )
        self.post_init()

    def forward(
        self, pixel_values: Float[torch.Tensor, "batch 4 256 256"]
    ) -> PixelVAEEncoderOutput:
        """Return posterior mean and log variance without sampling."""
        _validate_pixel_values(pixel_values)
        features = self.backbone(pixel_values)
        return PixelVAEEncoderOutput(
            posterior_mean=self.z_mean(features),
            posterior_log_variance=self.z_log_variance(features),
        )

    def encode(
        self, pixel_values: Float[torch.Tensor, "batch 4 256 256"]
    ) -> Float[torch.Tensor, "batch latent"]:
        """Return the float32 posterior mean used by Crello documents."""
        result = self(pixel_values)
        assert result.posterior_mean is not None
        return result.posterior_mean

    def _init_weights(self, module: nn.Module) -> None:
        if isinstance(module, nn.Linear):
            nn.init.xavier_uniform_(module.weight)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Conv2d):
            nn.init.xavier_uniform_(module.weight)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
        elif isinstance(module, PixelVAEBatchNorm2d):
            nn.init.ones_(module.weight)
            nn.init.zeros_(module.bias)

__init__

__init__(config: PixelVAEConfig) -> None

Create the feature extractor and posterior heads.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
259
260
261
262
263
264
265
266
267
def __init__(self, config: PixelVAEConfig) -> None:
    """Create the feature extractor and posterior heads."""
    super().__init__(config)
    self.backbone = PixelVAEMobileNetV2(config)
    self.z_mean = nn.Linear(self.backbone.output_channels, config.latent_dim)
    self.z_log_variance = nn.Linear(
        self.backbone.output_channels, config.latent_dim
    )
    self.post_init()

forward

forward(
    pixel_values: Float[Tensor, "batch 4 256 256"],
) -> PixelVAEEncoderOutput

Return posterior mean and log variance without sampling.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
269
270
271
272
273
274
275
276
277
278
def forward(
    self, pixel_values: Float[torch.Tensor, "batch 4 256 256"]
) -> PixelVAEEncoderOutput:
    """Return posterior mean and log variance without sampling."""
    _validate_pixel_values(pixel_values)
    features = self.backbone(pixel_values)
    return PixelVAEEncoderOutput(
        posterior_mean=self.z_mean(features),
        posterior_log_variance=self.z_log_variance(features),
    )

encode

encode(
    pixel_values: Float[Tensor, "batch 4 256 256"],
) -> Float[torch.Tensor, "batch latent"]

Return the float32 posterior mean used by Crello documents.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
280
281
282
283
284
285
286
def encode(
    self, pixel_values: Float[torch.Tensor, "batch 4 256 256"]
) -> Float[torch.Tensor, "batch latent"]:
    """Return the float32 posterior mean used by Crello documents."""
    result = self(pixel_values)
    assert result.posterior_mean is not None
    return result.posterior_mean

PixelVAEEncoderOutput dataclass

Bases: ModelOutput

Posterior statistics emitted by :class:PixelVAEEncoder.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
32
33
34
35
36
37
@dataclass
class PixelVAEEncoderOutput(ModelOutput):
    """Posterior statistics emitted by :class:`PixelVAEEncoder`."""

    posterior_mean: Float[torch.Tensor, "batch latent"] | None = None
    posterior_log_variance: Float[torch.Tensor, "batch latent"] | None = None

PixelVAEModel

Bases: PreTrainedModel

Transformers model implementing the RGBA PixelVAE objective.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
class PixelVAEModel(PreTrainedModel):
    """Transformers model implementing the RGBA PixelVAE objective."""

    config_class = PixelVAEConfig
    base_model_prefix = "pixel_vae"
    main_input_name = "pixel_values"

    def __init__(self, config: PixelVAEConfig) -> None:
        """Create the posterior encoder and autoregressive image decoder."""
        super().__init__(config)
        self.encoder = PixelVAEEncoder(config)
        self.decoder = PixelVAEDecoder(config)
        self.post_init()

    def encode(
        self, pixel_values: Float[torch.Tensor, "batch 4 256 256"]
    ) -> Float[torch.Tensor, "batch latent"]:
        """Export the encoder posterior mean without sampling."""
        return self.encoder.encode(pixel_values)

    def forward(
        self,
        pixel_values: Float[torch.Tensor, "batch 4 256 256"],
        *,
        sample: bool | None = None,
        epsilon: Float[torch.Tensor, "batch latent"] | None = None,
    ) -> PixelVAEOutput:
        """Compute logits and the source reconstruction, KL, and L2 losses.

        Args:
            pixel_values: Float32 RGBA images scaled to ``[0, 1]``.
            sample: Whether to sample the posterior; defaults to module mode.
            epsilon: Optional fixed standard-normal noise for deterministic runs.

        Returns:
            Decoder logits, posterior statistics, latent vectors, and losses.

        Raises:
            ValueError: If image dimensions or sampling-noise shape are invalid.
            TypeError: If inputs are not float32 tensors.

        Examples:
            >>> model = PixelVAEModel(PixelVAEConfig(latent_dim=8)).eval()
            >>> image = torch.zeros(1, 4, 256, 256)
            >>> model(image).posterior_mean.shape
            torch.Size([1, 8])
        """
        encoded = self.encoder(pixel_values)
        assert encoded.posterior_mean is not None
        assert encoded.posterior_log_variance is not None
        z_mean, z_log_variance = encoded.posterior_mean, encoded.posterior_log_variance
        should_sample = self.training if sample is None else sample
        if should_sample:
            if epsilon is None:
                epsilon = randn(
                    z_mean.shape,
                    device=z_mean.device,
                    dtype=z_mean.dtype,
                )

            if epsilon.shape != z_mean.shape:
                raise ValueError("epsilon must have the same shape as the posterior")

            latents = z_mean + z_log_variance.mul(0.5).exp() * epsilon
        else:
            if epsilon is not None:
                raise ValueError("epsilon is only valid when sample=True")

            latents = z_mean

        logits = self.decoder(latents)
        reconstruction_loss = pixelvae_reconstruction_loss(
            pixel_values, logits, self.config.quantize_factor
        )
        kl_divergence = (
            -0.5 * (1 + z_log_variance - z_mean.square() - z_log_variance.exp()).mean()
        )
        l2_loss = self.config.l2_weight * (
            self.encoder.z_mean.weight.square().sum()
            + self.encoder.z_mean.bias.square().sum()
            + self.encoder.z_log_variance.weight.square().sum()
            + self.encoder.z_log_variance.bias.square().sum()
        )
        loss = reconstruction_loss + self.config.kl_weight * kl_divergence + l2_loss
        return PixelVAEOutput(
            loss=loss,
            reconstruction_loss=reconstruction_loss,
            kl_divergence=kl_divergence,
            logits=logits,
            posterior_mean=z_mean,
            posterior_log_variance=z_log_variance,
            latents=latents,
        )

    def _init_weights(self, module: nn.Module) -> None:
        if isinstance(module, nn.Linear):
            nn.init.xavier_uniform_(module.weight)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Conv2d | nn.ConvTranspose2d):
            nn.init.xavier_uniform_(module.weight)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
        elif isinstance(module, PixelVAEBatchNorm2d):
            nn.init.ones_(module.weight)
            nn.init.zeros_(module.bias)

__init__

__init__(config: PixelVAEConfig) -> None

Create the posterior encoder and autoregressive image decoder.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
371
372
373
374
375
376
def __init__(self, config: PixelVAEConfig) -> None:
    """Create the posterior encoder and autoregressive image decoder."""
    super().__init__(config)
    self.encoder = PixelVAEEncoder(config)
    self.decoder = PixelVAEDecoder(config)
    self.post_init()

encode

encode(
    pixel_values: Float[Tensor, "batch 4 256 256"],
) -> Float[torch.Tensor, "batch latent"]

Export the encoder posterior mean without sampling.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
378
379
380
381
382
def encode(
    self, pixel_values: Float[torch.Tensor, "batch 4 256 256"]
) -> Float[torch.Tensor, "batch latent"]:
    """Export the encoder posterior mean without sampling."""
    return self.encoder.encode(pixel_values)

forward

forward(
    pixel_values: Float[Tensor, "batch 4 256 256"],
    *,
    sample: bool | None = None,
    epsilon: Float[Tensor, "batch latent"] | None = None,
) -> PixelVAEOutput

Compute logits and the source reconstruction, KL, and L2 losses.

Parameters:

Name Type Description Default
pixel_values Float[Tensor, 'batch 4 256 256']

Float32 RGBA images scaled to [0, 1].

required
sample bool | None

Whether to sample the posterior; defaults to module mode.

None
epsilon Float[Tensor, 'batch latent'] | None

Optional fixed standard-normal noise for deterministic runs.

None

Returns:

Type Description
PixelVAEOutput

Decoder logits, posterior statistics, latent vectors, and losses.

Raises:

Type Description
ValueError

If image dimensions or sampling-noise shape are invalid.

TypeError

If inputs are not float32 tensors.

Examples:

>>> model = PixelVAEModel(PixelVAEConfig(latent_dim=8)).eval()
>>> image = torch.zeros(1, 4, 256, 256)
>>> model(image).posterior_mean.shape
torch.Size([1, 8])
Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
def forward(
    self,
    pixel_values: Float[torch.Tensor, "batch 4 256 256"],
    *,
    sample: bool | None = None,
    epsilon: Float[torch.Tensor, "batch latent"] | None = None,
) -> PixelVAEOutput:
    """Compute logits and the source reconstruction, KL, and L2 losses.

    Args:
        pixel_values: Float32 RGBA images scaled to ``[0, 1]``.
        sample: Whether to sample the posterior; defaults to module mode.
        epsilon: Optional fixed standard-normal noise for deterministic runs.

    Returns:
        Decoder logits, posterior statistics, latent vectors, and losses.

    Raises:
        ValueError: If image dimensions or sampling-noise shape are invalid.
        TypeError: If inputs are not float32 tensors.

    Examples:
        >>> model = PixelVAEModel(PixelVAEConfig(latent_dim=8)).eval()
        >>> image = torch.zeros(1, 4, 256, 256)
        >>> model(image).posterior_mean.shape
        torch.Size([1, 8])
    """
    encoded = self.encoder(pixel_values)
    assert encoded.posterior_mean is not None
    assert encoded.posterior_log_variance is not None
    z_mean, z_log_variance = encoded.posterior_mean, encoded.posterior_log_variance
    should_sample = self.training if sample is None else sample
    if should_sample:
        if epsilon is None:
            epsilon = randn(
                z_mean.shape,
                device=z_mean.device,
                dtype=z_mean.dtype,
            )

        if epsilon.shape != z_mean.shape:
            raise ValueError("epsilon must have the same shape as the posterior")

        latents = z_mean + z_log_variance.mul(0.5).exp() * epsilon
    else:
        if epsilon is not None:
            raise ValueError("epsilon is only valid when sample=True")

        latents = z_mean

    logits = self.decoder(latents)
    reconstruction_loss = pixelvae_reconstruction_loss(
        pixel_values, logits, self.config.quantize_factor
    )
    kl_divergence = (
        -0.5 * (1 + z_log_variance - z_mean.square() - z_log_variance.exp()).mean()
    )
    l2_loss = self.config.l2_weight * (
        self.encoder.z_mean.weight.square().sum()
        + self.encoder.z_mean.bias.square().sum()
        + self.encoder.z_log_variance.weight.square().sum()
        + self.encoder.z_log_variance.bias.square().sum()
    )
    loss = reconstruction_loss + self.config.kl_weight * kl_divergence + l2_loss
    return PixelVAEOutput(
        loss=loss,
        reconstruction_loss=reconstruction_loss,
        kl_divergence=kl_divergence,
        logits=logits,
        posterior_mean=z_mean,
        posterior_log_variance=z_log_variance,
        latents=latents,
    )

PixelVAEOutput dataclass

Bases: ModelOutput

Reconstruction, posterior statistics, and losses from PixelVAE.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
40
41
42
43
44
45
46
47
48
49
50
@dataclass
class PixelVAEOutput(ModelOutput):
    """Reconstruction, posterior statistics, and losses from PixelVAE."""

    loss: Float[torch.Tensor, ""] | None = None
    reconstruction_loss: Float[torch.Tensor, ""] | None = None
    kl_divergence: Float[torch.Tensor, ""] | None = None
    logits: Float[torch.Tensor, "batch height width channels levels"] | None = None
    posterior_mean: Float[torch.Tensor, "batch latent"] | None = None
    posterior_log_variance: Float[torch.Tensor, "batch latent"] | None = None
    latents: Float[torch.Tensor, "batch latent"] | None = None

pixelvae_reconstruction_loss

pixelvae_reconstruction_loss(
    pixel_values: Float[Tensor, "batch 4 256 256"],
    logits: Float[
        Tensor, "batch height width channels levels"
    ],
    quantize_factor: int = 4,
) -> Float[torch.Tensor, ""]

Compute the original alpha-masked RGB and unmasked alpha loss.

Parameters:

Name Type Description Default
pixel_values Float[Tensor, 'batch 4 256 256']

Float32 RGBA images scaled from source bytes.

required
logits Float[Tensor, 'batch height width channels levels']

Per-channel categorical logits.

required
quantize_factor int

Number of low source bits removed before CE.

4

Returns:

Type Description
Float[Tensor, '']

Reconstruction loss summed over rows and averaged over batch, columns,

Float[Tensor, '']

and channels.

Source code in models/pixel-vae/src/pixel_vae/modeling_pixel_vae.py
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def pixelvae_reconstruction_loss(
    pixel_values: Float[torch.Tensor, "batch 4 256 256"],
    logits: Float[torch.Tensor, "batch height width channels levels"],
    quantize_factor: int = 4,
) -> Float[torch.Tensor, ""]:
    """Compute the original alpha-masked RGB and unmasked alpha loss.

    Args:
        pixel_values: Float32 RGBA images scaled from source bytes.
        logits: Per-channel categorical logits.
        quantize_factor: Number of low source bits removed before CE.

    Returns:
        Reconstruction loss summed over rows and averaged over batch, columns,
        and channels.
    """
    if pixel_values.ndim != 4 or tuple(pixel_values.shape[1:]) != (
        4,
        IMAGE_SIDE,
        IMAGE_SIDE,
    ):
        raise ValueError("pixel_values must have shape [batch, 4, 256, 256]")

    targets = torch.round(pixel_values * 255).to(torch.uint8).permute(0, 2, 3, 1)
    targets = torch.bitwise_right_shift(targets, quantize_factor).long()
    if logits.shape != (*targets.shape, QUANTIZATION_LEVELS):
        raise ValueError("logits must have shape [batch, 256, 256, 4, 16]")

    per_channel = F.cross_entropy(
        logits.permute(0, 4, 1, 2, 3), targets, reduction="none"
    )
    rgb_mask = (targets[..., 3:4] > 0).to(per_channel.dtype)
    rgb_loss = per_channel[..., :3] * rgb_mask
    alpha_loss = per_channel[..., 3:4]
    return (rgb_loss + alpha_loss).sum(dim=1).mean()

options: show_submodules: true