| """Consistency-Distillation / Consistency-Training generator wrapper for CIFAR10. |
| |
| Wraps OpenAI's CIFAR10 consistency model (`consistency_models_cifar10`, package |
| `jcm`) so it can stand in as a generator with a decode-style API: build once, |
| then `decode(z)` maps a noise-space input to a 32x32x3 image in [0, 1]. |
| |
| Latents are flat vectors of shape `(batch, 3072)` (== 32*32*3), drawn from |
| N(0, sigma_max**2 * I) and reshaped internally. |
| |
| Only the generator path is exercised - LPIPS / classifier heads / training-time |
| losses are not imported. `distiller_fn` math is inlined (KVE-SDE Karras et al. |
| 2022 conditioning) so we don't depend on `jcm.sde_lib`. |
| """ |
|
|
| from pathlib import Path |
|
|
| import jax |
| import jax.numpy as jnp |
| import ml_collections |
|
|
| |
| from jcm.models import ncsnpp |
| from jcm.models import utils as mutils |
|
|
| Array = jax.Array |
|
|
| IMAGE_SIZE = 32 |
| NUM_CHANNELS = 3 |
| NOISE_DIM = IMAGE_SIZE * IMAGE_SIZE * NUM_CHANNELS |
| SIGMA_MAX = 80.0 |
| SIGMA_MIN = 0.002 |
| DATA_STD = 0.5 |
|
|
|
|
| def _default_config() -> ml_collections.ConfigDict: |
| """NCSN++ config used by the CT-LPIPS CIFAR10 checkpoint.""" |
| c = ml_collections.ConfigDict() |
| c.data = ml_collections.ConfigDict( |
| dict( |
| dataset="CIFAR10", |
| image_size=IMAGE_SIZE, |
| num_channels=NUM_CHANNELS, |
| random_flip=False, |
| uniform_dequantization=False, |
| ) |
| ) |
| c.model = ml_collections.ConfigDict( |
| dict( |
| name="ncsnpp", |
| ema_rate=0.9999, |
| normalization="GroupNorm", |
| nonlinearity="swish", |
| nf=128, |
| ch_mult=(2, 2, 2), |
| num_res_blocks=4, |
| attn_resolutions=(16,), |
| resamp_with_conv=True, |
| conditional=True, |
| fir=True, |
| fir_kernel=[1, 3, 3, 1], |
| skip_rescale=True, |
| resblock_type="biggan", |
| progressive="none", |
| progressive_input="residual", |
| progressive_combine="sum", |
| attention_type="ddpm", |
| embedding_type="fourier", |
| init_scale=0.0, |
| fourier_scale=16, |
| conv_size=3, |
| rho=7.0, |
| data_std=DATA_STD, |
| num_scales=18, |
| dropout=0.0, |
| sigma_min=0.02, |
| sigma_max=100, |
| beta_min=0.1, |
| beta_max=20.0, |
| t_min=SIGMA_MIN, |
| t_max=SIGMA_MAX, |
| double_heads=False, |
| ) |
| ) |
| c.training = ml_collections.ConfigDict(dict(sde="kvesde")) |
| c.sampling = ml_collections.ConfigDict(dict(method="onestep", std=SIGMA_MAX)) |
| return c |
|
|
|
|
| def _build_model_and_shapes(config: ml_collections.ConfigDict, seed: int): |
| import functools |
|
|
| model_def = functools.partial(mutils.get_model("ncsnpp"), config=config) |
| model = model_def() |
| rng = jax.random.PRNGKey(seed) |
| p_rng, d_rng = jax.random.split(rng) |
| fake_x = jnp.zeros((1, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)) |
| fake_t = jnp.zeros((1,), dtype=jnp.float32) |
| variables = model.init({"params": p_rng, "dropout": d_rng}, fake_x, fake_t) |
| return model, variables["params"] |
|
|
|
|
| class CD: |
| """Consistency-Distillation / Consistency-Training generator. |
| |
| Instantiate with `pretrained` naming a checkpoint kind and an explicit |
| `checkpoint_path` pointing at the slim pickle bundle |
| (`{"params", "meta"}`). Latents are flat vectors of shape `(batch, 3072)` |
| for consistency with the target's expected `n_dim`; the wrapper reshapes |
| internally. `decode(z)` returns 32x32x3 images in [0, 1]. |
| """ |
|
|
| z_dim: int = NOISE_DIM |
| image_size: int = IMAGE_SIZE |
| num_channels: int = NUM_CHANNELS |
|
|
| def __init__( |
| self, |
| pretrained: str = "ct-lpips", |
| checkpoint_path: str | Path | None = None, |
| seed: int = 0, |
| ) -> None: |
| if pretrained != "ct-lpips": |
| raise ValueError( |
| f"only 'ct-lpips' is wired up; got {pretrained!r}. " |
| "Add another config in `_default_config` to support others." |
| ) |
| if checkpoint_path is None: |
| raise ValueError("checkpoint_path is required (path to the slim .pkl bundle)") |
|
|
| self.pretrained = pretrained |
| self.sigma_max = SIGMA_MAX |
| self.sigma_min = SIGMA_MIN |
| self.data_std = DATA_STD |
|
|
| self.config = _default_config() |
| self.model, init_params = _build_model_and_shapes(self.config, seed) |
|
|
| import pickle |
|
|
| from flax import serialization |
|
|
| with open(checkpoint_path, "rb") as f: |
| bundle = pickle.load(f) |
| self.params = serialization.from_state_dict(init_params, bundle["params"]) |
|
|
| def _decode(z_flat: Array) -> Array: |
| batch = z_flat.shape[0] |
| |
| x = z_flat.reshape(batch, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS) * SIGMA_MAX |
| img = _distill_one_step(self.model, self.params, x, SIGMA_MAX) |
| |
| return jnp.clip((img + 1.0) * 0.5, 0.0, 1.0) |
|
|
| self._decode = jax.jit(_decode) |
|
|
| def decode(self, z: Array) -> Array: |
| return self._decode(z) |
|
|
| def sample_prior(self, key: Array, n: int) -> Array: |
| z = jax.random.normal(key, (n, self.z_dim)) |
| return self.decode(z) |
|
|
|
|
| def _distill_one_step(model, params, x: Array, sigma: float) -> Array: |
| """Inline KVE-SDE consistency-model one-step distillation (Karras et al. 2022). |
| |
| x: (B, H, W, C) noise-scaled inputs (i.e. x ~ N(0, sigma**2 * I) reshaped). |
| sigma: scalar noise level (== SIGMA_MAX for one-step from prior). |
| """ |
| t = jnp.asarray(sigma, dtype=x.dtype) |
| pred_t = SIGMA_MIN |
| d2 = DATA_STD * DATA_STD |
| in_scale = 1.0 / jnp.sqrt(t * t + d2) |
| cond_t = 0.25 * jnp.log(t) |
| cond_batch = jnp.full((x.shape[0],), cond_t, dtype=x.dtype) |
| raw = model.apply({"params": params}, x * in_scale, cond_batch, train=False) |
| out_scale = (t - pred_t) * DATA_STD / jnp.sqrt(t * t + d2) |
| skip_scale = d2 / ((t - pred_t) ** 2 + d2) |
| return skip_scale * x + out_scale * raw |
|
|