MiniMax-H3-Image-mlx / h3_mlx /coreml_vae.py
devin-lai's picture
Release v0.4.0: selective MLX model downloads
3da88e3 verified
Raw History Blame Contribute Delete
3.38 kB
"""Optional fixed-tile Core ML decoder with the native MLX tile assembly.
Core ML uses FP16 internally. This is an explicitly approximate alternative to
the native BF16 decoder; use the saved-latent image comparisons before choosing.
"""
from __future__ import annotations
from pathlib import Path
import mlx.core as mx
import numpy as np
from .vae import VAE
class CoreMLVAE(VAE):
@staticmethod
def validate_metadata(meta, source_fingerprint, tile_batch):
if meta.get("h3.decoder.format") != "h3-coreml-tile-v1":
raise ValueError("This ML Package lacks H3 decoder provenance metadata")
if meta.get("h3.decoder.source") != source_fingerprint:
raise ValueError(
"Core ML decoder does not match this source VAE checkpoint"
)
fixed_batch = int(meta["h3.decoder.batch"])
if tile_batch != fixed_batch:
raise ValueError(f"ML Package was exported for --vae-batch {fixed_batch}")
return fixed_batch
def __init__(
self,
path,
*,
package,
compute_units="CPU_AND_GPU",
tile_batch=8,
dtype="bfloat16",
compile_blocks=False,
):
if dtype != "bfloat16":
raise ValueError(
"Core ML tile assembly currently requires the native BF16 policy"
)
import coremltools as ct
super().__init__(path, tile_batch=tile_batch, dtype=dtype)
self.package = Path(package)
self.model = ct.models.MLModel(
str(self.package),
compute_units=getattr(ct.ComputeUnit, compute_units),
optimization_hints={
"specializationStrategy": ct.SpecializationStrategy.FastPrediction
},
)
meta = self.model.user_defined_metadata
self.fixed_batch = self.validate_metadata(
meta, self.store.fingerprint(), tile_batch
)
def decode_tiles(self, raw):
if raw.shape[1:] != (24, 16, 16) or raw.shape[0] > self.fixed_batch:
raise ValueError(
"Core ML export requires native 256px tiles and its fixed batch size"
)
count = raw.shape[0]
# BF16 source values are exactly representable by FP16 in the ordinary
# decoder range. Preserve the native input rounding before conversion.
x = raw.astype(mx.bfloat16).astype(mx.float16)
if count < self.fixed_batch:
x = mx.pad(x, ((0, self.fixed_batch - count), (0, 0), (0, 0), (0, 0)))
pixels = self.model.predict({"raw": np.ascontiguousarray(np.asarray(x))})[
"pixels"
]
if not np.isfinite(pixels).all():
raise FloatingPointError("Core ML decoder produced non-finite pixels")
return mx.array(np.ascontiguousarray(pixels[:count])).astype(mx.bfloat16)
def clear(self):
self.model = None
super().clear()
def source_metadata(path, batch):
"""Metadata shared with the exporter, without importing the converter."""
from .weights import WeightStore
return {
"h3.decoder.format": "h3-coreml-tile-v1",
"h3.decoder.source": WeightStore(path).fingerprint(),
"h3.decoder.batch": str(batch),
"h3.decoder.precision": "Source BF16 weights and inputs, FP16 internal arithmetic, BF16 tile assembly",
}