Download capacitor_diffae/model.py from data-archetype/semdisdiffae: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/data-archetype/semdisdiffae/resolve/0cfb8fe7a2b348df85f5ecfcb4148e29e804ae3f/capacitor_diffae/model.py
- Command line
-
hf download hf://data-archetype/semdisdiffae@0cfb8fe7a2b348df85f5ecfcb4148e29e804ae3f/capacitor_diffae/model.py
-
curl -L -o model.py https://huggingface.co/data-archetype/semdisdiffae/resolve/0cfb8fe7a2b348df85f5ecfcb4148e29e804ae3f/capacitor_diffae/model.py
10.4 kB
| """CapacitorDiffAE: standalone HuggingFace-compatible diffusion autoencoder.""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| import torch | |
| from torch import Tensor, nn | |
| from .config import CapacitorDiffAEConfig, CapacitorDiffAEInferenceConfig | |
| from .decoder import Decoder | |
| from .encoder import Encoder, EncoderPosterior | |
| from .samplers import run_ddim, run_dpmpp_2m | |
| from .vp_diffusion import get_schedule, make_initial_state, sample_noise | |
| def _resolve_model_dir( | |
| path_or_repo_id: str | Path, | |
| *, | |
| revision: str | None, | |
| cache_dir: str | Path | None, | |
| ) -> Path: | |
| """Resolve a local path or HuggingFace Hub repo ID to a local directory.""" | |
| local = Path(path_or_repo_id) | |
| if local.is_dir(): | |
| return local | |
| repo_id = str(path_or_repo_id) | |
| try: | |
| from huggingface_hub import snapshot_download | |
| except ImportError: | |
| raise ImportError( | |
| f"'{repo_id}' is not an existing local directory. " | |
| "To download from HuggingFace Hub, install huggingface_hub: " | |
| "pip install huggingface_hub" | |
| ) | |
| cache_dir_str = str(cache_dir) if cache_dir is not None else None | |
| local_dir = snapshot_download( | |
| repo_id, | |
| revision=revision, | |
| cache_dir=cache_dir_str, | |
| ) | |
| return Path(local_dir) | |
| class CapacitorDiffAE(nn.Module): | |
| """Standalone Capacitor DiffAE model for HuggingFace distribution. | |
| A diffusion autoencoder built on FCDM (Fully Convolutional Diffusion Model) | |
| blocks. Encodes images to compact 128-channel spatial latents via a | |
| VP-parameterized diagonal Gaussian posterior, and decodes them back via | |
| iterative VP diffusion with a skip-concat decoder. | |
| Usage:: | |
| model = CapacitorDiffAE.from_pretrained("path/to/weights") | |
| model = model.to("cuda", dtype=torch.bfloat16) | |
| # Encode (returns posterior mode by default) | |
| latents = model.encode(images) # images: [B,3,H,W] in [-1,1] | |
| # Decode (1 step by default — PSNR-optimal) | |
| recon = model.decode(latents, height=H, width=W) | |
| # Reconstruct (encode + 1-step decode) | |
| recon = model.reconstruct(images) | |
| """ | |
| def __init__(self, config: CapacitorDiffAEConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.encoder = Encoder( | |
| in_channels=config.in_channels, | |
| patch_size=config.patch_size, | |
| model_dim=config.model_dim, | |
| depth=config.encoder_depth, | |
| bottleneck_dim=config.bottleneck_dim, | |
| mlp_ratio=config.mlp_ratio, | |
| depthwise_kernel_size=config.depthwise_kernel_size, | |
| bottleneck_posterior_kind=config.bottleneck_posterior_kind, | |
| bottleneck_norm_mode=config.bottleneck_norm_mode, | |
| ) | |
| self.decoder = Decoder( | |
| in_channels=config.in_channels, | |
| patch_size=config.patch_size, | |
| model_dim=config.model_dim, | |
| depth=config.decoder_depth, | |
| start_block_count=config.decoder_start_blocks, | |
| end_block_count=config.decoder_end_blocks, | |
| bottleneck_dim=config.bottleneck_dim, | |
| mlp_ratio=config.mlp_ratio, | |
| depthwise_kernel_size=config.depthwise_kernel_size, | |
| adaln_low_rank_rank=config.adaln_low_rank_rank, | |
| ) | |
| def from_pretrained( | |
| cls, | |
| path_or_repo_id: str | Path, | |
| *, | |
| dtype: torch.dtype = torch.bfloat16, | |
| device: str | torch.device = "cpu", | |
| revision: str | None = None, | |
| cache_dir: str | Path | None = None, | |
| ) -> CapacitorDiffAE: | |
| """Load a pretrained model from a local directory or HuggingFace Hub. | |
| The directory (or repo) should contain: | |
| - config.json: Model architecture config. | |
| - model.safetensors (preferred) or model.pt: Model weights. | |
| Args: | |
| path_or_repo_id: Local directory path or HuggingFace Hub repo ID. | |
| dtype: Load weights in this dtype (float32 or bfloat16). | |
| device: Target device. | |
| revision: Git revision for Hub downloads. | |
| cache_dir: Where to cache Hub downloads. | |
| Returns: | |
| Loaded model in eval mode. | |
| """ | |
| model_dir = _resolve_model_dir( | |
| path_or_repo_id, revision=revision, cache_dir=cache_dir | |
| ) | |
| config = CapacitorDiffAEConfig.load(model_dir / "config.json") | |
| model = cls(config) | |
| safetensors_path = model_dir / "model.safetensors" | |
| pt_path = model_dir / "model.pt" | |
| if safetensors_path.exists(): | |
| try: | |
| from safetensors.torch import load_file | |
| state_dict = load_file(str(safetensors_path), device=str(device)) | |
| except ImportError: | |
| raise ImportError( | |
| "safetensors package required to load .safetensors files. " | |
| "Install with: pip install safetensors" | |
| ) | |
| elif pt_path.exists(): | |
| state_dict = torch.load( | |
| str(pt_path), map_location=device, weights_only=True | |
| ) | |
| else: | |
| raise FileNotFoundError( | |
| f"No model weights found in {model_dir}. " | |
| "Expected model.safetensors or model.pt." | |
| ) | |
| model.load_state_dict(state_dict) | |
| model = model.to(dtype=dtype, device=torch.device(device)) | |
| model.eval() | |
| return model | |
| def encode(self, images: Tensor) -> Tensor: | |
| """Encode images to latents (posterior mode). | |
| Args: | |
| images: [B, 3, H, W] in [-1, 1], H and W divisible by patch_size. | |
| Returns: | |
| Latents [B, bottleneck_dim, H/patch, W/patch]. | |
| """ | |
| try: | |
| model_dtype = next(self.parameters()).dtype | |
| except StopIteration: | |
| model_dtype = torch.float32 | |
| return self.encoder(images.to(dtype=model_dtype)) | |
| def encode_posterior(self, images: Tensor) -> EncoderPosterior: | |
| """Encode images and return the full posterior (mean + logsnr). | |
| Args: | |
| images: [B, 3, H, W] in [-1, 1], H and W divisible by patch_size. | |
| Returns: | |
| EncoderPosterior with mean and logsnr tensors. | |
| """ | |
| try: | |
| model_dtype = next(self.parameters()).dtype | |
| except StopIteration: | |
| model_dtype = torch.float32 | |
| return self.encoder.encode_posterior(images.to(dtype=model_dtype)) | |
| def decode( | |
| self, | |
| latents: Tensor, | |
| height: int, | |
| width: int, | |
| *, | |
| inference_config: CapacitorDiffAEInferenceConfig | None = None, | |
| ) -> Tensor: | |
| """Decode latents to images via VP diffusion. | |
| Args: | |
| latents: [B, bottleneck_dim, h, w] encoder latents. | |
| height: Output image height (divisible by patch_size). | |
| width: Output image width (divisible by patch_size). | |
| inference_config: Optional inference parameters. | |
| Returns: | |
| Reconstructed images [B, 3, H, W] in float32. | |
| """ | |
| cfg = inference_config or CapacitorDiffAEInferenceConfig() | |
| config = self.config | |
| batch = int(latents.shape[0]) | |
| device = latents.device | |
| try: | |
| model_dtype = next(self.parameters()).dtype | |
| except StopIteration: | |
| model_dtype = torch.float32 | |
| if height % config.patch_size != 0 or width % config.patch_size != 0: | |
| raise ValueError( | |
| f"height={height} and width={width} must be divisible by " | |
| f"patch_size={config.patch_size}" | |
| ) | |
| shape = (batch, config.in_channels, height, width) | |
| noise = sample_noise( | |
| shape, | |
| noise_std=config.pixel_noise_std, | |
| seed=cfg.seed, | |
| device=torch.device("cpu"), | |
| dtype=torch.float32, | |
| ) | |
| schedule = get_schedule(cfg.schedule, cfg.num_steps).to(device=device) | |
| initial_state = make_initial_state( | |
| noise=noise.to(device=device), | |
| t_start=schedule[0:1], | |
| logsnr_min=config.logsnr_min, | |
| logsnr_max=config.logsnr_max, | |
| ) | |
| device_type = "cuda" if device.type == "cuda" else "cpu" | |
| with torch.autocast(device_type=device_type, enabled=False): | |
| latents_in = latents.to(device=device) | |
| def _forward_fn( | |
| x_t: Tensor, | |
| t: Tensor, | |
| latents: Tensor, | |
| *, | |
| drop_middle_blocks: bool = False, | |
| mask_latent_tokens: bool = False, | |
| ) -> Tensor: | |
| return self.decoder( | |
| x_t.to(dtype=model_dtype), | |
| t, | |
| latents.to(dtype=model_dtype), | |
| drop_middle_blocks=drop_middle_blocks, | |
| ) | |
| pdg_mode = "path_drop" if cfg.pdg else "disabled" | |
| if cfg.sampler == "ddim": | |
| sampler_fn = run_ddim | |
| elif cfg.sampler == "dpmpp_2m": | |
| sampler_fn = run_dpmpp_2m | |
| else: | |
| raise ValueError( | |
| f"Unsupported sampler: {cfg.sampler!r}. Use 'ddim' or 'dpmpp_2m'." | |
| ) | |
| result = sampler_fn( | |
| forward_fn=_forward_fn, | |
| initial_state=initial_state, | |
| schedule=schedule, | |
| latents=latents_in, | |
| logsnr_min=config.logsnr_min, | |
| logsnr_max=config.logsnr_max, | |
| pdg_mode=pdg_mode, | |
| pdg_strength=cfg.pdg_strength, | |
| device=device, | |
| ) | |
| return result | |
| def reconstruct( | |
| self, | |
| images: Tensor, | |
| *, | |
| inference_config: CapacitorDiffAEInferenceConfig | None = None, | |
| ) -> Tensor: | |
| """Encode then decode. Convenience wrapper. | |
| Args: | |
| images: [B, 3, H, W] in [-1, 1]. | |
| inference_config: Optional inference parameters. | |
| Returns: | |
| Reconstructed images [B, 3, H, W] in float32. | |
| """ | |
| latents = self.encode(images) | |
| _, _, h, w = images.shape | |
| return self.decode( | |
| latents, height=h, width=w, inference_config=inference_config | |
| ) | |