Spaces:
Running on Zero
Running on Zero
Initial UniSpace demo
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +4 -0
- LICENSE-UNISPACE +29 -0
- README.md +47 -15
- THIRD_PARTY.md +19 -0
- app.py +507 -719
- examples/background_change.png +3 -0
- examples/color_alter.png +3 -0
- examples/material_alter.png +3 -0
- examples/subject-add.png +3 -0
- patch-reparameterization/configs/decoder/ViTXL_nockpt/config.json +1 -1
- patch-reparameterization/configs/release/pr-dinov2-imagenet256-generation.yaml +55 -0
- patch-reparameterization/configs/release/pr-dinov2-imagenet256.yaml +66 -0
- patch-reparameterization/configs/release/pr-qwen-vit-imagenet256.yaml +26 -0
- patch-reparameterization/configs/release/pr-siglip2-imagenet256-generation.yaml +55 -0
- patch-reparameterization/configs/release/pr-siglip2-imagenet256.yaml +73 -0
- patch-reparameterization/src/eval/__init__.py +580 -0
- patch-reparameterization/src/eval/clip_t.py +363 -0
- patch-reparameterization/src/eval/clip_test.py +567 -0
- patch-reparameterization/src/eval/clip_zero_shot.py +714 -0
- patch-reparameterization/src/eval/clip_zero_shot_bigvision.py +562 -0
- patch-reparameterization/src/eval/fid.py +238 -0
- patch-reparameterization/src/eval/ref_iqa.py +93 -0
- patch-reparameterization/src/eval/utils.py +155 -0
- patch-reparameterization/src/eval_only.py +927 -0
- patch-reparameterization/src/sample_ddp.py +753 -0
- patch-reparameterization/src/stage1/encoders/__init__.py +5 -0
- patch-reparameterization/src/stage1/encoders/dinov2_unified.py +124 -0
- patch-reparameterization/src/stage1/encoders/qwen3_frozen.py +130 -0
- patch-reparameterization/src/stage1/encoders/qwen3_unified.py +11 -5
- patch-reparameterization/src/stage2/__init__.py +88 -0
- patch-reparameterization/src/stage2/models/DDT.py +919 -0
- patch-reparameterization/src/stage2/models/__init__.py +15 -0
- patch-reparameterization/src/stage2/models/lightningDiT.py +305 -0
- patch-reparameterization/src/stage2/models/model_utils.py +438 -0
- patch-reparameterization/src/stage2/transport/__init__.py +79 -0
- patch-reparameterization/src/stage2/transport/integrators.py +134 -0
- patch-reparameterization/src/stage2/transport/path.py +242 -0
- patch-reparameterization/src/stage2/transport/transport.py +614 -0
- patch-reparameterization/src/stage2/transport/utils.py +29 -0
- patch-reparameterization/src/stage2/transport/xpred.py +131 -0
- requirements.txt +13 -16
- unispace/LICENSE-APACHE +201 -0
- unispace/NOTICE +29 -0
- unispace/modeling/__init__.py +4 -1
- unispace/modeling/autoencoder.py +382 -0
- unispace/modeling/autoencoder_flux2.py +412 -0
- unispace/modeling/siglip/__init__.py +98 -0
- unispace/modeling/siglip/configuration_siglip.py +287 -0
- unispace/modeling/siglip/convert_siglip_to_hf.py +401 -0
- unispace/modeling/siglip/image_processing_siglip.py +230 -0
.gitattributes
CHANGED
|
@@ -37,3 +37,7 @@ examples/background_change-input.png filter=lfs diff=lfs merge=lfs -text
|
|
| 37 |
examples/color_alter-input.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
examples/material_alter-input.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
examples/motion_change-input.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
examples/color_alter-input.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
examples/material_alter-input.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
examples/motion_change-input.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/background_change.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/color_alter.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
examples/material_alter.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
examples/subject-add.png filter=lfs diff=lfs merge=lfs -text
|
LICENSE-UNISPACE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 UniSpace Authors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
Note: The `unispace/` subdirectory (the unified multimodal model) contains
|
| 26 |
+
code originally derived from the ByteDance Bagel project and is licensed
|
| 27 |
+
under the Apache License, Version 2.0. See unispace/LICENSE-APACHE and
|
| 28 |
+
unispace/NOTICE for details. The remaining components are licensed under the
|
| 29 |
+
MIT License above.
|
README.md
CHANGED
|
@@ -1,33 +1,65 @@
|
|
| 1 |
---
|
| 2 |
title: UniSpace
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.25.0
|
| 8 |
app_file: app.py
|
| 9 |
-
short_description:
|
| 10 |
python_version: "3.12"
|
| 11 |
startup_duration_timeout: 1h
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
# UniSpace
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
|
| 21 |
-
- **Image Editing**: Provide an input image and an editing instruction.
|
| 22 |
|
| 23 |
-
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
-
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: UniSpace
|
| 3 |
+
emoji: 🛰️
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.25.0
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: Unified text-to-image generation and instruction editing
|
| 10 |
python_version: "3.12"
|
| 11 |
startup_duration_timeout: 1h
|
| 12 |
+
pinned: false
|
| 13 |
+
license: mit
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# UniSpace
|
| 17 |
|
| 18 |
+
Interactive demo of **UniSpace: Unified Visual Representation and Scalable
|
| 19 |
+
Multimodal Modeling** — an 8B Qwen3 Mixture-of-Transformers built on a single
|
| 20 |
+
patch-reparameterized ("PR") visual tokenizer that handles both image
|
| 21 |
+
generation and instruction-based image editing.
|
| 22 |
|
| 23 |
+
* Paper: <https://huggingface.co/papers/2608.08676>
|
| 24 |
+
* Weights: <https://huggingface.co/yjb6/UniSpace>
|
| 25 |
+
* Code: <https://github.com/yjb6/UniSpace>
|
| 26 |
|
| 27 |
+
## What runs here
|
|
|
|
| 28 |
|
| 29 |
+
The Space loads the released `unispace-sft-0012000` checkpoint together with
|
| 30 |
+
the `pr-qwen-vit` tokenizer/decoder and exposes the authors' two reference
|
| 31 |
+
inference paths:
|
| 32 |
|
| 33 |
+
| Tab | Reference script | Settings |
|
| 34 |
+
|---|---|---|
|
| 35 |
+
| Text to image | `eval/gen/gen_images_qwen3_unified_mot.py` | 1024², 50 steps, CFG 10, `timestep_shift=0.112`, `cfg_renorm_min=0.9` |
|
| 36 |
+
| Instruction editing | `eval/gen/gen_images_imgedit_qwen3_unified_mot.py` | `--match-ref-size --ref-use-mar` (mar_1024 buckets), 50 steps, CFG 10, `timestep_shift=0.112`, `cfg_renorm_min=0.0` |
|
| 37 |
|
| 38 |
+
Both share one model instance built with the release configuration
|
| 39 |
+
(`max_latent_size=96`, `share_unified2llm=False`, `use_spatial_merge_und=True`,
|
| 40 |
+
`use_spatial_merge_gen=False`, `latent_patch_size=1`).
|
| 41 |
|
| 42 |
+
The sampling code in `unispace_infer.py` is copied verbatim from those two
|
| 43 |
+
scripts; only the argparse/torchrun driver was removed. Model construction
|
| 44 |
+
differs from the scripts in two ways that are required by the hosting
|
| 45 |
+
environment, not by the model:
|
| 46 |
+
|
| 47 |
+
1. The base Qwen3-8B weights are never downloaded — every `language_model.*`
|
| 48 |
+
tensor is present in the UniSpace checkpoint, so only its config and
|
| 49 |
+
tokenizer are fetched.
|
| 50 |
+
2. The 17-shard fp32 checkpoint is streamed one shard at a time, cast to
|
| 51 |
+
bf16 and released, and the PatchReparam encoder loads only the Qwen3-VL
|
| 52 |
+
vision tower (instead of the full 17.5 GB VLM with `device_map="cuda:N"`,
|
| 53 |
+
which is incompatible with ZeroGPU).
|
| 54 |
+
|
| 55 |
+
## Credits & licensing
|
| 56 |
+
|
| 57 |
+
UniSpace code and weights are MIT-licensed (© 2026 UniSpace Authors); the
|
| 58 |
+
vendored `unispace/` and `patch-reparameterization/` sources carry the
|
| 59 |
+
upstream Apache-2.0 / MIT notices (`LICENSE-UNISPACE`, `unispace/NOTICE`,
|
| 60 |
+
`THIRD_PARTY.md`).
|
| 61 |
+
|
| 62 |
+
The four editing examples in `examples/` are the input images from the
|
| 63 |
+
UniSpace project page gallery, which come from **GEdit-Bench** (MIT, see
|
| 64 |
+
`THIRD_PARTY.md`). Text-to-image example prompts are taken from the UniSpace
|
| 65 |
+
repository's own test prompt set.
|
THIRD_PARTY.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Third-party evaluation components
|
| 2 |
+
|
| 3 |
+
The repository-level MIT license does not relicense third-party benchmarks,
|
| 4 |
+
datasets, models, or services. Obtain external assets from their official
|
| 5 |
+
sources and follow their terms.
|
| 6 |
+
|
| 7 |
+
| Component | How this repository uses it | Upstream terms |
|
| 8 |
+
|---|---|---|
|
| 9 |
+
| GenEval | Adapted prompt/evaluation utilities under `unispace/eval/gen/geneval/` | MIT notices are retained in the source files. |
|
| 10 |
+
| DPG-Bench | Evaluation package under `unispace/eval/gen/dpg/` | See the bundled `unispace/eval/gen/dpg/LICENSE`. |
|
| 11 |
+
| VIEScore | Adapted evaluator utilities under `unispace/eval/gen/gedit/viescore/` | MIT; the upstream license is bundled in that directory. |
|
| 12 |
+
| GEdit-Bench | Downloaded externally at evaluation time; the dataset is not redistributed here. | The official `stepfun-ai/GEdit-Bench` dataset card declares MIT. |
|
| 13 |
+
| OneIG-Bench | `score.sh` invokes a separately obtained official checkout; official prompts, models, and scorer code are not redistributed here. | The official dataset card declares CC BY-NC 4.0. The official GitHub scorer repository did not provide a root license when checked for this preview, so users must review its current terms before use or redistribution. |
|
| 14 |
+
| ImgEdit | The repository contains adapted benchmark orchestration and score aggregation; benchmark data is external. | The official `PKU-YuanGroup/ImgEdit` repository did not provide a root license when checked for this preview. Obtain the benchmark separately and review its current data, model, and judge terms before use or redistribution. |
|
| 15 |
+
|
| 16 |
+
Model dependencies such as Qwen3, SigLIP2, DINOv2, and evaluator checkpoints
|
| 17 |
+
are not redistributed by this source tree and remain subject to their own
|
| 18 |
+
licenses. API-based judging also remains subject to the selected provider's
|
| 19 |
+
terms.
|
app.py
CHANGED
|
@@ -1,766 +1,554 @@
|
|
| 1 |
-
"""UniSpace
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
import os
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 10 |
|
| 11 |
-
import
|
| 12 |
-
import time
|
| 13 |
-
import gc
|
| 14 |
-
import shutil
|
| 15 |
-
from pathlib import Path
|
| 16 |
-
from dataclasses import dataclass
|
| 17 |
-
|
| 18 |
-
import spaces # MUST come before torch / any CUDA-touching import
|
| 19 |
-
import torch
|
| 20 |
-
import numpy as np
|
| 21 |
-
from PIL import Image
|
| 22 |
-
import gradio as gr
|
| 23 |
-
|
| 24 |
-
# ─── Path setup ─────────────────────────────────────────────────────────────
|
| 25 |
-
# The Space repo includes the unispace/ and patch-reparameterization/ packages
|
| 26 |
-
# The unispace/ package uses absolute imports like `from modeling.X` so we add
|
| 27 |
-
# the unispace/ directory itself to sys.path
|
| 28 |
-
SPACE_ROOT = Path(__file__).parent
|
| 29 |
-
sys.path.insert(0, str(SPACE_ROOT / "unispace"))
|
| 30 |
-
sys.path.insert(0, str(SPACE_ROOT / "patch-reparameterization" / "src"))
|
| 31 |
-
|
| 32 |
-
# ─── HF model paths ────────────────────────────────────────────────────────
|
| 33 |
-
UNISPACE_CKPT_ID = "yjb6/UniSpace"
|
| 34 |
-
UNISPACE_SFT_SUBDIR = "unispace-sft-0012000"
|
| 35 |
-
QWEN3_8B_ID = "Qwen/Qwen3-8B" # for config + tokenizer only (no weights download)
|
| 36 |
-
QWEN3_VL_8B_ID = "Qwen/Qwen3-VL-8B-Instruct" # for VAE encoder backbone
|
| 37 |
-
VAE_CONFIG_PATH = str(SPACE_ROOT / "patch-reparameterization" / "configs" / "vae" / "qwen3-temp-run-mar1024_eval.yaml")
|
| 38 |
-
|
| 39 |
-
# ─── Inference config (from eval_sft_0012000.example.json) ─────────────────
|
| 40 |
-
CFG_SCALE = 10.0
|
| 41 |
-
INFERENCE_STEPS = 50
|
| 42 |
-
TIMESTEP_SHIFT = 0.112
|
| 43 |
-
MAX_LATENT_SIZE = 96
|
| 44 |
-
RESOLUTION = 1024
|
| 45 |
-
SHARE_UNIFIED2LLM = False
|
| 46 |
-
USE_SPATIAL_MERGE_UND = True
|
| 47 |
-
|
| 48 |
-
print(f"[app.py] SPACE_ROOT={SPACE_ROOT}", flush=True)
|
| 49 |
-
print(f"[app.py] VAE_CONFIG_PATH={VAE_CONFIG_PATH}", flush=True)
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
# ─── Model loading ──────────────────────────────────────────────────────────
|
| 53 |
-
def _download_vae_assets():
|
| 54 |
-
"""Download the PR-Qwen-ViT tokenizer checkpoint and normalization stats."""
|
| 55 |
-
from huggingface_hub import hf_hub_download
|
| 56 |
-
vae_ckpt = hf_hub_download(UNISPACE_CKPT_ID, "encoders/pr-qwen-vit-tokenizer.pt", repo_type="model")
|
| 57 |
-
stats_ckpt = hf_hub_download(UNISPACE_CKPT_ID, "stats/pr-qwen-vit-normalization-stats.pt", repo_type="model")
|
| 58 |
-
return vae_ckpt, stats_ckpt
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
def _download_safetensors_index():
|
| 62 |
-
"""Download the safetensors index json for the SFT checkpoint."""
|
| 63 |
-
from huggingface_hub import hf_hub_download
|
| 64 |
-
return hf_hub_download(
|
| 65 |
-
UNISPACE_CKPT_ID,
|
| 66 |
-
f"{UNISPACE_SFT_SUBDIR}/model.safetensors.index.json",
|
| 67 |
-
repo_type="model",
|
| 68 |
-
)
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
"target": "stage1.PatchReparam",
|
| 77 |
-
"params": {
|
| 78 |
-
"encoder_cls": "Qwen3Unified",
|
| 79 |
-
"encoder_config_path": QWEN3_VL_8B_ID,
|
| 80 |
-
"encoder_input_size": 256,
|
| 81 |
-
"encoder_params": {
|
| 82 |
-
"model_name": QWEN3_VL_8B_ID,
|
| 83 |
-
"recon_proj_dim": 128,
|
| 84 |
-
"pixel_format": "c_major",
|
| 85 |
-
},
|
| 86 |
-
"decoder_config_path": "configs/decoder/ViTXL_nockpt",
|
| 87 |
-
"decoder_cls": "ViTDecoder",
|
| 88 |
-
"noise_tau": 0.8,
|
| 89 |
-
"reshape_to_2d": True,
|
| 90 |
-
"normalization_stat_path": stats_path,
|
| 91 |
-
},
|
| 92 |
-
"checkpoint": {
|
| 93 |
-
"path": ckpt_path,
|
| 94 |
-
"use_ema": True,
|
| 95 |
-
},
|
| 96 |
-
},
|
| 97 |
-
}
|
| 98 |
-
tmp_path = "/tmp/vae_config_resolved.yaml"
|
| 99 |
-
with open(tmp_path, "w") as f:
|
| 100 |
-
yaml.dump(config, f, default_flow_style=False)
|
| 101 |
-
return tmp_path
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
def load_unispace_model():
|
| 105 |
-
"""Load the UniSpace model for T2I generation and image editing."""
|
| 106 |
-
from transformers import AutoTokenizer, AutoConfig
|
| 107 |
-
|
| 108 |
-
# Import model code
|
| 109 |
-
from modeling.qwen3.configuration_qwen3 import Qwen3Config
|
| 110 |
-
from modeling.unimm.unimm_mot import UnimmMoT, UnimmConfig
|
| 111 |
-
from modeling.unimm.qwen3_mot import Qwen3MoTForConditionalGeneration
|
| 112 |
-
from modeling.autoencoder_rae import load_rae
|
| 113 |
-
|
| 114 |
-
import torch._dynamo
|
| 115 |
-
torch._dynamo.config.disable = True
|
| 116 |
-
|
| 117 |
-
device = "cuda"
|
| 118 |
-
|
| 119 |
-
# 1. Download VAE assets
|
| 120 |
-
print("[load] Downloading VAE assets...", flush=True)
|
| 121 |
-
vae_ckpt_path, stats_path = _download_vae_assets()
|
| 122 |
-
print(f"[load] VAE checkpoint: {vae_ckpt_path}", flush=True)
|
| 123 |
-
print(f"[load] Stats: {stats_path}", flush=True)
|
| 124 |
-
|
| 125 |
-
# 2. Create resolved VAE config
|
| 126 |
-
vae_config_path = _make_vae_config_yaml(stats_path, vae_ckpt_path)
|
| 127 |
-
|
| 128 |
-
# 3. Load Qwen3-8B config (no weights — we use SFT checkpoint)
|
| 129 |
-
print("[load] Loading Qwen3-8B config...", flush=True)
|
| 130 |
-
llm_config = Qwen3Config.from_pretrained(QWEN3_8B_ID)
|
| 131 |
-
llm_config.qk_norm = True
|
| 132 |
-
llm_config.tie_word_embeddings = False
|
| 133 |
-
llm_config._attn_implementation = "sdpa"
|
| 134 |
-
# Ensure pad_token_id exists (transformers 5.x may not set it)
|
| 135 |
-
if not hasattr(llm_config, "pad_token_id") or llm_config.pad_token_id is None:
|
| 136 |
-
llm_config.pad_token_id = llm_config.eos_token_id
|
| 137 |
-
# Fix rope_scaling for transformers 5.x: 'default' was removed, use 'linear' with factor=1.0
|
| 138 |
-
if hasattr(llm_config, "rope_scaling") and isinstance(llm_config.rope_scaling, dict):
|
| 139 |
-
if llm_config.rope_scaling.get("rope_type") == "default":
|
| 140 |
-
llm_config.rope_scaling = {
|
| 141 |
-
"rope_type": "linear",
|
| 142 |
-
"factor": 1.0,
|
| 143 |
-
"rope_theta": llm_config.rope_scaling.get("rope_theta", 1000000.0),
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
# 4. Load VAE (RAE / PatchReparam)
|
| 147 |
-
print("[load] Loading RAE VAE...", flush=True)
|
| 148 |
-
vae_model, vae_config = load_rae(
|
| 149 |
-
local_path=vae_config_path,
|
| 150 |
-
rae_accelerate_root=str(SPACE_ROOT / "patch-reparameterization"),
|
| 151 |
-
)
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
llm_config=llm_config,
|
| 158 |
-
vit_config=None,
|
| 159 |
-
vae_config=vae_config,
|
| 160 |
-
vit_max_num_patch_per_side=MAX_LATENT_SIZE,
|
| 161 |
-
connector_act="gelu_pytorch_tanh",
|
| 162 |
-
latent_patch_size=1,
|
| 163 |
-
max_latent_size=MAX_LATENT_SIZE,
|
| 164 |
-
use_qwen_vit=False,
|
| 165 |
-
use_moe=True,
|
| 166 |
-
use_qwen3_unified=True,
|
| 167 |
-
share_unified2llm=SHARE_UNIFIED2LLM,
|
| 168 |
-
use_spatial_merge=False,
|
| 169 |
-
use_spatial_merge_und=USE_SPATIAL_MERGE_UND,
|
| 170 |
-
use_mrope=False,
|
| 171 |
-
)
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
# 7. Load tokenizer
|
| 179 |
-
print("[load] Loading tokenizer...", flush=True)
|
| 180 |
-
tokenizer = AutoTokenizer.from_pretrained(QWEN3_8B_ID)
|
| 181 |
-
|
| 182 |
-
# 8. Build UnimmMoT
|
| 183 |
-
print("[load] Building UnimmMoT...", flush=True)
|
| 184 |
-
model = UnimmMoT(language_model, None, tokenizer, config)
|
| 185 |
-
|
| 186 |
-
new_token_ids = {
|
| 187 |
-
"bos_token_id": tokenizer.bos_token_id if tokenizer.bos_token_id else tokenizer.encode("<|endoftext|>")[0],
|
| 188 |
-
"eos_token_id": tokenizer.eos_token_id if tokenizer.eos_token_id else tokenizer.encode("<|endoftext|>")[0],
|
| 189 |
-
"pad_token_id": tokenizer.pad_token_id or tokenizer.eos_token_id,
|
| 190 |
-
"start_of_image": tokenizer.encode("<|vision_start|>")[0],
|
| 191 |
-
"end_of_image": tokenizer.encode("<|vision_end|>")[0],
|
| 192 |
-
}
|
| 193 |
-
|
| 194 |
-
# 9. Load SFT checkpoint
|
| 195 |
-
print("[load] Loading SFT checkpoint...", flush=True)
|
| 196 |
-
from safetensors.torch import load_file
|
| 197 |
-
from huggingface_hub import snapshot_download
|
| 198 |
-
|
| 199 |
-
# Download all SFT shards
|
| 200 |
-
sft_dir = snapshot_download(
|
| 201 |
-
UNISPACE_CKPT_ID,
|
| 202 |
-
allow_patterns=[f"{UNISPACE_SFT_SUBDIR}/*.safetensors", f"{UNISPACE_SFT_SUBDIR}/*.json"],
|
| 203 |
-
repo_type="model",
|
| 204 |
-
)
|
| 205 |
-
sft_dir = os.path.join(sft_dir, UNISPACE_SFT_SUBDIR)
|
| 206 |
-
|
| 207 |
-
# Load all shards into a single state dict
|
| 208 |
-
index_path = os.path.join(sft_dir, "model.safetensors.index.json")
|
| 209 |
-
import json
|
| 210 |
-
with open(index_path) as f:
|
| 211 |
-
index = json.load(f)
|
| 212 |
-
|
| 213 |
-
state_dict = {}
|
| 214 |
-
shard_files = sorted(set(index["weight_map"].values()))
|
| 215 |
-
print(f"[load] Loading {len(shard_files)} shards...", flush=True)
|
| 216 |
-
for shard_file in shard_files:
|
| 217 |
-
shard_path = os.path.join(sft_dir, shard_file)
|
| 218 |
-
print(f" Loading {shard_file}...", flush=True)
|
| 219 |
-
shard_sd = load_file(shard_path, device="cpu")
|
| 220 |
-
state_dict.update(shard_sd)
|
| 221 |
-
del shard_sd
|
| 222 |
-
gc.collect()
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
)
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
-
|
| 238 |
-
state_dict.pop("vit_pos_embed.pos_embed", None)
|
| 239 |
|
| 240 |
-
msg = model.load_state_dict(state_dict, strict=False)
|
| 241 |
-
print(f"[load] Checkpoint loaded: missing={len(msg.missing_keys)}, unexpected={len(msg.unexpected_keys)}", flush=True)
|
| 242 |
-
if msg.missing_keys:
|
| 243 |
-
print(f" Missing: {msg.missing_keys[:10]}...", flush=True)
|
| 244 |
-
if msg.unexpected_keys:
|
| 245 |
-
print(f" Unexpected: {msg.unexpected_keys[:10]}...", flush=True)
|
| 246 |
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
gc.collect()
|
|
|
|
|
|
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
| 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 |
-
from data.transforms import MaxLongEdgeMinShortEdgeResize
|
| 294 |
-
from data.data_utils import get_flattened_position_ids_extrapolate
|
| 295 |
-
|
| 296 |
-
img = pil_image.convert("RGB")
|
| 297 |
-
|
| 298 |
-
if ref_use_mar:
|
| 299 |
-
target_h, target_w = _compute_target_size_from_ref(pil_image, ref_mar_resolution)
|
| 300 |
-
img = img.resize((target_w, target_h), Image.BICUBIC)
|
| 301 |
-
h_img, w_img = target_h, target_w
|
| 302 |
-
elif ref_max_size is not None:
|
| 303 |
-
resizer = MaxLongEdgeMinShortEdgeResize(
|
| 304 |
-
max_size=ref_max_size, min_size=ref_min_size,
|
| 305 |
-
stride=32, max_pixels=ref_max_pixels,
|
| 306 |
-
)
|
| 307 |
-
img = resizer(img)
|
| 308 |
-
w_img, h_img = img.size
|
| 309 |
-
else:
|
| 310 |
-
img = img.resize((image_size, image_size), Image.BICUBIC)
|
| 311 |
-
h_img = w_img = image_size
|
| 312 |
-
|
| 313 |
-
img_tensor = TF.to_tensor(img) * 2.0 - 1.0
|
| 314 |
-
img_tensor = img_tensor.unsqueeze(0).to(device)
|
| 315 |
-
|
| 316 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 317 |
-
z = vae_model.encode(img_tensor)
|
| 318 |
-
|
| 319 |
-
h_grid = h_img // 16
|
| 320 |
-
w_grid = w_img // 16
|
| 321 |
-
z_tokens = z[0].permute(1, 2, 0).reshape(h_grid * w_grid, -1).to(dtype=torch.bfloat16)
|
| 322 |
-
|
| 323 |
-
if mot_model.config.use_spatial_merge_und:
|
| 324 |
-
from modeling.unimm.unimm_mot import UnimmMoT
|
| 325 |
-
merged_tokens = UnimmMoT.spatial_merge(z_tokens, h_grid, w_grid)
|
| 326 |
-
patch_size = 32
|
| 327 |
-
else:
|
| 328 |
-
merged_tokens = z_tokens
|
| 329 |
-
patch_size = 16
|
| 330 |
-
|
| 331 |
-
pos_ids = get_flattened_position_ids_extrapolate(
|
| 332 |
-
h_img, w_img, patch_size=patch_size,
|
| 333 |
-
max_num_patches_per_side=mot_model.max_latent_size,
|
| 334 |
-
).to(device)
|
| 335 |
-
|
| 336 |
-
return merged_tokens, pos_ids
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
def _compute_target_size_from_ref(ref_image, resolution, stride=16):
|
| 340 |
-
from data.data_utils import MULTI_RESOLUTION_MAP
|
| 341 |
-
bucket_key = f"mar_{resolution}"
|
| 342 |
-
if bucket_key in MULTI_RESOLUTION_MAP:
|
| 343 |
-
bucket = MULTI_RESOLUTION_MAP[bucket_key]
|
| 344 |
-
pil_w, pil_h = ref_image.size
|
| 345 |
-
ref_ratio = pil_h / pil_w
|
| 346 |
-
best_key = min(bucket.keys(), key=lambda k: abs(float(k) - ref_ratio))
|
| 347 |
-
H, W = bucket[best_key]
|
| 348 |
-
return (int(H), int(W))
|
| 349 |
-
else:
|
| 350 |
-
pil_w, pil_h = ref_image.size
|
| 351 |
-
if pil_w >= pil_h:
|
| 352 |
-
target_w = resolution
|
| 353 |
-
target_h = resolution * pil_h / pil_w
|
| 354 |
-
else:
|
| 355 |
-
target_h = resolution
|
| 356 |
-
target_w = resolution * pil_w / pil_h
|
| 357 |
-
target_h = max(stride, round(target_h / stride) * stride)
|
| 358 |
-
target_w = max(stride, round(target_w / stride) * stride)
|
| 359 |
-
return (int(target_h), int(target_w))
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
def _prepare_unified_vit_input(curr_kvlens, curr_rope, vit_tokens, pos_ids, new_token_ids, device):
|
| 363 |
-
packed_text_ids, packed_text_indexes = [], []
|
| 364 |
-
packed_vit_token_indexes = []
|
| 365 |
-
packed_position_ids_list, packed_seqlens, packed_indexes = [], [], []
|
| 366 |
-
packed_key_value_indexes = []
|
| 367 |
-
|
| 368 |
-
_curr = curr = 0
|
| 369 |
-
newlens, new_rope_out = [], []
|
| 370 |
-
for curr_kvlen, curr_position_id in zip(curr_kvlens, curr_rope):
|
| 371 |
-
packed_key_value_indexes.extend(range(curr, curr + curr_kvlen))
|
| 372 |
-
curr += curr_kvlen
|
| 373 |
-
|
| 374 |
-
packed_text_ids.append(new_token_ids["start_of_image"])
|
| 375 |
-
packed_text_indexes.append(_curr)
|
| 376 |
-
packed_indexes.append(curr)
|
| 377 |
-
curr += 1
|
| 378 |
-
_curr += 1
|
| 379 |
-
|
| 380 |
-
num_img_tokens = vit_tokens.shape[0]
|
| 381 |
-
packed_vit_token_indexes.extend(range(_curr, _curr + num_img_tokens))
|
| 382 |
-
packed_indexes.extend(range(curr, curr + num_img_tokens))
|
| 383 |
-
curr += num_img_tokens
|
| 384 |
-
_curr += num_img_tokens
|
| 385 |
-
|
| 386 |
-
packed_text_ids.append(new_token_ids["end_of_image"])
|
| 387 |
-
packed_text_indexes.append(_curr)
|
| 388 |
-
packed_indexes.append(curr)
|
| 389 |
-
curr += 1
|
| 390 |
-
_curr += 1
|
| 391 |
-
|
| 392 |
-
pos_1d = torch.full((num_img_tokens + 2,), curr_position_id, dtype=torch.long)
|
| 393 |
-
packed_position_ids_list.append(pos_1d)
|
| 394 |
-
packed_seqlens.append(num_img_tokens + 2)
|
| 395 |
-
newlens.append(curr_kvlen + num_img_tokens + 2)
|
| 396 |
-
new_rope_out.append(curr_position_id + num_img_tokens + 2)
|
| 397 |
-
|
| 398 |
-
return {
|
| 399 |
-
"packed_text_ids": torch.tensor(packed_text_ids, dtype=torch.long).to(device),
|
| 400 |
-
"packed_text_indexes": torch.tensor(packed_text_indexes, dtype=torch.long).to(device),
|
| 401 |
-
"packed_vit_tokens": vit_tokens,
|
| 402 |
-
"packed_vit_token_indexes": torch.tensor(packed_vit_token_indexes, dtype=torch.long).to(device),
|
| 403 |
-
"packed_vit_position_ids": pos_ids,
|
| 404 |
-
"packed_position_ids": torch.cat(packed_position_ids_list).to(device),
|
| 405 |
-
"packed_seqlens": torch.tensor(packed_seqlens, dtype=torch.int).to(device),
|
| 406 |
-
"packed_indexes": torch.tensor(packed_indexes, dtype=torch.long).to(device),
|
| 407 |
-
"packed_key_value_indexes": torch.tensor(packed_key_value_indexes, dtype=torch.long).to(device),
|
| 408 |
-
"key_values_lens": torch.tensor(curr_kvlens, dtype=torch.int).to(device),
|
| 409 |
-
}, newlens, new_rope_out
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
# ─── Inference functions ───────────────────────────────────────────────────
|
| 413 |
-
|
| 414 |
-
@spaces.GPU(duration=240, size="xlarge")
|
| 415 |
-
def generate_image(
|
| 416 |
prompt: str,
|
| 417 |
-
steps: int =
|
| 418 |
-
cfg_scale: float =
|
| 419 |
-
seed: int = 42,
|
| 420 |
resolution: int = 1024,
|
|
|
|
|
|
|
| 421 |
progress=gr.Progress(track_tqdm=True),
|
| 422 |
):
|
| 423 |
-
"""Generate
|
| 424 |
|
| 425 |
Args:
|
| 426 |
prompt: Text description of the image to generate.
|
| 427 |
-
steps: Number of
|
| 428 |
-
cfg_scale: Classifier-free guidance
|
| 429 |
-
|
| 430 |
-
|
|
|
|
| 431 |
|
| 432 |
Returns:
|
| 433 |
-
|
| 434 |
"""
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
prompts=[prompt], tokenizer=tokenizer,
|
| 457 |
new_token_ids=new_token_ids,
|
|
|
|
|
|
|
| 458 |
)
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
past_key_values = gen_model.forward_cache_update_text(past_key_values, **generation_input_text)
|
| 462 |
-
|
| 463 |
-
# 2. Prepare VAE latent slots
|
| 464 |
-
generation_input = gen_model.prepare_vae_latent(
|
| 465 |
-
curr_kvlens=newlens, curr_rope=new_rope,
|
| 466 |
-
image_sizes=[(resolution, resolution)],
|
| 467 |
-
new_token_ids=new_token_ids, generator=generator,
|
| 468 |
-
)
|
| 469 |
-
generation_input = _move_to_device(generation_input, device)
|
| 470 |
-
|
| 471 |
-
# 3. CFG prefix (text-free)
|
| 472 |
-
cfg_past_key_values = NaiveCache(num_layers)
|
| 473 |
-
cfg_newlens = [0]
|
| 474 |
-
cfg_new_rope = [0]
|
| 475 |
-
cfg_prompt_input, cfg_newlens, cfg_new_rope = gen_model.prepare_cfg_prompts(
|
| 476 |
-
curr_kvlens=cfg_newlens, curr_rope=cfg_new_rope,
|
| 477 |
-
num_images=1, tokenizer=tokenizer, new_token_ids=new_token_ids,
|
| 478 |
-
)
|
| 479 |
-
cfg_prompt_input = _move_to_device(cfg_prompt_input, device)
|
| 480 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 481 |
-
cfg_past_key_values = gen_model.forward_cache_update_text(cfg_past_key_values, **cfg_prompt_input)
|
| 482 |
-
|
| 483 |
-
generation_input_cfg = gen_model.prepare_vae_latent_cfg(
|
| 484 |
-
curr_kvlens=cfg_newlens, curr_rope=cfg_new_rope,
|
| 485 |
-
image_sizes=[(resolution, resolution)],
|
| 486 |
-
)
|
| 487 |
-
generation_input_cfg = _move_to_device(generation_input_cfg, device)
|
| 488 |
-
|
| 489 |
-
# 4. Flow matching denoising
|
| 490 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 491 |
-
unpacked_latent = gen_model.generate_image(
|
| 492 |
-
past_key_values=past_key_values,
|
| 493 |
-
num_timesteps=steps,
|
| 494 |
-
cfg_text_scale=cfg_scale,
|
| 495 |
-
cfg_interval=[0, 1.0],
|
| 496 |
-
cfg_renorm_min=0.9,
|
| 497 |
-
timestep_shift=TIMESTEP_SHIFT,
|
| 498 |
-
cfg_text_past_key_values=cfg_past_key_values,
|
| 499 |
-
cfg_text_packed_position_ids=generation_input_cfg["cfg_packed_position_ids"],
|
| 500 |
-
cfg_text_key_values_lens=generation_input_cfg["cfg_key_values_lens"],
|
| 501 |
-
cfg_text_packed_query_indexes=generation_input_cfg["cfg_packed_query_indexes"],
|
| 502 |
-
cfg_text_packed_key_value_indexes=generation_input_cfg["cfg_packed_key_value_indexes"],
|
| 503 |
-
inference_mode="flash",
|
| 504 |
-
**generation_input,
|
| 505 |
-
)
|
| 506 |
|
| 507 |
-
# 5. VAE decode
|
| 508 |
-
latent_ch = gen_model.config.vae_config.z_channels
|
| 509 |
-
latent_ds = gen_model.config.vae_config.downsample
|
| 510 |
-
h = w = resolution // latent_ds
|
| 511 |
-
use_merge_gen = gen_model.config.use_spatial_merge_gen
|
| 512 |
-
arr = _decode_latent(unpacked_latent[0].float(), h, w, latent_ch, vae_model, use_merge_gen)
|
| 513 |
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
@spaces.GPU(duration=240, size="xlarge")
|
| 518 |
-
def edit_image(
|
| 519 |
-
input_image: Image.Image,
|
| 520 |
instruction: str,
|
| 521 |
-
steps: int =
|
| 522 |
-
cfg_scale: float =
|
| 523 |
seed: int = 42,
|
| 524 |
-
|
| 525 |
progress=gr.Progress(track_tqdm=True),
|
| 526 |
):
|
| 527 |
-
"""Edit an image
|
| 528 |
|
| 529 |
Args:
|
| 530 |
-
|
| 531 |
-
instruction:
|
| 532 |
-
steps: Number of
|
| 533 |
-
cfg_scale: Classifier-free guidance
|
| 534 |
-
seed: Random seed
|
| 535 |
-
|
| 536 |
|
| 537 |
Returns:
|
| 538 |
-
|
| 539 |
"""
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
vit_inp, newlens, new_rope = _prepare_unified_vit_input(
|
| 573 |
-
newlens, new_rope, vit_tokens, pos_ids, new_token_ids, device)
|
| 574 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 575 |
-
past_key_values = gen_model.forward_cache_update_unified_vit(past_key_values, **vit_inp)
|
| 576 |
-
|
| 577 |
-
# user\n{instruction}
|
| 578 |
-
inp_text, newlens, new_rope = gen_model.prepare_prompts(
|
| 579 |
-
curr_kvlens=newlens, curr_rope=new_rope,
|
| 580 |
-
prompts=[f"user\n{instruction}"], tokenizer=tokenizer,
|
| 581 |
-
new_token_ids=new_token_ids, raw=True,
|
| 582 |
-
)
|
| 583 |
-
inp_text = _move_to_device(inp_text, device)
|
| 584 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 585 |
-
past_key_values = gen_model.forward_cache_update_text(past_key_values, **inp_text)
|
| 586 |
-
|
| 587 |
-
# assistant\n
|
| 588 |
-
inp_asst, newlens, new_rope = gen_model.prepare_prompts(
|
| 589 |
-
curr_kvlens=newlens, curr_rope=new_rope,
|
| 590 |
-
prompts=["assistant\n"], tokenizer=tokenizer,
|
| 591 |
-
new_token_ids=new_token_ids, raw=True,
|
| 592 |
-
)
|
| 593 |
-
inp_asst = _move_to_device(inp_asst, device)
|
| 594 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 595 |
-
past_key_values = gen_model.forward_cache_update_text(past_key_values, **inp_asst)
|
| 596 |
-
|
| 597 |
-
# Target image latent slot
|
| 598 |
-
generation_input = gen_model.prepare_vae_latent(
|
| 599 |
-
curr_kvlens=newlens, curr_rope=new_rope,
|
| 600 |
-
image_sizes=[(h, w)], new_token_ids=new_token_ids, generator=generator,
|
| 601 |
-
)
|
| 602 |
-
generation_input = _move_to_device(generation_input, device)
|
| 603 |
-
|
| 604 |
-
# === Unconditional path (only ref image, drop instruction) ===
|
| 605 |
-
cfg_past_key_values = NaiveCache(num_layers)
|
| 606 |
-
cfg_newlens = [0]
|
| 607 |
-
cfg_new_rope = [0]
|
| 608 |
-
|
| 609 |
-
vit_inp_cfg, cfg_newlens, cfg_new_rope = _prepare_unified_vit_input(
|
| 610 |
-
cfg_newlens, cfg_new_rope, vit_tokens, pos_ids, new_token_ids, device)
|
| 611 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 612 |
-
cfg_past_key_values = gen_model.forward_cache_update_unified_vit(cfg_past_key_values, **vit_inp_cfg)
|
| 613 |
-
|
| 614 |
-
inp_asst_cfg, cfg_newlens, cfg_new_rope = gen_model.prepare_prompts(
|
| 615 |
-
curr_kvlens=cfg_newlens, curr_rope=cfg_new_rope,
|
| 616 |
-
prompts=["assistant\n"], tokenizer=tokenizer,
|
| 617 |
-
new_token_ids=new_token_ids, raw=True,
|
| 618 |
-
)
|
| 619 |
-
inp_asst_cfg = _move_to_device(inp_asst_cfg, device)
|
| 620 |
-
with torch.no_grad(), torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 621 |
-
cfg_past_key_values = gen_model.forward_cache_update_text(cfg_past_key_values, **inp_asst_cfg)
|
| 622 |
-
|
| 623 |
-
generation_input_cfg = gen_model.prepare_vae_latent_cfg(
|
| 624 |
-
curr_kvlens=cfg_newlens, curr_rope=cfg_new_rope,
|
| 625 |
-
image_sizes=[(h, w)],
|
| 626 |
)
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
|
|
|
|
|
|
|
|
|
| 662 |
"""
|
| 663 |
|
| 664 |
-
with gr.Blocks(theme=gr.themes.Citrus(), css=
|
| 665 |
-
gr.
|
| 666 |
-
|
| 667 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
)
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
with gr.Column(scale=1):
|
| 679 |
-
t2i_prompt = gr.Textbox(
|
| 680 |
-
label="Prompt",
|
| 681 |
-
placeholder="Describe the image you want to generate...",
|
| 682 |
-
lines=3,
|
| 683 |
-
value="A festive red and gold promotional poster for Chinese New Year. In the center, a 3D rendered gift box is open, revealing cosmetics products. Large, bold golden Chinese characters at the top read '新春大促' (New Year Sale). Below it, smaller white text says '全场五折起' (50% off all items). The background features traditional cloud patterns and lanterns. A button at the bottom reads '立即抢购' (Buy Now).",
|
| 684 |
-
)
|
| 685 |
-
t2i_btn = gr.Button("Generate", variant="primary", size="lg")
|
| 686 |
-
with gr.Accordion("Advanced Settings", open=False):
|
| 687 |
-
t2i_steps = gr.Slider(10, 100, value=50, step=1, label="Steps")
|
| 688 |
-
t2i_cfg = gr.Slider(1.0, 20.0, value=10.0, step=0.5, label="CFG Scale")
|
| 689 |
-
t2i_seed = gr.Number(value=42, label="Seed", precision=0)
|
| 690 |
-
t2i_res = gr.Slider(256, 1024, value=1024, step=128, label="Resolution")
|
| 691 |
-
|
| 692 |
-
with gr.Column(scale=1):
|
| 693 |
-
t2i_output = gr.Image(label="Generated Image", type="pil")
|
| 694 |
-
|
| 695 |
-
t2i_btn.click(
|
| 696 |
-
fn=generate_image,
|
| 697 |
-
inputs=[t2i_prompt, t2i_steps, t2i_cfg, t2i_seed, t2i_res],
|
| 698 |
-
outputs=t2i_output,
|
| 699 |
-
)
|
| 700 |
-
|
| 701 |
-
gr.Examples(
|
| 702 |
-
examples=[
|
| 703 |
-
["An expansive field, blanketed by the soft light of morning, cradles a collection of eight cabbages, their green heads round and plump. These vegetables are nestled among rows of rich soil, dotted with glistening droplets of dew that cling to their crinkled leaves. As wisps of mist begin to lift, the cabbages lie poised, ready for the day's impending harvest."],
|
| 704 |
-
["A photo of a press conference with microphones and a blurred crowd in the background, realistic news style"],
|
| 705 |
-
["A screenshot of a modern landing page for a tech startup, clean UI, blue and white color scheme"],
|
| 706 |
-
["A candid street photography shot of people walking in busy Tokyo, daytime, realistic lighting"],
|
| 707 |
-
],
|
| 708 |
-
inputs=t2i_prompt,
|
| 709 |
-
fn=generate_image,
|
| 710 |
-
cache_examples=True,
|
| 711 |
-
cache_mode="lazy",
|
| 712 |
-
outputs=t2i_output,
|
| 713 |
-
)
|
| 714 |
-
|
| 715 |
-
# ─── Image Editing Tab ────────────────────────────────────────────
|
| 716 |
-
with gr.Tab("🖼️ Image Editing"):
|
| 717 |
-
with gr.Row():
|
| 718 |
-
with gr.Column(scale=1):
|
| 719 |
-
edit_input = gr.Image(label="Input Image", type="pil")
|
| 720 |
-
edit_instruction = gr.Textbox(
|
| 721 |
-
label="Edit Instruction",
|
| 722 |
-
placeholder="Describe the edit you want to apply...",
|
| 723 |
-
lines=2,
|
| 724 |
-
value="Change the background to a forest.",
|
| 725 |
-
)
|
| 726 |
-
edit_btn = gr.Button("Apply Edit", variant="primary", size="lg")
|
| 727 |
-
with gr.Accordion("Advanced Settings", open=False):
|
| 728 |
-
edit_steps = gr.Slider(10, 100, value=50, step=1, label="Steps")
|
| 729 |
-
edit_cfg = gr.Slider(1.0, 20.0, value=4.0, step=0.5, label="CFG Scale")
|
| 730 |
-
edit_seed = gr.Number(value=42, label="Seed", precision=0)
|
| 731 |
-
edit_res = gr.Slider(256, 1024, value=1024, step=128, label="Resolution")
|
| 732 |
-
|
| 733 |
-
with gr.Column(scale=1):
|
| 734 |
-
edit_output = gr.Image(label="Edited Image", type="pil")
|
| 735 |
-
|
| 736 |
-
edit_btn.click(
|
| 737 |
-
fn=edit_image,
|
| 738 |
-
inputs=[edit_input, edit_instruction, edit_steps, edit_cfg, edit_seed, edit_res],
|
| 739 |
-
outputs=edit_output,
|
| 740 |
-
)
|
| 741 |
-
|
| 742 |
-
gr.Examples(
|
| 743 |
-
examples=[
|
| 744 |
-
["examples/background_change-input.png", "Change the background to a forest."],
|
| 745 |
-
["examples/color_alter-input.png", "Alter the color of the mirror frame to orange."],
|
| 746 |
-
["examples/material_alter-input.png", "Change the cup in hand to ceramic."],
|
| 747 |
-
["examples/motion_change-input.png", "Make the expression more sorrowful."],
|
| 748 |
-
],
|
| 749 |
-
inputs=[edit_input, edit_instruction],
|
| 750 |
-
fn=edit_image,
|
| 751 |
-
cache_examples=True,
|
| 752 |
-
cache_mode="lazy",
|
| 753 |
-
outputs=edit_output,
|
| 754 |
-
)
|
| 755 |
-
|
| 756 |
-
gr.Markdown(
|
| 757 |
-
"""
|
| 758 |
-
---
|
| 759 |
-
**Model**: [yjb6/UniSpace](https://huggingface.co/yjb6/UniSpace) |
|
| 760 |
-
**Paper**: [2608.08676](https://arxiv.org/abs/2608.08676) |
|
| 761 |
-
**Code**: [GitHub](https://github.com/yjb6/UniSpace) |
|
| 762 |
-
**Project Page**: [yjb6.github.io/UniSpace](https://yjb6.github.io/UniSpace/)
|
| 763 |
-
"""
|
| 764 |
)
|
| 765 |
|
| 766 |
-
|
|
|
|
|
|
| 1 |
+
"""UniSpace — unified text-to-image generation and instruction-based image editing.
|
| 2 |
|
| 3 |
+
Paper: UniSpace: Unified Visual Representation and Scalable Multimodal Modeling
|
| 4 |
+
Model: https://huggingface.co/yjb6/UniSpace
|
| 5 |
+
Code: https://github.com/yjb6/UniSpace
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os
|
| 9 |
+
|
| 10 |
+
# Must be set before torch / the UniSpace modeling code is imported.
|
| 11 |
+
os.environ.setdefault("ATTENTION_BACKEND", "torch_sdpa")
|
| 12 |
+
os.environ.setdefault("USE_FLEX_ATTENTION", "0")
|
| 13 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 14 |
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 15 |
|
| 16 |
+
import spaces # noqa: E402 (must precede torch / CUDA-touching imports)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
import gc # noqa: E402
|
| 19 |
+
import json # noqa: E402
|
| 20 |
+
import random # noqa: E402
|
| 21 |
+
import sys # noqa: E402
|
| 22 |
+
import time # noqa: E402
|
| 23 |
|
| 24 |
+
import gradio as gr # noqa: E402
|
| 25 |
+
import torch # noqa: E402
|
| 26 |
+
from huggingface_hub import hf_hub_download # noqa: E402
|
| 27 |
+
from omegaconf import OmegaConf # noqa: E402
|
| 28 |
+
from safetensors import safe_open # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
ROOT = os.path.dirname(os.path.abspath(__file__))
|
| 31 |
+
PR_ROOT = os.path.join(ROOT, "patch-reparameterization")
|
| 32 |
+
sys.path.insert(0, os.path.join(PR_ROOT, "src"))
|
| 33 |
+
sys.path.insert(0, os.path.join(ROOT, "unispace"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
MODEL_REPO = "yjb6/UniSpace"
|
| 36 |
+
CKPT_DIR = "unispace-sft-0012000"
|
| 37 |
+
QWEN_LLM = "Qwen/Qwen3-8B"
|
| 38 |
+
QWEN_VL = "Qwen/Qwen3-VL-8B-Instruct"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
# Official inference settings (unispace/scripts/eval/run_release_generation.sh +
|
| 41 |
+
# unispace/eval/gen/eval_sft_0012000.example.json).
|
| 42 |
+
MAX_LATENT_SIZE = 96
|
| 43 |
+
TIMESTEP_SHIFT = 0.112
|
| 44 |
+
DEFAULT_STEPS = 50
|
| 45 |
+
DEFAULT_CFG = 10.0
|
| 46 |
+
GEN_CFG_RENORM_MIN = 0.9 # gen_images_qwen3_unified_mot.py main()
|
| 47 |
+
EDIT_CFG_RENORM_MIN = 0.0 # gen_images_imgedit_qwen3_unified_mot.py argparse default
|
| 48 |
+
EDIT_RESOLUTION = 1024
|
| 49 |
+
|
| 50 |
+
DEVICE = "cuda"
|
| 51 |
+
DEVICE_TYPE = "cuda"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _log(msg):
|
| 55 |
+
print(f"[unispace] {msg}", flush=True)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _free_cached_file(path):
|
| 59 |
+
"""Delete a hub-cached file (symlink + blob) to keep the Space disk small."""
|
| 60 |
+
try:
|
| 61 |
+
real = os.path.realpath(path)
|
| 62 |
+
if os.path.islink(path):
|
| 63 |
+
os.unlink(path)
|
| 64 |
+
if os.path.exists(real):
|
| 65 |
+
os.remove(real)
|
| 66 |
+
except OSError as exc: # pragma: no cover
|
| 67 |
+
_log(f"could not free {path}: {exc}")
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# ---------------------------------------------------------------------------
|
| 71 |
+
# 1. Vision-tower-only loader for the PatchReparam encoder.
|
| 72 |
+
#
|
| 73 |
+
# stage1/encoders/qwen3_unified.py loads the *full* Qwen3-VL-8B-Instruct
|
| 74 |
+
# (17.5 GB) only to keep `.visual`, and does so with device_map="cuda:N",
|
| 75 |
+
# which is incompatible with the ZeroGPU hijack. We swap in a loader that
|
| 76 |
+
# builds the vision tower on CPU and pulls its weights from the single shard
|
| 77 |
+
# that holds them (2.7 GB).
|
| 78 |
+
# ---------------------------------------------------------------------------
|
| 79 |
+
import transformers.models.qwen3_vl.modeling_qwen3_vl as _q3vl_mod # noqa: E402
|
| 80 |
+
from transformers import AutoConfig, AutoTokenizer # noqa: E402
|
| 81 |
+
from transformers.models.qwen3_vl.modeling_qwen3_vl import Qwen3VLVisionModel # noqa: E402
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
from transformers.modeling_utils import no_init_weights
|
| 85 |
+
except ImportError: # pragma: no cover
|
| 86 |
+
from contextlib import nullcontext as no_init_weights
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class _VisionOnlyQwen3VL:
|
| 90 |
+
"""Minimal stand-in exposing the two attributes Qwen3Unified touches."""
|
| 91 |
+
|
| 92 |
+
def __init__(self, visual):
|
| 93 |
+
self.visual = visual
|
| 94 |
+
self.model = None
|
| 95 |
+
|
| 96 |
+
@classmethod
|
| 97 |
+
def from_pretrained(cls, model_name, torch_dtype=None, device_map=None, **kwargs):
|
| 98 |
+
cfg = AutoConfig.from_pretrained(model_name)
|
| 99 |
+
vis_cfg = cfg.vision_config
|
| 100 |
+
vis_cfg._attn_implementation = "sdpa"
|
| 101 |
+
t0 = time.time()
|
| 102 |
+
with no_init_weights():
|
| 103 |
+
visual = Qwen3VLVisionModel(vis_cfg)
|
| 104 |
+
visual = visual.to(torch.float32)
|
| 105 |
+
shard = hf_hub_download(model_name, "model-00004-of-00004.safetensors")
|
| 106 |
+
prefix = "model.visual."
|
| 107 |
+
state = {}
|
| 108 |
+
with safe_open(shard, framework="pt", device="cpu") as f:
|
| 109 |
+
for key in f.keys():
|
| 110 |
+
if key.startswith(prefix):
|
| 111 |
+
state[key[len(prefix):]] = f.get_tensor(key).to(torch.float32)
|
| 112 |
+
missing, unexpected = visual.load_state_dict(state, strict=False)
|
| 113 |
+
_log(
|
| 114 |
+
f"vision tower: {len(state)} tensors, missing={len(missing)}, "
|
| 115 |
+
f"unexpected={len(unexpected)} ({time.time() - t0:.1f}s)"
|
| 116 |
)
|
| 117 |
+
if missing:
|
| 118 |
+
_log(f"vision tower missing keys (first 10): {missing[:10]}")
|
| 119 |
+
del state
|
| 120 |
+
_free_cached_file(shard)
|
| 121 |
+
gc.collect()
|
| 122 |
+
return cls(visual)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
_q3vl_mod.Qwen3VLForConditionalGeneration = _VisionOnlyQwen3VL
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# ---------------------------------------------------------------------------
|
| 129 |
+
# 2. PatchReparam ("RAE") tokenizer / decoder
|
| 130 |
+
# ---------------------------------------------------------------------------
|
| 131 |
+
from modeling.autoencoder_unified import load_unified_vae # noqa: E402
|
| 132 |
+
from modeling.qwen3.configuration_qwen3 import Qwen3Config # noqa: E402
|
| 133 |
+
from modeling.unimm.qwen3_mot import Qwen3MoTForConditionalGeneration # noqa: E402
|
| 134 |
+
from modeling.unimm.unimm_mot import UnimmConfig, UnimmMoT # noqa: E402
|
| 135 |
|
| 136 |
+
import unispace_infer as ui # noqa: E402
|
|
|
|
| 137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
+
def _build_vae_config_yaml():
|
| 140 |
+
src = os.path.join(PR_ROOT, "configs", "vae", "qwen3-temp-run-mar1024_eval.yaml")
|
| 141 |
+
cfg = OmegaConf.load(src)
|
| 142 |
+
cfg.stage_1.params.encoder_config_path = QWEN_VL
|
| 143 |
+
cfg.stage_1.params.encoder_params.model_name = QWEN_VL
|
| 144 |
+
cfg.stage_1.params.normalization_stat_path = hf_hub_download(
|
| 145 |
+
MODEL_REPO, "stats/pr-qwen-vit-normalization-stats.pt"
|
| 146 |
+
)
|
| 147 |
+
cfg.stage_1.checkpoint.path = hf_hub_download(
|
| 148 |
+
MODEL_REPO, "encoders/pr-qwen-vit-tokenizer.pt"
|
| 149 |
+
)
|
| 150 |
+
out = os.path.join(ROOT, "_vae_eval_resolved.yaml")
|
| 151 |
+
OmegaConf.save(cfg, out, resolve=False)
|
| 152 |
+
return out, str(cfg.stage_1.checkpoint.path)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
_log("loading PatchReparam tokenizer/decoder ...")
|
| 156 |
+
_t0 = time.time()
|
| 157 |
+
_vae_yaml, _vae_ckpt = _build_vae_config_yaml()
|
| 158 |
+
vae_model, vae_config = load_unified_vae(local_path=_vae_yaml, patch_reparam_root=PR_ROOT)
|
| 159 |
+
vae_model = vae_model.eval()
|
| 160 |
+
_free_cached_file(_vae_ckpt)
|
| 161 |
+
gc.collect()
|
| 162 |
+
_log(
|
| 163 |
+
f"PatchReparam ready in {time.time() - _t0:.1f}s "
|
| 164 |
+
f"(downsample={vae_config.downsample}, z_channels={vae_config.z_channels})"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# ---------------------------------------------------------------------------
|
| 169 |
+
# 3. Qwen3-8B Mixture-of-Transformers backbone
|
| 170 |
+
#
|
| 171 |
+
# Every `language_model.*` tensor lives in the UniSpace checkpoint, so the base
|
| 172 |
+
# Qwen3-8B weights are never needed — only its config + tokenizer. The 17-shard
|
| 173 |
+
# fp32 checkpoint (61 GB) is streamed one shard at a time, cast to bf16 and
|
| 174 |
+
# released, so peak disk stays around 4 GB.
|
| 175 |
+
# ---------------------------------------------------------------------------
|
| 176 |
+
_log("building Qwen3-8B MoT backbone from config ...")
|
| 177 |
+
_t0 = time.time()
|
| 178 |
+
llm_config = Qwen3Config.from_pretrained(QWEN_LLM)
|
| 179 |
+
llm_config.qk_norm = True
|
| 180 |
+
llm_config.tie_word_embeddings = False
|
| 181 |
+
llm_config._attn_implementation = "sdpa"
|
| 182 |
+
|
| 183 |
+
_orig_linear_reset = torch.nn.Linear.reset_parameters
|
| 184 |
+
_orig_embedding_reset = torch.nn.Embedding.reset_parameters
|
| 185 |
+
torch.nn.Linear.reset_parameters = lambda self: None
|
| 186 |
+
torch.nn.Embedding.reset_parameters = lambda self: None
|
| 187 |
+
torch.set_default_dtype(torch.bfloat16)
|
| 188 |
+
try:
|
| 189 |
+
with no_init_weights():
|
| 190 |
+
language_model = Qwen3MoTForConditionalGeneration(llm_config)
|
| 191 |
+
finally:
|
| 192 |
+
torch.set_default_dtype(torch.float32)
|
| 193 |
+
torch.nn.Linear.reset_parameters = _orig_linear_reset
|
| 194 |
+
torch.nn.Embedding.reset_parameters = _orig_embedding_reset
|
| 195 |
+
_log(f"backbone skeleton built in {time.time() - _t0:.1f}s")
|
| 196 |
+
|
| 197 |
+
unimm_config = UnimmConfig(
|
| 198 |
+
visual_gen=True,
|
| 199 |
+
visual_und=True,
|
| 200 |
+
llm_config=llm_config,
|
| 201 |
+
vit_config=None,
|
| 202 |
+
vae_config=vae_config,
|
| 203 |
+
vit_max_num_patch_per_side=MAX_LATENT_SIZE,
|
| 204 |
+
connector_act="gelu_pytorch_tanh",
|
| 205 |
+
latent_patch_size=1,
|
| 206 |
+
max_latent_size=MAX_LATENT_SIZE,
|
| 207 |
+
use_qwen_vit=False,
|
| 208 |
+
use_moe=True,
|
| 209 |
+
use_qwen3_unified=True,
|
| 210 |
+
share_unified2llm=False,
|
| 211 |
+
use_spatial_merge=False,
|
| 212 |
+
use_spatial_merge_gen=False,
|
| 213 |
+
use_spatial_merge_und=True,
|
| 214 |
+
use_mrope=False,
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
tokenizer = AutoTokenizer.from_pretrained(QWEN_LLM)
|
| 218 |
+
model = UnimmMoT(language_model, None, tokenizer, unimm_config)
|
| 219 |
+
del language_model
|
| 220 |
+
gc.collect()
|
| 221 |
+
|
| 222 |
+
new_token_ids = {
|
| 223 |
+
"bos_token_id": tokenizer.encode("<|im_start|>")[0],
|
| 224 |
+
"eos_token_id": tokenizer.encode("<|im_end|>")[0],
|
| 225 |
+
"pad_token_id": tokenizer.pad_token_id or tokenizer.eos_token_id,
|
| 226 |
+
"start_of_image": tokenizer.encode("<|vision_start|>")[0],
|
| 227 |
+
"end_of_image": tokenizer.encode("<|vision_end|>")[0],
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
# The two positional embeddings are frozen 2-D sincos tables rebuilt at
|
| 231 |
+
# construction time — the official edit script drops them from the checkpoint.
|
| 232 |
+
SKIP_KEYS = {"latent_pos_embed.pos_embed", "vit_pos_embed.pos_embed"}
|
| 233 |
+
|
| 234 |
+
_log("streaming UniSpace SFT checkpoint ...")
|
| 235 |
+
_t0 = time.time()
|
| 236 |
+
_index = json.load(
|
| 237 |
+
open(hf_hub_download(MODEL_REPO, f"{CKPT_DIR}/model.safetensors.index.json"))
|
| 238 |
+
)
|
| 239 |
+
_shards = sorted(set(_index["weight_map"].values()))
|
| 240 |
+
_loaded = set()
|
| 241 |
+
for _i, _shard in enumerate(_shards, 1):
|
| 242 |
+
_path = hf_hub_download(MODEL_REPO, f"{CKPT_DIR}/{_shard}")
|
| 243 |
+
_sd = {}
|
| 244 |
+
with safe_open(_path, framework="pt", device="cpu") as f:
|
| 245 |
+
for _k in f.keys():
|
| 246 |
+
if _k in SKIP_KEYS:
|
| 247 |
+
continue
|
| 248 |
+
_sd[_k] = f.get_tensor(_k).to(torch.bfloat16)
|
| 249 |
+
_msg = model.load_state_dict(_sd, strict=False)
|
| 250 |
+
if _msg.unexpected_keys:
|
| 251 |
+
_log(f" shard {_i}: unexpected {_msg.unexpected_keys[:5]}")
|
| 252 |
+
_loaded.update(_sd.keys())
|
| 253 |
+
del _sd, _msg
|
| 254 |
gc.collect()
|
| 255 |
+
_free_cached_file(_path)
|
| 256 |
+
_log(f" shard {_i}/{len(_shards)} loaded ({time.time() - _t0:.0f}s elapsed)")
|
| 257 |
|
| 258 |
+
_expected = set(model.state_dict().keys())
|
| 259 |
+
_missing = sorted(_expected - _loaded - SKIP_KEYS)
|
| 260 |
+
_log(f"checkpoint loaded: {len(_loaded)} tensors, missing={len(_missing)}")
|
| 261 |
+
if _missing:
|
| 262 |
+
_log(f"MISSING KEYS (first 20): {_missing[:20]}")
|
| 263 |
+
|
| 264 |
+
model = model.to(torch.bfloat16).eval()
|
| 265 |
+
for _p in model.parameters():
|
| 266 |
+
_p.requires_grad_(False)
|
| 267 |
+
for _p in vae_model.parameters():
|
| 268 |
+
_p.requires_grad_(False)
|
| 269 |
+
gc.collect()
|
| 270 |
+
|
| 271 |
+
_log("moving weights to GPU ...")
|
| 272 |
+
model = model.to(DEVICE)
|
| 273 |
+
vae_model = vae_model.to(DEVICE)
|
| 274 |
+
_log("model ready")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# ---------------------------------------------------------------------------
|
| 278 |
+
# 4. Gradio handlers
|
| 279 |
+
# ---------------------------------------------------------------------------
|
| 280 |
+
MAX_SEED = 2**31 - 1
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _duration_generate(prompt="", steps=DEFAULT_STEPS, *args, **kwargs):
|
| 284 |
+
try:
|
| 285 |
+
steps = int(steps)
|
| 286 |
+
except (TypeError, ValueError):
|
| 287 |
+
steps = DEFAULT_STEPS
|
| 288 |
+
return min(240, 40 + int(steps * 1.6))
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _duration_edit(image=None, instruction="", steps=DEFAULT_STEPS, *args, **kwargs):
|
| 292 |
+
try:
|
| 293 |
+
steps = int(steps)
|
| 294 |
+
except (TypeError, ValueError):
|
| 295 |
+
steps = DEFAULT_STEPS
|
| 296 |
+
return min(280, 55 + int(steps * 2.0))
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
@spaces.GPU(duration=_duration_generate)
|
| 300 |
+
def generate(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
prompt: str,
|
| 302 |
+
steps: int = DEFAULT_STEPS,
|
| 303 |
+
cfg_scale: float = DEFAULT_CFG,
|
|
|
|
| 304 |
resolution: int = 1024,
|
| 305 |
+
seed: int = 42,
|
| 306 |
+
randomize_seed: bool = False,
|
| 307 |
progress=gr.Progress(track_tqdm=True),
|
| 308 |
):
|
| 309 |
+
"""Generate a square image from a text prompt with UniSpace.
|
| 310 |
|
| 311 |
Args:
|
| 312 |
prompt: Text description of the image to generate.
|
| 313 |
+
steps: Number of flow-matching sampling steps.
|
| 314 |
+
cfg_scale: Classifier-free guidance strength.
|
| 315 |
+
resolution: Output width/height in pixels.
|
| 316 |
+
seed: Random seed.
|
| 317 |
+
randomize_seed: Draw a fresh random seed instead of using `seed`.
|
| 318 |
|
| 319 |
Returns:
|
| 320 |
+
The generated PIL image, the seed that was used, and a timing note.
|
| 321 |
"""
|
| 322 |
+
if not prompt or not prompt.strip():
|
| 323 |
+
raise gr.Error("Please enter a prompt.")
|
| 324 |
+
if randomize_seed:
|
| 325 |
+
seed = random.randint(0, MAX_SEED)
|
| 326 |
+
seed = int(seed)
|
| 327 |
+
t0 = time.perf_counter()
|
| 328 |
+
images = ui.generate_image(
|
| 329 |
+
prompt=prompt.strip(),
|
| 330 |
+
num_timesteps=int(steps),
|
| 331 |
+
cfg_scale=float(cfg_scale),
|
| 332 |
+
cfg_interval=[0, 1.0],
|
| 333 |
+
cfg_renorm_min=GEN_CFG_RENORM_MIN,
|
| 334 |
+
timestep_shift=TIMESTEP_SHIFT,
|
| 335 |
+
max_t=1.0,
|
| 336 |
+
num_images=1,
|
| 337 |
+
resolution=int(resolution),
|
| 338 |
+
inference_mode="flash",
|
| 339 |
+
device=DEVICE,
|
| 340 |
+
device_type=DEVICE_TYPE,
|
| 341 |
+
gen_model=model,
|
| 342 |
+
tokenizer=tokenizer,
|
|
|
|
| 343 |
new_token_ids=new_token_ids,
|
| 344 |
+
vae_model=vae_model,
|
| 345 |
+
seed=seed,
|
| 346 |
)
|
| 347 |
+
elapsed = time.perf_counter() - t0
|
| 348 |
+
return images[0], seed, f"{resolution}×{resolution} · {steps} steps · {elapsed:.1f}s"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
+
@spaces.GPU(duration=_duration_edit)
|
| 352 |
+
def edit(
|
| 353 |
+
image,
|
|
|
|
|
|
|
|
|
|
| 354 |
instruction: str,
|
| 355 |
+
steps: int = DEFAULT_STEPS,
|
| 356 |
+
cfg_scale: float = DEFAULT_CFG,
|
| 357 |
seed: int = 42,
|
| 358 |
+
randomize_seed: bool = False,
|
| 359 |
progress=gr.Progress(track_tqdm=True),
|
| 360 |
):
|
| 361 |
+
"""Edit an image by following a natural-language instruction.
|
| 362 |
|
| 363 |
Args:
|
| 364 |
+
image: The source image to edit.
|
| 365 |
+
instruction: What to change, e.g. "Change the background to a forest.".
|
| 366 |
+
steps: Number of flow-matching sampling steps.
|
| 367 |
+
cfg_scale: Classifier-free guidance strength on the instruction.
|
| 368 |
+
seed: Random seed.
|
| 369 |
+
randomize_seed: Draw a fresh random seed instead of using `seed`.
|
| 370 |
|
| 371 |
Returns:
|
| 372 |
+
The edited PIL image, the seed that was used, and a timing note.
|
| 373 |
"""
|
| 374 |
+
if image is None:
|
| 375 |
+
raise gr.Error("Please upload an image to edit.")
|
| 376 |
+
if not instruction or not instruction.strip():
|
| 377 |
+
raise gr.Error("Please enter an editing instruction.")
|
| 378 |
+
if randomize_seed:
|
| 379 |
+
seed = random.randint(0, MAX_SEED)
|
| 380 |
+
seed = int(seed)
|
| 381 |
+
ref = image.convert("RGB")
|
| 382 |
+
target_size = ui.compute_target_size_from_ref(ref, EDIT_RESOLUTION)
|
| 383 |
+
t0 = time.perf_counter()
|
| 384 |
+
out = ui.editing_image(
|
| 385 |
+
gen_model=model,
|
| 386 |
+
tokenizer=tokenizer,
|
| 387 |
+
new_token_ids=new_token_ids,
|
| 388 |
+
vae_model=vae_model,
|
| 389 |
+
ref_image=ref,
|
| 390 |
+
prompt=instruction.strip(),
|
| 391 |
+
ref_image_size=448,
|
| 392 |
+
ref_max_size=None,
|
| 393 |
+
ref_min_size=256,
|
| 394 |
+
ref_use_mar=True,
|
| 395 |
+
ref_mar_resolution=EDIT_RESOLUTION,
|
| 396 |
+
target_size=target_size,
|
| 397 |
+
num_timesteps=int(steps),
|
| 398 |
+
cfg_text_scale=float(cfg_scale),
|
| 399 |
+
cfg_interval=[0, 1.0],
|
| 400 |
+
cfg_renorm_min=EDIT_CFG_RENORM_MIN,
|
| 401 |
+
timestep_shift=TIMESTEP_SHIFT,
|
| 402 |
+
device=DEVICE,
|
| 403 |
+
device_type=DEVICE_TYPE,
|
| 404 |
+
seed=seed,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
)
|
| 406 |
+
elapsed = time.perf_counter() - t0
|
| 407 |
+
return out, seed, f"{target_size[1]}×{target_size[0]} · {steps} steps · {elapsed:.1f}s"
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
# Thin wrappers for gr.Examples. They deliberately take no `gr.Progress`
|
| 411 |
+
# parameter: Gradio injects the Progress object at the index of that parameter
|
| 412 |
+
# and would otherwise land it in `steps` when an example row supplies fewer
|
| 413 |
+
# values than the full signature.
|
| 414 |
+
def generate_example(prompt: str):
|
| 415 |
+
"""Run the text-to-image example row with default settings."""
|
| 416 |
+
return generate(prompt)
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
def edit_example(image, instruction: str):
|
| 420 |
+
"""Run the image-editing example row with default settings."""
|
| 421 |
+
return edit(image, instruction)
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
# ---------------------------------------------------------------------------
|
| 425 |
+
# 5. UI
|
| 426 |
+
# ---------------------------------------------------------------------------
|
| 427 |
+
GEN_EXAMPLES = [
|
| 428 |
+
["A photo of a press conference with microphones and a blurred crowd in the background, realistic news style"],
|
| 429 |
+
["A watercolor painting of a lighthouse during a winter sunrise."],
|
| 430 |
+
["An ink wash painting style image illustrating a Chinese poem. A solitary boat floats on a misty river near mountains. Vertical traditional Chinese text on the right reads: '孤舟蓑笠翁,独钓寒江雪'. The mood is serene and melancholic. A red seal stamp (chop) is visible in the bottom left corner."],
|
| 431 |
+
["A realistic photo of a modern airport terminal sign hanging from the ceiling. The sign has a yellow background with black text. It shows directions with arrows. The text is in three languages: English 'Departures', Chinese '出发', and Japanese '出発'. An icon of an airplane taking off is next to the text. The background shows a blurred terminal hall with travelers."],
|
| 432 |
+
["A professional product photo of a sneaker on a white background, studio lighting, advertising style"],
|
| 433 |
+
]
|
| 434 |
+
|
| 435 |
+
EDIT_EXAMPLES = [
|
| 436 |
+
["examples/background_change.png", "Change the background to a forest."],
|
| 437 |
+
["examples/color_alter.png", "Alter the color of the mirror frame to orange."],
|
| 438 |
+
["examples/material_alter.png", "Change the cup in hand to ceramic."],
|
| 439 |
+
["examples/subject-add.png", "Add a balloon decoration strip below the airplane."],
|
| 440 |
+
]
|
| 441 |
+
|
| 442 |
+
CSS = """
|
| 443 |
+
#col-container { max-width: 1100px; margin: 0 auto; }
|
| 444 |
"""
|
| 445 |
|
| 446 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="UniSpace") as demo:
|
| 447 |
+
with gr.Column(elem_id="col-container"):
|
| 448 |
+
gr.Markdown(
|
| 449 |
+
"""
|
| 450 |
+
# 🛰️ UniSpace
|
| 451 |
+
**Unified Visual Representation and Scalable Multimodal Modeling** — an 8B
|
| 452 |
+
Mixture-of-Transformers that generates and edits images on top of a single
|
| 453 |
+
patch-reparameterized visual tokenizer.
|
| 454 |
+
|
| 455 |
+
[Paper](https://huggingface.co/papers/2608.08676) ·
|
| 456 |
+
[Model](https://huggingface.co/yjb6/UniSpace) ·
|
| 457 |
+
[Code](https://github.com/yjb6/UniSpace)
|
| 458 |
+
"""
|
| 459 |
+
)
|
| 460 |
|
| 461 |
+
with gr.Tabs():
|
| 462 |
+
with gr.Tab("Text to image"):
|
| 463 |
+
with gr.Row():
|
| 464 |
+
with gr.Column():
|
| 465 |
+
g_prompt = gr.Textbox(
|
| 466 |
+
label="Prompt",
|
| 467 |
+
lines=3,
|
| 468 |
+
placeholder="A watercolor painting of a lighthouse during a winter sunrise.",
|
| 469 |
+
)
|
| 470 |
+
g_run = gr.Button("Generate", variant="primary")
|
| 471 |
+
with gr.Accordion("Advanced settings", open=False):
|
| 472 |
+
g_resolution = gr.Radio(
|
| 473 |
+
choices=[512, 768, 1024],
|
| 474 |
+
value=1024,
|
| 475 |
+
label="Resolution",
|
| 476 |
+
)
|
| 477 |
+
g_steps = gr.Slider(
|
| 478 |
+
8, 60, value=DEFAULT_STEPS, step=1, label="Sampling steps"
|
| 479 |
+
)
|
| 480 |
+
g_cfg = gr.Slider(
|
| 481 |
+
1.0, 15.0, value=DEFAULT_CFG, step=0.5,
|
| 482 |
+
label="Guidance scale (CFG)",
|
| 483 |
+
)
|
| 484 |
+
g_seed = gr.Slider(
|
| 485 |
+
0, MAX_SEED, value=42, step=1, label="Seed"
|
| 486 |
+
)
|
| 487 |
+
g_rand = gr.Checkbox(value=False, label="Randomize seed")
|
| 488 |
+
with gr.Column():
|
| 489 |
+
g_out = gr.Image(label="Result", type="pil", height=520)
|
| 490 |
+
g_used_seed = gr.Number(label="Seed used", interactive=False)
|
| 491 |
+
g_info = gr.Textbox(label="Run info", interactive=False)
|
| 492 |
+
|
| 493 |
+
gr.Examples(
|
| 494 |
+
examples=GEN_EXAMPLES,
|
| 495 |
+
inputs=[g_prompt],
|
| 496 |
+
outputs=[g_out, g_used_seed, g_info],
|
| 497 |
+
fn=generate_example,
|
| 498 |
+
cache_examples=True,
|
| 499 |
+
cache_mode="lazy",
|
| 500 |
+
label="Prompts from the UniSpace release",
|
| 501 |
+
)
|
| 502 |
+
|
| 503 |
+
with gr.Tab("Instruction editing"):
|
| 504 |
+
with gr.Row():
|
| 505 |
+
with gr.Column():
|
| 506 |
+
e_image = gr.Image(label="Input image", type="pil", height=340)
|
| 507 |
+
e_prompt = gr.Textbox(
|
| 508 |
+
label="Editing instruction",
|
| 509 |
+
lines=2,
|
| 510 |
+
placeholder="Change the background to a forest.",
|
| 511 |
+
)
|
| 512 |
+
e_run = gr.Button("Edit", variant="primary")
|
| 513 |
+
with gr.Accordion("Advanced settings", open=False):
|
| 514 |
+
e_steps = gr.Slider(
|
| 515 |
+
8, 60, value=DEFAULT_STEPS, step=1, label="Sampling steps"
|
| 516 |
+
)
|
| 517 |
+
e_cfg = gr.Slider(
|
| 518 |
+
1.0, 15.0, value=DEFAULT_CFG, step=0.5,
|
| 519 |
+
label="Guidance scale (CFG)",
|
| 520 |
+
)
|
| 521 |
+
e_seed = gr.Slider(
|
| 522 |
+
0, MAX_SEED, value=42, step=1, label="Seed"
|
| 523 |
+
)
|
| 524 |
+
e_rand = gr.Checkbox(value=False, label="Randomize seed")
|
| 525 |
+
with gr.Column():
|
| 526 |
+
e_out = gr.Image(label="Result", type="pil", height=520)
|
| 527 |
+
e_used_seed = gr.Number(label="Seed used", interactive=False)
|
| 528 |
+
e_info = gr.Textbox(label="Run info", interactive=False)
|
| 529 |
+
|
| 530 |
+
gr.Examples(
|
| 531 |
+
examples=EDIT_EXAMPLES,
|
| 532 |
+
inputs=[e_image, e_prompt],
|
| 533 |
+
outputs=[e_out, e_used_seed, e_info],
|
| 534 |
+
fn=edit_example,
|
| 535 |
+
cache_examples=True,
|
| 536 |
+
cache_mode="lazy",
|
| 537 |
+
label="Examples from the UniSpace project page (GEdit-Bench, MIT)",
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
g_run.click(
|
| 541 |
+
fn=generate,
|
| 542 |
+
inputs=[g_prompt, g_steps, g_cfg, g_resolution, g_seed, g_rand],
|
| 543 |
+
outputs=[g_out, g_used_seed, g_info],
|
| 544 |
+
api_name="generate",
|
| 545 |
)
|
| 546 |
+
e_run.click(
|
| 547 |
+
fn=edit,
|
| 548 |
+
inputs=[e_image, e_prompt, e_steps, e_cfg, e_seed, e_rand],
|
| 549 |
+
outputs=[e_out, e_used_seed, e_info],
|
| 550 |
+
api_name="edit",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
)
|
| 552 |
|
| 553 |
+
if __name__ == "__main__":
|
| 554 |
+
demo.queue().launch(mcp_server=True)
|
examples/background_change.png
ADDED
|
Git LFS Details
|
examples/color_alter.png
ADDED
|
Git LFS Details
|
examples/material_alter.png
ADDED
|
Git LFS Details
|
examples/subject-add.png
ADDED
|
Git LFS Details
|
patch-reparameterization/configs/decoder/ViTXL_nockpt/config.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
| 21 |
"num_attention_heads": 12,
|
| 22 |
"num_channels": 3,
|
| 23 |
"num_hidden_layers": 12,
|
| 24 |
-
"patch_size":
|
| 25 |
"qkv_bias": true,
|
| 26 |
"torch_dtype": "float32",
|
| 27 |
"transformers_version": "4.42.3",
|
|
|
|
| 21 |
"num_attention_heads": 12,
|
| 22 |
"num_channels": 3,
|
| 23 |
"num_hidden_layers": 12,
|
| 24 |
+
"patch_size": "SHOULD BE RELOADED",
|
| 25 |
"qkv_bias": true,
|
| 26 |
"torch_dtype": "float32",
|
| 27 |
"transformers_version": "4.42.3",
|
patch-reparameterization/configs/release/pr-dinov2-imagenet256-generation.yaml
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Release generation config for PR-DINOv2.
|
| 2 |
+
stage_1:
|
| 3 |
+
target: stage1.PatchReparam
|
| 4 |
+
params:
|
| 5 |
+
encoder_cls: DINOv2Unified
|
| 6 |
+
encoder_config_path: ${oc.env:MODEL_ROOT}/huggingface.co/facebook/dinov2-with-registers-base
|
| 7 |
+
encoder_input_size: 224
|
| 8 |
+
encoder_params:
|
| 9 |
+
model_name: ${oc.env:MODEL_ROOT}/huggingface.co/facebook/dinov2-with-registers-base
|
| 10 |
+
recon_proj_dim: 128
|
| 11 |
+
decoder_config_path: configs/decoder/ViTXL_nockpt
|
| 12 |
+
noise_tau: 0.8
|
| 13 |
+
decode_noise: true
|
| 14 |
+
reshape_to_2d: true
|
| 15 |
+
normalization_stat_path: ${oc.env:PR_STATS_ROOT}/pr-dinov2-normalization-stats.pt
|
| 16 |
+
norm_feature: merged_tokens
|
| 17 |
+
checkpoint:
|
| 18 |
+
path: ${oc.env:PR_CHECKPOINT_ROOT}/pr-dinov2-tokenizer.pt
|
| 19 |
+
use_ema: true
|
| 20 |
+
stage_2:
|
| 21 |
+
target: stage2.models.DDT.DiTwDDTHead
|
| 22 |
+
params:
|
| 23 |
+
input_size: 16
|
| 24 |
+
patch_size: 1
|
| 25 |
+
in_channels: 896
|
| 26 |
+
hidden_size: [1152, 2048]
|
| 27 |
+
depth: [28, 2]
|
| 28 |
+
num_heads: [16, 16]
|
| 29 |
+
mlp_ratio: 4.0
|
| 30 |
+
class_dropout_prob: 0.1
|
| 31 |
+
num_classes: 1000
|
| 32 |
+
use_qknorm: false
|
| 33 |
+
use_swiglu: true
|
| 34 |
+
use_rope: true
|
| 35 |
+
use_rmsnorm: true
|
| 36 |
+
wo_shift: false
|
| 37 |
+
use_pos_embed: true
|
| 38 |
+
ckpt: ${oc.env:PR_CHECKPOINT_ROOT}/pr-dinov2-dit.pt
|
| 39 |
+
transport:
|
| 40 |
+
params: {path_type: Linear, prediction: velocity, loss_weight: null, time_dist_type: logit-normal_0_1}
|
| 41 |
+
sampler:
|
| 42 |
+
mode: ODE
|
| 43 |
+
params: {sampling_method: euler, num_steps: 50, atol: 1.0e-6, rtol: 1.0e-3, reverse: false}
|
| 44 |
+
guidance: {method: cfg, scale: 1.0, t_min: 0.0, t_max: 1.0}
|
| 45 |
+
misc:
|
| 46 |
+
save_latents: false
|
| 47 |
+
latent_size: [896, 16, 16]
|
| 48 |
+
num_classes: 1000
|
| 49 |
+
time_dist_shift_dim: 229376
|
| 50 |
+
time_dist_shift_base: 4096
|
| 51 |
+
training:
|
| 52 |
+
encoder_output_key: merged_tokens_norm
|
| 53 |
+
sem_dim: 768
|
| 54 |
+
recon_loss_weight: 18.0
|
| 55 |
+
normalize_channel_weight: true
|
patch-reparameterization/configs/release/pr-dinov2-imagenet256.yaml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Release evaluation config for PR-DINOv2.
|
| 2 |
+
# Set MODEL_ROOT, PR_CHECKPOINT_ROOT, and PR_STATS_ROOT before running.
|
| 3 |
+
stage_1:
|
| 4 |
+
target: stage1.PatchReparam
|
| 5 |
+
params:
|
| 6 |
+
encoder_cls: DINOv2Unified
|
| 7 |
+
encoder_config_path: ${oc.env:MODEL_ROOT}/huggingface.co/facebook/dinov2-with-registers-base
|
| 8 |
+
encoder_input_size: 224
|
| 9 |
+
encoder_params:
|
| 10 |
+
model_name: ${oc.env:MODEL_ROOT}/huggingface.co/facebook/dinov2-with-registers-base
|
| 11 |
+
recon_proj_dim: 128
|
| 12 |
+
decoder_config_path: configs/decoder/ViTXL_nockpt
|
| 13 |
+
noise_tau: 0.8
|
| 14 |
+
decode_noise: false
|
| 15 |
+
reshape_to_2d: true
|
| 16 |
+
normalization_stat_path: ${oc.env:PR_STATS_ROOT}/pr-dinov2-normalization-stats.pt
|
| 17 |
+
checkpoint:
|
| 18 |
+
path: ${oc.env:PR_CHECKPOINT_ROOT}/pr-dinov2-tokenizer.pt
|
| 19 |
+
use_ema: true
|
| 20 |
+
|
| 21 |
+
stage_2:
|
| 22 |
+
target: stage2.models.DDT.DiTwDDTHead
|
| 23 |
+
params:
|
| 24 |
+
input_size: 16
|
| 25 |
+
patch_size: 1
|
| 26 |
+
in_channels: 896
|
| 27 |
+
hidden_size: [1152, 2048]
|
| 28 |
+
depth: [28, 2]
|
| 29 |
+
num_heads: [16, 16]
|
| 30 |
+
mlp_ratio: 4.0
|
| 31 |
+
class_dropout_prob: 0.1
|
| 32 |
+
num_classes: 1000
|
| 33 |
+
use_qknorm: false
|
| 34 |
+
use_swiglu: true
|
| 35 |
+
use_rope: true
|
| 36 |
+
use_rmsnorm: true
|
| 37 |
+
wo_shift: false
|
| 38 |
+
use_pos_embed: true
|
| 39 |
+
ckpt: ${oc.env:PR_CHECKPOINT_ROOT}/pr-dinov2-dit.pt
|
| 40 |
+
|
| 41 |
+
transport:
|
| 42 |
+
params:
|
| 43 |
+
path_type: Linear
|
| 44 |
+
prediction: velocity
|
| 45 |
+
loss_weight: null
|
| 46 |
+
time_dist_type: logit-normal_0_1
|
| 47 |
+
sampler:
|
| 48 |
+
mode: ODE
|
| 49 |
+
params: {sampling_method: euler, num_steps: 50, atol: 1.0e-6, rtol: 1.0e-3, reverse: false}
|
| 50 |
+
guidance: {method: cfg, scale: 1.0, t_min: 0.0, t_max: 1.0}
|
| 51 |
+
misc:
|
| 52 |
+
save_latents: false
|
| 53 |
+
latent_size: [896, 16, 16]
|
| 54 |
+
num_classes: 1000
|
| 55 |
+
time_dist_shift_dim: 229376
|
| 56 |
+
time_dist_shift_base: 4096
|
| 57 |
+
training:
|
| 58 |
+
encoder_output_key: merged_tokens_norm
|
| 59 |
+
sem_dim: 768
|
| 60 |
+
recon_loss_weight: 18.0
|
| 61 |
+
normalize_channel_weight: true
|
| 62 |
+
eval:
|
| 63 |
+
data_path: ${oc.env:IMAGENET_ROOT}/val
|
| 64 |
+
reference_npz_path: ${oc.env:IMAGENET_VAL_NPZ}
|
| 65 |
+
metrics: [psnr, ssim, rfid]
|
| 66 |
+
eval_weights: ema_only
|
patch-reparameterization/configs/release/pr-qwen-vit-imagenet256.yaml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Release reconstruction config for PR-Qwen-ViT (Table 3).
|
| 2 |
+
# Set MODEL_ROOT, PR_CHECKPOINT_ROOT, and PR_STATS_ROOT before running.
|
| 3 |
+
stage_1:
|
| 4 |
+
target: stage1.PatchReparam
|
| 5 |
+
params:
|
| 6 |
+
encoder_cls: Qwen3Unified
|
| 7 |
+
encoder_config_path: ${oc.env:QWEN_MODEL_ROOT}/Qwen/Qwen3-VL-8B-Instruct
|
| 8 |
+
encoder_input_size: 256
|
| 9 |
+
encoder_params:
|
| 10 |
+
model_name: ${oc.env:QWEN_MODEL_ROOT}/Qwen/Qwen3-VL-8B-Instruct
|
| 11 |
+
recon_proj_dim: 128
|
| 12 |
+
pixel_format: c_major
|
| 13 |
+
decoder_config_path: configs/decoder/ViTXL_nockpt
|
| 14 |
+
decoder_cls: ViTDecoder
|
| 15 |
+
noise_tau: 0.8
|
| 16 |
+
reshape_to_2d: true
|
| 17 |
+
normalization_stat_path: ${oc.env:PR_STATS_ROOT}/pr-qwen-vit-normalization-stats.pt
|
| 18 |
+
checkpoint:
|
| 19 |
+
path: ${oc.env:PR_CHECKPOINT_ROOT}/pr-qwen-vit-tokenizer.pt
|
| 20 |
+
use_ema: true
|
| 21 |
+
|
| 22 |
+
eval:
|
| 23 |
+
data_path: ${oc.env:IMAGENET_ROOT}/val
|
| 24 |
+
reference_npz_path: ${oc.env:IMAGENET_VAL_NPZ}
|
| 25 |
+
metrics: [psnr, ssim, rfid]
|
| 26 |
+
eval_weights: ema_only
|
patch-reparameterization/configs/release/pr-siglip2-imagenet256-generation.yaml
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Release generation config for PR-SigLIP2.
|
| 2 |
+
stage_1:
|
| 3 |
+
target: stage1.PatchReparam
|
| 4 |
+
params:
|
| 5 |
+
encoder_cls: SigLIP2Unified
|
| 6 |
+
encoder_config_path: ${oc.env:MODEL_ROOT}/siglip2-base-patch16-256/main
|
| 7 |
+
encoder_input_size: 256
|
| 8 |
+
encoder_params:
|
| 9 |
+
model_name: ${oc.env:MODEL_ROOT}/siglip2-base-patch16-256/main
|
| 10 |
+
recon_proj_dim: 128
|
| 11 |
+
decoder_config_path: configs/decoder/ViTXL_nockpt
|
| 12 |
+
noise_tau: 0.8
|
| 13 |
+
decode_noise: true
|
| 14 |
+
reshape_to_2d: true
|
| 15 |
+
normalization_stat_path: ${oc.env:PR_STATS_ROOT}/pr-siglip2-normalization-stats.pt
|
| 16 |
+
norm_feature: merged_tokens
|
| 17 |
+
checkpoint:
|
| 18 |
+
path: ${oc.env:PR_CHECKPOINT_ROOT}/pr-siglip2-tokenizer.pt
|
| 19 |
+
use_ema: true
|
| 20 |
+
stage_2:
|
| 21 |
+
target: stage2.models.DDT.DiTwDDTHead
|
| 22 |
+
params:
|
| 23 |
+
input_size: 16
|
| 24 |
+
patch_size: 1
|
| 25 |
+
in_channels: 896
|
| 26 |
+
hidden_size: [1152, 2048]
|
| 27 |
+
depth: [28, 2]
|
| 28 |
+
num_heads: [16, 16]
|
| 29 |
+
mlp_ratio: 4.0
|
| 30 |
+
class_dropout_prob: 0.1
|
| 31 |
+
num_classes: 1000
|
| 32 |
+
use_qknorm: false
|
| 33 |
+
use_swiglu: true
|
| 34 |
+
use_rope: true
|
| 35 |
+
use_rmsnorm: true
|
| 36 |
+
wo_shift: false
|
| 37 |
+
use_pos_embed: true
|
| 38 |
+
ckpt: ${oc.env:PR_CHECKPOINT_ROOT}/pr-siglip2-dit.pt
|
| 39 |
+
transport:
|
| 40 |
+
params: {path_type: Linear, prediction: velocity, loss_weight: null, time_dist_type: logit-normal_0_1}
|
| 41 |
+
sampler:
|
| 42 |
+
mode: ODE
|
| 43 |
+
params: {sampling_method: euler, num_steps: 50, atol: 1.0e-6, rtol: 1.0e-3, reverse: false}
|
| 44 |
+
guidance: {method: cfg, scale: 1.0, t_min: 0.0, t_max: 1.0}
|
| 45 |
+
misc:
|
| 46 |
+
save_latents: false
|
| 47 |
+
latent_size: [896, 16, 16]
|
| 48 |
+
num_classes: 1000
|
| 49 |
+
time_dist_shift_dim: 229376
|
| 50 |
+
time_dist_shift_base: 4096
|
| 51 |
+
training:
|
| 52 |
+
encoder_output_key: merged_tokens_norm
|
| 53 |
+
sem_dim: 768
|
| 54 |
+
recon_loss_weight: 18.0
|
| 55 |
+
normalize_channel_weight: true
|
patch-reparameterization/configs/release/pr-siglip2-imagenet256.yaml
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Release evaluation config for PR-SigLIP2.
|
| 2 |
+
# Set MODEL_ROOT, PR_CHECKPOINT_ROOT, and PR_STATS_ROOT before running.
|
| 3 |
+
stage_1:
|
| 4 |
+
target: stage1.PatchReparam
|
| 5 |
+
params:
|
| 6 |
+
encoder_cls: SigLIP2wTwoBackbone
|
| 7 |
+
encoder_config_path: ${oc.env:MODEL_ROOT}/siglip2-base-patch16-256/main
|
| 8 |
+
encoder_input_size: 256
|
| 9 |
+
encoder_params:
|
| 10 |
+
sementic_model_name: ${oc.env:MODEL_ROOT}/siglip2-base-patch16-256/main
|
| 11 |
+
recon_model_name: ${oc.env:MODEL_ROOT}/siglip2-base-patch16-256/main
|
| 12 |
+
num_tokens: 256
|
| 13 |
+
recon_post_norm_type: no_norm
|
| 14 |
+
sementic_post_norm_type: no_norm
|
| 15 |
+
merger_cls: ConcatMerger
|
| 16 |
+
merger_kwargs:
|
| 17 |
+
recon_proj_dim: 128
|
| 18 |
+
recon_proj_cls: LinearAdapter
|
| 19 |
+
recon_proj_kwargs: {}
|
| 20 |
+
decoder_config_path: configs/decoder/ViTXL_nockpt
|
| 21 |
+
noise_tau: 0.8
|
| 22 |
+
decode_noise: false
|
| 23 |
+
reshape_to_2d: true
|
| 24 |
+
checkpoint:
|
| 25 |
+
path: ${oc.env:PR_SIGLIP2_TOKENIZER_CKPT}
|
| 26 |
+
use_ema: true
|
| 27 |
+
|
| 28 |
+
stage_2:
|
| 29 |
+
target: stage2.models.DDT.DiTwDDTHead
|
| 30 |
+
params:
|
| 31 |
+
input_size: 16
|
| 32 |
+
patch_size: 1
|
| 33 |
+
in_channels: 896
|
| 34 |
+
hidden_size: [1152, 2048]
|
| 35 |
+
depth: [28, 2]
|
| 36 |
+
num_heads: [16, 16]
|
| 37 |
+
mlp_ratio: 4.0
|
| 38 |
+
class_dropout_prob: 0.1
|
| 39 |
+
num_classes: 1000
|
| 40 |
+
use_qknorm: false
|
| 41 |
+
use_swiglu: true
|
| 42 |
+
use_rope: true
|
| 43 |
+
use_rmsnorm: true
|
| 44 |
+
wo_shift: false
|
| 45 |
+
use_pos_embed: true
|
| 46 |
+
ckpt: ${oc.env:PR_CHECKPOINT_ROOT}/pr-siglip2-dit.pt
|
| 47 |
+
|
| 48 |
+
transport:
|
| 49 |
+
params:
|
| 50 |
+
path_type: Linear
|
| 51 |
+
prediction: velocity
|
| 52 |
+
loss_weight: null
|
| 53 |
+
time_dist_type: logit-normal_0_1
|
| 54 |
+
sampler:
|
| 55 |
+
mode: ODE
|
| 56 |
+
params: {sampling_method: euler, num_steps: 50, atol: 1.0e-6, rtol: 1.0e-3, reverse: false}
|
| 57 |
+
guidance: {method: cfg, scale: 1.0, t_min: 0.0, t_max: 1.0}
|
| 58 |
+
misc:
|
| 59 |
+
save_latents: false
|
| 60 |
+
latent_size: [896, 16, 16]
|
| 61 |
+
num_classes: 1000
|
| 62 |
+
time_dist_shift_dim: 229376
|
| 63 |
+
time_dist_shift_base: 4096
|
| 64 |
+
training:
|
| 65 |
+
encoder_output_key: merged_tokens_norm
|
| 66 |
+
sem_dim: 768
|
| 67 |
+
recon_loss_weight: 18.0
|
| 68 |
+
normalize_channel_weight: true
|
| 69 |
+
eval:
|
| 70 |
+
data_path: ${oc.env:IMAGENET_ROOT}/val
|
| 71 |
+
reference_npz_path: ${oc.env:IMAGENET_VAL_NPZ}
|
| 72 |
+
metrics: [psnr, ssim, rfid]
|
| 73 |
+
eval_weights: ema_only
|
patch-reparameterization/src/eval/__init__.py
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .ref_iqa import calculate_psnr, calculate_lpips, calculate_ssim
|
| 2 |
+
from .fid import calculate_rfid, calculate_gfid
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from torch.cuda.amp import autocast
|
| 8 |
+
from typing import Union, Dict, Optional
|
| 9 |
+
try:
|
| 10 |
+
from accelerate import Accelerator
|
| 11 |
+
except ImportError:
|
| 12 |
+
Accelerator = None
|
| 13 |
+
from torch.utils.data import DataLoader, Subset
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
from functools import lru_cache
|
| 18 |
+
@lru_cache(maxsize=4)
|
| 19 |
+
def _load_encoder_cached(model_path: str, device_str: str):
|
| 20 |
+
"""
|
| 21 |
+
懒加载并缓存 encoder(按 model_path + device 缓存,进程内只加载一次)。
|
| 22 |
+
使用 transformers.AutoModel + AutoProcessor。
|
| 23 |
+
"""
|
| 24 |
+
from transformers import AutoModel, AutoProcessor
|
| 25 |
+
print(f"[CosSim] Loading encoder from {model_path} onto {device_str} (cached)...")
|
| 26 |
+
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
|
| 27 |
+
model = AutoModel.from_pretrained(model_path, trust_remote_code=True)
|
| 28 |
+
model = model.to(device_str).eval()
|
| 29 |
+
return model, processor
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@torch.no_grad()
|
| 33 |
+
def calculate_encoder_cos_sim(
|
| 34 |
+
ref_arr: np.ndarray,
|
| 35 |
+
rec_arr: np.ndarray,
|
| 36 |
+
encoder_model: str,
|
| 37 |
+
device: torch.device,
|
| 38 |
+
batch_size: int = 64,
|
| 39 |
+
) -> float:
|
| 40 |
+
"""
|
| 41 |
+
用指定 encoder 对 GT 图像和重建图像编码,计算 image embedding 的 cos 相似度均值。
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
ref_arr: GT 图像 [N, H, W, C] uint8
|
| 45 |
+
rec_arr: 重建图像 [N, H, W, C] uint8
|
| 46 |
+
encoder_model: HuggingFace 模型路径或名称
|
| 47 |
+
device: 计算设备
|
| 48 |
+
batch_size: 每批编码的图像数
|
| 49 |
+
|
| 50 |
+
Returns:
|
| 51 |
+
所有样本的 cos 相似度均值 (float)
|
| 52 |
+
"""
|
| 53 |
+
device_str = "cuda" if device.type == "cuda" else "cpu"
|
| 54 |
+
model, processor = _load_encoder_cached(encoder_model, device_str)
|
| 55 |
+
|
| 56 |
+
N = len(ref_arr)
|
| 57 |
+
cos_sims = []
|
| 58 |
+
|
| 59 |
+
for start in range(0, N, batch_size):
|
| 60 |
+
end = min(start + batch_size, N)
|
| 61 |
+
ref_batch = [Image.fromarray(img) for img in ref_arr[start:end]]
|
| 62 |
+
rec_batch = [Image.fromarray(img) for img in rec_arr[start:end]]
|
| 63 |
+
|
| 64 |
+
# 预处理:使用 processor 处理两组图像
|
| 65 |
+
ref_inputs = processor(images=ref_batch, return_tensors="pt").to(device_str)
|
| 66 |
+
rec_inputs = processor(images=rec_batch, return_tensors="pt").to(device_str)
|
| 67 |
+
|
| 68 |
+
# 提取 image embedding
|
| 69 |
+
ref_feats = model.get_image_features(**ref_inputs) # [B, D]
|
| 70 |
+
rec_feats = model.get_image_features(**rec_inputs) # [B, D]
|
| 71 |
+
|
| 72 |
+
# L2 归一化后计算 cos 相似度
|
| 73 |
+
ref_feats = F.normalize(ref_feats.float(), dim=-1)
|
| 74 |
+
rec_feats = F.normalize(rec_feats.float(), dim=-1)
|
| 75 |
+
batch_cos = (ref_feats * rec_feats).sum(dim=-1) # [B]
|
| 76 |
+
cos_sims.append(batch_cos.cpu())
|
| 77 |
+
|
| 78 |
+
cos_sim_mean = torch.cat(cos_sims).mean().item()
|
| 79 |
+
return cos_sim_mean
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def compute_reconstruction_metrics(
|
| 83 |
+
ref_arr: np.ndarray,
|
| 84 |
+
rec_arr: np.ndarray,
|
| 85 |
+
device: torch.device,
|
| 86 |
+
batch_size: int = 128,
|
| 87 |
+
metrics_to_compute = ("psnr", "ssim", "rfid"),
|
| 88 |
+
disable_bar: bool = True,
|
| 89 |
+
encoder_model: Optional[str] = None,
|
| 90 |
+
psnr_resize: Optional[int] = None,
|
| 91 |
+
l1_resize: Optional[int] = None,
|
| 92 |
+
) -> Dict[str, float]:
|
| 93 |
+
"""
|
| 94 |
+
Compute reconstruction metrics between reference and reconstructed images.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
ref_arr: Reference images [N, H, W, C] uint8
|
| 98 |
+
rec_arr: Reconstructed images [N, H, W, C] uint8
|
| 99 |
+
device: Device for computation
|
| 100 |
+
batch_size: Batch size for metric computation
|
| 101 |
+
|
| 102 |
+
Returns:
|
| 103 |
+
Dictionary with metrics: eval/psnr, eval/ssim, eval/rfid
|
| 104 |
+
Note: LPIPS is not computed here since it's already tracked during training
|
| 105 |
+
"""
|
| 106 |
+
device_str = "cuda" if device.type == "cuda" else "cpu"
|
| 107 |
+
# For paired metrics (psnr, ssim, l1, cos_sim), align ref/rec count
|
| 108 |
+
n_paired = min(len(ref_arr), len(rec_arr))
|
| 109 |
+
ref_paired = ref_arr[:n_paired]
|
| 110 |
+
rec_paired = rec_arr[:n_paired]
|
| 111 |
+
results_dict = {}
|
| 112 |
+
if 'psnr' in metrics_to_compute:
|
| 113 |
+
psnr = calculate_psnr(ref_paired, rec_paired, batch_size, device_str, disable_bar=disable_bar, psnr_resize=psnr_resize)
|
| 114 |
+
results_dict["psnr"] = psnr
|
| 115 |
+
if 'ssim' in metrics_to_compute:
|
| 116 |
+
ssim = calculate_ssim(ref_paired, rec_paired, batch_size, device_str, disable_bar=disable_bar)
|
| 117 |
+
results_dict["ssim"] = ssim
|
| 118 |
+
if 'rfid' in metrics_to_compute:
|
| 119 |
+
rfid = calculate_rfid(ref_arr, rec_arr, batch_size, device_str)
|
| 120 |
+
results_dict["rfid"] = rfid
|
| 121 |
+
if 'l1' in metrics_to_compute:
|
| 122 |
+
# L1 = mean(|rec - ref|), same as train: (recon - images).abs().mean()
|
| 123 |
+
ref_f = ref_paired.astype(np.float32) / 255.0
|
| 124 |
+
rec_f = rec_paired.astype(np.float32) / 255.0
|
| 125 |
+
if l1_resize is not None:
|
| 126 |
+
# resize via torch interpolate to keep consistent with psnr_resize style
|
| 127 |
+
# Process in small batches to avoid CUDA OOM on large datasets
|
| 128 |
+
device_str = "cuda" if device.type == "cuda" else "cpu"
|
| 129 |
+
resize_batch = 64
|
| 130 |
+
n = ref_f.shape[0]
|
| 131 |
+
l1_sum = 0.0
|
| 132 |
+
l1_count = 0
|
| 133 |
+
for i in range(0, n, resize_batch):
|
| 134 |
+
ref_b = torch.from_numpy(ref_f[i:i+resize_batch]).permute(0, 3, 1, 2).to(device_str)
|
| 135 |
+
rec_b = torch.from_numpy(rec_f[i:i+resize_batch]).permute(0, 3, 1, 2).to(device_str)
|
| 136 |
+
ref_b = F.interpolate(ref_b, size=(l1_resize, l1_resize), mode="bilinear", align_corners=False)
|
| 137 |
+
rec_b = F.interpolate(rec_b, size=(l1_resize, l1_resize), mode="bilinear", align_corners=False)
|
| 138 |
+
l1_sum += (ref_b - rec_b).abs().sum().item()
|
| 139 |
+
l1_count += ref_b.numel()
|
| 140 |
+
l1 = l1_sum / l1_count
|
| 141 |
+
else:
|
| 142 |
+
l1 = np.abs(rec_f - ref_f).mean()
|
| 143 |
+
results_dict["l1"] = float(l1)
|
| 144 |
+
if 'cos_sim' in metrics_to_compute:
|
| 145 |
+
assert encoder_model is not None, \
|
| 146 |
+
"'cos_sim' metric requires encoder_model (HuggingFace model path) to be specified."
|
| 147 |
+
print(f"[CosSim] Computing encoder cos_sim with model: {encoder_model}")
|
| 148 |
+
cos_sim = calculate_encoder_cos_sim(
|
| 149 |
+
ref_paired, rec_paired,
|
| 150 |
+
encoder_model=encoder_model,
|
| 151 |
+
device=device,
|
| 152 |
+
batch_size=batch_size,
|
| 153 |
+
)
|
| 154 |
+
results_dict["cos_sim"] = cos_sim
|
| 155 |
+
assert len(results_dict) > 0, "No metrics were computed."
|
| 156 |
+
return results_dict
|
| 157 |
+
def compute_generation_metrics(
|
| 158 |
+
ref_arr: np.ndarray,
|
| 159 |
+
rec_arr: np.ndarray,
|
| 160 |
+
device: torch.device,
|
| 161 |
+
batch_size: int = 128,
|
| 162 |
+
):
|
| 163 |
+
device_str = "cuda" if device.type == "cuda" else "cpu"
|
| 164 |
+
# only eval FID
|
| 165 |
+
fid = calculate_gfid(rec_arr, ref_arr, batch_size, device_str)
|
| 166 |
+
return {
|
| 167 |
+
'fid': fid
|
| 168 |
+
}
|
| 169 |
+
@torch.no_grad()
|
| 170 |
+
def evaluate_generation_distributed(
|
| 171 |
+
model_fn,
|
| 172 |
+
sample_fn,
|
| 173 |
+
latent_size, # for noise
|
| 174 |
+
additional_model_kwargs,
|
| 175 |
+
use_guidance: bool,
|
| 176 |
+
pr,
|
| 177 |
+
val_dataset,
|
| 178 |
+
num_samples: int,
|
| 179 |
+
batch_size: int,
|
| 180 |
+
rank: Union[int, None] = None,
|
| 181 |
+
world_size: Union[int, None] = None,
|
| 182 |
+
device: torch.device = None,
|
| 183 |
+
experiment_dir: str = None,
|
| 184 |
+
global_step: int = None,
|
| 185 |
+
autocast_kwargs: dict = None,
|
| 186 |
+
metric_batch_size: int = 128,
|
| 187 |
+
reference_npz_path: Optional[str] = None,
|
| 188 |
+
accelerator: Union[Accelerator, None] = None,
|
| 189 |
+
) -> Optional[Dict[str, float]]:
|
| 190 |
+
"""
|
| 191 |
+
Evaluate generation metrics using all GPUs in a distributed manner.
|
| 192 |
+
|
| 193 |
+
Args:
|
| 194 |
+
val_dataset: Validation dataset
|
| 195 |
+
batch_size: Batch size per GPU for generation
|
| 196 |
+
rank: Current GPU rank (deprecated, use accelerator instead)
|
| 197 |
+
world_size: Total number of GPUs (deprecated, use accelerator instead)
|
| 198 |
+
device: Device to use (deprecated, use accelerator instead)
|
| 199 |
+
experiment_dir: Experiment directory
|
| 200 |
+
global_step: Current training step
|
| 201 |
+
autocast_kwargs: Autocast configuration
|
| 202 |
+
metric_batch_size: Batch size for metric computation (on rank 0)
|
| 203 |
+
reference_npz_path: Optional path to existing reference NPZ file
|
| 204 |
+
accelerator: Accelerator instance (preferred over rank/world_size/device)
|
| 205 |
+
|
| 206 |
+
Returns:
|
| 207 |
+
Dictionary of metrics (only on rank 0, None on other ranks)
|
| 208 |
+
"""
|
| 209 |
+
# Support both Accelerator and legacy DDP interface
|
| 210 |
+
if accelerator is not None:
|
| 211 |
+
rank = accelerator.process_index
|
| 212 |
+
world_size = accelerator.num_processes
|
| 213 |
+
device = accelerator.device
|
| 214 |
+
is_main_process = accelerator.is_main_process
|
| 215 |
+
wait_for_everyone = accelerator.wait_for_everyone
|
| 216 |
+
else:
|
| 217 |
+
# Legacy DDP mode
|
| 218 |
+
import torch.distributed as dist
|
| 219 |
+
if rank is None or world_size is None or device is None:
|
| 220 |
+
raise ValueError("Either accelerator must be provided, or rank/world_size/device must be provided")
|
| 221 |
+
is_main_process = (rank == 0)
|
| 222 |
+
wait_for_everyone = dist.barrier
|
| 223 |
+
|
| 224 |
+
# model.eval()
|
| 225 |
+
# Save shard NPZ
|
| 226 |
+
temp_dir = os.path.join(experiment_dir, "eval_npzs")
|
| 227 |
+
if is_main_process:
|
| 228 |
+
print(f"\n[Eval] Starting distributed sampling evaluation at step {global_step}")
|
| 229 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 230 |
+
|
| 231 |
+
# Wait for rank 0 to create the directory before other ranks try to save
|
| 232 |
+
wait_for_everyone()
|
| 233 |
+
# print(f"[Rank {rank}] Starting sampling...")
|
| 234 |
+
# Each rank processes its shard
|
| 235 |
+
N = min(len(val_dataset), num_samples)
|
| 236 |
+
chunk = N // world_size
|
| 237 |
+
|
| 238 |
+
if rank < world_size - 1:
|
| 239 |
+
start = rank * chunk
|
| 240 |
+
end = (rank + 1) * chunk
|
| 241 |
+
else:
|
| 242 |
+
# Last rank takes the remainder (and handles N < world_size gracefully)
|
| 243 |
+
start = rank * chunk
|
| 244 |
+
end = N
|
| 245 |
+
|
| 246 |
+
rank_indices = list(range(start, end))
|
| 247 |
+
subset = Subset(val_dataset, rank_indices)
|
| 248 |
+
loader = DataLoader(
|
| 249 |
+
subset,
|
| 250 |
+
batch_size=batch_size,
|
| 251 |
+
shuffle=False,
|
| 252 |
+
num_workers=4,
|
| 253 |
+
pin_memory=True,
|
| 254 |
+
drop_last=False,
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
# Reconstruct images on this rank
|
| 258 |
+
generations = []
|
| 259 |
+
iterator = tqdm(loader, desc=f"[Rank {rank}] Sampling", file=sys.stdout) if is_main_process else loader
|
| 260 |
+
|
| 261 |
+
with torch.inference_mode():
|
| 262 |
+
for _, label in iterator: # don't actually need images at sampling time
|
| 263 |
+
n = label.size(0)
|
| 264 |
+
z = torch.randn(n, *latent_size, device = device)
|
| 265 |
+
y = label.to(device)
|
| 266 |
+
if use_guidance:
|
| 267 |
+
z = torch.cat([z, z], dim=0)
|
| 268 |
+
y_null = torch.full((n,), null_label, device=device)
|
| 269 |
+
y = torch.cat([y, y_null], dim=0)
|
| 270 |
+
model_kwargs = dict(y=y, **additional_model_kwargs)
|
| 271 |
+
with autocast(**autocast_kwargs):
|
| 272 |
+
samples = sample_fn(z, model_fn, **model_kwargs)[-1]
|
| 273 |
+
if use_guidance:
|
| 274 |
+
samples = samples.chunk(2, dim = 0)
|
| 275 |
+
if callable(pr) and not hasattr(pr, 'decode'):
|
| 276 |
+
samples = pr(samples)
|
| 277 |
+
else:
|
| 278 |
+
samples = pr.decode(samples)
|
| 279 |
+
if hasattr(samples, 'x_rec'):
|
| 280 |
+
samples = samples.x_rec
|
| 281 |
+
samples = samples.clamp(0, 1)
|
| 282 |
+
gen_np = samples.mul(255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
|
| 283 |
+
for img in gen_np:
|
| 284 |
+
generations.append(img)
|
| 285 |
+
|
| 286 |
+
generations = np.stack(generations)
|
| 287 |
+
shard_path = os.path.join(temp_dir, f"gen_{global_step:07d}_{rank:02d}.npz")
|
| 288 |
+
np.savez(shard_path, arr_0=generations)
|
| 289 |
+
|
| 290 |
+
if is_main_process:
|
| 291 |
+
print(f"[Rank {rank}] Saved {len(generations)} generation to {shard_path}")
|
| 292 |
+
|
| 293 |
+
# Wait for all ranks to finish generation
|
| 294 |
+
wait_for_everyone()
|
| 295 |
+
|
| 296 |
+
# Rank 0 computes metrics
|
| 297 |
+
metrics = None
|
| 298 |
+
if is_main_process:
|
| 299 |
+
# Combine all generation shards
|
| 300 |
+
all_gens = []
|
| 301 |
+
for r in range(world_size):
|
| 302 |
+
shard_file = os.path.join(temp_dir, f"gen_{global_step:07d}_{r:02d}.npz")
|
| 303 |
+
shard_data = np.load(shard_file)["arr_0"]
|
| 304 |
+
all_gens.append(shard_data)
|
| 305 |
+
|
| 306 |
+
combined_recons = np.concatenate(all_gens, axis=0)[:num_samples]
|
| 307 |
+
print(f"[Eval] Combined generation NPZ shape: {combined_recons.shape}")
|
| 308 |
+
|
| 309 |
+
# Load reference NPZ
|
| 310 |
+
ref_npz_path = reference_npz_path
|
| 311 |
+
|
| 312 |
+
if not os.path.exists(ref_npz_path):
|
| 313 |
+
raise FileNotFoundError(f"Reference NPZ not found at {ref_npz_path}")
|
| 314 |
+
|
| 315 |
+
ref_stats = np.load(ref_npz_path)
|
| 316 |
+
print(f"[Eval] Loaded reference NPZ from {ref_npz_path}")
|
| 317 |
+
|
| 318 |
+
# Compute metrics
|
| 319 |
+
print("[Eval] Computing metrics...")
|
| 320 |
+
metrics = compute_generation_metrics(
|
| 321 |
+
ref_stats,
|
| 322 |
+
combined_recons,
|
| 323 |
+
device,
|
| 324 |
+
metric_batch_size,
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
# Print results
|
| 328 |
+
print(f"[Eval] Step {global_step} Metrics:")
|
| 329 |
+
for key, value in metrics.items():
|
| 330 |
+
print(f" {key}: {value:.6f}")
|
| 331 |
+
|
| 332 |
+
# Cleanup reconstruction shards
|
| 333 |
+
for r in range(world_size):
|
| 334 |
+
shard_file = os.path.join(temp_dir, f"gen_{global_step:07d}_{r:02d}.npz")
|
| 335 |
+
if os.path.exists(shard_file):
|
| 336 |
+
os.remove(shard_file)
|
| 337 |
+
|
| 338 |
+
wait_for_everyone()
|
| 339 |
+
return metrics
|
| 340 |
+
@torch.no_grad()
|
| 341 |
+
def evaluate_reconstruction_distributed(
|
| 342 |
+
model,
|
| 343 |
+
val_dataset,
|
| 344 |
+
num_samples: int,
|
| 345 |
+
batch_size: int,
|
| 346 |
+
rank: Union[int, None] = None,
|
| 347 |
+
world_size: Union[int, None] = None,
|
| 348 |
+
device: torch.device = None,
|
| 349 |
+
experiment_dir: str = None,
|
| 350 |
+
global_step: int = None,
|
| 351 |
+
autocast_kwargs: dict = None,
|
| 352 |
+
metric_batch_size: int = 128,
|
| 353 |
+
reference_npz_path: Optional[str] = None,
|
| 354 |
+
metrics_to_compute: Optional[list] = ("psnr", "ssim", "rfid"),
|
| 355 |
+
accelerator: Union[Accelerator, None] = None,
|
| 356 |
+
save_recon_dir: Optional[str] = None,
|
| 357 |
+
image_features_mode: Optional[str] = None,
|
| 358 |
+
encoder_cos_sim_model: Optional[str] = None,
|
| 359 |
+
psnr_resize: Optional[int] = None,
|
| 360 |
+
l1_resize: Optional[int] = None,
|
| 361 |
+
) -> Optional[Dict[str, float]]:
|
| 362 |
+
"""
|
| 363 |
+
Evaluate reconstruction metrics using all GPUs in a distributed manner.
|
| 364 |
+
|
| 365 |
+
Args:
|
| 366 |
+
model: Model to evaluate (should be in eval mode)
|
| 367 |
+
val_dataset: Validation dataset
|
| 368 |
+
batch_size: Batch size per GPU for reconstruction
|
| 369 |
+
rank: Current GPU rank (deprecated, use accelerator instead)
|
| 370 |
+
world_size: Total number of GPUs (deprecated, use accelerator instead)
|
| 371 |
+
device: Device to use (deprecated, use accelerator instead)
|
| 372 |
+
experiment_dir: Experiment directory
|
| 373 |
+
global_step: Current training step
|
| 374 |
+
autocast_kwargs: Autocast configuration
|
| 375 |
+
metric_batch_size: Batch size for metric computation (on rank 0)
|
| 376 |
+
reference_npz_path: Optional path to existing reference NPZ file
|
| 377 |
+
accelerator: Accelerator instance (preferred over rank/world_size/device)
|
| 378 |
+
|
| 379 |
+
Returns:
|
| 380 |
+
Dictionary of metrics (only on rank 0, None on other ranks)
|
| 381 |
+
"""
|
| 382 |
+
# Support both Accelerator and legacy DDP interface
|
| 383 |
+
if accelerator is not None:
|
| 384 |
+
rank = accelerator.process_index
|
| 385 |
+
world_size = accelerator.num_processes
|
| 386 |
+
device = accelerator.device
|
| 387 |
+
is_main_process = accelerator.is_main_process
|
| 388 |
+
wait_for_everyone = accelerator.wait_for_everyone
|
| 389 |
+
else:
|
| 390 |
+
# Legacy DDP mode
|
| 391 |
+
import torch.distributed as dist
|
| 392 |
+
if rank is None or world_size is None or device is None:
|
| 393 |
+
raise ValueError("Either accelerator must be provided, or rank/world_size/device must be provided")
|
| 394 |
+
is_main_process = (rank == 0)
|
| 395 |
+
wait_for_everyone = dist.barrier
|
| 396 |
+
|
| 397 |
+
model.eval()
|
| 398 |
+
# Save shard NPZ
|
| 399 |
+
temp_dir = os.path.join(experiment_dir, "eval_npzs")
|
| 400 |
+
if is_main_process:
|
| 401 |
+
print(f"\n[Eval] Starting distributed reconstruction evaluation at step {global_step}")
|
| 402 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 403 |
+
# Wait for rank 0 to create the directory before other ranks try to save
|
| 404 |
+
wait_for_everyone()
|
| 405 |
+
# print(f"[Rank {rank}] Starting reconstruction...")
|
| 406 |
+
# Each rank processes its shard
|
| 407 |
+
N = min(len(val_dataset), num_samples)
|
| 408 |
+
chunk = N // world_size
|
| 409 |
+
|
| 410 |
+
if rank < world_size - 1:
|
| 411 |
+
start = rank * chunk
|
| 412 |
+
end = (rank + 1) * chunk
|
| 413 |
+
else:
|
| 414 |
+
# Last rank takes the remainder (and handles N < world_size gracefully)
|
| 415 |
+
start = rank * chunk
|
| 416 |
+
end = N
|
| 417 |
+
|
| 418 |
+
rank_indices = list(range(start, end))
|
| 419 |
+
subset = Subset(val_dataset, rank_indices)
|
| 420 |
+
loader = DataLoader(
|
| 421 |
+
subset,
|
| 422 |
+
batch_size=batch_size,
|
| 423 |
+
shuffle=False,
|
| 424 |
+
num_workers=4,
|
| 425 |
+
pin_memory=True,
|
| 426 |
+
drop_last=False,
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
# Reconstruct images on this rank, keep originals for distributed metrics
|
| 430 |
+
reconstructions = []
|
| 431 |
+
originals = []
|
| 432 |
+
iterator = tqdm(loader, desc=f"[Rank {rank}] Reconstructing", file=sys.stdout) if is_main_process else loader
|
| 433 |
+
|
| 434 |
+
with torch.inference_mode():
|
| 435 |
+
for images, _ in iterator:
|
| 436 |
+
images = images.to(device, non_blocking=True)
|
| 437 |
+
# Save originals before model forward
|
| 438 |
+
orig_np = images.clamp(0, 1).mul(255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
|
| 439 |
+
with autocast(**autocast_kwargs):
|
| 440 |
+
if image_features_mode is not None:
|
| 441 |
+
recon = model(images, image_features_mode=image_features_mode)
|
| 442 |
+
else:
|
| 443 |
+
recon = model(images)
|
| 444 |
+
|
| 445 |
+
# Handle DecodeOutput from split_adaln decoder
|
| 446 |
+
if hasattr(recon, 'x_rec'):
|
| 447 |
+
recon = recon.x_rec
|
| 448 |
+
# Convert to numpy uint8 [H, W, C]
|
| 449 |
+
recon = recon.clamp(0, 1)
|
| 450 |
+
recon_np = recon.mul(255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
|
| 451 |
+
|
| 452 |
+
for img in recon_np:
|
| 453 |
+
reconstructions.append(img)
|
| 454 |
+
for img in orig_np:
|
| 455 |
+
originals.append(img)
|
| 456 |
+
|
| 457 |
+
reconstructions = np.stack(reconstructions)
|
| 458 |
+
originals = np.stack(originals)
|
| 459 |
+
|
| 460 |
+
# Save shard for backward compat
|
| 461 |
+
shard_path = os.path.join(temp_dir, f"recon_{global_step:07d}_{rank:02d}.npz")
|
| 462 |
+
np.savez(shard_path, arr_0=reconstructions)
|
| 463 |
+
if is_main_process:
|
| 464 |
+
print(f"[Rank {rank}] Saved {len(reconstructions)} reconstructions to {shard_path}")
|
| 465 |
+
|
| 466 |
+
# Wait for all ranks to finish reconstruction
|
| 467 |
+
wait_for_everyone()
|
| 468 |
+
|
| 469 |
+
# ── Distributed metrics: each rank computes on its own subset ──────────
|
| 470 |
+
import torch.distributed as dist
|
| 471 |
+
from .fid import compute_inception_features, calculate_rfid_from_features
|
| 472 |
+
local_n = len(reconstructions)
|
| 473 |
+
|
| 474 |
+
# PSNR / SSIM: compute locally then all_reduce weighted sum
|
| 475 |
+
scalar_metrics_to_compute = [m for m in (metrics_to_compute or []) if m in ('psnr', 'ssim', 'lpips')]
|
| 476 |
+
local_scalar = {}
|
| 477 |
+
if scalar_metrics_to_compute:
|
| 478 |
+
if is_main_process:
|
| 479 |
+
print(f"[Eval] Rank {rank}: computing {scalar_metrics_to_compute} on {local_n} images...")
|
| 480 |
+
local_scalar = compute_reconstruction_metrics(
|
| 481 |
+
originals, reconstructions, device, metric_batch_size,
|
| 482 |
+
metrics_to_compute=scalar_metrics_to_compute,
|
| 483 |
+
disable_bar=is_main_process,
|
| 484 |
+
encoder_model=encoder_cos_sim_model,
|
| 485 |
+
psnr_resize=psnr_resize,
|
| 486 |
+
l1_resize=l1_resize,
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
# Weighted all_reduce: sum(metric * count) / total_count
|
| 490 |
+
global_scalar = {}
|
| 491 |
+
count_t = torch.tensor(float(local_n), device=device)
|
| 492 |
+
distributed = world_size > 1 and dist.is_available() and dist.is_initialized()
|
| 493 |
+
if distributed:
|
| 494 |
+
dist.all_reduce(count_t, op=dist.ReduceOp.SUM)
|
| 495 |
+
for k, v in local_scalar.items():
|
| 496 |
+
val_t = torch.tensor(v * local_n, dtype=torch.float64, device=device)
|
| 497 |
+
if distributed:
|
| 498 |
+
dist.all_reduce(val_t, op=dist.ReduceOp.SUM)
|
| 499 |
+
global_scalar[k] = (val_t / count_t).item()
|
| 500 |
+
|
| 501 |
+
# rFID: each rank computes inception features, all_gather, rank 0 computes FID
|
| 502 |
+
global_rfid = {}
|
| 503 |
+
if 'rfid' in (metrics_to_compute or []):
|
| 504 |
+
if is_main_process:
|
| 505 |
+
print(f"[Eval] Rank {rank}: computing inception features on {local_n} images...")
|
| 506 |
+
local_recon_feats = compute_inception_features(reconstructions, metric_batch_size, device)
|
| 507 |
+
local_ref_feats = compute_inception_features(originals, metric_batch_size, device)
|
| 508 |
+
|
| 509 |
+
recon_t = torch.from_numpy(local_recon_feats).to(device)
|
| 510 |
+
ref_t = torch.from_numpy(local_ref_feats).to(device)
|
| 511 |
+
|
| 512 |
+
if distributed:
|
| 513 |
+
all_recon = [torch.zeros_like(recon_t) for _ in range(world_size)]
|
| 514 |
+
all_ref = [torch.zeros_like(ref_t) for _ in range(world_size)]
|
| 515 |
+
dist.all_gather(all_recon, recon_t)
|
| 516 |
+
dist.all_gather(all_ref, ref_t)
|
| 517 |
+
else:
|
| 518 |
+
all_recon = [recon_t]
|
| 519 |
+
all_ref = [ref_t]
|
| 520 |
+
|
| 521 |
+
if is_main_process:
|
| 522 |
+
combined_recon_feats = torch.cat(all_recon, dim=0).cpu().double().numpy()
|
| 523 |
+
combined_ref_feats = torch.cat(all_ref, dim=0).cpu().double().numpy()
|
| 524 |
+
rfid = calculate_rfid_from_features(combined_recon_feats, combined_ref_feats)
|
| 525 |
+
global_rfid = {'rfid': rfid}
|
| 526 |
+
|
| 527 |
+
metrics = None
|
| 528 |
+
if is_main_process:
|
| 529 |
+
metrics = {**global_scalar, **global_rfid}
|
| 530 |
+
# Persist the all-reduced count so release validation proves how many
|
| 531 |
+
# examples were actually processed rather than trusting CLI intent.
|
| 532 |
+
metrics["_evaluated_samples"] = int(count_t.item())
|
| 533 |
+
print(f"[Eval] Step {global_step} Metrics:")
|
| 534 |
+
for key, value in metrics.items():
|
| 535 |
+
print(f" {key}: {value:.6f}")
|
| 536 |
+
|
| 537 |
+
# Optionally save reconstructed images
|
| 538 |
+
if save_recon_dir:
|
| 539 |
+
import threading
|
| 540 |
+
recons_copy = np.copy(reconstructions)
|
| 541 |
+
save_dir = save_recon_dir
|
| 542 |
+
def _save_recon_images():
|
| 543 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 544 |
+
for i, img in enumerate(recons_copy):
|
| 545 |
+
Image.fromarray(img).save(os.path.join(save_dir, f"recon_{i:06d}.png"))
|
| 546 |
+
print(f"[Eval] Saved {len(recons_copy)} reconstruction images to {save_dir}")
|
| 547 |
+
threading.Thread(target=_save_recon_images, daemon=False).start()
|
| 548 |
+
|
| 549 |
+
# Cleanup shards
|
| 550 |
+
for r in range(world_size):
|
| 551 |
+
shard_file = os.path.join(temp_dir, f"recon_{global_step:07d}_{r:02d}.npz")
|
| 552 |
+
if os.path.exists(shard_file):
|
| 553 |
+
os.remove(shard_file)
|
| 554 |
+
|
| 555 |
+
wait_for_everyone()
|
| 556 |
+
model.train()
|
| 557 |
+
|
| 558 |
+
return metrics
|
| 559 |
+
if __name__ == "__main__":
|
| 560 |
+
import argparse
|
| 561 |
+
parser = argparse.ArgumentParser()
|
| 562 |
+
parser.add_argument("--ref-img", type=str, default="samples/imagenet-256-val.npz")
|
| 563 |
+
parser.add_argument("--rec-img", type=str, default="samples/sdvae-ft-mse-f8d4.npz")
|
| 564 |
+
parser.add_argument("--bs", type=int, default=128)
|
| 565 |
+
args = parser.parse_args()
|
| 566 |
+
|
| 567 |
+
# Load images
|
| 568 |
+
device = "cuda"
|
| 569 |
+
ref_img = np.load(args.ref_img)["arr_0"]
|
| 570 |
+
rec_img = np.load(args.rec_img)["arr_0"]
|
| 571 |
+
print(f"Loaded images: ref: {ref_img.shape}, rec: {rec_img.shape}")
|
| 572 |
+
|
| 573 |
+
psnr = calculate_psnr(ref_img, rec_img, args.bs, device)
|
| 574 |
+
print(f"PSNR: {psnr:.6f}")
|
| 575 |
+
lpips = calculate_lpips(ref_img, rec_img, args.bs, device)
|
| 576 |
+
print(f"LPIPS: {lpips:.6f}")
|
| 577 |
+
ssim_val = calculate_ssim(ref_img, rec_img, args.bs, device)
|
| 578 |
+
print(f"SSIM: {ssim_val:.6f}")
|
| 579 |
+
rfid = calculate_rfid(ref_img, rec_img, args.bs, device)
|
| 580 |
+
print(f"rFID: {rfid:.6f}")
|
patch-reparameterization/src/eval/clip_t.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
from torch.utils.data import DataLoader
|
| 4 |
+
from torchvision import datasets
|
| 5 |
+
from transformers import SiglipModel, SiglipProcessor
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# =================配置部分=================
|
| 10 |
+
# 本地模型路径 (包含 model.safetensors, config.json, preprocessor_config.json 等)
|
| 11 |
+
# MODEL_PATH = HF_HOME 下 siglip2-base-patch16-256
|
| 12 |
+
# MODEL_PATH = CKPT_ROOT 下 siglip2/test/ep-last/
|
| 13 |
+
MODEL_PATH = os.environ.get("SIGLIP2_MODEL_PATH", "google/siglip2-base-patch16-256")
|
| 14 |
+
# ImageNet 验证集根目录 (即包含 n01440764 等文件夹的上一级目录)
|
| 15 |
+
IMAGENET_VAL_PATH = os.path.join(os.environ.get("CLIP_BENCH_ROOT", "/path/to/clip_benchmark"), "imagenet-1k", "val")
|
| 16 |
+
|
| 17 |
+
BATCH_SIZE = 256
|
| 18 |
+
NUM_WORKERS = 8
|
| 19 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 20 |
+
|
| 21 |
+
# =================ImageNet 类别标签=================
|
| 22 |
+
# 标准 ImageNet-1k 的 1000 个类别名称 (对应 WordNet ID 排序)
|
| 23 |
+
# 为了代码简洁,这里列出前几个作为示例,完整列表请在代码下方查看说明或从外部加载
|
| 24 |
+
# 这里我们使用一个简单的函数来获取标签,通常实际评测会使用 OpenAI 的 1000 个类名
|
| 25 |
+
try:
|
| 26 |
+
# 尝试从网络加载标准标签,如果无法联网请手动替换 IMAGENET_CLASSES 列表
|
| 27 |
+
import requests
|
| 28 |
+
url = "https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt"
|
| 29 |
+
IMAGENET_CLASSES = requests.get(url).text.strip().split('\n')
|
| 30 |
+
print(f"成功加载 {len(IMAGENET_CLASSES)} 个类别标签。")
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print("无法下载标签,请确保联网或手动填入 IMAGENET_CLASSES。")
|
| 33 |
+
# 示例: IMAGENET_CLASSES = ["tench", "goldfish", ...]
|
| 34 |
+
# raise e
|
| 35 |
+
IMAGENET_CLASSES = (
|
| 36 |
+
"tench", "goldfish", "great white shark", "tiger shark", "hammerhead shark", "electric ray",
|
| 37 |
+
"stingray", "rooster", "hen", "ostrich", "brambling", "goldfinch", "house finch", "junco",
|
| 38 |
+
"indigo bunting", "American robin", "bulbul", "jay", "magpie", "chickadee", "American dipper",
|
| 39 |
+
"kite (bird of prey)", "bald eagle", "vulture", "great grey owl", "fire salamander",
|
| 40 |
+
"smooth newt", "newt", "spotted salamander", "axolotl", "American bullfrog", "tree frog",
|
| 41 |
+
"tailed frog", "loggerhead sea turtle", "leatherback sea turtle", "mud turtle", "terrapin",
|
| 42 |
+
"box turtle", "banded gecko", "green iguana", "Carolina anole",
|
| 43 |
+
"desert grassland whiptail lizard", "agama", "frilled-necked lizard", "alligator lizard",
|
| 44 |
+
"Gila monster", "European green lizard", "chameleon", "Komodo dragon", "Nile crocodile",
|
| 45 |
+
"American alligator", "triceratops", "worm snake", "ring-necked snake",
|
| 46 |
+
"eastern hog-nosed snake", "smooth green snake", "kingsnake", "garter snake", "water snake",
|
| 47 |
+
"vine snake", "night snake", "boa constrictor", "African rock python", "Indian cobra",
|
| 48 |
+
"green mamba", "sea snake", "Saharan horned viper", "eastern diamondback rattlesnake",
|
| 49 |
+
"sidewinder rattlesnake", "trilobite", "harvestman", "scorpion", "yellow garden spider",
|
| 50 |
+
"barn spider", "European garden spider", "southern black widow", "tarantula", "wolf spider",
|
| 51 |
+
"tick", "centipede", "black grouse", "ptarmigan", "ruffed grouse", "prairie grouse", "peafowl",
|
| 52 |
+
"quail", "partridge", "african grey parrot", "macaw", "sulphur-crested cockatoo", "lorikeet",
|
| 53 |
+
"coucal", "bee eater", "hornbill", "hummingbird", "jacamar", "toucan", "duck",
|
| 54 |
+
"red-breasted merganser", "goose", "black swan", "tusker", "echidna", "platypus", "wallaby",
|
| 55 |
+
"koala", "wombat", "jellyfish", "sea anemone", "brain coral", "flatworm", "nematode", "conch",
|
| 56 |
+
"snail", "slug", "sea slug", "chiton", "chambered nautilus", "Dungeness crab", "rock crab",
|
| 57 |
+
"fiddler crab", "red king crab", "American lobster", "spiny lobster", "crayfish", "hermit crab",
|
| 58 |
+
"isopod", "white stork", "black stork", "spoonbill", "flamingo", "little blue heron",
|
| 59 |
+
"great egret", "bittern bird", "crane bird", "limpkin", "common gallinule", "American coot",
|
| 60 |
+
"bustard", "ruddy turnstone", "dunlin", "common redshank", "dowitcher", "oystercatcher",
|
| 61 |
+
"pelican", "king penguin", "albatross", "grey whale", "killer whale", "dugong", "sea lion",
|
| 62 |
+
"Chihuahua", "Japanese Chin", "Maltese", "Pekingese", "Shih Tzu", "King Charles Spaniel",
|
| 63 |
+
"Papillon", "toy terrier", "Rhodesian Ridgeback", "Afghan Hound", "Basset Hound", "Beagle",
|
| 64 |
+
"Bloodhound", "Bluetick Coonhound", "Black and Tan Coonhound", "Treeing Walker Coonhound",
|
| 65 |
+
"English foxhound", "Redbone Coonhound", "borzoi", "Irish Wolfhound", "Italian Greyhound",
|
| 66 |
+
"Whippet", "Ibizan Hound", "Norwegian Elkhound", "Otterhound", "Saluki", "Scottish Deerhound",
|
| 67 |
+
"Weimaraner", "Staffordshire Bull Terrier", "American Staffordshire Terrier",
|
| 68 |
+
"Bedlington Terrier", "Border Terrier", "Kerry Blue Terrier", "Irish Terrier",
|
| 69 |
+
"Norfolk Terrier", "Norwich Terrier", "Yorkshire Terrier", "Wire Fox Terrier",
|
| 70 |
+
"Lakeland Terrier", "Sealyham Terrier", "Airedale Terrier", "Cairn Terrier",
|
| 71 |
+
"Australian Terrier", "Dandie Dinmont Terrier", "Boston Terrier", "Miniature Schnauzer",
|
| 72 |
+
"Giant Schnauzer", "Standard Schnauzer", "Scottish Terrier", "Tibetan Terrier",
|
| 73 |
+
"Australian Silky Terrier", "Soft-coated Wheaten Terrier", "West Highland White Terrier",
|
| 74 |
+
"Lhasa Apso", "Flat-Coated Retriever", "Curly-coated Retriever", "Golden Retriever",
|
| 75 |
+
"Labrador Retriever", "Chesapeake Bay Retriever", "German Shorthaired Pointer", "Vizsla",
|
| 76 |
+
"English Setter", "Irish Setter", "Gordon Setter", "Brittany dog", "Clumber Spaniel",
|
| 77 |
+
"English Springer Spaniel", "Welsh Springer Spaniel", "Cocker Spaniel", "Sussex Spaniel",
|
| 78 |
+
"Irish Water Spaniel", "Kuvasz", "Schipperke", "Groenendael dog", "Malinois", "Briard",
|
| 79 |
+
"Australian Kelpie", "Komondor", "Old English Sheepdog", "Shetland Sheepdog", "collie",
|
| 80 |
+
"Border Collie", "Bouvier des Flandres dog", "Rottweiler", "German Shepherd Dog", "Dobermann",
|
| 81 |
+
"Miniature Pinscher", "Greater Swiss Mountain Dog", "Bernese Mountain Dog",
|
| 82 |
+
"Appenzeller Sennenhund", "Entlebucher Sennenhund", "Boxer", "Bullmastiff", "Tibetan Mastiff",
|
| 83 |
+
"French Bulldog", "Great Dane", "St. Bernard", "husky", "Alaskan Malamute", "Siberian Husky",
|
| 84 |
+
"Dalmatian", "Affenpinscher", "Basenji", "pug", "Leonberger", "Newfoundland dog",
|
| 85 |
+
"Great Pyrenees dog", "Samoyed", "Pomeranian", "Chow Chow", "Keeshond", "brussels griffon",
|
| 86 |
+
"Pembroke Welsh Corgi", "Cardigan Welsh Corgi", "Toy Poodle", "Miniature Poodle",
|
| 87 |
+
"Standard Poodle", "Mexican hairless dog (xoloitzcuintli)", "grey wolf", "Alaskan tundra wolf",
|
| 88 |
+
"red wolf or maned wolf", "coyote", "dingo", "dhole", "African wild dog", "hyena", "red fox",
|
| 89 |
+
"kit fox", "Arctic fox", "grey fox", "tabby cat", "tiger cat", "Persian cat", "Siamese cat",
|
| 90 |
+
"Egyptian Mau", "cougar", "lynx", "leopard", "snow leopard", "jaguar", "lion", "tiger",
|
| 91 |
+
"cheetah", "brown bear", "American black bear", "polar bear", "sloth bear", "mongoose",
|
| 92 |
+
"meerkat", "tiger beetle", "ladybug", "ground beetle", "longhorn beetle", "leaf beetle",
|
| 93 |
+
"dung beetle", "rhinoceros beetle", "weevil", "fly", "bee", "ant", "grasshopper",
|
| 94 |
+
"cricket insect", "stick insect", "cockroach", "praying mantis", "cicada", "leafhopper",
|
| 95 |
+
"lacewing", "dragonfly", "damselfly", "red admiral butterfly", "ringlet butterfly",
|
| 96 |
+
"monarch butterfly", "small white butterfly", "sulphur butterfly", "gossamer-winged butterfly",
|
| 97 |
+
"starfish", "sea urchin", "sea cucumber", "cottontail rabbit", "hare", "Angora rabbit",
|
| 98 |
+
"hamster", "porcupine", "fox squirrel", "marmot", "beaver", "guinea pig", "common sorrel horse",
|
| 99 |
+
"zebra", "pig", "wild boar", "warthog", "hippopotamus", "ox", "water buffalo", "bison",
|
| 100 |
+
"ram (adult male sheep)", "bighorn sheep", "Alpine ibex", "hartebeest", "impala (antelope)",
|
| 101 |
+
"gazelle", "arabian camel", "llama", "weasel", "mink", "European polecat",
|
| 102 |
+
"black-footed ferret", "otter", "skunk", "badger", "armadillo", "three-toed sloth", "orangutan",
|
| 103 |
+
"gorilla", "chimpanzee", "gibbon", "siamang", "guenon", "patas monkey", "baboon", "macaque",
|
| 104 |
+
"langur", "black-and-white colobus", "proboscis monkey", "marmoset", "white-headed capuchin",
|
| 105 |
+
"howler monkey", "titi monkey", "Geoffroy's spider monkey", "common squirrel monkey",
|
| 106 |
+
"ring-tailed lemur", "indri", "Asian elephant", "African bush elephant", "red panda",
|
| 107 |
+
"giant panda", "snoek fish", "eel", "silver salmon", "rock beauty fish", "clownfish",
|
| 108 |
+
"sturgeon", "gar fish", "lionfish", "pufferfish", "abacus", "abaya", "academic gown",
|
| 109 |
+
"accordion", "acoustic guitar", "aircraft carrier", "airliner", "airship", "altar", "ambulance",
|
| 110 |
+
"amphibious vehicle", "analog clock", "apiary", "apron", "trash can", "assault rifle",
|
| 111 |
+
"backpack", "bakery", "balance beam", "balloon", "ballpoint pen", "Band-Aid", "banjo",
|
| 112 |
+
"baluster / handrail", "barbell", "barber chair", "barbershop", "barn", "barometer", "barrel",
|
| 113 |
+
"wheelbarrow", "baseball", "basketball", "bassinet", "bassoon", "swimming cap", "bath towel",
|
| 114 |
+
"bathtub", "station wagon", "lighthouse", "beaker", "military hat (bearskin or shako)",
|
| 115 |
+
"beer bottle", "beer glass", "bell tower", "baby bib", "tandem bicycle", "bikini",
|
| 116 |
+
"ring binder", "binoculars", "birdhouse", "boathouse", "bobsleigh", "bolo tie", "poke bonnet",
|
| 117 |
+
"bookcase", "bookstore", "bottle cap", "hunting bow", "bow tie", "brass memorial plaque", "bra",
|
| 118 |
+
"breakwater", "breastplate", "broom", "bucket", "buckle", "bulletproof vest",
|
| 119 |
+
"high-speed train", "butcher shop", "taxicab", "cauldron", "candle", "cannon", "canoe",
|
| 120 |
+
"can opener", "cardigan", "car mirror", "carousel", "tool kit", "cardboard box / carton",
|
| 121 |
+
"car wheel", "automated teller machine", "cassette", "cassette player", "castle", "catamaran",
|
| 122 |
+
"CD player", "cello", "mobile phone", "chain", "chain-link fence", "chain mail", "chainsaw",
|
| 123 |
+
"storage chest", "chiffonier", "bell or wind chime", "china cabinet", "Christmas stocking",
|
| 124 |
+
"church", "movie theater", "cleaver", "cliff dwelling", "cloak", "clogs", "cocktail shaker",
|
| 125 |
+
"coffee mug", "coffeemaker", "spiral or coil", "combination lock", "computer keyboard",
|
| 126 |
+
"candy store", "container ship", "convertible", "corkscrew", "cornet", "cowboy boot",
|
| 127 |
+
"cowboy hat", "cradle", "construction crane", "crash helmet", "crate", "infant bed",
|
| 128 |
+
"Crock Pot", "croquet ball", "crutch", "cuirass", "dam", "desk", "desktop computer",
|
| 129 |
+
"rotary dial telephone", "diaper", "digital clock", "digital watch", "dining table",
|
| 130 |
+
"dishcloth", "dishwasher", "disc brake", "dock", "dog sled", "dome", "doormat", "drilling rig",
|
| 131 |
+
"drum", "drumstick", "dumbbell", "Dutch oven", "electric fan", "electric guitar",
|
| 132 |
+
"electric locomotive", "entertainment center", "envelope", "espresso machine", "face powder",
|
| 133 |
+
"feather boa", "filing cabinet", "fireboat", "fire truck", "fire screen", "flagpole", "flute",
|
| 134 |
+
"folding chair", "football helmet", "forklift", "fountain", "fountain pen", "four-poster bed",
|
| 135 |
+
"freight car", "French horn", "frying pan", "fur coat", "garbage truck",
|
| 136 |
+
"gas mask or respirator", "gas pump", "goblet", "go-kart", "golf ball", "golf cart", "gondola",
|
| 137 |
+
"gong", "gown", "grand piano", "greenhouse", "radiator grille", "grocery store", "guillotine",
|
| 138 |
+
"hair clip", "hair spray", "half-track", "hammer", "hamper", "hair dryer", "hand-held computer",
|
| 139 |
+
"handkerchief", "hard disk drive", "harmonica", "harp", "combine harvester", "hatchet",
|
| 140 |
+
"holster", "home theater", "honeycomb", "hook", "hoop skirt", "gymnastic horizontal bar",
|
| 141 |
+
"horse-drawn vehicle", "hourglass", "iPod", "clothes iron", "carved pumpkin", "jeans", "jeep",
|
| 142 |
+
"T-shirt", "jigsaw puzzle", "rickshaw", "joystick", "kimono", "knee pad", "knot", "lab coat",
|
| 143 |
+
"ladle", "lampshade", "laptop computer", "lawn mower", "lens cap", "letter opener", "library",
|
| 144 |
+
"lifeboat", "lighter", "limousine", "ocean liner", "lipstick", "slip-on shoe", "lotion",
|
| 145 |
+
"music speaker", "loupe magnifying glass", "sawmill", "magnetic compass", "messenger bag",
|
| 146 |
+
"mailbox", "tights", "one-piece bathing suit", "manhole cover", "maraca", "marimba", "mask",
|
| 147 |
+
"matchstick", "maypole", "maze", "measuring cup", "medicine cabinet", "megalith", "microphone",
|
| 148 |
+
"microwave oven", "military uniform", "milk can", "minibus", "miniskirt", "minivan", "missile",
|
| 149 |
+
"mitten", "mixing bowl", "mobile home", "ford model t", "modem", "monastery", "monitor",
|
| 150 |
+
"moped", "mortar and pestle", "graduation cap", "mosque", "mosquito net", "vespa",
|
| 151 |
+
"mountain bike", "tent", "computer mouse", "mousetrap", "moving van", "muzzle", "metal nail",
|
| 152 |
+
"neck brace", "necklace", "baby pacifier", "notebook computer", "obelisk", "oboe", "ocarina",
|
| 153 |
+
"odometer", "oil filter", "pipe organ", "oscilloscope", "overskirt", "bullock cart",
|
| 154 |
+
"oxygen mask", "product packet / packaging", "paddle", "paddle wheel", "padlock", "paintbrush",
|
| 155 |
+
"pajamas", "palace", "pan flute", "paper towel", "parachute", "parallel bars", "park bench",
|
| 156 |
+
"parking meter", "railroad car", "patio", "payphone", "pedestal", "pencil case",
|
| 157 |
+
"pencil sharpener", "perfume", "Petri dish", "photocopier", "plectrum", "Pickelhaube",
|
| 158 |
+
"picket fence", "pickup truck", "pier", "piggy bank", "pill bottle", "pillow", "ping-pong ball",
|
| 159 |
+
"pinwheel", "pirate ship", "drink pitcher", "block plane", "planetarium", "plastic bag",
|
| 160 |
+
"plate rack", "farm plow", "plunger", "Polaroid camera", "pole", "police van", "poncho",
|
| 161 |
+
"pool table", "soda bottle", "plant pot", "potter's wheel", "power drill", "prayer rug",
|
| 162 |
+
"printer", "prison", "missile", "projector", "hockey puck", "punching bag", "purse", "quill",
|
| 163 |
+
"quilt", "race car", "racket", "radiator", "radio", "radio telescope", "rain barrel",
|
| 164 |
+
"recreational vehicle", "fishing casting reel", "reflex camera", "refrigerator",
|
| 165 |
+
"remote control", "restaurant", "revolver", "rifle", "rocking chair", "rotisserie", "eraser",
|
| 166 |
+
"rugby ball", "ruler measuring stick", "sneaker", "safe", "safety pin", "salt shaker", "sandal",
|
| 167 |
+
"sarong", "saxophone", "scabbard", "weighing scale", "school bus", "schooner", "scoreboard",
|
| 168 |
+
"CRT monitor", "screw", "screwdriver", "seat belt", "sewing machine", "shield", "shoe store",
|
| 169 |
+
"shoji screen / room divider", "shopping basket", "shopping cart", "shovel", "shower cap",
|
| 170 |
+
"shower curtain", "ski", "balaclava ski mask", "sleeping bag", "slide rule", "sliding door",
|
| 171 |
+
"slot machine", "snorkel", "snowmobile", "snowplow", "soap dispenser", "soccer ball", "sock",
|
| 172 |
+
"solar thermal collector", "sombrero", "soup bowl", "keyboard space bar", "space heater",
|
| 173 |
+
"space shuttle", "spatula", "motorboat", "spider web", "spindle", "sports car", "spotlight",
|
| 174 |
+
"stage", "steam locomotive", "through arch bridge", "steel drum", "stethoscope", "scarf",
|
| 175 |
+
"stone wall", "stopwatch", "stove", "strainer", "tram", "stretcher", "couch", "stupa",
|
| 176 |
+
"submarine", "suit", "sundial", "sunglasses", "sunglasses", "sunscreen", "suspension bridge",
|
| 177 |
+
"mop", "sweatshirt", "swim trunks / shorts", "swing", "electrical switch", "syringe",
|
| 178 |
+
"table lamp", "tank", "tape player", "teapot", "teddy bear", "television", "tennis ball",
|
| 179 |
+
"thatched roof", "front curtain", "thimble", "threshing machine", "throne", "tile roof",
|
| 180 |
+
"toaster", "tobacco shop", "toilet seat", "torch", "totem pole", "tow truck", "toy store",
|
| 181 |
+
"tractor", "semi-trailer truck", "tray", "trench coat", "tricycle", "trimaran", "tripod",
|
| 182 |
+
"triumphal arch", "trolleybus", "trombone", "hot tub", "turnstile", "typewriter keyboard",
|
| 183 |
+
"umbrella", "unicycle", "upright piano", "vacuum cleaner", "vase", "vaulted or arched ceiling",
|
| 184 |
+
"velvet fabric", "vending machine", "vestment", "viaduct", "violin", "volleyball",
|
| 185 |
+
"waffle iron", "wall clock", "wallet", "wardrobe", "military aircraft", "sink",
|
| 186 |
+
"washing machine", "water bottle", "water jug", "water tower", "whiskey jug", "whistle",
|
| 187 |
+
"hair wig", "window screen", "window shade", "Windsor tie", "wine bottle", "airplane wing",
|
| 188 |
+
"wok", "wooden spoon", "wool", "split-rail fence", "shipwreck", "sailboat", "yurt", "website",
|
| 189 |
+
"comic book", "crossword", "traffic or street sign", "traffic light", "dust jacket", "menu",
|
| 190 |
+
"plate", "guacamole", "consomme", "hot pot", "trifle", "ice cream", "popsicle", "baguette",
|
| 191 |
+
"bagel", "pretzel", "cheeseburger", "hot dog", "mashed potatoes", "cabbage", "broccoli",
|
| 192 |
+
"cauliflower", "zucchini", "spaghetti squash", "acorn squash", "butternut squash", "cucumber",
|
| 193 |
+
"artichoke", "bell pepper", "cardoon", "mushroom", "Granny Smith apple", "strawberry", "orange",
|
| 194 |
+
"lemon", "fig", "pineapple", "banana", "jackfruit", "cherimoya (custard apple)", "pomegranate",
|
| 195 |
+
"hay", "carbonara", "chocolate syrup", "dough", "meatloaf", "pizza", "pot pie", "burrito",
|
| 196 |
+
"red wine", "espresso", "tea cup", "eggnog", "mountain", "bubble", "cliff", "coral reef",
|
| 197 |
+
"geyser", "lakeshore", "promontory", "sandbar", "beach", "valley", "volcano", "baseball player",
|
| 198 |
+
"bridegroom", "scuba diver", "rapeseed", "daisy", "yellow lady's slipper", "corn", "acorn",
|
| 199 |
+
"rose hip", "horse chestnut seed", "coral fungus", "agaric", "gyromitra", "stinkhorn mushroom",
|
| 200 |
+
"earth star fungus", "hen of the woods mushroom", "bolete", "corn cob", "toilet paper"
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
IMAGENET_CLASSES = tuple(label.lower() for label in IMAGENET_CLASSES)
|
| 204 |
+
|
| 205 |
+
# SigLIP 2 推荐的 Prompt 模板 (也可以使用 ensemble)
|
| 206 |
+
TEMPLATE = "a photo of a {}"
|
| 207 |
+
|
| 208 |
+
def load_model(model_path):
|
| 209 |
+
"""加载本地 SigLIP 模型和处理器"""
|
| 210 |
+
print(f"正在加载模型: {model_path} ...")
|
| 211 |
+
# SigLIP 2 在 HF 中通常复用 SiglipModel 结构
|
| 212 |
+
model = SiglipModel.from_pretrained(model_path, torch_dtype=torch.float16)
|
| 213 |
+
processor = SiglipProcessor.from_pretrained(model_path)
|
| 214 |
+
model.to(DEVICE)
|
| 215 |
+
model.eval()
|
| 216 |
+
return model, processor
|
| 217 |
+
|
| 218 |
+
def build_zero_shot_classifier(model, processor, class_names, template):
|
| 219 |
+
"""
|
| 220 |
+
预计算文本嵌入 (Text Embeddings) 作为分类器权重
|
| 221 |
+
"""
|
| 222 |
+
print("构建 Zero-Shot 分类器 (文本编码)...")
|
| 223 |
+
|
| 224 |
+
# 构建 Prompts: ["a photo of a tench", "a photo of a goldfish", ...]
|
| 225 |
+
texts = [template.format(label) for label in class_names]
|
| 226 |
+
|
| 227 |
+
# 分批处理文本以防 OOM
|
| 228 |
+
text_features_list = []
|
| 229 |
+
batch_size = 256
|
| 230 |
+
|
| 231 |
+
# 修复1: 使用 torch.amp.autocast('cuda') 替代过期的 torch.cuda.amp.autocast()
|
| 232 |
+
with torch.no_grad(), torch.amp.autocast('cuda'):
|
| 233 |
+
for i in range(0, len(texts), batch_size):
|
| 234 |
+
batch_texts = texts[i : i + batch_size]
|
| 235 |
+
inputs = processor(
|
| 236 |
+
text=batch_texts,
|
| 237 |
+
padding="max_length",
|
| 238 |
+
max_length=64, # 修复2: 显式指定 max_length (SigLIP 通常为 64),解决 padding 失败导致的维度错误
|
| 239 |
+
return_tensors="pt",
|
| 240 |
+
truncation=True
|
| 241 |
+
).to(DEVICE)
|
| 242 |
+
|
| 243 |
+
# 获取文本特征
|
| 244 |
+
text_outputs = model.get_text_features(**inputs)
|
| 245 |
+
|
| 246 |
+
# 归一化 (SigLIP/CLIP 必须步骤)
|
| 247 |
+
text_outputs = text_outputs / text_outputs.norm(p=2, dim=-1, keepdim=True)
|
| 248 |
+
text_features_list.append(text_outputs)
|
| 249 |
+
|
| 250 |
+
# 合并所有类别的特征 [1000, Hidden_Dim]
|
| 251 |
+
text_classifier = torch.cat(text_features_list, dim=0)
|
| 252 |
+
return text_classifier
|
| 253 |
+
|
| 254 |
+
class ImageNetDataset(torch.utils.data.Dataset):
|
| 255 |
+
"""
|
| 256 |
+
自定义 Dataset 包装器,利用 SiglipProcessor 处理图像
|
| 257 |
+
"""
|
| 258 |
+
def __init__(self, root_dir, processor):
|
| 259 |
+
# 使用 ImageFolder 自动处理文件夹结构和标签索引
|
| 260 |
+
self.dataset = datasets.ImageFolder(root_dir)
|
| 261 |
+
self.processor = processor
|
| 262 |
+
|
| 263 |
+
def __len__(self):
|
| 264 |
+
return len(self.dataset)
|
| 265 |
+
|
| 266 |
+
def __getitem__(self, idx):
|
| 267 |
+
image, label = self.dataset[idx]
|
| 268 |
+
# 这里的 image 是 PIL Image
|
| 269 |
+
return image, label
|
| 270 |
+
|
| 271 |
+
def collate_fn(batch, processor):
|
| 272 |
+
"""
|
| 273 |
+
自定义 collate_fn,将 PIL 图片列表转换为 Tensor
|
| 274 |
+
"""
|
| 275 |
+
images, labels = zip(*batch)
|
| 276 |
+
# processor 处理图片: Resize, Rescale, Normalize
|
| 277 |
+
# 修复: zip(*batch) 返回的是 tuple,必须转为 list 才能被 processor 接受
|
| 278 |
+
inputs = processor(images=list(images), return_tensors="pt")
|
| 279 |
+
return inputs["pixel_values"], torch.tensor(labels)
|
| 280 |
+
|
| 281 |
+
def evaluate(model, classifier, dataloader):
|
| 282 |
+
"""执行评测循环"""
|
| 283 |
+
print("开始评测 ImageNet-1k...")
|
| 284 |
+
top1_correct = 0
|
| 285 |
+
top5_correct = 0
|
| 286 |
+
total_samples = 0
|
| 287 |
+
|
| 288 |
+
# SigLIP 的 Logit Scale (Temperature)
|
| 289 |
+
logit_scale = model.logit_scale.exp()
|
| 290 |
+
# 如果是 SigLIP 2,bias 也是需要的
|
| 291 |
+
logit_bias = model.logit_bias if hasattr(model, 'logit_bias') else 0
|
| 292 |
+
|
| 293 |
+
# 修复3: 同步更新 evaluate 中的 autocast 写法
|
| 294 |
+
with torch.no_grad(), torch.amp.autocast('cuda'):
|
| 295 |
+
for pixel_values, targets in tqdm(dataloader):
|
| 296 |
+
pixel_values = pixel_values.to(DEVICE).to(torch.float16)
|
| 297 |
+
targets = targets.to(DEVICE)
|
| 298 |
+
|
| 299 |
+
# 1. 图像编码
|
| 300 |
+
# SigLIP image encoder 输出
|
| 301 |
+
image_features = model.get_image_features(pixel_values=pixel_values)
|
| 302 |
+
|
| 303 |
+
# 2. 归一化图像特征
|
| 304 |
+
image_features = image_features / image_features.norm(p=2, dim=-1, keepdim=True)
|
| 305 |
+
|
| 306 |
+
# 3. 计算 Logits
|
| 307 |
+
# Logits = (Image_Feat @ Text_Feat.T) * Scale + Bias
|
| 308 |
+
logits = (torch.matmul(image_features, classifier.t()) * logit_scale) + logit_bias
|
| 309 |
+
|
| 310 |
+
# 4. 计算准确率
|
| 311 |
+
# Top-1
|
| 312 |
+
pred_top1 = logits.argmax(dim=1)
|
| 313 |
+
top1_correct += (pred_top1 == targets).sum().item()
|
| 314 |
+
|
| 315 |
+
# Top-5
|
| 316 |
+
_, pred_top5 = logits.topk(5, dim=1)
|
| 317 |
+
top5_correct += (pred_top5 == targets.unsqueeze(1)).sum().item()
|
| 318 |
+
|
| 319 |
+
total_samples += targets.size(0)
|
| 320 |
+
|
| 321 |
+
acc1 = top1_correct / total_samples
|
| 322 |
+
acc5 = top5_correct / total_samples
|
| 323 |
+
|
| 324 |
+
print(f"\n====== 评测完成 ======")
|
| 325 |
+
print(f"Total Samples: {total_samples}")
|
| 326 |
+
print(f"Top-1 Accuracy: {acc1 * 100:.2f}%")
|
| 327 |
+
print(f"Top-5 Accuracy: {acc5 * 100:.2f}%")
|
| 328 |
+
|
| 329 |
+
return acc1, acc5
|
| 330 |
+
|
| 331 |
+
# =================主执行逻辑=================
|
| 332 |
+
if __name__ == "__main__":
|
| 333 |
+
from functools import partial
|
| 334 |
+
|
| 335 |
+
# 1. 加载模型
|
| 336 |
+
# 确保你的本地路径正确
|
| 337 |
+
if not os.path.exists(MODEL_PATH):
|
| 338 |
+
raise FileNotFoundError(f"找不到模型路径: {MODEL_PATH}")
|
| 339 |
+
|
| 340 |
+
model, processor = load_model(MODEL_PATH)
|
| 341 |
+
|
| 342 |
+
# 2. 构建 Zero-Shot 文本分类器
|
| 343 |
+
# 注意:IMAGENET_CLASSES 必须与 datasets.ImageFolder 的类别顺序一致
|
| 344 |
+
# ImageFolder 默认按照文件夹名称(n01440764...)的字母顺序排序
|
| 345 |
+
# 这里我们假设下载的 IMAGENET_CLASSES 是已经按照 WordNet ID 排序好的
|
| 346 |
+
classifier = build_zero_shot_classifier(model, processor, IMAGENET_CLASSES, TEMPLATE)
|
| 347 |
+
|
| 348 |
+
# 3. 准备数据加载器
|
| 349 |
+
# 使用 partial 将 processor 传入 collate_fn
|
| 350 |
+
collate_func = partial(collate_fn, processor=processor)
|
| 351 |
+
|
| 352 |
+
dataset = ImageNetDataset(IMAGENET_VAL_PATH, processor)
|
| 353 |
+
dataloader = DataLoader(
|
| 354 |
+
dataset,
|
| 355 |
+
batch_size=BATCH_SIZE,
|
| 356 |
+
shuffle=False,
|
| 357 |
+
num_workers=NUM_WORKERS,
|
| 358 |
+
collate_fn=collate_func,
|
| 359 |
+
pin_memory=True
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
# 4. 开始评测
|
| 363 |
+
evaluate(model, classifier, dataloader)
|
patch-reparameterization/src/eval/clip_test.py
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Zero-shot evaluation script for VTP.
|
| 4 |
+
|
| 5 |
+
Modified from OpenCLIP.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python tools/test_zero_shot_hf.py --model_path /path/to/vtp_hf_model --data_path /path/to/imagenet/val
|
| 9 |
+
|
| 10 |
+
# With custom settings
|
| 11 |
+
python tools/test_zero_shot_hf.py \
|
| 12 |
+
--model_path /path/to/vtp_hf_model \
|
| 13 |
+
--data_path /path/to/imagenet/val \
|
| 14 |
+
--batch_size 256 \
|
| 15 |
+
--device cuda:0 \
|
| 16 |
+
--precision bf16
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import sys
|
| 23 |
+
from functools import partial
|
| 24 |
+
from itertools import islice
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
from typing import Callable, List, Sequence, Tuple, Union
|
| 27 |
+
|
| 28 |
+
import torch
|
| 29 |
+
import torch.nn.functional as F
|
| 30 |
+
from torch.utils.data import DataLoader
|
| 31 |
+
from torchvision import transforms
|
| 32 |
+
from torchvision.datasets import ImageFolder
|
| 33 |
+
from tqdm import tqdm
|
| 34 |
+
|
| 35 |
+
project_root = Path(__file__).parent.parent
|
| 36 |
+
sys.path.insert(0, str(project_root))
|
| 37 |
+
|
| 38 |
+
from vtp.models.vtp_hf import VTPConfig, VTPModel
|
| 39 |
+
from vtp.tokenizers import get_tokenizer
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# ============================================================================
|
| 43 |
+
# ImageNet classnames and templates (copied from tools/zero_shot/templates.py)
|
| 44 |
+
# ============================================================================
|
| 45 |
+
|
| 46 |
+
IMAGENET_CLASSNAMES = (
|
| 47 |
+
"tench", "goldfish", "great white shark", "tiger shark", "hammerhead shark", "electric ray",
|
| 48 |
+
"stingray", "rooster", "hen", "ostrich", "brambling", "goldfinch", "house finch", "junco",
|
| 49 |
+
"indigo bunting", "American robin", "bulbul", "jay", "magpie", "chickadee", "American dipper",
|
| 50 |
+
"kite (bird of prey)", "bald eagle", "vulture", "great grey owl", "fire salamander",
|
| 51 |
+
"smooth newt", "newt", "spotted salamander", "axolotl", "American bullfrog", "tree frog",
|
| 52 |
+
"tailed frog", "loggerhead sea turtle", "leatherback sea turtle", "mud turtle", "terrapin",
|
| 53 |
+
"box turtle", "banded gecko", "green iguana", "Carolina anole",
|
| 54 |
+
"desert grassland whiptail lizard", "agama", "frilled-necked lizard", "alligator lizard",
|
| 55 |
+
"Gila monster", "European green lizard", "chameleon", "Komodo dragon", "Nile crocodile",
|
| 56 |
+
"American alligator", "triceratops", "worm snake", "ring-necked snake",
|
| 57 |
+
"eastern hog-nosed snake", "smooth green snake", "kingsnake", "garter snake", "water snake",
|
| 58 |
+
"vine snake", "night snake", "boa constrictor", "African rock python", "Indian cobra",
|
| 59 |
+
"green mamba", "sea snake", "Saharan horned viper", "eastern diamondback rattlesnake",
|
| 60 |
+
"sidewinder rattlesnake", "trilobite", "harvestman", "scorpion", "yellow garden spider",
|
| 61 |
+
"barn spider", "European garden spider", "southern black widow", "tarantula", "wolf spider",
|
| 62 |
+
"tick", "centipede", "black grouse", "ptarmigan", "ruffed grouse", "prairie grouse", "peafowl",
|
| 63 |
+
"quail", "partridge", "african grey parrot", "macaw", "sulphur-crested cockatoo", "lorikeet",
|
| 64 |
+
"coucal", "bee eater", "hornbill", "hummingbird", "jacamar", "toucan", "duck",
|
| 65 |
+
"red-breasted merganser", "goose", "black swan", "tusker", "echidna", "platypus", "wallaby",
|
| 66 |
+
"koala", "wombat", "jellyfish", "sea anemone", "brain coral", "flatworm", "nematode", "conch",
|
| 67 |
+
"snail", "slug", "sea slug", "chiton", "chambered nautilus", "Dungeness crab", "rock crab",
|
| 68 |
+
"fiddler crab", "red king crab", "American lobster", "spiny lobster", "crayfish", "hermit crab",
|
| 69 |
+
"isopod", "white stork", "black stork", "spoonbill", "flamingo", "little blue heron",
|
| 70 |
+
"great egret", "bittern bird", "crane bird", "limpkin", "common gallinule", "American coot",
|
| 71 |
+
"bustard", "ruddy turnstone", "dunlin", "common redshank", "dowitcher", "oystercatcher",
|
| 72 |
+
"pelican", "king penguin", "albatross", "grey whale", "killer whale", "dugong", "sea lion",
|
| 73 |
+
"Chihuahua", "Japanese Chin", "Maltese", "Pekingese", "Shih Tzu", "King Charles Spaniel",
|
| 74 |
+
"Papillon", "toy terrier", "Rhodesian Ridgeback", "Afghan Hound", "Basset Hound", "Beagle",
|
| 75 |
+
"Bloodhound", "Bluetick Coonhound", "Black and Tan Coonhound", "Treeing Walker Coonhound",
|
| 76 |
+
"English foxhound", "Redbone Coonhound", "borzoi", "Irish Wolfhound", "Italian Greyhound",
|
| 77 |
+
"Whippet", "Ibizan Hound", "Norwegian Elkhound", "Otterhound", "Saluki", "Scottish Deerhound",
|
| 78 |
+
"Weimaraner", "Staffordshire Bull Terrier", "American Staffordshire Terrier",
|
| 79 |
+
"Bedlington Terrier", "Border Terrier", "Kerry Blue Terrier", "Irish Terrier",
|
| 80 |
+
"Norfolk Terrier", "Norwich Terrier", "Yorkshire Terrier", "Wire Fox Terrier",
|
| 81 |
+
"Lakeland Terrier", "Sealyham Terrier", "Airedale Terrier", "Cairn Terrier",
|
| 82 |
+
"Australian Terrier", "Dandie Dinmont Terrier", "Boston Terrier", "Miniature Schnauzer",
|
| 83 |
+
"Giant Schnauzer", "Standard Schnauzer", "Scottish Terrier", "Tibetan Terrier",
|
| 84 |
+
"Australian Silky Terrier", "Soft-coated Wheaten Terrier", "West Highland White Terrier",
|
| 85 |
+
"Lhasa Apso", "Flat-Coated Retriever", "Curly-coated Retriever", "Golden Retriever",
|
| 86 |
+
"Labrador Retriever", "Chesapeake Bay Retriever", "German Shorthaired Pointer", "Vizsla",
|
| 87 |
+
"English Setter", "Irish Setter", "Gordon Setter", "Brittany dog", "Clumber Spaniel",
|
| 88 |
+
"English Springer Spaniel", "Welsh Springer Spaniel", "Cocker Spaniel", "Sussex Spaniel",
|
| 89 |
+
"Irish Water Spaniel", "Kuvasz", "Schipperke", "Groenendael dog", "Malinois", "Briard",
|
| 90 |
+
"Australian Kelpie", "Komondor", "Old English Sheepdog", "Shetland Sheepdog", "collie",
|
| 91 |
+
"Border Collie", "Bouvier des Flandres dog", "Rottweiler", "German Shepherd Dog", "Dobermann",
|
| 92 |
+
"Miniature Pinscher", "Greater Swiss Mountain Dog", "Bernese Mountain Dog",
|
| 93 |
+
"Appenzeller Sennenhund", "Entlebucher Sennenhund", "Boxer", "Bullmastiff", "Tibetan Mastiff",
|
| 94 |
+
"French Bulldog", "Great Dane", "St. Bernard", "husky", "Alaskan Malamute", "Siberian Husky",
|
| 95 |
+
"Dalmatian", "Affenpinscher", "Basenji", "pug", "Leonberger", "Newfoundland dog",
|
| 96 |
+
"Great Pyrenees dog", "Samoyed", "Pomeranian", "Chow Chow", "Keeshond", "brussels griffon",
|
| 97 |
+
"Pembroke Welsh Corgi", "Cardigan Welsh Corgi", "Toy Poodle", "Miniature Poodle",
|
| 98 |
+
"Standard Poodle", "Mexican hairless dog (xoloitzcuintli)", "grey wolf", "Alaskan tundra wolf",
|
| 99 |
+
"red wolf or maned wolf", "coyote", "dingo", "dhole", "African wild dog", "hyena", "red fox",
|
| 100 |
+
"kit fox", "Arctic fox", "grey fox", "tabby cat", "tiger cat", "Persian cat", "Siamese cat",
|
| 101 |
+
"Egyptian Mau", "cougar", "lynx", "leopard", "snow leopard", "jaguar", "lion", "tiger",
|
| 102 |
+
"cheetah", "brown bear", "American black bear", "polar bear", "sloth bear", "mongoose",
|
| 103 |
+
"meerkat", "tiger beetle", "ladybug", "ground beetle", "longhorn beetle", "leaf beetle",
|
| 104 |
+
"dung beetle", "rhinoceros beetle", "weevil", "fly", "bee", "ant", "grasshopper",
|
| 105 |
+
"cricket insect", "stick insect", "cockroach", "praying mantis", "cicada", "leafhopper",
|
| 106 |
+
"lacewing", "dragonfly", "damselfly", "red admiral butterfly", "ringlet butterfly",
|
| 107 |
+
"monarch butterfly", "small white butterfly", "sulphur butterfly", "gossamer-winged butterfly",
|
| 108 |
+
"starfish", "sea urchin", "sea cucumber", "cottontail rabbit", "hare", "Angora rabbit",
|
| 109 |
+
"hamster", "porcupine", "fox squirrel", "marmot", "beaver", "guinea pig", "common sorrel horse",
|
| 110 |
+
"zebra", "pig", "wild boar", "warthog", "hippopotamus", "ox", "water buffalo", "bison",
|
| 111 |
+
"ram (adult male sheep)", "bighorn sheep", "Alpine ibex", "hartebeest", "impala (antelope)",
|
| 112 |
+
"gazelle", "arabian camel", "llama", "weasel", "mink", "European polecat",
|
| 113 |
+
"black-footed ferret", "otter", "skunk", "badger", "armadillo", "three-toed sloth", "orangutan",
|
| 114 |
+
"gorilla", "chimpanzee", "gibbon", "siamang", "guenon", "patas monkey", "baboon", "macaque",
|
| 115 |
+
"langur", "black-and-white colobus", "proboscis monkey", "marmoset", "white-headed capuchin",
|
| 116 |
+
"howler monkey", "titi monkey", "Geoffroy's spider monkey", "common squirrel monkey",
|
| 117 |
+
"ring-tailed lemur", "indri", "Asian elephant", "African bush elephant", "red panda",
|
| 118 |
+
"giant panda", "snoek fish", "eel", "silver salmon", "rock beauty fish", "clownfish",
|
| 119 |
+
"sturgeon", "gar fish", "lionfish", "pufferfish", "abacus", "abaya", "academic gown",
|
| 120 |
+
"accordion", "acoustic guitar", "aircraft carrier", "airliner", "airship", "altar", "ambulance",
|
| 121 |
+
"amphibious vehicle", "analog clock", "apiary", "apron", "trash can", "assault rifle",
|
| 122 |
+
"backpack", "bakery", "balance beam", "balloon", "ballpoint pen", "Band-Aid", "banjo",
|
| 123 |
+
"baluster / handrail", "barbell", "barber chair", "barbershop", "barn", "barometer", "barrel",
|
| 124 |
+
"wheelbarrow", "baseball", "basketball", "bassinet", "bassoon", "swimming cap", "bath towel",
|
| 125 |
+
"bathtub", "station wagon", "lighthouse", "beaker", "military hat (bearskin or shako)",
|
| 126 |
+
"beer bottle", "beer glass", "bell tower", "baby bib", "tandem bicycle", "bikini",
|
| 127 |
+
"ring binder", "binoculars", "birdhouse", "boathouse", "bobsleigh", "bolo tie", "poke bonnet",
|
| 128 |
+
"bookcase", "bookstore", "bottle cap", "hunting bow", "bow tie", "brass memorial plaque", "bra",
|
| 129 |
+
"breakwater", "breastplate", "broom", "bucket", "buckle", "bulletproof vest",
|
| 130 |
+
"high-speed train", "butcher shop", "taxicab", "cauldron", "candle", "cannon", "canoe",
|
| 131 |
+
"can opener", "cardigan", "car mirror", "carousel", "tool kit", "cardboard box / carton",
|
| 132 |
+
"car wheel", "automated teller machine", "cassette", "cassette player", "castle", "catamaran",
|
| 133 |
+
"CD player", "cello", "mobile phone", "chain", "chain-link fence", "chain mail", "chainsaw",
|
| 134 |
+
"storage chest", "chiffonier", "bell or wind chime", "china cabinet", "Christmas stocking",
|
| 135 |
+
"church", "movie theater", "cleaver", "cliff dwelling", "cloak", "clogs", "cocktail shaker",
|
| 136 |
+
"coffee mug", "coffeemaker", "spiral or coil", "combination lock", "computer keyboard",
|
| 137 |
+
"candy store", "container ship", "convertible", "corkscrew", "cornet", "cowboy boot",
|
| 138 |
+
"cowboy hat", "cradle", "construction crane", "crash helmet", "crate", "infant bed",
|
| 139 |
+
"Crock Pot", "croquet ball", "crutch", "cuirass", "dam", "desk", "desktop computer",
|
| 140 |
+
"rotary dial telephone", "diaper", "digital clock", "digital watch", "dining table",
|
| 141 |
+
"dishcloth", "dishwasher", "disc brake", "dock", "dog sled", "dome", "doormat", "drilling rig",
|
| 142 |
+
"drum", "drumstick", "dumbbell", "Dutch oven", "electric fan", "electric guitar",
|
| 143 |
+
"electric locomotive", "entertainment center", "envelope", "espresso machine", "face powder",
|
| 144 |
+
"feather boa", "filing cabinet", "fireboat", "fire truck", "fire screen", "flagpole", "flute",
|
| 145 |
+
"folding chair", "football helmet", "forklift", "fountain", "fountain pen", "four-poster bed",
|
| 146 |
+
"freight car", "French horn", "frying pan", "fur coat", "garbage truck",
|
| 147 |
+
"gas mask or respirator", "gas pump", "goblet", "go-kart", "golf ball", "golf cart", "gondola",
|
| 148 |
+
"gong", "gown", "grand piano", "greenhouse", "radiator grille", "grocery store", "guillotine",
|
| 149 |
+
"hair clip", "hair spray", "half-track", "hammer", "hamper", "hair dryer", "hand-held computer",
|
| 150 |
+
"handkerchief", "hard disk drive", "harmonica", "harp", "combine harvester", "hatchet",
|
| 151 |
+
"holster", "home theater", "honeycomb", "hook", "hoop skirt", "gymnastic horizontal bar",
|
| 152 |
+
"horse-drawn vehicle", "hourglass", "iPod", "clothes iron", "carved pumpkin", "jeans", "jeep",
|
| 153 |
+
"T-shirt", "jigsaw puzzle", "rickshaw", "joystick", "kimono", "knee pad", "knot", "lab coat",
|
| 154 |
+
"ladle", "lampshade", "laptop computer", "lawn mower", "lens cap", "letter opener", "library",
|
| 155 |
+
"lifeboat", "lighter", "limousine", "ocean liner", "lipstick", "slip-on shoe", "lotion",
|
| 156 |
+
"music speaker", "loupe magnifying glass", "sawmill", "magnetic compass", "messenger bag",
|
| 157 |
+
"mailbox", "tights", "one-piece bathing suit", "manhole cover", "maraca", "marimba", "mask",
|
| 158 |
+
"matchstick", "maypole", "maze", "measuring cup", "medicine cabinet", "megalith", "microphone",
|
| 159 |
+
"microwave oven", "military uniform", "milk can", "minibus", "miniskirt", "minivan", "missile",
|
| 160 |
+
"mitten", "mixing bowl", "mobile home", "ford model t", "modem", "monastery", "monitor",
|
| 161 |
+
"moped", "mortar and pestle", "graduation cap", "mosque", "mosquito net", "vespa",
|
| 162 |
+
"mountain bike", "tent", "computer mouse", "mousetrap", "moving van", "muzzle", "metal nail",
|
| 163 |
+
"neck brace", "necklace", "baby pacifier", "notebook computer", "obelisk", "oboe", "ocarina",
|
| 164 |
+
"odometer", "oil filter", "pipe organ", "oscilloscope", "overskirt", "bullock cart",
|
| 165 |
+
"oxygen mask", "product packet / packaging", "paddle", "paddle wheel", "padlock", "paintbrush",
|
| 166 |
+
"pajamas", "palace", "pan flute", "paper towel", "parachute", "parallel bars", "park bench",
|
| 167 |
+
"parking meter", "railroad car", "patio", "payphone", "pedestal", "pencil case",
|
| 168 |
+
"pencil sharpener", "perfume", "Petri dish", "photocopier", "plectrum", "Pickelhaube",
|
| 169 |
+
"picket fence", "pickup truck", "pier", "piggy bank", "pill bottle", "pillow", "ping-pong ball",
|
| 170 |
+
"pinwheel", "pirate ship", "drink pitcher", "block plane", "planetarium", "plastic bag",
|
| 171 |
+
"plate rack", "farm plow", "plunger", "Polaroid camera", "pole", "police van", "poncho",
|
| 172 |
+
"pool table", "soda bottle", "plant pot", "potter's wheel", "power drill", "prayer rug",
|
| 173 |
+
"printer", "prison", "missile", "projector", "hockey puck", "punching bag", "purse", "quill",
|
| 174 |
+
"quilt", "race car", "racket", "radiator", "radio", "radio telescope", "rain barrel",
|
| 175 |
+
"recreational vehicle", "fishing casting reel", "reflex camera", "refrigerator",
|
| 176 |
+
"remote control", "restaurant", "revolver", "rifle", "rocking chair", "rotisserie", "eraser",
|
| 177 |
+
"rugby ball", "ruler measuring stick", "sneaker", "safe", "safety pin", "salt shaker", "sandal",
|
| 178 |
+
"sarong", "saxophone", "scabbard", "weighing scale", "school bus", "schooner", "scoreboard",
|
| 179 |
+
"CRT monitor", "screw", "screwdriver", "seat belt", "sewing machine", "shield", "shoe store",
|
| 180 |
+
"shoji screen / room divider", "shopping basket", "shopping cart", "shovel", "shower cap",
|
| 181 |
+
"shower curtain", "ski", "balaclava ski mask", "sleeping bag", "slide rule", "sliding door",
|
| 182 |
+
"slot machine", "snorkel", "snowmobile", "snowplow", "soap dispenser", "soccer ball", "sock",
|
| 183 |
+
"solar thermal collector", "sombrero", "soup bowl", "keyboard space bar", "space heater",
|
| 184 |
+
"space shuttle", "spatula", "motorboat", "spider web", "spindle", "sports car", "spotlight",
|
| 185 |
+
"stage", "steam locomotive", "through arch bridge", "steel drum", "stethoscope", "scarf",
|
| 186 |
+
"stone wall", "stopwatch", "stove", "strainer", "tram", "stretcher", "couch", "stupa",
|
| 187 |
+
"submarine", "suit", "sundial", "sunglasses", "sunglasses", "sunscreen", "suspension bridge",
|
| 188 |
+
"mop", "sweatshirt", "swim trunks / shorts", "swing", "electrical switch", "syringe",
|
| 189 |
+
"table lamp", "tank", "tape player", "teapot", "teddy bear", "television", "tennis ball",
|
| 190 |
+
"thatched roof", "front curtain", "thimble", "threshing machine", "throne", "tile roof",
|
| 191 |
+
"toaster", "tobacco shop", "toilet seat", "torch", "totem pole", "tow truck", "toy store",
|
| 192 |
+
"tractor", "semi-trailer truck", "tray", "trench coat", "tricycle", "trimaran", "tripod",
|
| 193 |
+
"triumphal arch", "trolleybus", "trombone", "hot tub", "turnstile", "typewriter keyboard",
|
| 194 |
+
"umbrella", "unicycle", "upright piano", "vacuum cleaner", "vase", "vaulted or arched ceiling",
|
| 195 |
+
"velvet fabric", "vending machine", "vestment", "viaduct", "violin", "volleyball",
|
| 196 |
+
"waffle iron", "wall clock", "wallet", "wardrobe", "military aircraft", "sink",
|
| 197 |
+
"washing machine", "water bottle", "water jug", "water tower", "whiskey jug", "whistle",
|
| 198 |
+
"hair wig", "window screen", "window shade", "Windsor tie", "wine bottle", "airplane wing",
|
| 199 |
+
"wok", "wooden spoon", "wool", "split-rail fence", "shipwreck", "sailboat", "yurt", "website",
|
| 200 |
+
"comic book", "crossword", "traffic or street sign", "traffic light", "dust jacket", "menu",
|
| 201 |
+
"plate", "guacamole", "consomme", "hot pot", "trifle", "ice cream", "popsicle", "baguette",
|
| 202 |
+
"bagel", "pretzel", "cheeseburger", "hot dog", "mashed potatoes", "cabbage", "broccoli",
|
| 203 |
+
"cauliflower", "zucchini", "spaghetti squash", "acorn squash", "butternut squash", "cucumber",
|
| 204 |
+
"artichoke", "bell pepper", "cardoon", "mushroom", "Granny Smith apple", "strawberry", "orange",
|
| 205 |
+
"lemon", "fig", "pineapple", "banana", "jackfruit", "cherimoya (custard apple)", "pomegranate",
|
| 206 |
+
"hay", "carbonara", "chocolate syrup", "dough", "meatloaf", "pizza", "pot pie", "burrito",
|
| 207 |
+
"red wine", "espresso", "tea cup", "eggnog", "mountain", "bubble", "cliff", "coral reef",
|
| 208 |
+
"geyser", "lakeshore", "promontory", "sandbar", "beach", "valley", "volcano", "baseball player",
|
| 209 |
+
"bridegroom", "scuba diver", "rapeseed", "daisy", "yellow lady's slipper", "corn", "acorn",
|
| 210 |
+
"rose hip", "horse chestnut seed", "coral fungus", "agaric", "gyromitra", "stinkhorn mushroom",
|
| 211 |
+
"earth star fungus", "hen of the woods mushroom", "bolete", "corn cob", "toilet paper"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
OPENAI_IMAGENET_TEMPLATES = (
|
| 215 |
+
lambda c: f'a bad photo of a {c}.',
|
| 216 |
+
lambda c: f'a photo of many {c}.',
|
| 217 |
+
lambda c: f'a sculpture of a {c}.',
|
| 218 |
+
lambda c: f'a photo of the hard to see {c}.',
|
| 219 |
+
lambda c: f'a low resolution photo of the {c}.',
|
| 220 |
+
lambda c: f'a rendering of a {c}.',
|
| 221 |
+
lambda c: f'graffiti of a {c}.',
|
| 222 |
+
lambda c: f'a bad photo of the {c}.',
|
| 223 |
+
lambda c: f'a cropped photo of the {c}.',
|
| 224 |
+
lambda c: f'a tattoo of a {c}.',
|
| 225 |
+
lambda c: f'the embroidered {c}.',
|
| 226 |
+
lambda c: f'a photo of a hard to see {c}.',
|
| 227 |
+
lambda c: f'a bright photo of a {c}.',
|
| 228 |
+
lambda c: f'a photo of a clean {c}.',
|
| 229 |
+
lambda c: f'a photo of a dirty {c}.',
|
| 230 |
+
lambda c: f'a dark photo of the {c}.',
|
| 231 |
+
lambda c: f'a drawing of a {c}.',
|
| 232 |
+
lambda c: f'a photo of my {c}.',
|
| 233 |
+
lambda c: f'the plastic {c}.',
|
| 234 |
+
lambda c: f'a photo of the cool {c}.',
|
| 235 |
+
lambda c: f'a close-up photo of a {c}.',
|
| 236 |
+
lambda c: f'a black and white photo of the {c}.',
|
| 237 |
+
lambda c: f'a painting of the {c}.',
|
| 238 |
+
lambda c: f'a painting of a {c}.',
|
| 239 |
+
lambda c: f'a pixelated photo of the {c}.',
|
| 240 |
+
lambda c: f'a sculpture of the {c}.',
|
| 241 |
+
lambda c: f'a bright photo of the {c}.',
|
| 242 |
+
lambda c: f'a cropped photo of a {c}.',
|
| 243 |
+
lambda c: f'a plastic {c}.',
|
| 244 |
+
lambda c: f'a photo of the dirty {c}.',
|
| 245 |
+
lambda c: f'a jpeg corrupted photo of a {c}.',
|
| 246 |
+
lambda c: f'a blurry photo of the {c}.',
|
| 247 |
+
lambda c: f'a photo of the {c}.',
|
| 248 |
+
lambda c: f'a good photo of the {c}.',
|
| 249 |
+
lambda c: f'a rendering of the {c}.',
|
| 250 |
+
lambda c: f'a {c} in a video game.',
|
| 251 |
+
lambda c: f'a photo of one {c}.',
|
| 252 |
+
lambda c: f'a doodle of a {c}.',
|
| 253 |
+
lambda c: f'a close-up photo of the {c}.',
|
| 254 |
+
lambda c: f'a photo of a {c}.',
|
| 255 |
+
lambda c: f'the origami {c}.',
|
| 256 |
+
lambda c: f'the {c} in a video game.',
|
| 257 |
+
lambda c: f'a sketch of a {c}.',
|
| 258 |
+
lambda c: f'a doodle of the {c}.',
|
| 259 |
+
lambda c: f'a origami {c}.',
|
| 260 |
+
lambda c: f'a low resolution photo of a {c}.',
|
| 261 |
+
lambda c: f'the toy {c}.',
|
| 262 |
+
lambda c: f'a rendition of the {c}.',
|
| 263 |
+
lambda c: f'a photo of the clean {c}.',
|
| 264 |
+
lambda c: f'a photo of a large {c}.',
|
| 265 |
+
lambda c: f'a rendition of a {c}.',
|
| 266 |
+
lambda c: f'a photo of a nice {c}.',
|
| 267 |
+
lambda c: f'a photo of a weird {c}.',
|
| 268 |
+
lambda c: f'a blurry photo of a {c}.',
|
| 269 |
+
lambda c: f'a cartoon {c}.',
|
| 270 |
+
lambda c: f'art of a {c}.',
|
| 271 |
+
lambda c: f'a sketch of the {c}.',
|
| 272 |
+
lambda c: f'a embroidered {c}.',
|
| 273 |
+
lambda c: f'a pixelated photo of a {c}.',
|
| 274 |
+
lambda c: f'itap of the {c}.',
|
| 275 |
+
lambda c: f'a jpeg corrupted photo of the {c}.',
|
| 276 |
+
lambda c: f'a good photo of a {c}.',
|
| 277 |
+
lambda c: f'a plushie {c}.',
|
| 278 |
+
lambda c: f'a photo of the nice {c}.',
|
| 279 |
+
lambda c: f'a photo of the small {c}.',
|
| 280 |
+
lambda c: f'a photo of the weird {c}.',
|
| 281 |
+
lambda c: f'the cartoon {c}.',
|
| 282 |
+
lambda c: f'art of the {c}.',
|
| 283 |
+
lambda c: f'a drawing of the {c}.',
|
| 284 |
+
lambda c: f'a photo of the large {c}.',
|
| 285 |
+
lambda c: f'a black and white photo of a {c}.',
|
| 286 |
+
lambda c: f'the plushie {c}.',
|
| 287 |
+
lambda c: f'a dark photo of a {c}.',
|
| 288 |
+
lambda c: f'itap of a {c}.',
|
| 289 |
+
lambda c: f'graffiti of the {c}.',
|
| 290 |
+
lambda c: f'a toy {c}.',
|
| 291 |
+
lambda c: f'itap of my {c}.',
|
| 292 |
+
lambda c: f'a photo of a cool {c}.',
|
| 293 |
+
lambda c: f'a photo of a small {c}.',
|
| 294 |
+
lambda c: f'a tattoo of the {c}.',
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
# ============================================================================
|
| 299 |
+
# Utility functions
|
| 300 |
+
# ============================================================================
|
| 301 |
+
|
| 302 |
+
def batched(iterable, n: int):
|
| 303 |
+
"""Batch data into lists of length n. The last batch may be shorter."""
|
| 304 |
+
it = iter(iterable)
|
| 305 |
+
while True:
|
| 306 |
+
batch = list(islice(it, n))
|
| 307 |
+
if not batch:
|
| 308 |
+
break
|
| 309 |
+
yield batch
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def accuracy(output: torch.Tensor, target: torch.Tensor, topk: Tuple[int, ...] = (1,)) -> List[float]:
|
| 313 |
+
"""Calculate top-k accuracy."""
|
| 314 |
+
pred = output.topk(max(topk), 1, True, True)[1].t()
|
| 315 |
+
correct = pred.eq(target.view(1, -1).expand_as(pred))
|
| 316 |
+
return [float(correct[:k].reshape(-1).float().sum(0, keepdim=True).cpu().numpy()) for k in topk]
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def get_input_dtype(precision: str) -> torch.dtype:
|
| 320 |
+
"""Get input dtype based on precision string."""
|
| 321 |
+
if precision in ('bf16', 'bfloat16'):
|
| 322 |
+
return torch.bfloat16
|
| 323 |
+
elif precision in ('fp16', 'float16'):
|
| 324 |
+
return torch.float16
|
| 325 |
+
return torch.float32
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def get_autocast_context(precision: str, device_type: str = 'cuda'):
|
| 329 |
+
"""Get autocast context manager based on precision."""
|
| 330 |
+
from contextlib import nullcontext
|
| 331 |
+
if precision in ('bf16', 'bfloat16'):
|
| 332 |
+
return partial(torch.amp.autocast, device_type=device_type, dtype=torch.bfloat16)
|
| 333 |
+
elif precision in ('fp16', 'float16'):
|
| 334 |
+
return partial(torch.amp.autocast, device_type=device_type, dtype=torch.float16)
|
| 335 |
+
return nullcontext
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
# ============================================================================
|
| 339 |
+
# Zero-shot classifier
|
| 340 |
+
# ============================================================================
|
| 341 |
+
|
| 342 |
+
def build_zero_shot_classifier(
|
| 343 |
+
model: VTPModel,
|
| 344 |
+
tokenizer,
|
| 345 |
+
classnames: Sequence[str],
|
| 346 |
+
templates: Sequence[Callable],
|
| 347 |
+
num_classes_per_batch: int = 10,
|
| 348 |
+
device: torch.device = None,
|
| 349 |
+
use_tqdm: bool = True,
|
| 350 |
+
) -> torch.Tensor:
|
| 351 |
+
"""Build zero-shot classifier weights.
|
| 352 |
+
|
| 353 |
+
Args:
|
| 354 |
+
model: VTPModel instance with get_clip_text_feature method.
|
| 355 |
+
tokenizer: Text tokenizer.
|
| 356 |
+
classnames: Sequence of class names.
|
| 357 |
+
templates: Sequence of template functions.
|
| 358 |
+
num_classes_per_batch: Number of classes to process per batch.
|
| 359 |
+
device: Device to use.
|
| 360 |
+
use_tqdm: Whether to show progress bar.
|
| 361 |
+
|
| 362 |
+
Returns:
|
| 363 |
+
Zero-shot classifier weights, shape (embed_dim, num_classes).
|
| 364 |
+
"""
|
| 365 |
+
num_templates = len(templates)
|
| 366 |
+
num_classes = len(classnames)
|
| 367 |
+
|
| 368 |
+
if use_tqdm:
|
| 369 |
+
num_iter = (num_classes - 1) // num_classes_per_batch + 1
|
| 370 |
+
iter_wrap = partial(tqdm, total=num_iter, desc="Building classifier", unit_scale=num_classes_per_batch)
|
| 371 |
+
else:
|
| 372 |
+
iter_wrap = iter
|
| 373 |
+
|
| 374 |
+
def _process_batch(batch_classnames: List[str]) -> torch.Tensor:
|
| 375 |
+
num_batch_classes = len(batch_classnames)
|
| 376 |
+
texts = [template(c) for c in batch_classnames for template in templates]
|
| 377 |
+
tokens = tokenizer(texts).to(device)
|
| 378 |
+
|
| 379 |
+
# Use VTPModel's get_clip_text_feature method
|
| 380 |
+
text_features = model.get_clip_text_feature(tokens, normalize=True)
|
| 381 |
+
|
| 382 |
+
# Reshape and average over templates
|
| 383 |
+
text_features = text_features.reshape(num_batch_classes, num_templates, -1).mean(dim=1)
|
| 384 |
+
text_features = F.normalize(text_features, dim=1)
|
| 385 |
+
return text_features.T # (embed_dim, num_batch_classes)
|
| 386 |
+
|
| 387 |
+
with torch.no_grad():
|
| 388 |
+
batched_embeds = [
|
| 389 |
+
_process_batch(batch)
|
| 390 |
+
for batch in iter_wrap(batched(classnames, num_classes_per_batch))
|
| 391 |
+
]
|
| 392 |
+
zeroshot_weights = torch.cat(batched_embeds, dim=1)
|
| 393 |
+
|
| 394 |
+
return zeroshot_weights
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
# ============================================================================
|
| 398 |
+
# Evaluation
|
| 399 |
+
# ============================================================================
|
| 400 |
+
|
| 401 |
+
def evaluate(
|
| 402 |
+
model: VTPModel,
|
| 403 |
+
classifier: torch.Tensor,
|
| 404 |
+
dataloader: DataLoader,
|
| 405 |
+
device: torch.device,
|
| 406 |
+
precision: str = 'fp32',
|
| 407 |
+
) -> Tuple[float, float]:
|
| 408 |
+
"""Run zero-shot evaluation.
|
| 409 |
+
|
| 410 |
+
Args:
|
| 411 |
+
model: VTPModel instance.
|
| 412 |
+
classifier: Zero-shot classifier weights, shape (embed_dim, num_classes).
|
| 413 |
+
dataloader: ImageNet validation dataloader.
|
| 414 |
+
device: Device to use.
|
| 415 |
+
precision: Precision for inference.
|
| 416 |
+
|
| 417 |
+
Returns:
|
| 418 |
+
Tuple of (top1_accuracy, top5_accuracy).
|
| 419 |
+
"""
|
| 420 |
+
autocast_ctx = get_autocast_context(precision, device_type=device.type)
|
| 421 |
+
input_dtype = get_input_dtype(precision)
|
| 422 |
+
|
| 423 |
+
top1, top5, n = 0.0, 0.0, 0
|
| 424 |
+
|
| 425 |
+
with torch.inference_mode():
|
| 426 |
+
for images, targets in tqdm(dataloader, desc="Evaluating"):
|
| 427 |
+
images = images.to(device=device, dtype=input_dtype)
|
| 428 |
+
targets = targets.to(device)
|
| 429 |
+
|
| 430 |
+
with autocast_ctx():
|
| 431 |
+
image_features = model.get_clip_image_feature(images, normalize=True)
|
| 432 |
+
logits = 100.0 * image_features @ classifier
|
| 433 |
+
|
| 434 |
+
acc1, acc5 = accuracy(logits, targets, topk=(1, 5))
|
| 435 |
+
top1 += acc1
|
| 436 |
+
top5 += acc5
|
| 437 |
+
n += images.size(0)
|
| 438 |
+
|
| 439 |
+
top1 = top1 / n * 100
|
| 440 |
+
top5 = top5 / n * 100
|
| 441 |
+
return top1, top5
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
# ============================================================================
|
| 445 |
+
# Main
|
| 446 |
+
# ============================================================================
|
| 447 |
+
|
| 448 |
+
def create_imagenet_dataloader(
|
| 449 |
+
data_path: str,
|
| 450 |
+
image_size: int = 256,
|
| 451 |
+
batch_size: int = 128,
|
| 452 |
+
num_workers: int = 8,
|
| 453 |
+
) -> DataLoader:
|
| 454 |
+
"""Create ImageNet validation dataloader."""
|
| 455 |
+
preprocess = transforms.Compose([
|
| 456 |
+
transforms.Resize((image_size, image_size)),
|
| 457 |
+
transforms.ToTensor(),
|
| 458 |
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
| 459 |
+
])
|
| 460 |
+
|
| 461 |
+
dataset = ImageFolder(root=data_path, transform=preprocess)
|
| 462 |
+
dataloader = DataLoader(
|
| 463 |
+
dataset,
|
| 464 |
+
batch_size=batch_size,
|
| 465 |
+
shuffle=False,
|
| 466 |
+
num_workers=num_workers,
|
| 467 |
+
pin_memory=True,
|
| 468 |
+
)
|
| 469 |
+
return dataloader
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def main():
|
| 473 |
+
parser = argparse.ArgumentParser(description="Zero-shot ImageNet evaluation for VTP HuggingFace model")
|
| 474 |
+
parser.add_argument("--model_path", type=str, required=True,
|
| 475 |
+
help="Path to VTP HuggingFace model directory")
|
| 476 |
+
parser.add_argument("--data_path", type=str, required=True,
|
| 477 |
+
help="Path to ImageNet validation dataset")
|
| 478 |
+
parser.add_argument("--batch_size", type=int, default=128,
|
| 479 |
+
help="Batch size for evaluation")
|
| 480 |
+
parser.add_argument("--num_workers", type=int, default=8,
|
| 481 |
+
help="Number of dataloader workers")
|
| 482 |
+
parser.add_argument("--device", type=str, default="cuda:0",
|
| 483 |
+
help="Device to use (e.g., cuda:0, cpu)")
|
| 484 |
+
parser.add_argument("--precision", type=str, default="fp32",
|
| 485 |
+
choices=["fp32", "fp16", "bf16"],
|
| 486 |
+
help="Precision for inference")
|
| 487 |
+
args = parser.parse_args()
|
| 488 |
+
|
| 489 |
+
device = torch.device(args.device)
|
| 490 |
+
|
| 491 |
+
print("=" * 60)
|
| 492 |
+
print("Zero-Shot ImageNet Evaluation (VTP HuggingFace)")
|
| 493 |
+
print("=" * 60)
|
| 494 |
+
print(f"Model path: {args.model_path}")
|
| 495 |
+
print(f"Data path: {args.data_path}")
|
| 496 |
+
print(f"Device: {device}")
|
| 497 |
+
print(f"Precision: {args.precision}")
|
| 498 |
+
print(f"Batch size: {args.batch_size}")
|
| 499 |
+
print()
|
| 500 |
+
|
| 501 |
+
# Load model
|
| 502 |
+
print("Loading model...")
|
| 503 |
+
model = VTPModel.from_pretrained(args.model_path)
|
| 504 |
+
model = model.to(device)
|
| 505 |
+
model.eval()
|
| 506 |
+
|
| 507 |
+
# Get image size from config
|
| 508 |
+
image_size = model.config.image_size
|
| 509 |
+
context_length = model.config.text_context_length
|
| 510 |
+
print(f"Image size: {image_size}")
|
| 511 |
+
print(f"Context length: {context_length}")
|
| 512 |
+
|
| 513 |
+
# Load tokenizer
|
| 514 |
+
print("Loading tokenizer...")
|
| 515 |
+
tokenizer = get_tokenizer('ViT-B-32', context_length=context_length)
|
| 516 |
+
|
| 517 |
+
# Create dataloader
|
| 518 |
+
print(f"Loading ImageNet validation: {args.data_path}")
|
| 519 |
+
dataloader = create_imagenet_dataloader(
|
| 520 |
+
args.data_path,
|
| 521 |
+
image_size=image_size,
|
| 522 |
+
batch_size=args.batch_size,
|
| 523 |
+
num_workers=args.num_workers,
|
| 524 |
+
)
|
| 525 |
+
num_samples = len(dataloader.dataset)
|
| 526 |
+
print(f"Number of samples: {num_samples}")
|
| 527 |
+
if num_samples != 50000:
|
| 528 |
+
print(f" [Warning] Expected 50000 samples for ImageNet validation, got {num_samples}")
|
| 529 |
+
print()
|
| 530 |
+
|
| 531 |
+
# Build classifier
|
| 532 |
+
print("Building zero-shot classifier...")
|
| 533 |
+
autocast_ctx = get_autocast_context(args.precision, device_type=device.type)
|
| 534 |
+
with autocast_ctx():
|
| 535 |
+
classifier = build_zero_shot_classifier(
|
| 536 |
+
model,
|
| 537 |
+
tokenizer,
|
| 538 |
+
classnames=IMAGENET_CLASSNAMES,
|
| 539 |
+
templates=OPENAI_IMAGENET_TEMPLATES,
|
| 540 |
+
num_classes_per_batch=10,
|
| 541 |
+
device=device,
|
| 542 |
+
use_tqdm=True,
|
| 543 |
+
)
|
| 544 |
+
print()
|
| 545 |
+
|
| 546 |
+
# Evaluate
|
| 547 |
+
print("Running evaluation...")
|
| 548 |
+
top1, top5 = evaluate(
|
| 549 |
+
model,
|
| 550 |
+
classifier,
|
| 551 |
+
dataloader,
|
| 552 |
+
device=device,
|
| 553 |
+
precision=args.precision,
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
print()
|
| 557 |
+
print("=" * 60)
|
| 558 |
+
print("Results:")
|
| 559 |
+
print(f" ImageNet Zero-Shot Top-1 Accuracy: {top1:.2f}%")
|
| 560 |
+
print(f" ImageNet Zero-Shot Top-5 Accuracy: {top5:.2f}%")
|
| 561 |
+
print("=" * 60)
|
| 562 |
+
|
| 563 |
+
return {"top1": top1, "top5": top5}
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
if __name__ == "__main__":
|
| 567 |
+
main()
|
patch-reparameterization/src/eval/clip_zero_shot.py
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Zero-shot evaluation script for SigLIP2.
|
| 4 |
+
|
| 5 |
+
Modified from OpenCLIP.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python clip_zero_shot.py --model_path /path/to/siglip2_model --data_path /path/to/imagenet/val
|
| 9 |
+
|
| 10 |
+
# With custom settings
|
| 11 |
+
python clip_zero_shot.py \
|
| 12 |
+
--model_path /path/to/siglip2_model \
|
| 13 |
+
--data_path /path/to/imagenet/val \
|
| 14 |
+
--batch_size 256 \
|
| 15 |
+
--device cuda:0 \
|
| 16 |
+
--precision bf16
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import json
|
| 23 |
+
import logging
|
| 24 |
+
import sys
|
| 25 |
+
import string
|
| 26 |
+
import re
|
| 27 |
+
|
| 28 |
+
from functools import partial
|
| 29 |
+
from itertools import islice
|
| 30 |
+
from pathlib import Path
|
| 31 |
+
from typing import Callable, List, Optional, Sequence, Tuple, Union
|
| 32 |
+
|
| 33 |
+
import torch
|
| 34 |
+
import torch.nn as nn
|
| 35 |
+
import torch.nn.functional as F
|
| 36 |
+
from torch.utils.data import DataLoader
|
| 37 |
+
from torchvision import transforms
|
| 38 |
+
from torchvision.datasets import ImageFolder
|
| 39 |
+
from PIL import Image
|
| 40 |
+
from tqdm import tqdm
|
| 41 |
+
from transformers import AutoProcessor, SiglipModel
|
| 42 |
+
|
| 43 |
+
logger = logging.getLogger(__name__)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# ============================================================================
|
| 47 |
+
# ImageNet classnames and templates (copied from tools/zero_shot/templates.py)
|
| 48 |
+
# ============================================================================
|
| 49 |
+
|
| 50 |
+
IMAGENET_CLASSNAMES = (
|
| 51 |
+
"tench", "goldfish", "great white shark", "tiger shark", "hammerhead shark", "electric ray",
|
| 52 |
+
"stingray", "rooster", "hen", "ostrich", "brambling", "goldfinch", "house finch", "junco",
|
| 53 |
+
"indigo bunting", "American robin", "bulbul", "jay", "magpie", "chickadee", "American dipper",
|
| 54 |
+
"kite (bird of prey)", "bald eagle", "vulture", "great grey owl", "fire salamander",
|
| 55 |
+
"smooth newt", "newt", "spotted salamander", "axolotl", "American bullfrog", "tree frog",
|
| 56 |
+
"tailed frog", "loggerhead sea turtle", "leatherback sea turtle", "mud turtle", "terrapin",
|
| 57 |
+
"box turtle", "banded gecko", "green iguana", "Carolina anole",
|
| 58 |
+
"desert grassland whiptail lizard", "agama", "frilled-necked lizard", "alligator lizard",
|
| 59 |
+
"Gila monster", "European green lizard", "chameleon", "Komodo dragon", "Nile crocodile",
|
| 60 |
+
"American alligator", "triceratops", "worm snake", "ring-necked snake",
|
| 61 |
+
"eastern hog-nosed snake", "smooth green snake", "kingsnake", "garter snake", "water snake",
|
| 62 |
+
"vine snake", "night snake", "boa constrictor", "African rock python", "Indian cobra",
|
| 63 |
+
"green mamba", "sea snake", "Saharan horned viper", "eastern diamondback rattlesnake",
|
| 64 |
+
"sidewinder rattlesnake", "trilobite", "harvestman", "scorpion", "yellow garden spider",
|
| 65 |
+
"barn spider", "European garden spider", "southern black widow", "tarantula", "wolf spider",
|
| 66 |
+
"tick", "centipede", "black grouse", "ptarmigan", "ruffed grouse", "prairie grouse", "peafowl",
|
| 67 |
+
"quail", "partridge", "african grey parrot", "macaw", "sulphur-crested cockatoo", "lorikeet",
|
| 68 |
+
"coucal", "bee eater", "hornbill", "hummingbird", "jacamar", "toucan", "duck",
|
| 69 |
+
"red-breasted merganser", "goose", "black swan", "tusker", "echidna", "platypus", "wallaby",
|
| 70 |
+
"koala", "wombat", "jellyfish", "sea anemone", "brain coral", "flatworm", "nematode", "conch",
|
| 71 |
+
"snail", "slug", "sea slug", "chiton", "chambered nautilus", "Dungeness crab", "rock crab",
|
| 72 |
+
"fiddler crab", "red king crab", "American lobster", "spiny lobster", "crayfish", "hermit crab",
|
| 73 |
+
"isopod", "white stork", "black stork", "spoonbill", "flamingo", "little blue heron",
|
| 74 |
+
"great egret", "bittern bird", "crane bird", "limpkin", "common gallinule", "American coot",
|
| 75 |
+
"bustard", "ruddy turnstone", "dunlin", "common redshank", "dowitcher", "oystercatcher",
|
| 76 |
+
"pelican", "king penguin", "albatross", "grey whale", "killer whale", "dugong", "sea lion",
|
| 77 |
+
"Chihuahua", "Japanese Chin", "Maltese", "Pekingese", "Shih Tzu", "King Charles Spaniel",
|
| 78 |
+
"Papillon", "toy terrier", "Rhodesian Ridgeback", "Afghan Hound", "Basset Hound", "Beagle",
|
| 79 |
+
"Bloodhound", "Bluetick Coonhound", "Black and Tan Coonhound", "Treeing Walker Coonhound",
|
| 80 |
+
"English foxhound", "Redbone Coonhound", "borzoi", "Irish Wolfhound", "Italian Greyhound",
|
| 81 |
+
"Whippet", "Ibizan Hound", "Norwegian Elkhound", "Otterhound", "Saluki", "Scottish Deerhound",
|
| 82 |
+
"Weimaraner", "Staffordshire Bull Terrier", "American Staffordshire Terrier",
|
| 83 |
+
"Bedlington Terrier", "Border Terrier", "Kerry Blue Terrier", "Irish Terrier",
|
| 84 |
+
"Norfolk Terrier", "Norwich Terrier", "Yorkshire Terrier", "Wire Fox Terrier",
|
| 85 |
+
"Lakeland Terrier", "Sealyham Terrier", "Airedale Terrier", "Cairn Terrier",
|
| 86 |
+
"Australian Terrier", "Dandie Dinmont Terrier", "Boston Terrier", "Miniature Schnauzer",
|
| 87 |
+
"Giant Schnauzer", "Standard Schnauzer", "Scottish Terrier", "Tibetan Terrier",
|
| 88 |
+
"Australian Silky Terrier", "Soft-coated Wheaten Terrier", "West Highland White Terrier",
|
| 89 |
+
"Lhasa Apso", "Flat-Coated Retriever", "Curly-coated Retriever", "Golden Retriever",
|
| 90 |
+
"Labrador Retriever", "Chesapeake Bay Retriever", "German Shorthaired Pointer", "Vizsla",
|
| 91 |
+
"English Setter", "Irish Setter", "Gordon Setter", "Brittany dog", "Clumber Spaniel",
|
| 92 |
+
"English Springer Spaniel", "Welsh Springer Spaniel", "Cocker Spaniel", "Sussex Spaniel",
|
| 93 |
+
"Irish Water Spaniel", "Kuvasz", "Schipperke", "Groenendael dog", "Malinois", "Briard",
|
| 94 |
+
"Australian Kelpie", "Komondor", "Old English Sheepdog", "Shetland Sheepdog", "collie",
|
| 95 |
+
"Border Collie", "Bouvier des Flandres dog", "Rottweiler", "German Shepherd Dog", "Dobermann",
|
| 96 |
+
"Miniature Pinscher", "Greater Swiss Mountain Dog", "Bernese Mountain Dog",
|
| 97 |
+
"Appenzeller Sennenhund", "Entlebucher Sennenhund", "Boxer", "Bullmastiff", "Tibetan Mastiff",
|
| 98 |
+
"French Bulldog", "Great Dane", "St. Bernard", "husky", "Alaskan Malamute", "Siberian Husky",
|
| 99 |
+
"Dalmatian", "Affenpinscher", "Basenji", "pug", "Leonberger", "Newfoundland dog",
|
| 100 |
+
"Great Pyrenees dog", "Samoyed", "Pomeranian", "Chow Chow", "Keeshond", "brussels griffon",
|
| 101 |
+
"Pembroke Welsh Corgi", "Cardigan Welsh Corgi", "Toy Poodle", "Miniature Poodle",
|
| 102 |
+
"Standard Poodle", "Mexican hairless dog (xoloitzcuintli)", "grey wolf", "Alaskan tundra wolf",
|
| 103 |
+
"red wolf or maned wolf", "coyote", "dingo", "dhole", "African wild dog", "hyena", "red fox",
|
| 104 |
+
"kit fox", "Arctic fox", "grey fox", "tabby cat", "tiger cat", "Persian cat", "Siamese cat",
|
| 105 |
+
"Egyptian Mau", "cougar", "lynx", "leopard", "snow leopard", "jaguar", "lion", "tiger",
|
| 106 |
+
"cheetah", "brown bear", "American black bear", "polar bear", "sloth bear", "mongoose",
|
| 107 |
+
"meerkat", "tiger beetle", "ladybug", "ground beetle", "longhorn beetle", "leaf beetle",
|
| 108 |
+
"dung beetle", "rhinoceros beetle", "weevil", "fly", "bee", "ant", "grasshopper",
|
| 109 |
+
"cricket insect", "stick insect", "cockroach", "praying mantis", "cicada", "leafhopper",
|
| 110 |
+
"lacewing", "dragonfly", "damselfly", "red admiral butterfly", "ringlet butterfly",
|
| 111 |
+
"monarch butterfly", "small white butterfly", "sulphur butterfly", "gossamer-winged butterfly",
|
| 112 |
+
"starfish", "sea urchin", "sea cucumber", "cottontail rabbit", "hare", "Angora rabbit",
|
| 113 |
+
"hamster", "porcupine", "fox squirrel", "marmot", "beaver", "guinea pig", "common sorrel horse",
|
| 114 |
+
"zebra", "pig", "wild boar", "warthog", "hippopotamus", "ox", "water buffalo", "bison",
|
| 115 |
+
"ram (adult male sheep)", "bighorn sheep", "Alpine ibex", "hartebeest", "impala (antelope)",
|
| 116 |
+
"gazelle", "arabian camel", "llama", "weasel", "mink", "European polecat",
|
| 117 |
+
"black-footed ferret", "otter", "skunk", "badger", "armadillo", "three-toed sloth", "orangutan",
|
| 118 |
+
"gorilla", "chimpanzee", "gibbon", "siamang", "guenon", "patas monkey", "baboon", "macaque",
|
| 119 |
+
"langur", "black-and-white colobus", "proboscis monkey", "marmoset", "white-headed capuchin",
|
| 120 |
+
"howler monkey", "titi monkey", "Geoffroy's spider monkey", "common squirrel monkey",
|
| 121 |
+
"ring-tailed lemur", "indri", "Asian elephant", "African bush elephant", "red panda",
|
| 122 |
+
"giant panda", "snoek fish", "eel", "silver salmon", "rock beauty fish", "clownfish",
|
| 123 |
+
"sturgeon", "gar fish", "lionfish", "pufferfish", "abacus", "abaya", "academic gown",
|
| 124 |
+
"accordion", "acoustic guitar", "aircraft carrier", "airliner", "airship", "altar", "ambulance",
|
| 125 |
+
"amphibious vehicle", "analog clock", "apiary", "apron", "trash can", "assault rifle",
|
| 126 |
+
"backpack", "bakery", "balance beam", "balloon", "ballpoint pen", "Band-Aid", "banjo",
|
| 127 |
+
"baluster / handrail", "barbell", "barber chair", "barbershop", "barn", "barometer", "barrel",
|
| 128 |
+
"wheelbarrow", "baseball", "basketball", "bassinet", "bassoon", "swimming cap", "bath towel",
|
| 129 |
+
"bathtub", "station wagon", "lighthouse", "beaker", "military hat (bearskin or shako)",
|
| 130 |
+
"beer bottle", "beer glass", "bell tower", "baby bib", "tandem bicycle", "bikini",
|
| 131 |
+
"ring binder", "binoculars", "birdhouse", "boathouse", "bobsleigh", "bolo tie", "poke bonnet",
|
| 132 |
+
"bookcase", "bookstore", "bottle cap", "hunting bow", "bow tie", "brass memorial plaque", "bra",
|
| 133 |
+
"breakwater", "breastplate", "broom", "bucket", "buckle", "bulletproof vest",
|
| 134 |
+
"high-speed train", "butcher shop", "taxicab", "cauldron", "candle", "cannon", "canoe",
|
| 135 |
+
"can opener", "cardigan", "car mirror", "carousel", "tool kit", "cardboard box / carton",
|
| 136 |
+
"car wheel", "automated teller machine", "cassette", "cassette player", "castle", "catamaran",
|
| 137 |
+
"CD player", "cello", "mobile phone", "chain", "chain-link fence", "chain mail", "chainsaw",
|
| 138 |
+
"storage chest", "chiffonier", "bell or wind chime", "china cabinet", "Christmas stocking",
|
| 139 |
+
"church", "movie theater", "cleaver", "cliff dwelling", "cloak", "clogs", "cocktail shaker",
|
| 140 |
+
"coffee mug", "coffeemaker", "spiral or coil", "combination lock", "computer keyboard",
|
| 141 |
+
"candy store", "container ship", "convertible", "corkscrew", "cornet", "cowboy boot",
|
| 142 |
+
"cowboy hat", "cradle", "construction crane", "crash helmet", "crate", "infant bed",
|
| 143 |
+
"Crock Pot", "croquet ball", "crutch", "cuirass", "dam", "desk", "desktop computer",
|
| 144 |
+
"rotary dial telephone", "diaper", "digital clock", "digital watch", "dining table",
|
| 145 |
+
"dishcloth", "dishwasher", "disc brake", "dock", "dog sled", "dome", "doormat", "drilling rig",
|
| 146 |
+
"drum", "drumstick", "dumbbell", "Dutch oven", "electric fan", "electric guitar",
|
| 147 |
+
"electric locomotive", "entertainment center", "envelope", "espresso machine", "face powder",
|
| 148 |
+
"feather boa", "filing cabinet", "fireboat", "fire truck", "fire screen", "flagpole", "flute",
|
| 149 |
+
"folding chair", "football helmet", "forklift", "fountain", "fountain pen", "four-poster bed",
|
| 150 |
+
"freight car", "French horn", "frying pan", "fur coat", "garbage truck",
|
| 151 |
+
"gas mask or respirator", "gas pump", "goblet", "go-kart", "golf ball", "golf cart", "gondola",
|
| 152 |
+
"gong", "gown", "grand piano", "greenhouse", "radiator grille", "grocery store", "guillotine",
|
| 153 |
+
"hair clip", "hair spray", "half-track", "hammer", "hamper", "hair dryer", "hand-held computer",
|
| 154 |
+
"handkerchief", "hard disk drive", "harmonica", "harp", "combine harvester", "hatchet",
|
| 155 |
+
"holster", "home theater", "honeycomb", "hook", "hoop skirt", "gymnastic horizontal bar",
|
| 156 |
+
"horse-drawn vehicle", "hourglass", "iPod", "clothes iron", "carved pumpkin", "jeans", "jeep",
|
| 157 |
+
"T-shirt", "jigsaw puzzle", "rickshaw", "joystick", "kimono", "knee pad", "knot", "lab coat",
|
| 158 |
+
"ladle", "lampshade", "laptop computer", "lawn mower", "lens cap", "letter opener", "library",
|
| 159 |
+
"lifeboat", "lighter", "limousine", "ocean liner", "lipstick", "slip-on shoe", "lotion",
|
| 160 |
+
"music speaker", "loupe magnifying glass", "sawmill", "magnetic compass", "messenger bag",
|
| 161 |
+
"mailbox", "tights", "one-piece bathing suit", "manhole cover", "maraca", "marimba", "mask",
|
| 162 |
+
"matchstick", "maypole", "maze", "measuring cup", "medicine cabinet", "megalith", "microphone",
|
| 163 |
+
"microwave oven", "military uniform", "milk can", "minibus", "miniskirt", "minivan", "missile",
|
| 164 |
+
"mitten", "mixing bowl", "mobile home", "ford model t", "modem", "monastery", "monitor",
|
| 165 |
+
"moped", "mortar and pestle", "graduation cap", "mosque", "mosquito net", "vespa",
|
| 166 |
+
"mountain bike", "tent", "computer mouse", "mousetrap", "moving van", "muzzle", "metal nail",
|
| 167 |
+
"neck brace", "necklace", "baby pacifier", "notebook computer", "obelisk", "oboe", "ocarina",
|
| 168 |
+
"odometer", "oil filter", "pipe organ", "oscilloscope", "overskirt", "bullock cart",
|
| 169 |
+
"oxygen mask", "product packet / packaging", "paddle", "paddle wheel", "padlock", "paintbrush",
|
| 170 |
+
"pajamas", "palace", "pan flute", "paper towel", "parachute", "parallel bars", "park bench",
|
| 171 |
+
"parking meter", "railroad car", "patio", "payphone", "pedestal", "pencil case",
|
| 172 |
+
"pencil sharpener", "perfume", "Petri dish", "photocopier", "plectrum", "Pickelhaube",
|
| 173 |
+
"picket fence", "pickup truck", "pier", "piggy bank", "pill bottle", "pillow", "ping-pong ball",
|
| 174 |
+
"pinwheel", "pirate ship", "drink pitcher", "block plane", "planetarium", "plastic bag",
|
| 175 |
+
"plate rack", "farm plow", "plunger", "Polaroid camera", "pole", "police van", "poncho",
|
| 176 |
+
"pool table", "soda bottle", "plant pot", "potter's wheel", "power drill", "prayer rug",
|
| 177 |
+
"printer", "prison", "missile", "projector", "hockey puck", "punching bag", "purse", "quill",
|
| 178 |
+
"quilt", "race car", "racket", "radiator", "radio", "radio telescope", "rain barrel",
|
| 179 |
+
"recreational vehicle", "fishing casting reel", "reflex camera", "refrigerator",
|
| 180 |
+
"remote control", "restaurant", "revolver", "rifle", "rocking chair", "rotisserie", "eraser",
|
| 181 |
+
"rugby ball", "ruler measuring stick", "sneaker", "safe", "safety pin", "salt shaker", "sandal",
|
| 182 |
+
"sarong", "saxophone", "scabbard", "weighing scale", "school bus", "schooner", "scoreboard",
|
| 183 |
+
"CRT monitor", "screw", "screwdriver", "seat belt", "sewing machine", "shield", "shoe store",
|
| 184 |
+
"shoji screen / room divider", "shopping basket", "shopping cart", "shovel", "shower cap",
|
| 185 |
+
"shower curtain", "ski", "balaclava ski mask", "sleeping bag", "slide rule", "sliding door",
|
| 186 |
+
"slot machine", "snorkel", "snowmobile", "snowplow", "soap dispenser", "soccer ball", "sock",
|
| 187 |
+
"solar thermal collector", "sombrero", "soup bowl", "keyboard space bar", "space heater",
|
| 188 |
+
"space shuttle", "spatula", "motorboat", "spider web", "spindle", "sports car", "spotlight",
|
| 189 |
+
"stage", "steam locomotive", "through arch bridge", "steel drum", "stethoscope", "scarf",
|
| 190 |
+
"stone wall", "stopwatch", "stove", "strainer", "tram", "stretcher", "couch", "stupa",
|
| 191 |
+
"submarine", "suit", "sundial", "sunglasses", "sunglasses", "sunscreen", "suspension bridge",
|
| 192 |
+
"mop", "sweatshirt", "swim trunks / shorts", "swing", "electrical switch", "syringe",
|
| 193 |
+
"table lamp", "tank", "tape player", "teapot", "teddy bear", "television", "tennis ball",
|
| 194 |
+
"thatched roof", "front curtain", "thimble", "threshing machine", "throne", "tile roof",
|
| 195 |
+
"toaster", "tobacco shop", "toilet seat", "torch", "totem pole", "tow truck", "toy store",
|
| 196 |
+
"tractor", "semi-trailer truck", "tray", "trench coat", "tricycle", "trimaran", "tripod",
|
| 197 |
+
"triumphal arch", "trolleybus", "trombone", "hot tub", "turnstile", "typewriter keyboard",
|
| 198 |
+
"umbrella", "unicycle", "upright piano", "vacuum cleaner", "vase", "vaulted or arched ceiling",
|
| 199 |
+
"velvet fabric", "vending machine", "vestment", "viaduct", "violin", "volleyball",
|
| 200 |
+
"waffle iron", "wall clock", "wallet", "wardrobe", "military aircraft", "sink",
|
| 201 |
+
"washing machine", "water bottle", "water jug", "water tower", "whiskey jug", "whistle",
|
| 202 |
+
"hair wig", "window screen", "window shade", "Windsor tie", "wine bottle", "airplane wing",
|
| 203 |
+
"wok", "wooden spoon", "wool", "split-rail fence", "shipwreck", "sailboat", "yurt", "website",
|
| 204 |
+
"comic book", "crossword", "traffic or street sign", "traffic light", "dust jacket", "menu",
|
| 205 |
+
"plate", "guacamole", "consomme", "hot pot", "trifle", "ice cream", "popsicle", "baguette",
|
| 206 |
+
"bagel", "pretzel", "cheeseburger", "hot dog", "mashed potatoes", "cabbage", "broccoli",
|
| 207 |
+
"cauliflower", "zucchini", "spaghetti squash", "acorn squash", "butternut squash", "cucumber",
|
| 208 |
+
"artichoke", "bell pepper", "cardoon", "mushroom", "Granny Smith apple", "strawberry", "orange",
|
| 209 |
+
"lemon", "fig", "pineapple", "banana", "jackfruit", "cherimoya (custard apple)", "pomegranate",
|
| 210 |
+
"hay", "carbonara", "chocolate syrup", "dough", "meatloaf", "pizza", "pot pie", "burrito",
|
| 211 |
+
"red wine", "espresso", "tea cup", "eggnog", "mountain", "bubble", "cliff", "coral reef",
|
| 212 |
+
"geyser", "lakeshore", "promontory", "sandbar", "beach", "valley", "volcano", "baseball player",
|
| 213 |
+
"bridegroom", "scuba diver", "rapeseed", "daisy", "yellow lady's slipper", "corn", "acorn",
|
| 214 |
+
"rose hip", "horse chestnut seed", "coral fungus", "agaric", "gyromitra", "stinkhorn mushroom",
|
| 215 |
+
"earth star fungus", "hen of the woods mushroom", "bolete", "corn cob", "toilet paper"
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
CLIP_PAPER_PROMPT_TEMPLATES = [
|
| 219 |
+
'a bad photo of a {}.', 'a photo of many {}.', 'a sculpture of a {}.',
|
| 220 |
+
'a photo of the hard to see {}.', 'a low resolution photo of the {}.',
|
| 221 |
+
'a rendering of a {}.', 'graffiti of a {}.', 'a bad photo of the {}.',
|
| 222 |
+
'a cropped photo of the {}.', 'a tattoo of a {}.', 'the embroidered {}.',
|
| 223 |
+
'a photo of a hard to see {}.', 'a bright photo of a {}.',
|
| 224 |
+
'a photo of a clean {}.', 'a photo of a dirty {}.', 'a dark photo of the {}.',
|
| 225 |
+
'a drawing of a {}.', 'a photo of my {}.', 'the plastic {}.',
|
| 226 |
+
'a photo of the cool {}.', 'a close-up photo of a {}.',
|
| 227 |
+
'a black and white photo of the {}.', 'a painting of the {}.',
|
| 228 |
+
'a painting of a {}.', 'a pixelated photo of the {}.', 'a sculpture of the {}.',
|
| 229 |
+
'a bright photo of the {}.', 'a cropped photo of a {}.', 'a plastic {}.',
|
| 230 |
+
'a photo of the dirty {}.', 'a jpeg corrupted photo of a {}.',
|
| 231 |
+
'a blurry photo of the {}.', 'a photo of the {}.', 'a good photo of the {}.',
|
| 232 |
+
'a rendering of the {}.', 'a {} in a video game.', 'a photo of one {}.',
|
| 233 |
+
'a doodle of a {}.', 'a close-up photo of the {}.', 'a photo of a {}.',
|
| 234 |
+
'the origami {}.', 'the {} in a video game.', 'a sketch of a {}.',
|
| 235 |
+
'a doodle of the {}.', 'a origami {}.', 'a low resolution photo of a {}.',
|
| 236 |
+
'the toy {}.', 'a rendition of the {}.', 'a photo of the clean {}.',
|
| 237 |
+
'a photo of a large {}.', 'a rendition of a {}.', 'a photo of a nice {}.',
|
| 238 |
+
'a photo of a weird {}.', 'a blurry photo of a {}.', 'a cartoon {}.',
|
| 239 |
+
'art of a {}.', 'a sketch of the {}.', 'a embroidered {}.',
|
| 240 |
+
'a pixelated photo of a {}.', 'itap of the {}.',
|
| 241 |
+
'a jpeg corrupted photo of the {}.', 'a good photo of a {}.',
|
| 242 |
+
'a plushie {}.', 'a photo of the nice {}.', 'a photo of the small {}.',
|
| 243 |
+
'a photo of the weird {}.', 'the cartoon {}.', 'art of the {}.',
|
| 244 |
+
'a drawing of the {}.', 'a photo of the large {}.',
|
| 245 |
+
'a black and white photo of a {}.', 'the plushie {}.',
|
| 246 |
+
'a dark photo of a {}.', 'itap of a {}.', 'graffiti of the {}.',
|
| 247 |
+
'a toy {}.', 'itap of my {}.', 'a photo of a cool {}.',
|
| 248 |
+
'a photo of a small {}.', 'a tattoo of the {}.', '{}',
|
| 249 |
+
]
|
| 250 |
+
def canonicalize_text(text: str, *, keep_punctuation_exact_string: Optional[str] = None) -> str:
|
| 251 |
+
"""Canonicalize text (lowercase, remove punctuation, normalize whitespace).
|
| 252 |
+
From big_vision.evaluators.proj.image_text.prompt_engineering.
|
| 253 |
+
"""
|
| 254 |
+
text = text.replace("_", " ")
|
| 255 |
+
if keep_punctuation_exact_string:
|
| 256 |
+
text = keep_punctuation_exact_string.join(
|
| 257 |
+
part.translate(str.maketrans("", "", string.punctuation))
|
| 258 |
+
for part in text.split(keep_punctuation_exact_string)
|
| 259 |
+
)
|
| 260 |
+
else:
|
| 261 |
+
text = text.translate(str.maketrans("", "", string.punctuation))
|
| 262 |
+
text = text.lower()
|
| 263 |
+
text = re.sub(r"\s+", " ", text)
|
| 264 |
+
return text.strip()
|
| 265 |
+
def get_canonicalized_class_names(class_names: List[str], first_only: bool = True) -> List[str]:
|
| 266 |
+
"""Canonicalize class names, optionally keeping first alias only."""
|
| 267 |
+
out = []
|
| 268 |
+
for name in class_names:
|
| 269 |
+
n = canonicalize_text(name, keep_punctuation_exact_string=",")
|
| 270 |
+
if first_only:
|
| 271 |
+
n = n.split(",")[0].strip() if "," in n else n
|
| 272 |
+
out.append(n)
|
| 273 |
+
return out
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def get_canonicalized_templates(templates: List[str]) -> List[str]:
|
| 277 |
+
"""Canonicalize prompt templates, preserving '{}' placeholder."""
|
| 278 |
+
return [
|
| 279 |
+
canonicalize_text(t, keep_punctuation_exact_string="{}")
|
| 280 |
+
for t in templates
|
| 281 |
+
]
|
| 282 |
+
def substitute_prompt(template: str, class_name: str) -> str:
|
| 283 |
+
"""Substitute '{}' in template with class_name. From discriminative_classifier."""
|
| 284 |
+
placeholder = "{}"
|
| 285 |
+
parts = template.split(placeholder)
|
| 286 |
+
assert len(parts) == 2, f"Template must have exactly one '{placeholder}': {template}"
|
| 287 |
+
return parts[0] + class_name + parts[1]
|
| 288 |
+
# ============================================================================
|
| 289 |
+
# Utility functions
|
| 290 |
+
# ============================================================================
|
| 291 |
+
|
| 292 |
+
def batched(iterable, n: int):
|
| 293 |
+
"""Batch data into lists of length n. The last batch may be shorter."""
|
| 294 |
+
it = iter(iterable)
|
| 295 |
+
while True:
|
| 296 |
+
batch = list(islice(it, n))
|
| 297 |
+
if not batch:
|
| 298 |
+
break
|
| 299 |
+
yield batch
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def accuracy(output: torch.Tensor, target: torch.Tensor, topk: Tuple[int, ...] = (1,)) -> List[float]:
|
| 303 |
+
"""Calculate top-k accuracy."""
|
| 304 |
+
pred = output.topk(max(topk), 1, True, True)[1].t()
|
| 305 |
+
correct = pred.eq(target.view(1, -1).expand_as(pred))
|
| 306 |
+
return [float(correct[:k].reshape(-1).float().sum(0, keepdim=True).cpu().numpy()) for k in topk]
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def get_input_dtype(precision: str) -> torch.dtype:
|
| 310 |
+
"""Get input dtype based on precision string."""
|
| 311 |
+
if precision in ('bf16', 'bfloat16'):
|
| 312 |
+
return torch.bfloat16
|
| 313 |
+
elif precision in ('fp16', 'float16'):
|
| 314 |
+
return torch.float16
|
| 315 |
+
return torch.float32
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def get_autocast_context(precision: str, device_type: str = 'cuda'):
|
| 319 |
+
"""Get autocast context manager based on precision."""
|
| 320 |
+
from contextlib import nullcontext
|
| 321 |
+
if precision in ('bf16', 'bfloat16'):
|
| 322 |
+
return partial(torch.amp.autocast, device_type=device_type, dtype=torch.bfloat16)
|
| 323 |
+
elif precision in ('fp16', 'float16'):
|
| 324 |
+
return partial(torch.amp.autocast, device_type=device_type, dtype=torch.float16)
|
| 325 |
+
return nullcontext
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
# ============================================================================
|
| 329 |
+
# Zero-shot classifier
|
| 330 |
+
# ============================================================================
|
| 331 |
+
|
| 332 |
+
@torch.no_grad()
|
| 333 |
+
def build_zero_shot_classifier(
|
| 334 |
+
model: AutoModel,
|
| 335 |
+
processor,
|
| 336 |
+
classnames: Sequence[str],
|
| 337 |
+
templates: Sequence[Callable],
|
| 338 |
+
num_classes_per_batch: int = 10,
|
| 339 |
+
device: torch.device = None,
|
| 340 |
+
use_tqdm: bool = True,
|
| 341 |
+
canonicalize: bool = True
|
| 342 |
+
|
| 343 |
+
) -> torch.Tensor:
|
| 344 |
+
"""Build zero-shot classifier weights.
|
| 345 |
+
|
| 346 |
+
Args:
|
| 347 |
+
model: SigLIP2 model instance.
|
| 348 |
+
processor: SigLIP processor for text tokenization.
|
| 349 |
+
classnames: Sequence of class names.
|
| 350 |
+
templates: Sequence of template functions.
|
| 351 |
+
num_classes_per_batch: Number of classes to process per batch.
|
| 352 |
+
device: Device to use.
|
| 353 |
+
use_tqdm: Whether to show progress bar.
|
| 354 |
+
|
| 355 |
+
Returns:
|
| 356 |
+
Zero-shot classifier weights, shape (embed_dim, num_classes).
|
| 357 |
+
"""
|
| 358 |
+
if canonicalize:
|
| 359 |
+
classnames = get_canonicalized_class_names(list(classnames))
|
| 360 |
+
templates = get_canonicalized_templates(list(templates))
|
| 361 |
+
|
| 362 |
+
num_templates = len(templates)
|
| 363 |
+
num_classes = len(classnames)
|
| 364 |
+
# 将classnames全部转为小写
|
| 365 |
+
classnames = [c.lower() for c in classnames]
|
| 366 |
+
if use_tqdm:
|
| 367 |
+
num_iter = (num_classes - 1) // num_classes_per_batch + 1
|
| 368 |
+
iter_wrap = partial(tqdm, total=num_iter, desc="Building classifier", unit_scale=num_classes_per_batch)
|
| 369 |
+
else:
|
| 370 |
+
iter_wrap = iter
|
| 371 |
+
|
| 372 |
+
def _process_batch(batch_classnames: List[str]) -> torch.Tensor:
|
| 373 |
+
num_batch_classes = len(batch_classnames)
|
| 374 |
+
texts = [
|
| 375 |
+
substitute_prompt(tpl, c)
|
| 376 |
+
for c in batch_classnames
|
| 377 |
+
for tpl in templates
|
| 378 |
+
]
|
| 379 |
+
# Use processor to tokenize texts
|
| 380 |
+
inputs = processor(text=texts, return_tensors="pt", padding="max_length", max_length=64 , truncation=True)
|
| 381 |
+
# inputs = processor(text=texts, return_tensors="pt", padding="max_length", max_length=64)
|
| 382 |
+
|
| 383 |
+
input_ids = inputs["input_ids"].to(device)
|
| 384 |
+
attention_mask = inputs.get("attention_mask", None)
|
| 385 |
+
if attention_mask is not None:
|
| 386 |
+
attention_mask = attention_mask.to(device)
|
| 387 |
+
|
| 388 |
+
# Get text features from SigLIP model (with projection head)
|
| 389 |
+
with torch.no_grad():
|
| 390 |
+
text_features = model.get_text_features(
|
| 391 |
+
input_ids=input_ids,
|
| 392 |
+
attention_mask=attention_mask,
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# Normalize text features
|
| 396 |
+
text_features = F.normalize(text_features, dim=-1)
|
| 397 |
+
|
| 398 |
+
# Reshape and average over templates
|
| 399 |
+
text_features = text_features.reshape(num_batch_classes, num_templates, -1).mean(dim=1)
|
| 400 |
+
text_features = F.normalize(text_features, dim=1)
|
| 401 |
+
return text_features.T # (embed_dim, num_batch_classes)
|
| 402 |
+
|
| 403 |
+
with torch.no_grad():
|
| 404 |
+
batched_embeds = [
|
| 405 |
+
_process_batch(batch)
|
| 406 |
+
for batch in iter_wrap(batched(classnames, num_classes_per_batch))
|
| 407 |
+
]
|
| 408 |
+
zeroshot_weights = torch.cat(batched_embeds, dim=1)
|
| 409 |
+
|
| 410 |
+
return zeroshot_weights
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
# ============================================================================
|
| 414 |
+
# Evaluation
|
| 415 |
+
# ============================================================================
|
| 416 |
+
@torch.no_grad()
|
| 417 |
+
def evaluate(
|
| 418 |
+
model: AutoModel,
|
| 419 |
+
processor,
|
| 420 |
+
classifier: torch.Tensor,
|
| 421 |
+
dataloader: DataLoader,
|
| 422 |
+
device: torch.device,
|
| 423 |
+
precision: str = 'fp32',
|
| 424 |
+
) -> Tuple[float, float]:
|
| 425 |
+
"""Run zero-shot evaluation.
|
| 426 |
+
|
| 427 |
+
Args:
|
| 428 |
+
model: SigLIP2 model instance.
|
| 429 |
+
processor: SigLIP processor for image preprocessing.
|
| 430 |
+
classifier: Zero-shot classifier weights, shape (embed_dim, num_classes).
|
| 431 |
+
dataloader: ImageNet validation dataloader.
|
| 432 |
+
device: Device to use.
|
| 433 |
+
precision: Precision for inference.
|
| 434 |
+
|
| 435 |
+
Returns:
|
| 436 |
+
Tuple of (top1_accuracy, top5_accuracy).
|
| 437 |
+
"""
|
| 438 |
+
autocast_ctx = get_autocast_context(precision, device_type=device.type)
|
| 439 |
+
input_dtype = get_input_dtype(precision)
|
| 440 |
+
|
| 441 |
+
top1, top5, n = 0.0, 0.0, 0
|
| 442 |
+
|
| 443 |
+
with torch.inference_mode():
|
| 444 |
+
for images, targets in tqdm(dataloader, desc="Evaluating"):
|
| 445 |
+
images = images.to(device=device, dtype=input_dtype)
|
| 446 |
+
targets = targets.to(device)
|
| 447 |
+
|
| 448 |
+
with autocast_ctx():
|
| 449 |
+
# Get image features from SigLIP model (with projection head)
|
| 450 |
+
image_features = model.get_image_features(pixel_values=images)
|
| 451 |
+
|
| 452 |
+
# Normalize image features
|
| 453 |
+
image_features = F.normalize(image_features, dim=-1)
|
| 454 |
+
logits = image_features @ classifier
|
| 455 |
+
logits = model.logit_scale.exp() * logits + model.logit_bias
|
| 456 |
+
acc1, acc5 = accuracy(logits, targets, topk=(1, 5))
|
| 457 |
+
top1 += acc1
|
| 458 |
+
top5 += acc5
|
| 459 |
+
n += images.size(0)
|
| 460 |
+
|
| 461 |
+
top1 = top1 / n * 100
|
| 462 |
+
top5 = top5 / n * 100
|
| 463 |
+
return top1, top5
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
# ============================================================================
|
| 467 |
+
# PatchReparam encoder wrapper and dataloader for training integration
|
| 468 |
+
# ============================================================================
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
@torch.no_grad()
|
| 472 |
+
class PatchReparamZeroShotWrapper(nn.Module):
|
| 473 |
+
"""Wrapper to run zero-shot eval with PatchReparam encoder (image) + full SigLIP (text + logit).
|
| 474 |
+
|
| 475 |
+
Used when integrating zero-shot into training: image features come from the
|
| 476 |
+
current PatchReparam encoder (with output_head), text features and logit scale/bias
|
| 477 |
+
from a frozen full SiglipModel.
|
| 478 |
+
"""
|
| 479 |
+
|
| 480 |
+
def __init__(self, encoder: nn.Module, full_siglip: SiglipModel):
|
| 481 |
+
super().__init__()
|
| 482 |
+
self.encoder = encoder
|
| 483 |
+
self._full_siglip = full_siglip
|
| 484 |
+
# Expose logit parameters for evaluate()
|
| 485 |
+
self.logit_scale = full_siglip.logit_scale
|
| 486 |
+
self.logit_bias = full_siglip.logit_bias
|
| 487 |
+
|
| 488 |
+
def get_image_features(self, pixel_values: torch.Tensor) -> torch.Tensor:
|
| 489 |
+
"""Compute image features using PatchReparam encoder + output head (same space as SigLIP)."""
|
| 490 |
+
features = self.encoder.get_image_features(pixel_values)
|
| 491 |
+
|
| 492 |
+
return F.normalize(features, dim=-1)
|
| 493 |
+
|
| 494 |
+
def get_text_features(
|
| 495 |
+
self,
|
| 496 |
+
input_ids: torch.Tensor,
|
| 497 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 498 |
+
) -> torch.Tensor:
|
| 499 |
+
"""Delegate to full SigLIP text tower."""
|
| 500 |
+
return self._full_siglip.get_text_features(
|
| 501 |
+
input_ids=input_ids,
|
| 502 |
+
attention_mask=attention_mask,
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
@torch.no_grad()
|
| 507 |
+
def create_imagenet_dataloader_patch_reparam(
|
| 508 |
+
data_path: str,
|
| 509 |
+
encoder_input_size: int,
|
| 510 |
+
encoder_mean: Union[torch.Tensor, List[float]],
|
| 511 |
+
encoder_std: Union[torch.Tensor, List[float]],
|
| 512 |
+
batch_size: int = 128,
|
| 513 |
+
num_workers: int = 8,
|
| 514 |
+
) -> DataLoader:
|
| 515 |
+
"""Create ImageNet val dataloader with PatchReparam encoder preprocessing (resize + normalize).
|
| 516 |
+
|
| 517 |
+
Use this when evaluating PatchReparam encoder zero-shot so input matches encoder expectations.
|
| 518 |
+
"""
|
| 519 |
+
if isinstance(encoder_mean, torch.Tensor):
|
| 520 |
+
mean = encoder_mean.squeeze().tolist()
|
| 521 |
+
else:
|
| 522 |
+
mean = encoder_mean
|
| 523 |
+
if isinstance(encoder_std, torch.Tensor):
|
| 524 |
+
std = encoder_std.squeeze().tolist()
|
| 525 |
+
else:
|
| 526 |
+
std = encoder_std
|
| 527 |
+
transform = transforms.Compose([
|
| 528 |
+
transforms.Resize(encoder_input_size, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 529 |
+
transforms.CenterCrop(encoder_input_size),
|
| 530 |
+
transforms.ToTensor(),
|
| 531 |
+
transforms.Normalize(mean=mean, std=std),
|
| 532 |
+
])
|
| 533 |
+
dataset = ImageFolder(root=data_path, transform=transform)
|
| 534 |
+
return DataLoader(
|
| 535 |
+
dataset,
|
| 536 |
+
batch_size=batch_size,
|
| 537 |
+
shuffle=False,
|
| 538 |
+
num_workers=num_workers,
|
| 539 |
+
pin_memory=True,
|
| 540 |
+
)
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
# ============================================================================
|
| 544 |
+
# Main
|
| 545 |
+
# ============================================================================
|
| 546 |
+
|
| 547 |
+
def create_imagenet_dataloader(
|
| 548 |
+
data_path: str,
|
| 549 |
+
processor,
|
| 550 |
+
batch_size: int = 128,
|
| 551 |
+
num_workers: int = 8,
|
| 552 |
+
) -> DataLoader:
|
| 553 |
+
"""Create ImageNet validation dataloader using processor for preprocessing."""
|
| 554 |
+
from PIL import Image
|
| 555 |
+
|
| 556 |
+
class ImageNetDataset(ImageFolder):
|
| 557 |
+
def __getitem__(self, index):
|
| 558 |
+
path, target = self.samples[index]
|
| 559 |
+
image = Image.open(path).convert('RGB')
|
| 560 |
+
# Use processor to preprocess image
|
| 561 |
+
inputs = processor(images=image, return_tensors="pt")
|
| 562 |
+
pixel_values = inputs["pixel_values"].squeeze(0) # Remove batch dimension
|
| 563 |
+
return pixel_values, target
|
| 564 |
+
|
| 565 |
+
dataset = ImageNetDataset(root=data_path)
|
| 566 |
+
# 验证类别顺序
|
| 567 |
+
|
| 568 |
+
dataloader = DataLoader(
|
| 569 |
+
dataset,
|
| 570 |
+
batch_size=batch_size,
|
| 571 |
+
shuffle=False,
|
| 572 |
+
num_workers=num_workers,
|
| 573 |
+
pin_memory=True,
|
| 574 |
+
)
|
| 575 |
+
return dataloader
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
def main():
|
| 579 |
+
parser = argparse.ArgumentParser(description="Zero-shot ImageNet evaluation for SigLIP2")
|
| 580 |
+
parser.add_argument("--model_path", type=str, required=True,
|
| 581 |
+
help="Path to SigLIP2 model directory")
|
| 582 |
+
parser.add_argument("--data_path", type=str, required=True,
|
| 583 |
+
help="Path to ImageNet validation dataset")
|
| 584 |
+
parser.add_argument("--batch_size", type=int, default=128,
|
| 585 |
+
help="Batch size for evaluation")
|
| 586 |
+
parser.add_argument("--num_workers", type=int, default=8,
|
| 587 |
+
help="Number of dataloader workers")
|
| 588 |
+
parser.add_argument("--device", type=str, default="cuda:0",
|
| 589 |
+
help="Device to use (e.g., cuda:0, cpu)")
|
| 590 |
+
parser.add_argument("--precision", type=str, default="fp32",
|
| 591 |
+
choices=["fp32", "fp16", "bf16"],
|
| 592 |
+
help="Precision for inference")
|
| 593 |
+
args = parser.parse_args()
|
| 594 |
+
|
| 595 |
+
device = torch.device(args.device)
|
| 596 |
+
|
| 597 |
+
print("=" * 60)
|
| 598 |
+
print("Zero-Shot ImageNet Evaluation (SigLIP2)")
|
| 599 |
+
print("=" * 60)
|
| 600 |
+
print(f"Model path: {args.model_path}")
|
| 601 |
+
print(f"Data path: {args.data_path}")
|
| 602 |
+
print(f"Device: {device}")
|
| 603 |
+
print(f"Precision: {args.precision}")
|
| 604 |
+
print(f"Batch size: {args.batch_size}")
|
| 605 |
+
print()
|
| 606 |
+
|
| 607 |
+
# Load model and processor
|
| 608 |
+
print("Loading model and processor...")
|
| 609 |
+
# model = AutoModel.from_pretrained(args.model_path, trust_remote_code=True)
|
| 610 |
+
model = SiglipModel.from_pretrained(args.model_path)
|
| 611 |
+
processor = AutoProcessor.from_pretrained(args.model_path)
|
| 612 |
+
model = model.to(device)
|
| 613 |
+
model.eval()
|
| 614 |
+
|
| 615 |
+
# Debug: print model structure
|
| 616 |
+
print(f"Model type: {type(model).__name__}")
|
| 617 |
+
if hasattr(model, 'vision_model'):
|
| 618 |
+
print(f" Has vision_model: {type(model.vision_model).__name__}")
|
| 619 |
+
if hasattr(model, 'text_model'):
|
| 620 |
+
print(f" Has text_model: {type(model.text_model).__name__}")
|
| 621 |
+
if hasattr(model, 'get_image_features'):
|
| 622 |
+
print(f" Has get_image_features method")
|
| 623 |
+
if hasattr(model, 'get_text_features'):
|
| 624 |
+
print(f" Has get_text_features method")
|
| 625 |
+
print()
|
| 626 |
+
|
| 627 |
+
# Get image size from processor
|
| 628 |
+
if hasattr(processor, 'image_processor'):
|
| 629 |
+
image_size = processor.image_processor.size
|
| 630 |
+
print(f"Image size: {image_size}")
|
| 631 |
+
print()
|
| 632 |
+
|
| 633 |
+
# Create dataloader
|
| 634 |
+
print(f"Loading ImageNet validation: {args.data_path}")
|
| 635 |
+
dataloader = create_imagenet_dataloader(
|
| 636 |
+
args.data_path,
|
| 637 |
+
processor=processor,
|
| 638 |
+
batch_size=args.batch_size,
|
| 639 |
+
num_workers=args.num_workers,
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
dataset = ImageFolder(root=args.data_path)
|
| 643 |
+
class_to_idx = dataset.class_to_idx # 字典:{文件夹名: 索引}
|
| 644 |
+
sorted_classes = sorted(class_to_idx.items(), key=lambda x: x[1]) # 按索引排序
|
| 645 |
+
|
| 646 |
+
# 检查前几个类别是否匹配
|
| 647 |
+
for i, (folder_name, idx) in enumerate(sorted_classes[:10]):
|
| 648 |
+
print(f"Index {idx}: {folder_name} -> {IMAGENET_CLASSNAMES[idx]}")
|
| 649 |
+
|
| 650 |
+
num_samples = len(dataloader.dataset)
|
| 651 |
+
print(f"Number of samples: {num_samples}")
|
| 652 |
+
if num_samples != 50000:
|
| 653 |
+
print(f" [Warning] Expected 50000 samples for ImageNet validation, got {num_samples}")
|
| 654 |
+
print()
|
| 655 |
+
|
| 656 |
+
# Build classifier
|
| 657 |
+
print("Building zero-shot classifier...")
|
| 658 |
+
autocast_ctx = get_autocast_context(args.precision, device_type=device.type)
|
| 659 |
+
with autocast_ctx():
|
| 660 |
+
classifier = build_zero_shot_classifier(
|
| 661 |
+
model,
|
| 662 |
+
processor,
|
| 663 |
+
classnames=IMAGENET_CLASSNAMES,
|
| 664 |
+
templates=CLIP_PAPER_PROMPT_TEMPLATES,
|
| 665 |
+
num_classes_per_batch=10,
|
| 666 |
+
device=device,
|
| 667 |
+
use_tqdm=True,
|
| 668 |
+
canonicalize=True,
|
| 669 |
+
)
|
| 670 |
+
print()
|
| 671 |
+
|
| 672 |
+
# Evaluate
|
| 673 |
+
print("Running evaluation...")
|
| 674 |
+
top1, top5 = evaluate(
|
| 675 |
+
model,
|
| 676 |
+
processor,
|
| 677 |
+
classifier,
|
| 678 |
+
dataloader,
|
| 679 |
+
device=device,
|
| 680 |
+
precision=args.precision,
|
| 681 |
+
)
|
| 682 |
+
|
| 683 |
+
print()
|
| 684 |
+
print("=" * 60)
|
| 685 |
+
print("Results:")
|
| 686 |
+
print(f" ImageNet Zero-Shot Top-1 Accuracy: {top1:.2f}%")
|
| 687 |
+
print(f" ImageNet Zero-Shot Top-5 Accuracy: {top5:.2f}%")
|
| 688 |
+
print("=" * 60)
|
| 689 |
+
|
| 690 |
+
results = {"top1": top1, "top5": top5}
|
| 691 |
+
# Write results to model_path
|
| 692 |
+
out_dir = Path(args.model_path)
|
| 693 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 694 |
+
out_json = out_dir / "zeroshot_imagenet_results.json"
|
| 695 |
+
with open(out_json, "w", encoding="utf-8") as f:
|
| 696 |
+
json.dump(
|
| 697 |
+
{
|
| 698 |
+
"top1": top1,
|
| 699 |
+
"top5": top5,
|
| 700 |
+
"data_path": args.data_path,
|
| 701 |
+
"precision": args.precision,
|
| 702 |
+
"batch_size": args.batch_size,
|
| 703 |
+
},
|
| 704 |
+
f,
|
| 705 |
+
indent=2,
|
| 706 |
+
ensure_ascii=False,
|
| 707 |
+
)
|
| 708 |
+
print(f"Results written to {out_json}")
|
| 709 |
+
|
| 710 |
+
return results
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
if __name__ == "__main__":
|
| 714 |
+
main()
|
patch-reparameterization/src/eval/clip_zero_shot_bigvision.py
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Zero-shot ImageNet evaluation, fully aligned with big_vision discriminative_classifier.
|
| 4 |
+
|
| 5 |
+
Differences from clip_zero_shot.py:
|
| 6 |
+
- Image: resize(224) | value_range(-1,1) [no mean/std]
|
| 7 |
+
- Logit: raw cosine similarity (no logit_scale, no logit_bias)
|
| 8 |
+
- Class names: canonicalize (lowercase, remove punctuation, normalize whitespace)
|
| 9 |
+
- Templates: CLIP_PAPER 77 templates with canonicalize
|
| 10 |
+
- Text: average embeddings per class, then L2 normalize
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
python clip_zero_shot_bigvision.py --model_path /path/to/model --data_path /path/to/imagenet/val
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import json
|
| 20 |
+
import re
|
| 21 |
+
import string
|
| 22 |
+
from functools import partial
|
| 23 |
+
from itertools import islice
|
| 24 |
+
from pathlib import Path
|
| 25 |
+
from typing import Callable, List, Optional, Sequence, Tuple, Union
|
| 26 |
+
|
| 27 |
+
import torch
|
| 28 |
+
import torch.nn.functional as F
|
| 29 |
+
from torch.utils.data import DataLoader
|
| 30 |
+
from torchvision import transforms
|
| 31 |
+
from torchvision.datasets import ImageFolder
|
| 32 |
+
from tqdm import tqdm
|
| 33 |
+
from transformers import AutoProcessor, SiglipModel
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# ============================================================================
|
| 37 |
+
# Aligned with big_vision prompt_engineering
|
| 38 |
+
# ============================================================================
|
| 39 |
+
|
| 40 |
+
def canonicalize_text(text: str, *, keep_punctuation_exact_string: Optional[str] = None) -> str:
|
| 41 |
+
"""Canonicalize text (lowercase, remove punctuation, normalize whitespace).
|
| 42 |
+
From big_vision.evaluators.proj.image_text.prompt_engineering.
|
| 43 |
+
"""
|
| 44 |
+
text = text.replace("_", " ")
|
| 45 |
+
if keep_punctuation_exact_string:
|
| 46 |
+
text = keep_punctuation_exact_string.join(
|
| 47 |
+
part.translate(str.maketrans("", "", string.punctuation))
|
| 48 |
+
for part in text.split(keep_punctuation_exact_string)
|
| 49 |
+
)
|
| 50 |
+
else:
|
| 51 |
+
text = text.translate(str.maketrans("", "", string.punctuation))
|
| 52 |
+
text = text.lower()
|
| 53 |
+
text = re.sub(r"\s+", " ", text)
|
| 54 |
+
return text.strip()
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# CLIP ImageNet class names (from big_vision.datasets.imagenet.class_names)
|
| 58 |
+
CLIP_IMAGENET_CLASS_NAMES = [
|
| 59 |
+
'tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead shark',
|
| 60 |
+
'electric ray', 'stingray', 'rooster', 'hen', 'ostrich', 'brambling',
|
| 61 |
+
'goldfinch', 'house finch', 'junco', 'indigo bunting', 'American robin',
|
| 62 |
+
'bulbul', 'jay', 'magpie', 'chickadee', 'American dipper',
|
| 63 |
+
'kite (bird of prey)', 'bald eagle', 'vulture', 'great grey owl',
|
| 64 |
+
'fire salamander', 'smooth newt', 'newt', 'spotted salamander', 'axolotl',
|
| 65 |
+
'American bullfrog', 'tree frog', 'tailed frog', 'loggerhead sea turtle',
|
| 66 |
+
'leatherback sea turtle', 'mud turtle', 'terrapin', 'box turtle',
|
| 67 |
+
'banded gecko', 'green iguana', 'Carolina anole',
|
| 68 |
+
'desert grassland whiptail lizard', 'agama', 'frilled-necked lizard',
|
| 69 |
+
'alligator lizard', 'Gila monster', 'European green lizard', 'chameleon',
|
| 70 |
+
'Komodo dragon', 'Nile crocodile', 'American alligator', 'triceratops',
|
| 71 |
+
'worm snake', 'ring-necked snake', 'eastern hog-nosed snake',
|
| 72 |
+
'smooth green snake', 'kingsnake', 'garter snake', 'water snake',
|
| 73 |
+
'vine snake', 'night snake', 'boa constrictor', 'African rock python',
|
| 74 |
+
'Indian cobra', 'green mamba', 'sea snake', 'Saharan horned viper',
|
| 75 |
+
'eastern diamondback rattlesnake', 'sidewinder rattlesnake', 'trilobite',
|
| 76 |
+
'harvestman', 'scorpion', 'yellow garden spider', 'barn spider',
|
| 77 |
+
'European garden spider', 'southern black widow', 'tarantula',
|
| 78 |
+
'wolf spider', 'tick', 'centipede', 'black grouse', 'ptarmigan',
|
| 79 |
+
'ruffed grouse', 'prairie grouse', 'peafowl', 'quail', 'partridge',
|
| 80 |
+
'african grey parrot', 'macaw', 'sulphur-crested cockatoo', 'lorikeet',
|
| 81 |
+
'coucal', 'bee eater', 'hornbill', 'hummingbird', 'jacamar', 'toucan',
|
| 82 |
+
'duck', 'red-breasted merganser', 'goose', 'black swan', 'tusker',
|
| 83 |
+
'echidna', 'platypus', 'wallaby', 'koala', 'wombat', 'jellyfish',
|
| 84 |
+
'sea anemone', 'brain coral', 'flatworm', 'nematode', 'conch', 'snail',
|
| 85 |
+
'slug', 'sea slug', 'chiton', 'chambered nautilus', 'Dungeness crab',
|
| 86 |
+
'rock crab', 'fiddler crab', 'red king crab', 'American lobster',
|
| 87 |
+
'spiny lobster', 'crayfish', 'hermit crab', 'isopod', 'white stork',
|
| 88 |
+
'black stork', 'spoonbill', 'flamingo', 'little blue heron', 'great egret',
|
| 89 |
+
'bittern bird', 'crane bird', 'limpkin', 'common gallinule',
|
| 90 |
+
'American coot', 'bustard', 'ruddy turnstone', 'dunlin', 'common redshank',
|
| 91 |
+
'dowitcher', 'oystercatcher', 'pelican', 'king penguin', 'albatross',
|
| 92 |
+
'grey whale', 'killer whale', 'dugong', 'sea lion', 'Chihuahua',
|
| 93 |
+
'Japanese Chin', 'Maltese', 'Pekingese', 'Shih Tzu', 'King Charles Spaniel',
|
| 94 |
+
'Papillon', 'toy terrier', 'Rhodesian Ridgeback', 'Afghan Hound',
|
| 95 |
+
'Basset Hound', 'Beagle', 'Bloodhound', 'Bluetick Coonhound',
|
| 96 |
+
'Black and Tan Coonhound', 'Treeing Walker Coonhound', 'English foxhound',
|
| 97 |
+
'Redbone Coonhound', 'borzoi', 'Irish Wolfhound', 'Italian Greyhound',
|
| 98 |
+
'Whippet', 'Ibizan Hound', 'Norwegian Elkhound', 'Otterhound', 'Saluki',
|
| 99 |
+
'Scottish Deerhound', 'Weimaraner', 'Staffordshire Bull Terrier',
|
| 100 |
+
'American Staffordshire Terrier', 'Bedlington Terrier', 'Border Terrier',
|
| 101 |
+
'Kerry Blue Terrier', 'Irish Terrier', 'Norfolk Terrier', 'Norwich Terrier',
|
| 102 |
+
'Yorkshire Terrier', 'Wire Fox Terrier', 'Lakeland Terrier',
|
| 103 |
+
'Sealyham Terrier', 'Airedale Terrier', 'Cairn Terrier',
|
| 104 |
+
'Australian Terrier', 'Dandie Dinmont Terrier', 'Boston Terrier',
|
| 105 |
+
'Miniature Schnauzer', 'Giant Schnauzer', 'Standard Schnauzer',
|
| 106 |
+
'Scottish Terrier', 'Tibetan Terrier', 'Australian Silky Terrier',
|
| 107 |
+
'Soft-coated Wheaten Terrier', 'West Highland White Terrier', 'Lhasa Apso',
|
| 108 |
+
'Flat-Coated Retriever', 'Curly-coated Retriever', 'Golden Retriever',
|
| 109 |
+
'Labrador Retriever', 'Chesapeake Bay Retriever',
|
| 110 |
+
'German Shorthaired Pointer', 'Vizsla', 'English Setter', 'Irish Setter',
|
| 111 |
+
'Gordon Setter', 'Brittany dog', 'Clumber Spaniel',
|
| 112 |
+
'English Springer Spaniel', 'Welsh Springer Spaniel', 'Cocker Spaniel',
|
| 113 |
+
'Sussex Spaniel', 'Irish Water Spaniel', 'Kuvasz', 'Schipperke',
|
| 114 |
+
'Groenendael dog', 'Malinois', 'Briard', 'Australian Kelpie', 'Komondor',
|
| 115 |
+
'Old English Sheepdog', 'Shetland Sheepdog', 'collie', 'Border Collie',
|
| 116 |
+
'Bouvier des Flandres dog', 'Rottweiler', 'German Shepherd Dog',
|
| 117 |
+
'Dobermann', 'Miniature Pinscher', 'Greater Swiss Mountain Dog',
|
| 118 |
+
'Bernese Mountain Dog', 'Appenzeller Sennenhund', 'Entlebucher Sennenhund',
|
| 119 |
+
'Boxer', 'Bullmastiff', 'Tibetan Mastiff', 'French Bulldog', 'Great Dane',
|
| 120 |
+
'St. Bernard', 'husky', 'Alaskan Malamute', 'Siberian Husky', 'Dalmatian',
|
| 121 |
+
'Affenpinscher', 'Basenji', 'pug', 'Leonberger', 'Newfoundland dog',
|
| 122 |
+
'Great Pyrenees dog', 'Samoyed', 'Pomeranian', 'Chow Chow', 'Keeshond',
|
| 123 |
+
'brussels griffon', 'Pembroke Welsh Corgi', 'Cardigan Welsh Corgi',
|
| 124 |
+
'Toy Poodle', 'Miniature Poodle', 'Standard Poodle',
|
| 125 |
+
'Mexican hairless dog (xoloitzcuintli)', 'grey wolf', 'Alaskan tundra wolf',
|
| 126 |
+
'red wolf or maned wolf', 'coyote', 'dingo', 'dhole', 'African wild dog',
|
| 127 |
+
'hyena', 'red fox', 'kit fox', 'Arctic fox', 'grey fox', 'tabby cat',
|
| 128 |
+
'tiger cat', 'Persian cat', 'Siamese cat', 'Egyptian Mau', 'cougar', 'lynx',
|
| 129 |
+
'leopard', 'snow leopard', 'jaguar', 'lion', 'tiger', 'cheetah',
|
| 130 |
+
'brown bear', 'American black bear', 'polar bear', 'sloth bear', 'mongoose',
|
| 131 |
+
'meerkat', 'tiger beetle', 'ladybug', 'ground beetle', 'longhorn beetle',
|
| 132 |
+
'leaf beetle', 'dung beetle', 'rhinoceros beetle', 'weevil', 'fly', 'bee',
|
| 133 |
+
'ant', 'grasshopper', 'cricket insect', 'stick insect', 'cockroach',
|
| 134 |
+
'praying mantis', 'cicada', 'leafhopper', 'lacewing', 'dragonfly',
|
| 135 |
+
'damselfly', 'red admiral butterfly', 'ringlet butterfly',
|
| 136 |
+
'monarch butterfly', 'small white butterfly', 'sulphur butterfly',
|
| 137 |
+
'gossamer-winged butterfly', 'starfish', 'sea urchin', 'sea cucumber',
|
| 138 |
+
'cottontail rabbit', 'hare', 'Angora rabbit', 'hamster', 'porcupine',
|
| 139 |
+
'fox squirrel', 'marmot', 'beaver', 'guinea pig', 'common sorrel horse',
|
| 140 |
+
'zebra', 'pig', 'wild boar', 'warthog', 'hippopotamus', 'ox',
|
| 141 |
+
'water buffalo', 'bison', 'ram (adult male sheep)', 'bighorn sheep',
|
| 142 |
+
'Alpine ibex', 'hartebeest', 'impala (antelope)', 'gazelle',
|
| 143 |
+
'arabian camel', 'llama', 'weasel', 'mink', 'European polecat',
|
| 144 |
+
'black-footed ferret', 'otter', 'skunk', 'badger', 'armadillo',
|
| 145 |
+
'three-toed sloth', 'orangutan', 'gorilla', 'chimpanzee', 'gibbon',
|
| 146 |
+
'siamang', 'guenon', 'patas monkey', 'baboon', 'macaque', 'langur',
|
| 147 |
+
'black-and-white colobus', 'proboscis monkey', 'marmoset',
|
| 148 |
+
'white-headed capuchin', 'howler monkey', 'titi monkey',
|
| 149 |
+
"Geoffroy's spider monkey", 'common squirrel monkey', 'ring-tailed lemur',
|
| 150 |
+
'indri', 'Asian elephant', 'African bush elephant', 'red panda',
|
| 151 |
+
'giant panda', 'snoek fish', 'eel', 'silver salmon', 'rock beauty fish',
|
| 152 |
+
'clownfish', 'sturgeon', 'gar fish', 'lionfish', 'pufferfish', 'abacus',
|
| 153 |
+
'abaya', 'academic gown', 'accordion', 'acoustic guitar', 'aircraft carrier',
|
| 154 |
+
'airliner', 'airship', 'altar', 'ambulance', 'amphibious vehicle',
|
| 155 |
+
'analog clock', 'apiary', 'apron', 'trash can', 'assault rifle',
|
| 156 |
+
'backpack', 'bakery', 'balance beam', 'balloon', 'ballpoint pen',
|
| 157 |
+
'Band-Aid', 'banjo', 'baluster / handrail', 'barbell', 'barber chair',
|
| 158 |
+
'barbershop', 'barn', 'barometer', 'barrel', 'wheelbarrow', 'baseball',
|
| 159 |
+
'basketball', 'bassinet', 'bassoon', 'swimming cap', 'bath towel',
|
| 160 |
+
'bathtub', 'station wagon', 'lighthouse', 'beaker',
|
| 161 |
+
'military hat (bearskin or shako)', 'beer bottle', 'beer glass',
|
| 162 |
+
'bell tower', 'baby bib', 'tandem bicycle', 'bikini', 'ring binder',
|
| 163 |
+
'binoculars', 'birdhouse', 'boathouse', 'bobsleigh', 'bolo tie',
|
| 164 |
+
'poke bonnet', 'bookcase', 'bookstore', 'bottle cap', 'hunting bow',
|
| 165 |
+
'bow tie', 'brass memorial plaque', 'bra', 'breakwater', 'breastplate',
|
| 166 |
+
'broom', 'bucket', 'buckle', 'bulletproof vest', 'high-speed train',
|
| 167 |
+
'butcher shop', 'taxicab', 'cauldron', 'candle', 'cannon', 'canoe',
|
| 168 |
+
'can opener', 'cardigan', 'car mirror', 'carousel', 'tool kit',
|
| 169 |
+
'cardboard box / carton', 'car wheel', 'automated teller machine',
|
| 170 |
+
'cassette', 'cassette player', 'castle', 'catamaran', 'CD player',
|
| 171 |
+
'cello', 'mobile phone', 'chain', 'chain-link fence', 'chain mail',
|
| 172 |
+
'chainsaw', 'storage chest', 'chiffonier', 'bell or wind chime',
|
| 173 |
+
'china cabinet', 'Christmas stocking', 'church', 'movie theater',
|
| 174 |
+
'cleaver', 'cliff dwelling', 'cloak', 'clogs', 'cocktail shaker',
|
| 175 |
+
'coffee mug', 'coffeemaker', 'spiral or coil', 'combination lock',
|
| 176 |
+
'computer keyboard', 'candy store', 'container ship', 'convertible',
|
| 177 |
+
'corkscrew', 'cornet', 'cowboy boot', 'cowboy hat', 'cradle',
|
| 178 |
+
'construction crane', 'crash helmet', 'crate', 'infant bed',
|
| 179 |
+
'Crock Pot', 'croquet ball', 'crutch', 'cuirass', 'dam', 'desk',
|
| 180 |
+
'desktop computer', 'rotary dial telephone', 'diaper', 'digital clock',
|
| 181 |
+
'digital watch', 'dining table', 'dishcloth', 'dishwasher', 'disc brake',
|
| 182 |
+
'dock', 'dog sled', 'dome', 'doormat', 'drilling rig', 'drum', 'drumstick',
|
| 183 |
+
'dumbbell', 'Dutch oven', 'electric fan', 'electric guitar',
|
| 184 |
+
'electric locomotive', 'entertainment center', 'envelope',
|
| 185 |
+
'espresso machine', 'face powder', 'feather boa', 'filing cabinet',
|
| 186 |
+
'fireboat', 'fire truck', 'fire screen', 'flagpole', 'flute',
|
| 187 |
+
'folding chair', 'football helmet', 'forklift', 'fountain', 'fountain pen',
|
| 188 |
+
'four-poster bed', 'freight car', 'French horn', 'frying pan', 'fur coat',
|
| 189 |
+
'garbage truck', 'gas mask or respirator', 'gas pump', 'goblet', 'go-kart',
|
| 190 |
+
'golf ball', 'golf cart', 'gondola', 'gong', 'gown', 'grand piano',
|
| 191 |
+
'greenhouse', 'radiator grille', 'grocery store', 'guillotine',
|
| 192 |
+
'hair clip', 'hair spray', 'half-track', 'hammer', 'hamper', 'hair dryer',
|
| 193 |
+
'hand-held computer', 'handkerchief', 'hard disk drive', 'harmonica',
|
| 194 |
+
'harp', 'combine harvester', 'hatchet', 'holster', 'home theater',
|
| 195 |
+
'honeycomb', 'hook', 'hoop skirt', 'gymnastic horizontal bar',
|
| 196 |
+
'horse-drawn vehicle', 'hourglass', 'iPod', 'clothes iron',
|
| 197 |
+
'carved pumpkin', 'jeans', 'jeep', 'T-shirt', 'jigsaw puzzle', 'rickshaw',
|
| 198 |
+
'joystick', 'kimono', 'knee pad', 'knot', 'lab coat', 'ladle', 'lampshade',
|
| 199 |
+
'laptop computer', 'lawn mower', 'lens cap', 'letter opener', 'library',
|
| 200 |
+
'lifeboat', 'lighter', 'limousine', 'ocean liner', 'lipstick',
|
| 201 |
+
'slip-on shoe', 'lotion', 'music speaker', 'loupe magnifying glass',
|
| 202 |
+
'sawmill', 'magnetic compass', 'messenger bag', 'mailbox', 'tights',
|
| 203 |
+
'one-piece bathing suit', 'manhole cover', 'maraca', 'marimba', 'mask',
|
| 204 |
+
'matchstick', 'maypole', 'maze', 'measuring cup', 'medicine cabinet',
|
| 205 |
+
'megalith', 'microphone', 'microwave oven', 'military uniform', 'milk can',
|
| 206 |
+
'minibus', 'miniskirt', 'minivan', 'missile', 'mitten', 'mixing bowl',
|
| 207 |
+
'mobile home', 'ford model t', 'modem', 'monastery', 'monitor', 'moped',
|
| 208 |
+
'mortar and pestle', 'graduation cap', 'mosque', 'mosquito net', 'vespa',
|
| 209 |
+
'mountain bike', 'tent', 'computer mouse', 'mousetrap', 'moving van',
|
| 210 |
+
'muzzle', 'metal nail', 'neck brace', 'necklace', 'baby pacifier',
|
| 211 |
+
'notebook computer', 'obelisk', 'oboe', 'ocarina', 'odometer', 'oil filter',
|
| 212 |
+
'pipe organ', 'oscilloscope', 'overskirt', 'bullock cart', 'oxygen mask',
|
| 213 |
+
'product packet / packaging', 'paddle', 'paddle wheel', 'padlock',
|
| 214 |
+
'paintbrush', 'pajamas', 'palace', 'pan flute', 'paper towel', 'parachute',
|
| 215 |
+
'parallel bars', 'park bench', 'parking meter', 'railroad car', 'patio',
|
| 216 |
+
'payphone', 'pedestal', 'pencil case', 'pencil sharpener', 'perfume',
|
| 217 |
+
'Petri dish', 'photocopier', 'plectrum', 'Pickelhaube', 'picket fence',
|
| 218 |
+
'pickup truck', 'pier', 'piggy bank', 'pill bottle', 'pillow',
|
| 219 |
+
'ping-pong ball', 'pinwheel', 'pirate ship', 'drink pitcher',
|
| 220 |
+
'block plane', 'planetarium', 'plastic bag', 'plate rack', 'farm plow',
|
| 221 |
+
'plunger', 'Polaroid camera', 'pole', 'police van', 'poncho', 'pool table',
|
| 222 |
+
'soda bottle', 'plant pot', "potter's wheel", 'power drill', 'prayer rug',
|
| 223 |
+
'printer', 'prison', 'missile', 'projector', 'hockey puck', 'punching bag',
|
| 224 |
+
'purse', 'quill', 'quilt', 'race car', 'racket', 'radiator', 'radio',
|
| 225 |
+
'radio telescope', 'rain barrel', 'recreational vehicle',
|
| 226 |
+
'fishing casting reel', 'reflex camera', 'refrigerator', 'remote control',
|
| 227 |
+
'restaurant', 'revolver', 'rifle', 'rocking chair', 'rotisserie', 'eraser',
|
| 228 |
+
'rugby ball', 'ruler measuring stick', 'sneaker', 'safe', 'safety pin',
|
| 229 |
+
'salt shaker', 'sandal', 'sarong', 'saxophone', 'scabbard', 'weighing scale',
|
| 230 |
+
'school bus', 'schooner', 'scoreboard', 'CRT monitor', 'screw', 'screwdriver',
|
| 231 |
+
'seat belt', 'sewing machine', 'shield', 'shoe store',
|
| 232 |
+
'shoji screen / room divider', 'shopping basket', 'shopping cart', 'shovel',
|
| 233 |
+
'shower cap', 'shower curtain', 'ski', 'balaclava ski mask', 'sleeping bag',
|
| 234 |
+
'slide rule', 'sliding door', 'slot machine', 'snorkel', 'snowmobile',
|
| 235 |
+
'snowplow', 'soap dispenser', 'soccer ball', 'sock',
|
| 236 |
+
'solar thermal collector', 'sombrero', 'soup bowl', 'keyboard space bar',
|
| 237 |
+
'space heater', 'space shuttle', 'spatula', 'motorboat', 'spider web',
|
| 238 |
+
'spindle', 'sports car', 'spotlight', 'stage', 'steam locomotive',
|
| 239 |
+
'through arch bridge', 'steel drum', 'stethoscope', 'scarf', 'stone wall',
|
| 240 |
+
'stopwatch', 'stove', 'strainer', 'tram', 'stretcher', 'couch', 'stupa',
|
| 241 |
+
'submarine', 'suit', 'sundial', 'sunglasses', 'sunglasses', 'sunscreen',
|
| 242 |
+
'suspension bridge', 'mop', 'sweatshirt', 'swim trunks / shorts', 'swing',
|
| 243 |
+
'electrical switch', 'syringe', 'table lamp', 'tank', 'tape player',
|
| 244 |
+
'teapot', 'teddy bear', 'television', 'tennis ball', 'thatched roof',
|
| 245 |
+
'front curtain', 'thimble', 'threshing machine', 'throne', 'tile roof',
|
| 246 |
+
'toaster', 'tobacco shop', 'toilet seat', 'torch', 'totem pole', 'tow truck',
|
| 247 |
+
'toy store', 'tractor', 'semi-trailer truck', 'tray', 'trench coat',
|
| 248 |
+
'tricycle', 'trimaran', 'tripod', 'triumphal arch', 'trolleybus',
|
| 249 |
+
'trombone', 'hot tub', 'turnstile', 'typewriter keyboard', 'umbrella',
|
| 250 |
+
'unicycle', 'upright piano', 'vacuum cleaner', 'vase',
|
| 251 |
+
'vaulted or arched ceiling', 'velvet fabric', 'vending machine', 'vestment',
|
| 252 |
+
'viaduct', 'violin', 'volleyball', 'waffle iron', 'wall clock', 'wallet',
|
| 253 |
+
'wardrobe', 'military aircraft', 'sink', 'washing machine', 'water bottle',
|
| 254 |
+
'water jug', 'water tower', 'whiskey jug', 'whistle', 'hair wig',
|
| 255 |
+
'window screen', 'window shade', 'Windsor tie', 'wine bottle',
|
| 256 |
+
'airplane wing', 'wok', 'wooden spoon', 'wool', 'split-rail fence',
|
| 257 |
+
'shipwreck', 'sailboat', 'yurt', 'website', 'comic book', 'crossword',
|
| 258 |
+
'traffic or street sign', 'traffic light', 'dust jacket', 'menu', 'plate',
|
| 259 |
+
'guacamole', 'consomme', 'hot pot', 'trifle', 'ice cream', 'popsicle',
|
| 260 |
+
'baguette', 'bagel', 'pretzel', 'cheeseburger', 'hot dog',
|
| 261 |
+
'mashed potatoes', 'cabbage', 'broccoli', 'cauliflower', 'zucchini',
|
| 262 |
+
'spaghetti squash', 'acorn squash', 'butternut squash', 'cucumber',
|
| 263 |
+
'artichoke', 'bell pepper', 'cardoon', 'mushroom', 'Granny Smith apple',
|
| 264 |
+
'strawberry', 'orange', 'lemon', 'fig', 'pineapple', 'banana', 'jackfruit',
|
| 265 |
+
'cherimoya (custard apple)', 'pomegranate', 'hay', 'carbonara',
|
| 266 |
+
'chocolate syrup', 'dough', 'meatloaf', 'pizza', 'pot pie', 'burrito',
|
| 267 |
+
'red wine', 'espresso', 'tea cup', 'eggnog', 'mountain', 'bubble',
|
| 268 |
+
'cliff', 'coral reef', 'geyser', 'lakeshore', 'promontory', 'sandbar',
|
| 269 |
+
'beach', 'valley', 'volcano', 'baseball player', 'bridegroom',
|
| 270 |
+
'scuba diver', 'rapeseed', 'daisy', 'yellow lady\'s slipper', 'corn',
|
| 271 |
+
'acorn', 'rose hip', 'horse chestnut seed', 'coral fungus', 'agaric',
|
| 272 |
+
'gyromitra', 'stinkhorn mushroom', 'earth star fungus',
|
| 273 |
+
'hen of the woods mushroom', 'bolete', 'corn cob', 'toilet paper',
|
| 274 |
+
]
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# CLIP_PAPER templates (from big_vision prompt_engineering_constants)
|
| 278 |
+
CLIP_PAPER_PROMPT_TEMPLATES = [
|
| 279 |
+
'a bad photo of a {}.', 'a photo of many {}.', 'a sculpture of a {}.',
|
| 280 |
+
'a photo of the hard to see {}.', 'a low resolution photo of the {}.',
|
| 281 |
+
'a rendering of a {}.', 'graffiti of a {}.', 'a bad photo of the {}.',
|
| 282 |
+
'a cropped photo of the {}.', 'a tattoo of a {}.', 'the embroidered {}.',
|
| 283 |
+
'a photo of a hard to see {}.', 'a bright photo of a {}.',
|
| 284 |
+
'a photo of a clean {}.', 'a photo of a dirty {}.', 'a dark photo of the {}.',
|
| 285 |
+
'a drawing of a {}.', 'a photo of my {}.', 'the plastic {}.',
|
| 286 |
+
'a photo of the cool {}.', 'a close-up photo of a {}.',
|
| 287 |
+
'a black and white photo of the {}.', 'a painting of the {}.',
|
| 288 |
+
'a painting of a {}.', 'a pixelated photo of the {}.', 'a sculpture of the {}.',
|
| 289 |
+
'a bright photo of the {}.', 'a cropped photo of a {}.', 'a plastic {}.',
|
| 290 |
+
'a photo of the dirty {}.', 'a jpeg corrupted photo of a {}.',
|
| 291 |
+
'a blurry photo of the {}.', 'a photo of the {}.', 'a good photo of the {}.',
|
| 292 |
+
'a rendering of the {}.', 'a {} in a video game.', 'a photo of one {}.',
|
| 293 |
+
'a doodle of a {}.', 'a close-up photo of the {}.', 'a photo of a {}.',
|
| 294 |
+
'the origami {}.', 'the {} in a video game.', 'a sketch of a {}.',
|
| 295 |
+
'a doodle of the {}.', 'a origami {}.', 'a low resolution photo of a {}.',
|
| 296 |
+
'the toy {}.', 'a rendition of the {}.', 'a photo of the clean {}.',
|
| 297 |
+
'a photo of a large {}.', 'a rendition of a {}.', 'a photo of a nice {}.',
|
| 298 |
+
'a photo of a weird {}.', 'a blurry photo of a {}.', 'a cartoon {}.',
|
| 299 |
+
'art of a {}.', 'a sketch of the {}.', 'a embroidered {}.',
|
| 300 |
+
'a pixelated photo of a {}.', 'itap of the {}.',
|
| 301 |
+
'a jpeg corrupted photo of the {}.', 'a good photo of a {}.',
|
| 302 |
+
'a plushie {}.', 'a photo of the nice {}.', 'a photo of the small {}.',
|
| 303 |
+
'a photo of the weird {}.', 'the cartoon {}.', 'art of the {}.',
|
| 304 |
+
'a drawing of the {}.', 'a photo of the large {}.',
|
| 305 |
+
'a black and white photo of a {}.', 'the plushie {}.',
|
| 306 |
+
'a dark photo of a {}.', 'itap of a {}.', 'graffiti of the {}.',
|
| 307 |
+
'a toy {}.', 'itap of my {}.', 'a photo of a cool {}.',
|
| 308 |
+
'a photo of a small {}.', 'a tattoo of the {}.', '{}',
|
| 309 |
+
]
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def get_canonicalized_class_names(class_names: List[str], first_only: bool = True) -> List[str]:
|
| 313 |
+
"""Canonicalize class names, optionally keeping first alias only."""
|
| 314 |
+
out = []
|
| 315 |
+
for name in class_names:
|
| 316 |
+
n = canonicalize_text(name, keep_punctuation_exact_string=",")
|
| 317 |
+
if first_only:
|
| 318 |
+
n = n.split(",")[0].strip() if "," in n else n
|
| 319 |
+
out.append(n)
|
| 320 |
+
return out
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def get_canonicalized_templates(templates: List[str]) -> List[str]:
|
| 324 |
+
"""Canonicalize prompt templates, preserving '{}' placeholder."""
|
| 325 |
+
return [
|
| 326 |
+
canonicalize_text(t, keep_punctuation_exact_string="{}")
|
| 327 |
+
for t in templates
|
| 328 |
+
]
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def substitute_prompt(template: str, class_name: str) -> str:
|
| 332 |
+
"""Substitute '{}' in template with class_name. From discriminative_classifier."""
|
| 333 |
+
placeholder = "{}"
|
| 334 |
+
parts = template.split(placeholder)
|
| 335 |
+
assert len(parts) == 2, f"Template must have exactly one '{placeholder}': {template}"
|
| 336 |
+
return parts[0] + class_name + parts[1]
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
# ============================================================================
|
| 340 |
+
# Image preprocessing: resize(224) | value_range(-1,1) [big_vision default]
|
| 341 |
+
# ============================================================================
|
| 342 |
+
|
| 343 |
+
def get_bigvision_imagenet_transform(image_size: int = 224):
|
| 344 |
+
"""Transform matching big_vision pp_img='resize(224)|value_range(-1,1)'."""
|
| 345 |
+
return transforms.Compose([
|
| 346 |
+
transforms.Resize((image_size, image_size), interpolation=transforms.InterpolationMode.BILINEAR),
|
| 347 |
+
transforms.ToTensor(),
|
| 348 |
+
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), # [0,1] -> [-1,1]
|
| 349 |
+
])
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
# ============================================================================
|
| 353 |
+
# Utility
|
| 354 |
+
# ============================================================================
|
| 355 |
+
|
| 356 |
+
def batched(iterable, n: int):
|
| 357 |
+
it = iter(iterable)
|
| 358 |
+
while True:
|
| 359 |
+
batch = list(islice(it, n))
|
| 360 |
+
if not batch:
|
| 361 |
+
break
|
| 362 |
+
yield batch
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def accuracy(output: torch.Tensor, target: torch.Tensor, topk: Tuple[int, ...] = (1,)) -> List[float]:
|
| 366 |
+
pred = output.topk(max(topk), 1, True, True)[1].t()
|
| 367 |
+
correct = pred.eq(target.view(1, -1).expand_as(pred))
|
| 368 |
+
return [float(correct[:k].reshape(-1).float().sum(0, keepdim=True).cpu().numpy()) for k in topk]
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
def get_input_dtype(precision: str) -> torch.dtype:
|
| 372 |
+
if precision in ('bf16', 'bfloat16'):
|
| 373 |
+
return torch.bfloat16
|
| 374 |
+
if precision in ('fp16', 'float16'):
|
| 375 |
+
return torch.float16
|
| 376 |
+
return torch.float32
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def get_autocast_context(precision: str, device_type: str = 'cuda'):
|
| 380 |
+
from contextlib import nullcontext
|
| 381 |
+
if precision in ('bf16', 'bfloat16'):
|
| 382 |
+
return partial(torch.amp.autocast, device_type=device_type, dtype=torch.bfloat16)
|
| 383 |
+
if precision in ('fp16', 'float16'):
|
| 384 |
+
return partial(torch.amp.autocast, device_type=device_type, dtype=torch.float16)
|
| 385 |
+
return nullcontext
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
# ============================================================================
|
| 389 |
+
# Zero-shot classifier (big_vision aligned: average per class, normalize)
|
| 390 |
+
# ============================================================================
|
| 391 |
+
|
| 392 |
+
@torch.no_grad()
|
| 393 |
+
def build_zero_shot_classifier(
|
| 394 |
+
model,
|
| 395 |
+
processor,
|
| 396 |
+
classnames: Sequence[str],
|
| 397 |
+
templates: Sequence[str],
|
| 398 |
+
num_classes_per_batch: int = 10,
|
| 399 |
+
device: torch.device = None,
|
| 400 |
+
use_tqdm: bool = True,
|
| 401 |
+
canonicalize: bool = True,
|
| 402 |
+
) -> torch.Tensor:
|
| 403 |
+
"""Build classifier weights. Aligned with discriminative_classifier._average_embeddings."""
|
| 404 |
+
if canonicalize:
|
| 405 |
+
classnames = get_canonicalized_class_names(list(classnames))
|
| 406 |
+
templates = get_canonicalized_templates(list(templates))
|
| 407 |
+
|
| 408 |
+
num_templates = len(templates)
|
| 409 |
+
num_classes = len(classnames)
|
| 410 |
+
|
| 411 |
+
if use_tqdm:
|
| 412 |
+
num_iter = (num_classes - 1) // num_classes_per_batch + 1
|
| 413 |
+
iter_wrap = partial(tqdm, total=num_iter, desc="Building classifier", unit_scale=num_classes_per_batch)
|
| 414 |
+
else:
|
| 415 |
+
iter_wrap = iter
|
| 416 |
+
|
| 417 |
+
def _process_batch(batch_classnames: List[str]) -> torch.Tensor:
|
| 418 |
+
num_batch_classes = len(batch_classnames)
|
| 419 |
+
texts = [
|
| 420 |
+
substitute_prompt(tpl, c)
|
| 421 |
+
for c in batch_classnames
|
| 422 |
+
for tpl in templates
|
| 423 |
+
]
|
| 424 |
+
inputs = processor(text=texts, return_tensors="pt", padding="max_length", max_length=64, truncation=True)
|
| 425 |
+
input_ids = inputs["input_ids"].to(device)
|
| 426 |
+
attention_mask = inputs.get("attention_mask")
|
| 427 |
+
if attention_mask is not None:
|
| 428 |
+
attention_mask = attention_mask.to(device)
|
| 429 |
+
|
| 430 |
+
text_features = model.get_text_features(input_ids=input_ids, attention_mask=attention_mask)
|
| 431 |
+
text_features = F.normalize(text_features, dim=-1)
|
| 432 |
+
text_features = text_features.reshape(num_batch_classes, num_templates, -1).mean(dim=1)
|
| 433 |
+
text_features = F.normalize(text_features, dim=1)
|
| 434 |
+
return text_features.T # (embed_dim, num_batch_classes)
|
| 435 |
+
|
| 436 |
+
batched_embeds = [
|
| 437 |
+
_process_batch(batch)
|
| 438 |
+
for batch in iter_wrap(batched(classnames, num_classes_per_batch))
|
| 439 |
+
]
|
| 440 |
+
return torch.cat(batched_embeds, dim=1)
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
# ============================================================================
|
| 444 |
+
# Evaluation (big_vision aligned: raw cosine, no logit_scale/logit_bias)
|
| 445 |
+
# ============================================================================
|
| 446 |
+
|
| 447 |
+
@torch.no_grad()
|
| 448 |
+
def evaluate(
|
| 449 |
+
model,
|
| 450 |
+
classifier: torch.Tensor,
|
| 451 |
+
dataloader: DataLoader,
|
| 452 |
+
device: torch.device,
|
| 453 |
+
precision: str = 'fp32',
|
| 454 |
+
) -> Tuple[float, float]:
|
| 455 |
+
"""Evaluate. Uses raw cosine similarity like discriminative_classifier."""
|
| 456 |
+
autocast_ctx = get_autocast_context(precision, device_type=device.type)
|
| 457 |
+
input_dtype = get_input_dtype(precision)
|
| 458 |
+
|
| 459 |
+
top1, top5, n = 0.0, 0.0, 0
|
| 460 |
+
with torch.inference_mode():
|
| 461 |
+
for images, targets in tqdm(dataloader, desc="Evaluating"):
|
| 462 |
+
images = images.to(device=device, dtype=input_dtype)
|
| 463 |
+
targets = targets.to(device)
|
| 464 |
+
|
| 465 |
+
with autocast_ctx():
|
| 466 |
+
image_features = model.get_image_features(pixel_values=images)
|
| 467 |
+
image_features = F.normalize(image_features, dim=-1)
|
| 468 |
+
# Raw cosine similarity (no logit_scale, no logit_bias)
|
| 469 |
+
logits = image_features @ classifier
|
| 470 |
+
|
| 471 |
+
acc1, acc5 = accuracy(logits, targets, topk=(1, 5))
|
| 472 |
+
top1 += acc1
|
| 473 |
+
top5 += acc5
|
| 474 |
+
n += images.size(0)
|
| 475 |
+
|
| 476 |
+
return top1 / n * 100, top5 / n * 100
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
# ============================================================================
|
| 480 |
+
# Main
|
| 481 |
+
# ============================================================================
|
| 482 |
+
|
| 483 |
+
def main():
|
| 484 |
+
parser = argparse.ArgumentParser(
|
| 485 |
+
description="Zero-shot ImageNet evaluation (big_vision discriminative_classifier aligned)"
|
| 486 |
+
)
|
| 487 |
+
parser.add_argument("--model_path", type=str, required=True)
|
| 488 |
+
parser.add_argument("--data_path", type=str, required=True)
|
| 489 |
+
parser.add_argument("--batch_size", type=int, default=128)
|
| 490 |
+
parser.add_argument("--num_workers", type=int, default=8)
|
| 491 |
+
parser.add_argument("--device", type=str, default="cuda:0")
|
| 492 |
+
parser.add_argument("--precision", type=str, default="fp32", choices=["fp32", "fp16", "bf16"])
|
| 493 |
+
parser.add_argument("--image_size", type=int, default=224,
|
| 494 |
+
help="Image size for resize (big_vision default 224)")
|
| 495 |
+
parser.add_argument("--no_canonicalize", action="store_true",
|
| 496 |
+
help="Disable canonicalize of class names and templates")
|
| 497 |
+
args = parser.parse_args()
|
| 498 |
+
|
| 499 |
+
device = torch.device(args.device)
|
| 500 |
+
|
| 501 |
+
print("=" * 60)
|
| 502 |
+
print("Zero-Shot ImageNet (big_vision aligned)")
|
| 503 |
+
print("=" * 60)
|
| 504 |
+
print(f"Model: {args.model_path}")
|
| 505 |
+
print(f"Data: {args.data_path}")
|
| 506 |
+
print(f"Image: resize({args.image_size})|value_range(-1,1)")
|
| 507 |
+
print(f"Logit: raw cosine (no scale/bias)")
|
| 508 |
+
print(f"Canonicalize: {not args.no_canonicalize}")
|
| 509 |
+
print()
|
| 510 |
+
|
| 511 |
+
model = SiglipModel.from_pretrained(args.model_path)
|
| 512 |
+
processor = AutoProcessor.from_pretrained(args.model_path)
|
| 513 |
+
model = model.to(device)
|
| 514 |
+
model.eval()
|
| 515 |
+
|
| 516 |
+
transform = get_bigvision_imagenet_transform(args.image_size)
|
| 517 |
+
dataset = ImageFolder(root=args.data_path, transform=transform)
|
| 518 |
+
dataloader = DataLoader(
|
| 519 |
+
dataset,
|
| 520 |
+
batch_size=args.batch_size,
|
| 521 |
+
shuffle=False,
|
| 522 |
+
num_workers=args.num_workers,
|
| 523 |
+
pin_memory=True,
|
| 524 |
+
)
|
| 525 |
+
|
| 526 |
+
print("Building zero-shot classifier...")
|
| 527 |
+
autocast_ctx = get_autocast_context(args.precision, device_type=device.type)
|
| 528 |
+
with autocast_ctx():
|
| 529 |
+
classifier = build_zero_shot_classifier(
|
| 530 |
+
model,
|
| 531 |
+
processor,
|
| 532 |
+
classnames=CLIP_IMAGENET_CLASS_NAMES,
|
| 533 |
+
templates=CLIP_PAPER_PROMPT_TEMPLATES,
|
| 534 |
+
num_classes_per_batch=10,
|
| 535 |
+
device=device,
|
| 536 |
+
use_tqdm=True,
|
| 537 |
+
canonicalize=not args.no_canonicalize,
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
print("Evaluating...")
|
| 541 |
+
top1, top5 = evaluate(model, classifier, dataloader, device=device, precision=args.precision)
|
| 542 |
+
|
| 543 |
+
print()
|
| 544 |
+
print("=" * 60)
|
| 545 |
+
print("Results:")
|
| 546 |
+
print(f" Top-1: {top1:.2f}%")
|
| 547 |
+
print(f" Top-5: {top5:.2f}%")
|
| 548 |
+
print("=" * 60)
|
| 549 |
+
|
| 550 |
+
out_dir = Path(args.model_path)
|
| 551 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 552 |
+
out_json = out_dir / "zeroshot_imagenet_bigvision_results.json"
|
| 553 |
+
with open(out_json, "w", encoding="utf-8") as f:
|
| 554 |
+
json.dump(
|
| 555 |
+
{"top1": top1, "top5": top5, "aligned": "big_vision", "data_path": args.data_path},
|
| 556 |
+
f, indent=2, ensure_ascii=False,
|
| 557 |
+
)
|
| 558 |
+
print(f"Results written to {out_json}")
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
if __name__ == "__main__":
|
| 562 |
+
main()
|
patch-reparameterization/src/eval/fid.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from torch_fidelity import calculate_metrics
|
| 6 |
+
from .utils import ImgArrDataset
|
| 7 |
+
|
| 8 |
+
from torch_fidelity.feature_extractor_inceptionv3 import FeatureExtractorInceptionV3 # original torch-fidelity :contentReference[oaicite:2]{index=2}
|
| 9 |
+
import scipy.linalg
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
def _fid_from_moments(mu1, sigma1, mu2, sigma2) -> float:
|
| 13 |
+
# FID formula :contentReference[oaicite:3]{index=3}
|
| 14 |
+
|
| 15 |
+
mu1 = np.asarray(mu1, dtype=np.float64)
|
| 16 |
+
mu2 = np.asarray(mu2, dtype=np.float64)
|
| 17 |
+
sigma1 = np.asarray(sigma1, dtype=np.float64)
|
| 18 |
+
sigma2 = np.asarray(sigma2, dtype=np.float64)
|
| 19 |
+
|
| 20 |
+
diff = mu1 - mu2
|
| 21 |
+
covmean = scipy.linalg.sqrtm(sigma1 @ sigma2)
|
| 22 |
+
|
| 23 |
+
if np.iscomplexobj(covmean): # numerical noise
|
| 24 |
+
covmean = covmean.real
|
| 25 |
+
|
| 26 |
+
fid = diff.dot(diff) + np.trace(sigma1 + sigma2 - 2.0 * covmean)
|
| 27 |
+
return float(max(fid, 0.0))
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@torch.no_grad()
|
| 31 |
+
def _compute_inception_moments_from_arr(arr: np.ndarray, batch_size: int, device: str) -> tuple[np.ndarray, np.ndarray]:
|
| 32 |
+
"""
|
| 33 |
+
Uses torch-fidelity's InceptionV3 feature extractor to get 2048-d pool features.
|
| 34 |
+
Assumes arr is [N,H,W,C] or [N,C,H,W], uint8 (0..255) or float (0..1 or 0..255).
|
| 35 |
+
"""
|
| 36 |
+
# Convert to torch in NCHW uint8 as the safest default.
|
| 37 |
+
x = arr
|
| 38 |
+
if x.ndim != 4:
|
| 39 |
+
raise ValueError(f"Expected 4D array, got shape {x.shape}")
|
| 40 |
+
|
| 41 |
+
if x.shape[-1] == 3: # NHWC -> NCHW
|
| 42 |
+
x = np.transpose(x, (0, 3, 1, 2))
|
| 43 |
+
|
| 44 |
+
if x.dtype != np.uint8:
|
| 45 |
+
# If float in [0,1], scale up; otherwise assume already 0..255-ish
|
| 46 |
+
x_f = x.astype(np.float32)
|
| 47 |
+
if x_f.max() <= 1.5:
|
| 48 |
+
x_f = x_f * 255.0
|
| 49 |
+
x = np.clip(x_f, 0, 255).astype(np.uint8)
|
| 50 |
+
|
| 51 |
+
xt = torch.from_numpy(x).to(device=device, dtype=torch.uint8)
|
| 52 |
+
|
| 53 |
+
fe = FeatureExtractorInceptionV3(name="inception-v3-compat", features_list=['2048']).to(device).eval() # preregistered extractor name :contentReference[oaicite:4]{index=4}
|
| 54 |
+
|
| 55 |
+
feats = []
|
| 56 |
+
for i in range(0, xt.shape[0], batch_size):
|
| 57 |
+
batch = xt[i : i + batch_size]
|
| 58 |
+
f = fe(batch)[0] # (B, 2048)
|
| 59 |
+
feats.append(f.detach().cpu())
|
| 60 |
+
|
| 61 |
+
feats = torch.cat(feats, dim=0).double().numpy() # (N, 2048) float64
|
| 62 |
+
mu = feats.mean(axis=0)
|
| 63 |
+
sigma = np.cov(feats, rowvar=False)
|
| 64 |
+
return mu, sigma
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@torch.no_grad()
|
| 68 |
+
def compute_inception_features(arr: np.ndarray, batch_size: int, device: str) -> np.ndarray:
|
| 69 |
+
"""Returns raw 2048-d InceptionV3 features for distributed FID computation."""
|
| 70 |
+
x = arr
|
| 71 |
+
if x.ndim != 4:
|
| 72 |
+
raise ValueError(f"Expected 4D array, got shape {x.shape}")
|
| 73 |
+
if x.shape[-1] == 3: # NHWC -> NCHW
|
| 74 |
+
x = np.transpose(x, (0, 3, 1, 2))
|
| 75 |
+
if x.dtype != np.uint8:
|
| 76 |
+
x_f = x.astype(np.float32)
|
| 77 |
+
if x_f.max() <= 1.5:
|
| 78 |
+
x_f = x_f * 255.0
|
| 79 |
+
x = np.clip(x_f, 0, 255).astype(np.uint8)
|
| 80 |
+
xt = torch.from_numpy(x).to(device=device, dtype=torch.uint8)
|
| 81 |
+
fe = FeatureExtractorInceptionV3(name="inception-v3-compat", features_list=['2048']).to(device).eval()
|
| 82 |
+
feats = []
|
| 83 |
+
for i in range(0, xt.shape[0], batch_size):
|
| 84 |
+
batch = xt[i: i + batch_size]
|
| 85 |
+
f = fe(batch)[0]
|
| 86 |
+
feats.append(f.detach().cpu())
|
| 87 |
+
return torch.cat(feats, dim=0).double().numpy() # (N, 2048)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def calculate_rfid_from_features(features1: np.ndarray, features2: np.ndarray) -> float:
|
| 91 |
+
"""Compute FID from pre-computed inception features (for distributed use)."""
|
| 92 |
+
mu1 = features1.mean(axis=0)
|
| 93 |
+
sigma1 = np.cov(features1, rowvar=False)
|
| 94 |
+
mu2 = features2.mean(axis=0)
|
| 95 |
+
sigma2 = np.cov(features2, rowvar=False)
|
| 96 |
+
return _fid_from_moments(mu1, sigma1, mu2, sigma2)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def calculate_gfid(
|
| 100 |
+
arr1: np.ndarray,
|
| 101 |
+
ref_arr: dict,
|
| 102 |
+
batch_size: int = 64,
|
| 103 |
+
device: str = "cuda",
|
| 104 |
+
) -> float:
|
| 105 |
+
mu_ref, sigma_ref = ref_arr['mu'], ref_arr['sigma']
|
| 106 |
+
mu_gen, sigma_gen = _compute_inception_moments_from_arr(arr1, batch_size=batch_size, device=device)
|
| 107 |
+
return _fid_from_moments(mu_gen, sigma_gen, mu_ref, sigma_ref)
|
| 108 |
+
|
| 109 |
+
def calculate_rfid(
|
| 110 |
+
arr1,
|
| 111 |
+
arr2=None,
|
| 112 |
+
bs=64,
|
| 113 |
+
device="cuda",
|
| 114 |
+
fid_statistics_file=None,
|
| 115 |
+
):
|
| 116 |
+
arr1_ds = ImgArrDataset(arr1)
|
| 117 |
+
|
| 118 |
+
if fid_statistics_file is not None:
|
| 119 |
+
metrics_kwargs = dict(
|
| 120 |
+
input1=arr1_ds,
|
| 121 |
+
input2=None,
|
| 122 |
+
fid_statistics_file=fid_statistics_file,
|
| 123 |
+
batch_size=bs,
|
| 124 |
+
fid=True,
|
| 125 |
+
cuda=(device == "cuda"),
|
| 126 |
+
)
|
| 127 |
+
else:
|
| 128 |
+
if arr2 is None:
|
| 129 |
+
raise ValueError("Either arr2 or fid_statistics_file must be provided.")
|
| 130 |
+
arr2_ds = ImgArrDataset(arr2)
|
| 131 |
+
metrics_kwargs = dict(
|
| 132 |
+
input1=arr1_ds,
|
| 133 |
+
input2=arr2_ds,
|
| 134 |
+
batch_size=bs,
|
| 135 |
+
fid=True,
|
| 136 |
+
cuda=(device == "cuda"),
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
metrics = calculate_metrics(**metrics_kwargs)
|
| 140 |
+
return metrics["frechet_inception_distance"]
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
if __name__ == "__main__":
|
| 144 |
+
import argparse
|
| 145 |
+
from pathlib import Path
|
| 146 |
+
import numpy as np
|
| 147 |
+
import torch
|
| 148 |
+
|
| 149 |
+
parser = argparse.ArgumentParser(description="Compute FID using original torch-fidelity")
|
| 150 |
+
parser.add_argument("--arr1", type=str, required=True,
|
| 151 |
+
help="Path to generated images array (.npy or .npz). Array should be N x H x W x 3 or N x 3 x H x W.")
|
| 152 |
+
parser.add_argument("--arr2", type=str, default=None,
|
| 153 |
+
help="Optional path to reference images array (.npy or .npz). If set, uses torch_fidelity.calculate_metrics.")
|
| 154 |
+
|
| 155 |
+
# reference stats path (npz with mu/sigma keys)
|
| 156 |
+
parser.add_argument("--ref", "--fid-statistics-file", dest="ref", type=str, default=None,
|
| 157 |
+
help="Path to reference stats (.npz with mu/sigma, mu_s/sigma_s, mu_clip/sigma_clip, etc.). "
|
| 158 |
+
"If set, computes moments for arr1 using torch-fidelity InceptionV3 and FID vs these stats.")
|
| 159 |
+
|
| 160 |
+
# which keys in the ref npz to use
|
| 161 |
+
parser.add_argument("--ref-mu-key", type=str, default="mu",
|
| 162 |
+
help="Key in --ref .npz for reference mean (default: mu).")
|
| 163 |
+
parser.add_argument("--ref-sigma-key", type=str, default="sigma",
|
| 164 |
+
help="Key in --ref .npz for reference covariance (default: sigma).")
|
| 165 |
+
|
| 166 |
+
parser.add_argument("--batch-size", type=int, default=64)
|
| 167 |
+
parser.add_argument("--device", type=str, default="cuda", choices=["cuda", "cpu"])
|
| 168 |
+
parser.add_argument("--no-scipy", action="store_true",
|
| 169 |
+
help="Do not use scipy.linalg.sqrtm; use torch eig fallback (slower/less identical).")
|
| 170 |
+
|
| 171 |
+
args = parser.parse_args()
|
| 172 |
+
|
| 173 |
+
# exactly one of arr2 or ref must be provided
|
| 174 |
+
if (args.arr2 is None) == (args.ref is None):
|
| 175 |
+
parser.error("Specify exactly one of --arr2 or --ref/--fid-statistics-file")
|
| 176 |
+
|
| 177 |
+
def load_array(path: str) -> np.ndarray:
|
| 178 |
+
p = Path(path)
|
| 179 |
+
if not p.exists():
|
| 180 |
+
raise FileNotFoundError(f"File not found: {p}")
|
| 181 |
+
if p.suffix == ".npy":
|
| 182 |
+
return np.load(p)
|
| 183 |
+
elif p.suffix == ".npz":
|
| 184 |
+
z = np.load(p)
|
| 185 |
+
# common conventions: 'arr_0' or 'images'
|
| 186 |
+
if "arr_0" in z.files:
|
| 187 |
+
return z["arr_0"]
|
| 188 |
+
if "images" in z.files:
|
| 189 |
+
return z["images"]
|
| 190 |
+
raise KeyError(f"{p} is .npz but has no 'arr_0' or 'images'. Keys={z.files}")
|
| 191 |
+
else:
|
| 192 |
+
raise ValueError(f"Unsupported array file type: {p.suffix} (expected .npy or .npz)")
|
| 193 |
+
|
| 194 |
+
arr1 = load_array(args.arr1)
|
| 195 |
+
print("[INFO] arr1:", arr1.shape, arr1.dtype)
|
| 196 |
+
|
| 197 |
+
if args.arr2 is not None:
|
| 198 |
+
arr2 = load_array(args.arr2)
|
| 199 |
+
print("[INFO] arr2:", arr2.shape, arr2.dtype)
|
| 200 |
+
|
| 201 |
+
fid = calculate_rfid(
|
| 202 |
+
arr1=arr1,
|
| 203 |
+
arr2=arr2,
|
| 204 |
+
bs=args.batch_size,
|
| 205 |
+
device=args.device,
|
| 206 |
+
fid_statistics_file=None, # unused in upstream path
|
| 207 |
+
)
|
| 208 |
+
print(f"[RESULT] FID: {fid:.6f}")
|
| 209 |
+
raise SystemExit(0)
|
| 210 |
+
|
| 211 |
+
# stats mode: arr1 vs ref npz moments
|
| 212 |
+
ref_path = Path(args.ref)
|
| 213 |
+
if not ref_path.exists():
|
| 214 |
+
raise FileNotFoundError(f"Ref stats not found: {ref_path}")
|
| 215 |
+
|
| 216 |
+
if ref_path.suffix != ".npz":
|
| 217 |
+
raise ValueError(
|
| 218 |
+
f"--ref must be a .npz containing mu/sigma. Got: {ref_path.suffix}. "
|
| 219 |
+
"Original torch-fidelity cannot use .pt stats without input2."
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
stats = np.load(ref_path)
|
| 223 |
+
if args.ref_mu_key not in stats.files or args.ref_sigma_key not in stats.files:
|
| 224 |
+
raise KeyError(
|
| 225 |
+
f"Missing '{args.ref_mu_key}'/'{args.ref_sigma_key}' in {ref_path}. "
|
| 226 |
+
f"Available keys: {list(stats.files)}"
|
| 227 |
+
)
|
| 228 |
+
print(f"[INFO] ref stats: {ref_path}")
|
| 229 |
+
# If you want to respect --no-scipy, thread it through:
|
| 230 |
+
# (edit your fid_arr_vs_npzstats / _fid_from_moments accordingly)
|
| 231 |
+
fid = calculate_gfid(
|
| 232 |
+
arr1=arr1,
|
| 233 |
+
ref_arr=stats,
|
| 234 |
+
batch_size=args.batch_size,
|
| 235 |
+
device=args.device,
|
| 236 |
+
# optionally add: use_scipy=(not args.no_scipy)
|
| 237 |
+
)
|
| 238 |
+
print(f"[RESULT] FID: {fid:.6f}")
|
patch-reparameterization/src/eval/ref_iqa.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import namedtuple
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
import math
|
| 5 |
+
from tqdm import trange
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from torchvision import models
|
| 9 |
+
from torchmetrics.functional.image.ssim import structural_similarity_index_measure
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Optional
|
| 14 |
+
from .utils import LPIPS, to_torch_tensor
|
| 15 |
+
|
| 16 |
+
@torch.no_grad()
|
| 17 |
+
def calculate_psnr(arr1, arr2, bs, device="cuda", disable_bar: bool = True, psnr_resize: Optional[int] = None) -> float:
|
| 18 |
+
"""
|
| 19 |
+
Computes PSNR between two single images or batches of images.
|
| 20 |
+
PSNR is averaged over the batch if given as (B, C, H, W).
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
psnr_resize: If set, both images are resized to (psnr_resize x psnr_resize)
|
| 24 |
+
before computing PSNR. Useful for evaluating at lower resolutions.
|
| 25 |
+
"""
|
| 26 |
+
B = arr1.shape[0]
|
| 27 |
+
n_batches = math.ceil(B / bs)
|
| 28 |
+
psnr = torch.zeros(B, device=device)
|
| 29 |
+
|
| 30 |
+
for i in trange(n_batches, desc="Calculating PSNR", file=sys.stdout, disable=disable_bar):
|
| 31 |
+
start_idx = i * bs
|
| 32 |
+
end_idx = min((i + 1) * bs, B)
|
| 33 |
+
# PSNR expects input in [0, 1], (B, C, H, W)
|
| 34 |
+
batch_arr1 = to_torch_tensor(arr1[start_idx:end_idx]).to(device)
|
| 35 |
+
batch_arr2 = to_torch_tensor(arr2[start_idx:end_idx]).to(device)
|
| 36 |
+
if psnr_resize is not None:
|
| 37 |
+
batch_arr1 = F.interpolate(batch_arr1, size=(psnr_resize, psnr_resize), mode="bilinear", align_corners=False)
|
| 38 |
+
batch_arr2 = F.interpolate(batch_arr2, size=(psnr_resize, psnr_resize), mode="bilinear", align_corners=False)
|
| 39 |
+
batch_mse = torch.mean((batch_arr1 - batch_arr2) ** 2, dim=[1,2,3]) # shape [bs]
|
| 40 |
+
batch_mse = torch.clamp(batch_mse, min=1e-10)
|
| 41 |
+
batch_psnr = 20.0 * torch.log10(1 / torch.sqrt(batch_mse)) # shape [bs]
|
| 42 |
+
psnr[start_idx:end_idx] = batch_psnr
|
| 43 |
+
return psnr.mean().item()
|
| 44 |
+
@torch.no_grad()
|
| 45 |
+
def calculate_lpips(arr1, arr2, bs, device="cuda", disable_bar: bool = True) -> float:
|
| 46 |
+
"""
|
| 47 |
+
Computes LPIPS between two single images or batches of images.
|
| 48 |
+
LPIPS is averaged over the batch if given as (B, C, H, W).
|
| 49 |
+
"""
|
| 50 |
+
B = arr1.shape[0]
|
| 51 |
+
n_batches = math.ceil(B / bs)
|
| 52 |
+
loss_fn = LPIPS().eval().to(device)
|
| 53 |
+
lpips = torch.zeros(B, device=device)
|
| 54 |
+
|
| 55 |
+
for i in trange(n_batches, desc="Calculating LPIPS", file=sys.stdout, disable=disable_bar):
|
| 56 |
+
start_idx = i * bs
|
| 57 |
+
end_idx = min((i + 1) * bs, B)
|
| 58 |
+
# LPIPS expects input in [-1, 1], (B, C, H, W)
|
| 59 |
+
batch_arr1 = (to_torch_tensor(arr1[start_idx:end_idx]).to(device) - 0.5) * 2.
|
| 60 |
+
batch_arr2 = (to_torch_tensor(arr2[start_idx:end_idx]).to(device) - 0.5) * 2.
|
| 61 |
+
batch_lpips = loss_fn(batch_arr1, batch_arr2).squeeze() # shape [bs]
|
| 62 |
+
lpips[start_idx:end_idx] = batch_lpips
|
| 63 |
+
return lpips.mean().item()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
######################################################
|
| 67 |
+
# 4. SSIM Calculation
|
| 68 |
+
######################################################
|
| 69 |
+
@torch.no_grad()
|
| 70 |
+
def calculate_ssim(arr1, arr2, bs, device="cuda", disable_bar: bool = True) -> float:
|
| 71 |
+
"""
|
| 72 |
+
Computes SSIM between two single images or batches of images.
|
| 73 |
+
SSIM is averaged over the batch if given as (B, C, H, W).
|
| 74 |
+
"""
|
| 75 |
+
B = arr1.shape[0]
|
| 76 |
+
n_batches = math.ceil(B / bs)
|
| 77 |
+
ssim_val = torch.zeros(B, device=device)
|
| 78 |
+
|
| 79 |
+
for i in trange(n_batches, desc="Calculating SSIM", file=sys.stdout, disable=disable_bar):
|
| 80 |
+
start_idx = i * bs
|
| 81 |
+
end_idx = min((i + 1) * bs, B)
|
| 82 |
+
# SSIM expects input in [0, 1], (B, C, H, W)
|
| 83 |
+
batch_arr1 = to_torch_tensor(arr1[start_idx:end_idx]).to(device)
|
| 84 |
+
batch_arr2 = to_torch_tensor(arr2[start_idx:end_idx]).to(device)
|
| 85 |
+
# SSIM expects input in [0, 1], (B, C, H, W)
|
| 86 |
+
batch_ssim = structural_similarity_index_measure(
|
| 87 |
+
target=batch_arr1,
|
| 88 |
+
preds=batch_arr2,
|
| 89 |
+
data_range=1.0,
|
| 90 |
+
reduction="none"
|
| 91 |
+
)
|
| 92 |
+
ssim_val[start_idx:end_idx] = batch_ssim
|
| 93 |
+
return ssim_val.mean().item()
|
patch-reparameterization/src/eval/utils.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import namedtuple
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
import math
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
from torchvision import models
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
# insert ../ as a module path
|
| 13 |
+
current_dir = Path(__file__).resolve().parent
|
| 14 |
+
parent_dir = current_dir.parent
|
| 15 |
+
if str(parent_dir) not in sys.path:
|
| 16 |
+
sys.path.insert(0, str(parent_dir))
|
| 17 |
+
from disc.lpips_utils import get_ckpt_path
|
| 18 |
+
|
| 19 |
+
def normalize_tensor(x, eps=1e-10):
|
| 20 |
+
norm_factor = torch.sqrt(torch.sum(x**2, dim=1, keepdim=True))
|
| 21 |
+
return x / (norm_factor + eps)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def spatial_average(x, keepdim=True):
|
| 25 |
+
return x.mean([2, 3],keepdim=keepdim)
|
| 26 |
+
|
| 27 |
+
class ScalingLayer(nn.Module):
|
| 28 |
+
def __init__(self):
|
| 29 |
+
super(ScalingLayer, self).__init__()
|
| 30 |
+
self.register_buffer('shift', torch.Tensor([-.030, -.088, -.188])[None, :, None, None])
|
| 31 |
+
self.register_buffer('scale', torch.Tensor([.458, .448, .450])[None, :, None, None])
|
| 32 |
+
|
| 33 |
+
def forward(self, inp):
|
| 34 |
+
return (inp - self.shift) / self.scale
|
| 35 |
+
|
| 36 |
+
class NetLinLayer(nn.Module):
|
| 37 |
+
""" A single linear layer which does a 1x1 conv """
|
| 38 |
+
def __init__(self, chn_in, chn_out=1, use_dropout=False):
|
| 39 |
+
super(NetLinLayer, self).__init__()
|
| 40 |
+
layers = [nn.Dropout(), ] if (use_dropout) else []
|
| 41 |
+
layers += [nn.Conv2d(chn_in, chn_out, 1, stride=1, padding=0, bias=False), ]
|
| 42 |
+
self.model = nn.Sequential(*layers)
|
| 43 |
+
|
| 44 |
+
class VGG16(torch.nn.Module):
|
| 45 |
+
def __init__(self, requires_grad=False):
|
| 46 |
+
super(VGG16, self).__init__()
|
| 47 |
+
vgg_pretrained_features = models.vgg16(weights='DEFAULT').features
|
| 48 |
+
self.slice1 = torch.nn.Sequential()
|
| 49 |
+
self.slice2 = torch.nn.Sequential()
|
| 50 |
+
self.slice3 = torch.nn.Sequential()
|
| 51 |
+
self.slice4 = torch.nn.Sequential()
|
| 52 |
+
self.slice5 = torch.nn.Sequential()
|
| 53 |
+
self.N_slices = 5
|
| 54 |
+
for x in range(4):
|
| 55 |
+
self.slice1.add_module(str(x), vgg_pretrained_features[x])
|
| 56 |
+
for x in range(4, 9):
|
| 57 |
+
self.slice2.add_module(str(x), vgg_pretrained_features[x])
|
| 58 |
+
for x in range(9, 16):
|
| 59 |
+
self.slice3.add_module(str(x), vgg_pretrained_features[x])
|
| 60 |
+
for x in range(16, 23):
|
| 61 |
+
self.slice4.add_module(str(x), vgg_pretrained_features[x])
|
| 62 |
+
for x in range(23, 30):
|
| 63 |
+
self.slice5.add_module(str(x), vgg_pretrained_features[x])
|
| 64 |
+
if not requires_grad:
|
| 65 |
+
for param in self.parameters():
|
| 66 |
+
param.requires_grad = False
|
| 67 |
+
|
| 68 |
+
def forward(self, X):
|
| 69 |
+
h = self.slice1(X)
|
| 70 |
+
h_relu1_2 = h
|
| 71 |
+
h = self.slice2(h)
|
| 72 |
+
h_relu2_2 = h
|
| 73 |
+
h = self.slice3(h)
|
| 74 |
+
h_relu3_3 = h
|
| 75 |
+
h = self.slice4(h)
|
| 76 |
+
h_relu4_3 = h
|
| 77 |
+
h = self.slice5(h)
|
| 78 |
+
h_relu5_3 = h
|
| 79 |
+
vgg_outputs = namedtuple("VggOutputs", ['relu1_2', 'relu2_2', 'relu3_3', 'relu4_3', 'relu5_3'])
|
| 80 |
+
out = vgg_outputs(h_relu1_2, h_relu2_2, h_relu3_3, h_relu4_3, h_relu5_3)
|
| 81 |
+
return out
|
| 82 |
+
|
| 83 |
+
class LPIPS(nn.Module):
|
| 84 |
+
# Learned perceptual metric
|
| 85 |
+
def __init__(self, use_dropout=True):
|
| 86 |
+
super().__init__()
|
| 87 |
+
self.scaling_layer = ScalingLayer()
|
| 88 |
+
self.chns = [64, 128, 256, 512, 512] # vg16 features
|
| 89 |
+
self.net = VGG16(requires_grad=False)
|
| 90 |
+
self.lin0 = NetLinLayer(self.chns[0], use_dropout=use_dropout)
|
| 91 |
+
self.lin1 = NetLinLayer(self.chns[1], use_dropout=use_dropout)
|
| 92 |
+
self.lin2 = NetLinLayer(self.chns[2], use_dropout=use_dropout)
|
| 93 |
+
self.lin3 = NetLinLayer(self.chns[3], use_dropout=use_dropout)
|
| 94 |
+
self.lin4 = NetLinLayer(self.chns[4], use_dropout=use_dropout)
|
| 95 |
+
self.load_from_pretrained()
|
| 96 |
+
for param in self.parameters():
|
| 97 |
+
param.requires_grad = False
|
| 98 |
+
|
| 99 |
+
def load_from_pretrained(self, name="vgg_lpips"):
|
| 100 |
+
ckpt = get_ckpt_path(name)
|
| 101 |
+
self.load_state_dict(
|
| 102 |
+
torch.load(
|
| 103 |
+
ckpt,
|
| 104 |
+
map_location=torch.device("cpu")),
|
| 105 |
+
strict=False
|
| 106 |
+
)
|
| 107 |
+
print("loaded pretrained LPIPS loss from {}".format(ckpt))
|
| 108 |
+
|
| 109 |
+
def forward(self, input, target):
|
| 110 |
+
in0_input, in1_input = (self.scaling_layer(input), self.scaling_layer(target))
|
| 111 |
+
outs0, outs1 = self.net(in0_input), self.net(in1_input)
|
| 112 |
+
feats0, feats1, diffs = {}, {}, {}
|
| 113 |
+
lins = [self.lin0, self.lin1, self.lin2, self.lin3, self.lin4]
|
| 114 |
+
for kk in range(len(self.chns)):
|
| 115 |
+
feats0[kk], feats1[kk] = normalize_tensor(outs0[kk]), normalize_tensor(outs1[kk])
|
| 116 |
+
diffs[kk] = (feats0[kk] - feats1[kk]) ** 2
|
| 117 |
+
|
| 118 |
+
res = [spatial_average(lins[kk].model(diffs[kk]), keepdim=True) for kk in range(len(self.chns))]
|
| 119 |
+
val = res[0]
|
| 120 |
+
for l in range(1, len(self.chns)):
|
| 121 |
+
val += res[l]
|
| 122 |
+
return val
|
| 123 |
+
|
| 124 |
+
class ImgArrDataset(Dataset):
|
| 125 |
+
"""
|
| 126 |
+
Serve as the wrapper purpose for `torch-fidelity` to calculate the metrics (FID),
|
| 127 |
+
which expects [B, C, H, W] tensor in uint8. We are returning a [C, H, W] tensor.
|
| 128 |
+
"""
|
| 129 |
+
def __init__(self, arr):
|
| 130 |
+
self.arr = arr
|
| 131 |
+
|
| 132 |
+
def __len__(self):
|
| 133 |
+
return len(self.arr)
|
| 134 |
+
|
| 135 |
+
def __getitem__(self, idx):
|
| 136 |
+
return torch.from_numpy(self.arr[idx]).permute(2, 0, 1)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
######################################################
|
| 140 |
+
# 1. Convert NumPy arrays to Torch tensors, if needed
|
| 141 |
+
######################################################
|
| 142 |
+
def to_torch_tensor(np_array):
|
| 143 |
+
"""
|
| 144 |
+
Converts a NumPy array of shape (B, H, W, C) to a (B, C, H, W) float32 Torch tensor.
|
| 145 |
+
Expects image to be in [0, 1] or [0, 255].
|
| 146 |
+
Adjust if necessary.
|
| 147 |
+
"""
|
| 148 |
+
tensor = torch.from_numpy(np_array).permute(0, 3, 1, 2)
|
| 149 |
+
|
| 150 |
+
# If your data is in [0, 255], convert to [0, 1].
|
| 151 |
+
if tensor.max() > 1.0:
|
| 152 |
+
tensor = tensor.float() / 255.0
|
| 153 |
+
else:
|
| 154 |
+
tensor = tensor.float()
|
| 155 |
+
return tensor
|
patch-reparameterization/src/eval_only.py
ADDED
|
@@ -0,0 +1,927 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright (c) Meta Platforms.
|
| 3 |
+
# Licensed under the MIT license.
|
| 4 |
+
"""
|
| 5 |
+
Standalone evaluation script for PatchReparam models (Stage-1 / Stage-2 checkpoints).
|
| 6 |
+
|
| 7 |
+
Loads a checkpoint, runs reconstruction metrics (PSNR, SSIM, rFID) and optionally
|
| 8 |
+
zero-shot ImageNet classification. Supports both single-GPU and multi-GPU via
|
| 9 |
+
accelerate launch.
|
| 10 |
+
|
| 11 |
+
Usage:
|
| 12 |
+
# Single GPU (with checkpoint)
|
| 13 |
+
python eval_only.py --config configs/stage2/xxx.yaml --ckpt path/to/ep-last.pt
|
| 14 |
+
|
| 15 |
+
# Pretrained only (no checkpoint, model created from config)
|
| 16 |
+
python eval_only.py --config configs/stage1/pretrained/xxx.yaml --output-dir ./eval_pretrained
|
| 17 |
+
|
| 18 |
+
# Multi-GPU
|
| 19 |
+
accelerate launch --num_processes 4 eval_only.py --config configs/stage2/xxx.yaml --ckpt path/to/ep-last.pt
|
| 20 |
+
|
| 21 |
+
# Override eval data from config
|
| 22 |
+
python eval_only.py --config xxx.yaml --ckpt xxx.pt --eval-data /path/to/val/ --reference-npz /path/to/val.npz
|
| 23 |
+
|
| 24 |
+
# Save reconstructed images (PNG) to eval_results/recon_images/
|
| 25 |
+
python eval_only.py --config xxx.yaml --ckpt xxx.pt --save-recon-images
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
import argparse
|
| 31 |
+
import json
|
| 32 |
+
import logging
|
| 33 |
+
import math
|
| 34 |
+
import os
|
| 35 |
+
import sys
|
| 36 |
+
from pathlib import Path
|
| 37 |
+
|
| 38 |
+
import numpy as np
|
| 39 |
+
import torch
|
| 40 |
+
from accelerate import Accelerator
|
| 41 |
+
from omegaconf import OmegaConf
|
| 42 |
+
from utils.config_utils import load_config # noqa: F401 (registers oc.env resolver)
|
| 43 |
+
from torch.cuda.amp import autocast
|
| 44 |
+
from torch.utils.data import DataLoader, Subset
|
| 45 |
+
from torchvision import transforms
|
| 46 |
+
from torchvision.datasets import ImageFolder
|
| 47 |
+
from tqdm import tqdm
|
| 48 |
+
|
| 49 |
+
from eval import evaluate_reconstruction_distributed, compute_reconstruction_metrics
|
| 50 |
+
from eval.clip_zero_shot import (
|
| 51 |
+
IMAGENET_CLASSNAMES,
|
| 52 |
+
CLIP_PAPER_PROMPT_TEMPLATES,
|
| 53 |
+
PatchReparamZeroShotWrapper,
|
| 54 |
+
build_zero_shot_classifier,
|
| 55 |
+
create_imagenet_dataloader_patch_reparam,
|
| 56 |
+
evaluate as evaluate_zero_shot,
|
| 57 |
+
)
|
| 58 |
+
from stage1 import PatchReparam
|
| 59 |
+
from utils.train_utils import center_crop_arr, parse_configs
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class ExternalVAEWrapper(torch.nn.Module):
|
| 63 |
+
"""Wraps diffusers AutoencoderKL to match the PatchReparam forward interface.
|
| 64 |
+
|
| 65 |
+
Input: images in [0, 1], shape (B, 3, H, W)
|
| 66 |
+
Output: reconstructed images in [0, 1], shape (B, 3, H, W)
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
def __init__(self, vae):
|
| 70 |
+
super().__init__()
|
| 71 |
+
self.vae = vae
|
| 72 |
+
self.scaling_factor = vae.config.scaling_factor
|
| 73 |
+
|
| 74 |
+
@torch.no_grad()
|
| 75 |
+
def forward(self, images, **kwargs):
|
| 76 |
+
# Disable autocast: Flux VAE requires fp32 precision for accurate reconstruction
|
| 77 |
+
with torch.amp.autocast("cuda", enabled=False):
|
| 78 |
+
images = images.float()
|
| 79 |
+
# AutoencoderKL expects [-1, 1]
|
| 80 |
+
z = self.vae.encode(images * 2 - 1).latent_dist.sample() * self.scaling_factor
|
| 81 |
+
decoded = self.vae.decode(z / self.scaling_factor).sample
|
| 82 |
+
return (decoded + 1) / 2 # back to [0, 1]
|
| 83 |
+
|
| 84 |
+
class Qwen3VarlenWrapper(torch.nn.Module):
|
| 85 |
+
"""Wraps Qwen3Unified PatchReparam to match the standard model(images) eval interface.
|
| 86 |
+
|
| 87 |
+
Input: images in [0, 1], shape (B, 3, 256, 256)
|
| 88 |
+
Output: reconstructed images in [0, 1], shape (B, 3, 256, 256)
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
def __init__(self, pr):
|
| 92 |
+
super().__init__()
|
| 93 |
+
self.pr = pr
|
| 94 |
+
|
| 95 |
+
def forward(self, images, **kwargs):
|
| 96 |
+
# images: [B, 3, H, W] in [0, 1]
|
| 97 |
+
B = images.shape[0]
|
| 98 |
+
H, W = images.shape[2], images.shape[3]
|
| 99 |
+
p = self.pr.decoder.config.patch_size
|
| 100 |
+
tp = int(self.pr.encoder.temporal_patch_size)
|
| 101 |
+
H_grid, W_grid = H // p, W // p
|
| 102 |
+
|
| 103 |
+
# 从模型取 normalize 参数,不 hardcode
|
| 104 |
+
enc_mean = self.pr.encoder_mean.to(images.device) # [1, 3, 1, 1]
|
| 105 |
+
enc_std = self.pr.encoder_std.to(images.device) # [1, 3, 1, 1]
|
| 106 |
+
frames = images.unsqueeze(1).expand(-1, tp, -1, -1, -1).contiguous() # [B, tp, 3, H, W]
|
| 107 |
+
frames = (frames - enc_mean.unsqueeze(1)) / enc_std.unsqueeze(1)
|
| 108 |
+
frames = frames.reshape(B, tp, 3, H_grid, p, W_grid, p)
|
| 109 |
+
pixel_format = getattr(self.pr.encoder, 'pixel_format', 't_major')
|
| 110 |
+
if pixel_format == 'c_major':
|
| 111 |
+
# C-major: [B, H_g, W_g, C, tp, p, p] → [B*N, C*tp*p*p]
|
| 112 |
+
frames = frames.permute(0, 3, 5, 2, 1, 4, 6).contiguous().reshape(B * H_grid * W_grid, 3 * tp * p * p)
|
| 113 |
+
else:
|
| 114 |
+
# T-major(旧行为,兼容已有 checkpoint)
|
| 115 |
+
frames = frames.permute(0, 3, 5, 1, 2, 4, 6).contiguous().reshape(B * H_grid * W_grid * tp, 3, p, p)
|
| 116 |
+
grid_thw = torch.tensor([[1, H_grid, W_grid]], dtype=torch.long, device=images.device).expand(B, -1)
|
| 117 |
+
|
| 118 |
+
x_recs = self.pr(varlen=True, pixel_values=frames, grid_thw=grid_thw)
|
| 119 |
+
# fast path (all same res) returns tensor [B,3,H,W]; slow path returns list
|
| 120 |
+
if isinstance(x_recs, list):
|
| 121 |
+
x_recs = torch.stack(x_recs)
|
| 122 |
+
return x_recs.clamp(0, 1)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
try:
|
| 126 |
+
from peft import LoraConfig, get_peft_model, TaskType
|
| 127 |
+
PEFT_AVAILABLE = True
|
| 128 |
+
except ImportError:
|
| 129 |
+
PEFT_AVAILABLE = False
|
| 130 |
+
class LoraConfig:
|
| 131 |
+
pass
|
| 132 |
+
class TaskType:
|
| 133 |
+
FEATURE_EXTRACTION = None
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def safe_load_checkpoint(path: str, map_location: str = "cpu"):
|
| 137 |
+
try:
|
| 138 |
+
return torch.load(path, map_location=map_location, weights_only=False)
|
| 139 |
+
except TypeError:
|
| 140 |
+
return torch.load(path, map_location=map_location)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def load_pr_from_checkpoint(
|
| 144 |
+
pr_config,
|
| 145 |
+
ckpt_path: str,
|
| 146 |
+
device: torch.device,
|
| 147 |
+
use_ema: bool = True,
|
| 148 |
+
) -> PatchReparam:
|
| 149 |
+
"""Build PatchReparam from config and load weights from checkpoint."""
|
| 150 |
+
from utils.model_utils import get_obj_from_str
|
| 151 |
+
|
| 152 |
+
cfg = OmegaConf.to_container(pr_config, resolve=True) if hasattr(pr_config, "_metadata") else dict(pr_config)
|
| 153 |
+
target = cfg.get("target")
|
| 154 |
+
params = cfg.get("params", {})
|
| 155 |
+
if not target:
|
| 156 |
+
raise KeyError("Config must have 'target' for PatchReparam instantiation.")
|
| 157 |
+
|
| 158 |
+
pr = get_obj_from_str(target)(**params)
|
| 159 |
+
pr = pr.to(device).eval()
|
| 160 |
+
pr.requires_grad_(False)
|
| 161 |
+
|
| 162 |
+
ckpt = safe_load_checkpoint(ckpt_path, map_location="cpu")
|
| 163 |
+
use_lora = ckpt.get("use_lora", False)
|
| 164 |
+
lora_config = ckpt.get("lora_config", None)
|
| 165 |
+
|
| 166 |
+
if use_lora and lora_config and PEFT_AVAILABLE:
|
| 167 |
+
if isinstance(lora_config, dict) and "default" in lora_config:
|
| 168 |
+
lora_config = lora_config["default"]
|
| 169 |
+
if isinstance(lora_config, dict):
|
| 170 |
+
lora_config = LoraConfig(
|
| 171 |
+
task_type=TaskType.FEATURE_EXTRACTION,
|
| 172 |
+
r=int(lora_config.get("r", 8)),
|
| 173 |
+
lora_alpha=int(lora_config.get("lora_alpha", 16)),
|
| 174 |
+
target_modules=lora_config.get("target_modules", ["q_proj", "v_proj", "k_proj", "out_proj"]),
|
| 175 |
+
lora_dropout=float(lora_config.get("lora_dropout", 0.1)),
|
| 176 |
+
)
|
| 177 |
+
pr.encoder = get_peft_model(pr.encoder, lora_config)
|
| 178 |
+
logging.info("Applied LoRA adapter from checkpoint.")
|
| 179 |
+
|
| 180 |
+
state_dict = ckpt.get("ema" if use_ema else "model")
|
| 181 |
+
if state_dict is None:
|
| 182 |
+
raise KeyError(f"Checkpoint must contain 'ema' or 'model'. Got keys: {list(ckpt.keys())}")
|
| 183 |
+
|
| 184 |
+
# Handle DDP/compile prefixes
|
| 185 |
+
clean_state = {}
|
| 186 |
+
for k, v in state_dict.items():
|
| 187 |
+
clean_k = k
|
| 188 |
+
for prefix in ["module._orig_mod.", "_orig_mod.", "module."]:
|
| 189 |
+
if clean_k.startswith(prefix):
|
| 190 |
+
clean_k = clean_k[len(prefix):]
|
| 191 |
+
break
|
| 192 |
+
clean_state[clean_k] = v
|
| 193 |
+
|
| 194 |
+
missing, unexpected = pr.load_state_dict(clean_state, strict=False)
|
| 195 |
+
if missing:
|
| 196 |
+
logging.warning(f"Missing keys ({len(missing)}): {missing[:5]}...")
|
| 197 |
+
if unexpected:
|
| 198 |
+
logging.warning(f"Unexpected keys ({len(unexpected)}): {unexpected[:5]}...")
|
| 199 |
+
logging.info(f"Loaded {'EMA' if use_ema else 'model'} weights from {ckpt_path}")
|
| 200 |
+
return pr
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def create_pr_from_config(pr_config, device: torch.device) -> PatchReparam:
|
| 204 |
+
"""Build PatchReparam from config only (pretrained weights, no checkpoint loading)."""
|
| 205 |
+
from utils.model_utils import get_obj_from_str
|
| 206 |
+
|
| 207 |
+
cfg = OmegaConf.to_container(pr_config, resolve=True) if hasattr(pr_config, "_metadata") else dict(pr_config)
|
| 208 |
+
target = cfg.get("target")
|
| 209 |
+
params = cfg.get("params", {})
|
| 210 |
+
if not target:
|
| 211 |
+
raise KeyError("Config must have 'target' for PatchReparam instantiation.")
|
| 212 |
+
|
| 213 |
+
pr = get_obj_from_str(target)(**params)
|
| 214 |
+
pr = pr.to(device).eval()
|
| 215 |
+
pr.requires_grad_(False)
|
| 216 |
+
logging.info("Created PatchReparam from config (pretrained, no checkpoint).")
|
| 217 |
+
return pr
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def parse_args():
|
| 221 |
+
parser = argparse.ArgumentParser(description="Evaluate PatchReparam checkpoint (reconstruction + optional zero-shot).")
|
| 222 |
+
parser.add_argument("--config", type=str, required=True, help="YAML config with stage_1 section.")
|
| 223 |
+
parser.add_argument("--ckpt", type=str, default=None, help="Path to checkpoint (.pt). If omitted, model is created from config only (pretrained).")
|
| 224 |
+
parser.add_argument("--eval-data", type=str, default=None, help="Eval dataset path (ImageFolder). Overrides config.")
|
| 225 |
+
parser.add_argument("--reference-npz", type=str, default=None, help="Reference NPZ for metrics. Overrides config.")
|
| 226 |
+
parser.add_argument("--output-dir", type=str, default=None, help="Experiment dir (parent of checkpoints). Default: ckpt's parent's parent.")
|
| 227 |
+
parser.add_argument("--image-size", type=int, default=256, help="Image size for center crop.")
|
| 228 |
+
parser.add_argument("--batch-size", type=int, default=64, help="Batch size per GPU for reconstruction.")
|
| 229 |
+
parser.add_argument("--metrics", type=str, nargs="+", default=None, help="Metrics: psnr, ssim, rfid. Default from config.")
|
| 230 |
+
parser.add_argument("--eval-weights", type=str, choices=["both", "ema_only", "model_only"], default="ema_only",
|
| 231 |
+
help="Which weights to eval: both (default), ema_only, model_only. Overrides config eval.eval_weights.")
|
| 232 |
+
parser.add_argument("--no-zeroshot", action="store_true", help="Disable zero-shot eval even if enabled in config.")
|
| 233 |
+
parser.add_argument("--save-recon-images", action="store_true", help="Save reconstructed images as PNG files to eval_results/recon_images/.")
|
| 234 |
+
parser.add_argument("--image-features-mode", type=str, default=None,
|
| 235 |
+
help="For SigLIP2 encoder: which tokens to use for reconstruction. Overrides config eval.eval_modes when set.")
|
| 236 |
+
parser.add_argument("--precision", choices=["fp32", "fp16", "bf16"], default="bf16")
|
| 237 |
+
parser.add_argument("--encoder-cos-sim-model", type=str, default=None,
|
| 238 |
+
help="HuggingFace model path for 'cos_sim' metric. Overrides config eval.encoder_cos_sim_model.")
|
| 239 |
+
parser.add_argument("--psnr-resize", type=int, default=None,
|
| 240 |
+
help="If set, resize images to this resolution (square) before computing PSNR. Overrides config eval.psnr_resize.")
|
| 241 |
+
parser.add_argument("--l1-resize", type=int, default=None,
|
| 242 |
+
help="If set, resize images to this resolution (square) before computing L1. Overrides config eval.l1_resize.")
|
| 243 |
+
# Denoise augmentation: encode -> noise -> DiT denoise -> decode
|
| 244 |
+
parser.add_argument("--denoise-augment", action="store_true",
|
| 245 |
+
help="Enable denoise augmentation: encode GT, add flow-matching noise, DiT denoise, then decode.")
|
| 246 |
+
parser.add_argument("--dit-config", type=str, default=None,
|
| 247 |
+
help="DiT sampling config YAML (must contain stage_2, transport, misc sections).")
|
| 248 |
+
parser.add_argument("--denoise-t", type=float, default=0.1,
|
| 249 |
+
help="Noise level t for flow-matching interpolation (0~1). Default 0.1.")
|
| 250 |
+
parser.add_argument("--denoise-steps", type=int, default=1,
|
| 251 |
+
help="Number of euler steps from t to 0. Default 1.")
|
| 252 |
+
parser.add_argument("--denoise-t-list", type=str, default=None,
|
| 253 |
+
help="Comma-separated t schedule (e.g. '0.8,0.67,0.5,0.0'). Overrides --denoise-t/--denoise-steps.")
|
| 254 |
+
parser.add_argument("--denoise-shift", type=float, default=None,
|
| 255 |
+
help="Time shift value. When set, auto-compute t schedule from shifted 50-step euler, taking last --denoise-steps steps from t_start.")
|
| 256 |
+
parser.add_argument("--denoise-shift-total-steps", type=int, default=50,
|
| 257 |
+
help="Total euler steps for shift schedule computation. Default 50.")
|
| 258 |
+
parser.add_argument("--save-denoise-latents", action="store_true",
|
| 259 |
+
help="Save denoised latents (.pt) and decoded images (.png) under output-dir/denoise_latents/.")
|
| 260 |
+
parser.add_argument("--num-samples", type=int, default=None,
|
| 261 |
+
help="Limit number of eval samples. Default: use all.")
|
| 262 |
+
# Recon gFID: reconstruct train images per-class, compute gFID vs precomputed stats
|
| 263 |
+
parser.add_argument("--recon-gfid", action="store_true",
|
| 264 |
+
help="Reconstruct train images (per-class sampling) and compute gFID.")
|
| 265 |
+
parser.add_argument("--train-data", type=str,
|
| 266 |
+
default=os.environ.get("DATA_ROOT", "/path/to/ImageNet1k") + "/train/",
|
| 267 |
+
help="Train dataset path (ImageFolder) for --recon-gfid.")
|
| 268 |
+
parser.add_argument("--gfid-npz", type=str,
|
| 269 |
+
default=os.path.join(os.environ.get("FID_STATS_ROOT", "/path/to/fid_stats"), "VIRTUAL_imagenet256_labeled.npz"),
|
| 270 |
+
help="Precomputed inception stats NPZ (keys: mu, sigma) for gFID.")
|
| 271 |
+
parser.add_argument("--samples-per-class", type=int, default=50,
|
| 272 |
+
help="Number of samples per class for --recon-gfid. Default 50.")
|
| 273 |
+
return parser.parse_args()
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def sample_per_class_indices(dataset, samples_per_class):
|
| 277 |
+
"""Return indices for uniform per-class sampling from an ImageFolder dataset."""
|
| 278 |
+
from collections import defaultdict
|
| 279 |
+
class_to_indices = defaultdict(list)
|
| 280 |
+
for idx, (_, label) in enumerate(dataset.samples):
|
| 281 |
+
class_to_indices[label].append(idx)
|
| 282 |
+
selected = []
|
| 283 |
+
for label in sorted(class_to_indices.keys()):
|
| 284 |
+
indices = class_to_indices[label]
|
| 285 |
+
if len(indices) >= samples_per_class:
|
| 286 |
+
selected.extend(indices[:samples_per_class])
|
| 287 |
+
else:
|
| 288 |
+
selected.extend(indices)
|
| 289 |
+
return selected
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
@torch.no_grad()
|
| 293 |
+
def evaluate_recon_gfid(
|
| 294 |
+
model,
|
| 295 |
+
dataset,
|
| 296 |
+
gfid_npz_path,
|
| 297 |
+
batch_size,
|
| 298 |
+
autocast_kwargs,
|
| 299 |
+
accelerator,
|
| 300 |
+
experiment_dir=None,
|
| 301 |
+
num_samples=None,
|
| 302 |
+
image_features_mode=None,
|
| 303 |
+
save_recon_dir=None,
|
| 304 |
+
):
|
| 305 |
+
"""Reconstruct train images and compute gFID vs precomputed inception stats."""
|
| 306 |
+
from eval.fid import calculate_gfid
|
| 307 |
+
rank = accelerator.process_index
|
| 308 |
+
world_size = accelerator.num_processes
|
| 309 |
+
device = accelerator.device
|
| 310 |
+
is_main = accelerator.is_main_process
|
| 311 |
+
|
| 312 |
+
model.eval()
|
| 313 |
+
N = num_samples if num_samples else len(dataset)
|
| 314 |
+
chunk = N // world_size
|
| 315 |
+
start = rank * chunk
|
| 316 |
+
end = (rank + 1) * chunk if rank < world_size - 1 else N
|
| 317 |
+
|
| 318 |
+
subset = Subset(dataset, list(range(start, end)))
|
| 319 |
+
loader = DataLoader(subset, batch_size=batch_size, shuffle=False,
|
| 320 |
+
num_workers=4, pin_memory=True, drop_last=False)
|
| 321 |
+
|
| 322 |
+
reconstructions = []
|
| 323 |
+
iterator = tqdm(loader, desc=f"[Rank {rank}] Recon gFID", file=sys.stdout) if is_main else loader
|
| 324 |
+
|
| 325 |
+
with torch.inference_mode():
|
| 326 |
+
for images, _ in iterator:
|
| 327 |
+
images = images.to(device, non_blocking=True)
|
| 328 |
+
with autocast(**autocast_kwargs):
|
| 329 |
+
if image_features_mode is not None:
|
| 330 |
+
recon = model(images, image_features_mode=image_features_mode)
|
| 331 |
+
else:
|
| 332 |
+
recon = model(images)
|
| 333 |
+
if hasattr(recon, 'x_rec'):
|
| 334 |
+
recon = recon.x_rec
|
| 335 |
+
recon = recon.clamp(0, 1)
|
| 336 |
+
recon_np = recon.mul(255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
|
| 337 |
+
for img in recon_np:
|
| 338 |
+
reconstructions.append(img)
|
| 339 |
+
|
| 340 |
+
reconstructions = np.stack(reconstructions)
|
| 341 |
+
|
| 342 |
+
# Gather across ranks
|
| 343 |
+
temp_dir = os.path.join(experiment_dir or ".", "eval_npzs_gfid")
|
| 344 |
+
if is_main:
|
| 345 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 346 |
+
accelerator.wait_for_everyone()
|
| 347 |
+
|
| 348 |
+
shard_path = os.path.join(temp_dir, f"recon_gfid_{rank:02d}.npz")
|
| 349 |
+
np.savez(shard_path, arr_0=reconstructions)
|
| 350 |
+
accelerator.wait_for_everyone()
|
| 351 |
+
|
| 352 |
+
metrics = None
|
| 353 |
+
if is_main:
|
| 354 |
+
all_recons = []
|
| 355 |
+
for r in range(world_size):
|
| 356 |
+
shard = np.load(os.path.join(temp_dir, f"recon_gfid_{r:02d}.npz"))["arr_0"]
|
| 357 |
+
all_recons.append(shard)
|
| 358 |
+
combined = np.concatenate(all_recons, axis=0)[:N]
|
| 359 |
+
|
| 360 |
+
ref_stats = np.load(gfid_npz_path)
|
| 361 |
+
print(f"[ReconGFID] Recon shape: {combined.shape}")
|
| 362 |
+
|
| 363 |
+
# Save reconstructed images in background thread (non-blocking, before metrics)
|
| 364 |
+
if save_recon_dir:
|
| 365 |
+
import threading
|
| 366 |
+
recons_copy = np.copy(combined)
|
| 367 |
+
def _save_images():
|
| 368 |
+
from PIL import Image as _Image
|
| 369 |
+
os.makedirs(save_recon_dir, exist_ok=True)
|
| 370 |
+
for i, img in enumerate(recons_copy):
|
| 371 |
+
_Image.fromarray(img).save(os.path.join(save_recon_dir, f"recon_{i:06d}.png"))
|
| 372 |
+
print(f"[ReconGFID] Saved {len(recons_copy)} images to {save_recon_dir}")
|
| 373 |
+
t = threading.Thread(target=_save_images, daemon=False)
|
| 374 |
+
t.start()
|
| 375 |
+
print(f"[ReconGFID] Saving {len(combined)} images to {save_recon_dir} in background...")
|
| 376 |
+
|
| 377 |
+
device_str = "cuda" if device.type == "cuda" else "cpu"
|
| 378 |
+
gfid = calculate_gfid(combined, ref_stats, batch_size=128, device=device_str)
|
| 379 |
+
metrics = {"gfid": float(gfid)}
|
| 380 |
+
print(f"[ReconGFID] gFID: {gfid:.4f}")
|
| 381 |
+
|
| 382 |
+
# cleanup
|
| 383 |
+
for r in range(world_size):
|
| 384 |
+
p = os.path.join(temp_dir, f"recon_gfid_{r:02d}.npz")
|
| 385 |
+
if os.path.exists(p):
|
| 386 |
+
os.remove(p)
|
| 387 |
+
|
| 388 |
+
accelerator.wait_for_everyone()
|
| 389 |
+
return metrics
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
def compute_denoise_t_list(t_start, denoise_steps, shift, shift_total_steps=50):
|
| 393 |
+
"""Compute shifted t schedule from t_start to 0, taking last k steps from full schedule."""
|
| 394 |
+
t_uniform = 1 - torch.linspace(0, 1, shift_total_steps + 1)
|
| 395 |
+
t_shifted = shift * t_uniform / (1 + (shift - 1) * t_uniform)
|
| 396 |
+
# Find first index where t_shifted <= t_start
|
| 397 |
+
below = (t_shifted <= t_start).nonzero(as_tuple=True)[0]
|
| 398 |
+
if len(below) == 0:
|
| 399 |
+
return torch.tensor([t_start, 0.0])
|
| 400 |
+
start_idx = below[0].item()
|
| 401 |
+
# Take denoise_steps steps from start_idx, always end at 0
|
| 402 |
+
remaining = t_shifted[start_idx:]
|
| 403 |
+
if denoise_steps >= len(remaining):
|
| 404 |
+
return remaining
|
| 405 |
+
# Evenly sample denoise_steps points from remaining (including endpoints)
|
| 406 |
+
indices = torch.linspace(0, len(remaining) - 1, denoise_steps + 1).long()
|
| 407 |
+
return remaining[indices]
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
def euler_denoise(x, dit_model, labels, t_start, steps, t_list=None):
|
| 411 |
+
"""Euler ODE integration from t_start to 0.
|
| 412 |
+
|
| 413 |
+
Args:
|
| 414 |
+
t_list: Optional pre-computed t schedule tensor (including endpoints).
|
| 415 |
+
If provided, t_start and steps are ignored.
|
| 416 |
+
"""
|
| 417 |
+
if t_list is not None:
|
| 418 |
+
ts = t_list.to(x.device)
|
| 419 |
+
else:
|
| 420 |
+
ts = torch.linspace(t_start, 0, steps + 1, device=x.device)
|
| 421 |
+
for i in range(len(ts) - 1):
|
| 422 |
+
t_cur = ts[i]
|
| 423 |
+
dt = ts[i + 1] - ts[i] # negative (going from t to 0)
|
| 424 |
+
t_batch = torch.full((x.shape[0],), t_cur, device=x.device)
|
| 425 |
+
v = dit_model(x, t_batch, y=labels)
|
| 426 |
+
x = x + v * dt
|
| 427 |
+
return x
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
@torch.no_grad()
|
| 431 |
+
def evaluate_denoise_reconstruction(
|
| 432 |
+
pr,
|
| 433 |
+
dit_model,
|
| 434 |
+
dataset,
|
| 435 |
+
denoise_t,
|
| 436 |
+
denoise_steps,
|
| 437 |
+
batch_size,
|
| 438 |
+
autocast_kwargs,
|
| 439 |
+
reference_npz_path,
|
| 440 |
+
metrics_to_compute,
|
| 441 |
+
accelerator,
|
| 442 |
+
experiment_dir=None,
|
| 443 |
+
encoder_cos_sim_model=None,
|
| 444 |
+
psnr_resize=None,
|
| 445 |
+
l1_resize=None,
|
| 446 |
+
num_samples=None,
|
| 447 |
+
save_recon_dir=None,
|
| 448 |
+
t_list=None,
|
| 449 |
+
save_latents_dir=None,
|
| 450 |
+
):
|
| 451 |
+
"""Evaluate reconstruction with denoise augmentation (encode -> noise -> DiT denoise -> decode)."""
|
| 452 |
+
rank = accelerator.process_index
|
| 453 |
+
world_size = accelerator.num_processes
|
| 454 |
+
device = accelerator.device
|
| 455 |
+
is_main = accelerator.is_main_process
|
| 456 |
+
|
| 457 |
+
pr.eval()
|
| 458 |
+
dit_model.eval()
|
| 459 |
+
|
| 460 |
+
N = min(len(dataset), num_samples) if num_samples else len(dataset)
|
| 461 |
+
chunk = N // world_size
|
| 462 |
+
start = rank * chunk
|
| 463 |
+
end = (rank + 1) * chunk if rank < world_size - 1 else N
|
| 464 |
+
|
| 465 |
+
subset = Subset(dataset, list(range(start, end)))
|
| 466 |
+
loader = DataLoader(subset, batch_size=batch_size, shuffle=False,
|
| 467 |
+
num_workers=4, pin_memory=True, drop_last=False)
|
| 468 |
+
|
| 469 |
+
reconstructions = []
|
| 470 |
+
iterator = tqdm(loader, desc=f"[Rank {rank}] Denoise Recon", file=sys.stdout) if is_main else loader
|
| 471 |
+
|
| 472 |
+
with torch.inference_mode():
|
| 473 |
+
for images, labels in iterator:
|
| 474 |
+
images = images.to(device, non_blocking=True)
|
| 475 |
+
labels = labels.to(device, non_blocking=True)
|
| 476 |
+
with autocast(**autocast_kwargs):
|
| 477 |
+
# 1. encode -> normalized latent
|
| 478 |
+
enc = pr.encode(images, return_aux=True)
|
| 479 |
+
z = enc.merged_tokens_norm # [B, C, H, W]
|
| 480 |
+
|
| 481 |
+
# 2. flow-matching noise: x_t = (1-t)*data + t*noise
|
| 482 |
+
noise = torch.randn_like(z)
|
| 483 |
+
x_t = (1 - denoise_t) * z + denoise_t * noise
|
| 484 |
+
|
| 485 |
+
# 3. DiT euler denoise with GT label
|
| 486 |
+
z_hat = euler_denoise(x_t, dit_model, labels, denoise_t, denoise_steps, t_list=t_list)
|
| 487 |
+
|
| 488 |
+
# 4. decode (z_hat is [B,C,H,W] normalized, decode will denormalize)
|
| 489 |
+
recon = pr.decode(z_hat)
|
| 490 |
+
|
| 491 |
+
if hasattr(recon, 'x_rec'):
|
| 492 |
+
recon = recon.x_rec
|
| 493 |
+
recon = recon.clamp(0, 1)
|
| 494 |
+
recon_np = recon.mul(255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
|
| 495 |
+
|
| 496 |
+
# Save denoised latents, decoded images, and GT images
|
| 497 |
+
if save_latents_dir is not None:
|
| 498 |
+
from PIL import Image as _Image
|
| 499 |
+
latents_sub = os.path.join(save_latents_dir, "latents")
|
| 500 |
+
gt_sub = os.path.join(save_latents_dir, "gt")
|
| 501 |
+
os.makedirs(latents_sub, exist_ok=True)
|
| 502 |
+
os.makedirs(gt_sub, exist_ok=True)
|
| 503 |
+
gt_np = images.clamp(0, 1).mul(255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
|
| 504 |
+
for j in range(z_hat.shape[0]):
|
| 505 |
+
global_idx = start + len(reconstructions) + j
|
| 506 |
+
torch.save(z_hat[j].cpu(), os.path.join(latents_sub, f"{global_idx:06d}.pt"))
|
| 507 |
+
_Image.fromarray(recon_np[j]).save(os.path.join(save_latents_dir, f"{global_idx:06d}.png"))
|
| 508 |
+
_Image.fromarray(gt_np[j]).save(os.path.join(gt_sub, f"{global_idx:06d}.png"))
|
| 509 |
+
|
| 510 |
+
for img in recon_np:
|
| 511 |
+
reconstructions.append(img)
|
| 512 |
+
|
| 513 |
+
reconstructions = np.stack(reconstructions)
|
| 514 |
+
|
| 515 |
+
# Gather across ranks via temp npz files
|
| 516 |
+
temp_dir = os.path.join(experiment_dir or ".", "eval_npzs_denoise")
|
| 517 |
+
if is_main:
|
| 518 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 519 |
+
accelerator.wait_for_everyone()
|
| 520 |
+
|
| 521 |
+
shard_path = os.path.join(temp_dir, f"denoise_recon_{rank:02d}.npz")
|
| 522 |
+
np.savez(shard_path, arr_0=reconstructions)
|
| 523 |
+
accelerator.wait_for_everyone()
|
| 524 |
+
|
| 525 |
+
metrics = None
|
| 526 |
+
if is_main:
|
| 527 |
+
all_recons = []
|
| 528 |
+
for r in range(world_size):
|
| 529 |
+
shard = np.load(os.path.join(temp_dir, f"denoise_recon_{r:02d}.npz"))["arr_0"]
|
| 530 |
+
all_recons.append(shard)
|
| 531 |
+
combined = np.concatenate(all_recons, axis=0)[:N]
|
| 532 |
+
|
| 533 |
+
ref_images = np.load(reference_npz_path)["arr_0"][:N]
|
| 534 |
+
print(f"[DenoiseEval] Combined shape: {combined.shape}, ref shape: {ref_images.shape}")
|
| 535 |
+
|
| 536 |
+
# Save reconstructed images in background thread (non-blocking, before metrics)
|
| 537 |
+
if save_recon_dir:
|
| 538 |
+
import threading
|
| 539 |
+
recons_copy = np.copy(combined)
|
| 540 |
+
def _save_images():
|
| 541 |
+
from PIL import Image as _Image
|
| 542 |
+
os.makedirs(save_recon_dir, exist_ok=True)
|
| 543 |
+
for i, img in enumerate(recons_copy):
|
| 544 |
+
_Image.fromarray(img).save(os.path.join(save_recon_dir, f"recon_{i:06d}.png"))
|
| 545 |
+
print(f"[DenoiseEval] Saved {len(recons_copy)} images to {save_recon_dir}")
|
| 546 |
+
t = threading.Thread(target=_save_images, daemon=False)
|
| 547 |
+
t.start()
|
| 548 |
+
print(f"[DenoiseEval] Saving {len(combined)} images to {save_recon_dir} in background...")
|
| 549 |
+
|
| 550 |
+
metrics = compute_reconstruction_metrics(
|
| 551 |
+
ref_images, combined, accelerator.device, 128,
|
| 552 |
+
metrics_to_compute=metrics_to_compute,
|
| 553 |
+
encoder_model=encoder_cos_sim_model,
|
| 554 |
+
psnr_resize=psnr_resize,
|
| 555 |
+
l1_resize=l1_resize,
|
| 556 |
+
)
|
| 557 |
+
print(f"[DenoiseEval] denoise_t={denoise_t}, steps={denoise_steps}")
|
| 558 |
+
for k, v in metrics.items():
|
| 559 |
+
print(f" {k}: {v:.6f}")
|
| 560 |
+
|
| 561 |
+
# cleanup
|
| 562 |
+
for r in range(world_size):
|
| 563 |
+
p = os.path.join(temp_dir, f"denoise_recon_{r:02d}.npz")
|
| 564 |
+
if os.path.exists(p):
|
| 565 |
+
os.remove(p)
|
| 566 |
+
|
| 567 |
+
accelerator.wait_for_everyone()
|
| 568 |
+
return metrics
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
def main():
|
| 572 |
+
args = parse_args()
|
| 573 |
+
|
| 574 |
+
mixed_precision = "fp16" if args.precision == "fp16" else ("bf16" if args.precision == "bf16" else "no")
|
| 575 |
+
accelerator = Accelerator(mixed_precision=mixed_precision)
|
| 576 |
+
|
| 577 |
+
# Only rank 0 logs; suppress other ranks to avoid duplicated output
|
| 578 |
+
root_logger = logging.getLogger()
|
| 579 |
+
if accelerator.is_main_process:
|
| 580 |
+
logging.basicConfig(
|
| 581 |
+
level=logging.INFO,
|
| 582 |
+
format="[%(asctime)s] [%(name)s] %(levelname)s: %(message)s",
|
| 583 |
+
datefmt="%Y-%m-%d %H:%M:%S",
|
| 584 |
+
force=True,
|
| 585 |
+
)
|
| 586 |
+
else:
|
| 587 |
+
root_logger.setLevel(logging.CRITICAL + 1)
|
| 588 |
+
for h in list(root_logger.handlers):
|
| 589 |
+
root_logger.removeHandler(h)
|
| 590 |
+
logger = logging.getLogger(__name__)
|
| 591 |
+
|
| 592 |
+
full_cfg = OmegaConf.load(args.config)
|
| 593 |
+
|
| 594 |
+
# External VAE mode (e.g. Flux/SDXL VAE): config has vae_decoder instead of stage_1
|
| 595 |
+
vae_decoder_cfg = full_cfg.get("vae_decoder", None)
|
| 596 |
+
use_external_vae = vae_decoder_cfg is not None
|
| 597 |
+
|
| 598 |
+
pr_config = None
|
| 599 |
+
if not use_external_vae:
|
| 600 |
+
pr_config, *_ = parse_configs(full_cfg)
|
| 601 |
+
if pr_config is None:
|
| 602 |
+
raise ValueError("Config must contain stage_1 or vae_decoder section.")
|
| 603 |
+
|
| 604 |
+
eval_section = full_cfg.get("eval", None)
|
| 605 |
+
eval_data = args.eval_data or (eval_section.get("data_path") if eval_section else None)
|
| 606 |
+
reference_npz = args.reference_npz or (eval_section.get("reference_npz_path") if eval_section else None)
|
| 607 |
+
eval_metrics = args.metrics or (list(eval_section.get("metrics", ["psnr", "ssim", "rfid"])) if eval_section else ["psnr", "ssim", "rfid"])
|
| 608 |
+
encoder_cos_sim_model = args.encoder_cos_sim_model or (eval_section.get("encoder_cos_sim_model") if eval_section else None)
|
| 609 |
+
psnr_resize = args.psnr_resize or (eval_section.get("psnr_resize") if eval_section else None)
|
| 610 |
+
l1_resize = args.l1_resize or (eval_section.get("l1_resize") if eval_section else None)
|
| 611 |
+
eval_weights = args.eval_weights or (eval_section.get("eval_weights", "both") if eval_section else "both")
|
| 612 |
+
if args.image_features_mode is not None:
|
| 613 |
+
eval_modes = [args.image_features_mode]
|
| 614 |
+
else:
|
| 615 |
+
eval_modes = list(eval_section.get("eval_modes", [None]) if eval_section else [None])
|
| 616 |
+
# print(eval_modes)
|
| 617 |
+
if use_external_vae:
|
| 618 |
+
models_to_eval = ["external_vae"]
|
| 619 |
+
elif args.ckpt is None:
|
| 620 |
+
models_to_eval = ["pretrained"]
|
| 621 |
+
else:
|
| 622 |
+
models_to_eval = ["ema", "model"] if eval_weights == "both" else (["ema"] if eval_weights == "ema_only" else ["model"])
|
| 623 |
+
|
| 624 |
+
if not eval_data or not reference_npz:
|
| 625 |
+
raise ValueError("Must specify --eval-data and --reference-npz, or ensure config eval.data_path and eval.reference_npz_path are set.")
|
| 626 |
+
if 'cos_sim' in eval_metrics:
|
| 627 |
+
assert encoder_cos_sim_model, (
|
| 628 |
+
"'cos_sim' metric requires --encoder-cos-sim-model or eval.encoder_cos_sim_model in config."
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
# exp_dir: same level as checkpoints (e.g. ckpts/stage1/exp_name/); when no ckpt, require output_dir
|
| 632 |
+
if args.ckpt is not None:
|
| 633 |
+
exp_dir = Path(args.output_dir) if args.output_dir else Path(args.ckpt).parent.parent
|
| 634 |
+
else:
|
| 635 |
+
exp_dir = Path(args.output_dir) if args.output_dir else Path("./eval_pretrained")
|
| 636 |
+
output_dir = str(exp_dir) # for eval_npzs temp files
|
| 637 |
+
eval_results_dir = exp_dir / "eval_results"
|
| 638 |
+
ckpt_version = "external_vae" if use_external_vae else ("pretrained" if args.ckpt is None else Path(args.ckpt).stem)
|
| 639 |
+
ckpt_output_dir = eval_results_dir / ckpt_version
|
| 640 |
+
os.makedirs(ckpt_output_dir, exist_ok=True)
|
| 641 |
+
|
| 642 |
+
# Save config and startup args for reproducibility (main process only)
|
| 643 |
+
if accelerator.is_main_process:
|
| 644 |
+
config_save_path = ckpt_output_dir / "eval_config.yaml"
|
| 645 |
+
OmegaConf.save(full_cfg, config_save_path)
|
| 646 |
+
logger.info("Config saved to %s", config_save_path)
|
| 647 |
+
args_dict = vars(args)
|
| 648 |
+
# Ensure JSON-serializable (e.g. None, bool, int, str, list)
|
| 649 |
+
args_save_path = ckpt_output_dir / "eval_args.json"
|
| 650 |
+
with open(args_save_path, "w", encoding="utf-8") as f:
|
| 651 |
+
json.dump(args_dict, f, indent=2, ensure_ascii=False)
|
| 652 |
+
logger.info("Startup args saved to %s", args_save_path)
|
| 653 |
+
|
| 654 |
+
device = accelerator.device
|
| 655 |
+
eval_dataset = ImageFolder(
|
| 656 |
+
str(eval_data),
|
| 657 |
+
transform=transforms.Compose([
|
| 658 |
+
transforms.Lambda(lambda pil_image: center_crop_arr(pil_image, args.image_size)),
|
| 659 |
+
transforms.ToTensor(),
|
| 660 |
+
]),
|
| 661 |
+
)
|
| 662 |
+
logger.info(f"Eval dataset: {eval_data}, {len(eval_dataset)} images. Models to eval: {models_to_eval} eval_mode:{eval_modes}")
|
| 663 |
+
|
| 664 |
+
autocast_kwargs = {
|
| 665 |
+
"enabled": accelerator.mixed_precision != "no",
|
| 666 |
+
"dtype": torch.bfloat16 if accelerator.mixed_precision == "bf16" else torch.float16 if accelerator.mixed_precision == "fp16" else torch.float32,
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
do_zeroshot = (
|
| 670 |
+
not args.no_zeroshot
|
| 671 |
+
and eval_section
|
| 672 |
+
and eval_section.get("zero_shot", {}).get("enabled", False)
|
| 673 |
+
)
|
| 674 |
+
zs_cfg = eval_section.get("zero_shot", {}) if eval_section else {}
|
| 675 |
+
zs_data_path = str(zs_cfg.get("data_path") or zs_cfg.get("imagenet_val_path", ""))
|
| 676 |
+
zs_batch_size = int(zs_cfg.get("batch_size", 128))
|
| 677 |
+
zs_num_workers = int(zs_cfg.get("num_workers", 8))
|
| 678 |
+
|
| 679 |
+
# ── Load DiT model if denoise augmentation is enabled ──
|
| 680 |
+
dit_model = None
|
| 681 |
+
if args.denoise_augment:
|
| 682 |
+
if not args.dit_config:
|
| 683 |
+
raise ValueError("--dit-config is required when --denoise-augment is set.")
|
| 684 |
+
from utils.model_utils import instantiate_from_config as _instantiate
|
| 685 |
+
dit_cfg = OmegaConf.load(args.dit_config)
|
| 686 |
+
_, dit_model_config, _, _, _, _, _, _ = parse_configs(dit_cfg)
|
| 687 |
+
logger.info("Loading DiT model for denoise augmentation...")
|
| 688 |
+
dit_model = _instantiate(dit_model_config).to(device).eval()
|
| 689 |
+
dit_model.requires_grad_(False)
|
| 690 |
+
logger.info("DiT loaded. denoise_t=%.3f, denoise_steps=%d", args.denoise_t, args.denoise_steps)
|
| 691 |
+
|
| 692 |
+
# ── Build per-class sampled train dataset if recon-gfid is enabled ──
|
| 693 |
+
gfid_dataset = None
|
| 694 |
+
if args.recon_gfid:
|
| 695 |
+
full_train = ImageFolder(
|
| 696 |
+
str(args.train_data),
|
| 697 |
+
transform=transforms.Compose([
|
| 698 |
+
transforms.Lambda(lambda pil_image: center_crop_arr(pil_image, args.image_size)),
|
| 699 |
+
transforms.ToTensor(),
|
| 700 |
+
]),
|
| 701 |
+
)
|
| 702 |
+
selected_indices = sample_per_class_indices(full_train, args.samples_per_class)
|
| 703 |
+
gfid_dataset = Subset(full_train, selected_indices)
|
| 704 |
+
logger.info(f"Recon gFID: {len(gfid_dataset)} images ({args.samples_per_class}/class) from {args.train_data}")
|
| 705 |
+
|
| 706 |
+
evaluated_samples = min(len(eval_dataset), args.num_samples) if args.num_samples else len(eval_dataset)
|
| 707 |
+
results = {
|
| 708 |
+
"ckpt": args.ckpt,
|
| 709 |
+
"config": args.config,
|
| 710 |
+
"evaluation": {
|
| 711 |
+
"dataset_size": len(eval_dataset),
|
| 712 |
+
"requested_samples": args.num_samples,
|
| 713 |
+
"evaluated_samples": evaluated_samples,
|
| 714 |
+
},
|
| 715 |
+
}
|
| 716 |
+
zs_initialized = False
|
| 717 |
+
|
| 718 |
+
for model_name in models_to_eval:
|
| 719 |
+
use_ema = model_name == "ema"
|
| 720 |
+
if use_external_vae:
|
| 721 |
+
from diffusers import AutoencoderKL
|
| 722 |
+
vae_path = vae_decoder_cfg.get("path") if hasattr(vae_decoder_cfg, "get") else str(vae_decoder_cfg)
|
| 723 |
+
logger.info("Loading external VAE from %s", vae_path)
|
| 724 |
+
external_vae = AutoencoderKL.from_pretrained(vae_path).to(device).eval()
|
| 725 |
+
external_vae.requires_grad_(False)
|
| 726 |
+
logger.info("External VAE loaded: scaling_factor=%s", external_vae.config.scaling_factor)
|
| 727 |
+
pr = ExternalVAEWrapper(external_vae)
|
| 728 |
+
elif args.ckpt is None:
|
| 729 |
+
logger.info("Creating pretrained model from config...")
|
| 730 |
+
pr = create_pr_from_config(pr_config, device)
|
| 731 |
+
else:
|
| 732 |
+
logger.info("Loading %s weights...", model_name)
|
| 733 |
+
pr = load_pr_from_checkpoint(pr_config, args.ckpt, device, use_ema=use_ema)
|
| 734 |
+
|
| 735 |
+
# Qwen3Unified 用 varlen 接口,需要 wrapper 转成标准 model(images) 接口
|
| 736 |
+
try:
|
| 737 |
+
encoder_cls = OmegaConf.to_container(pr_config, resolve=True).get("params", {}).get("encoder_cls", "")
|
| 738 |
+
except Exception:
|
| 739 |
+
encoder_cls = ""
|
| 740 |
+
if encoder_cls == "Qwen3Unified":
|
| 741 |
+
pr = Qwen3VarlenWrapper(pr)
|
| 742 |
+
logger.info("Wrapped PatchReparam with Qwen3VarlenWrapper for varlen eval.")
|
| 743 |
+
|
| 744 |
+
pr = accelerator.prepare(pr)
|
| 745 |
+
eval_mod = accelerator.unwrap_model(pr)
|
| 746 |
+
|
| 747 |
+
model_results = {}
|
| 748 |
+
|
| 749 |
+
# ── Denoise augmentation mode ──
|
| 750 |
+
if args.denoise_augment and dit_model is not None:
|
| 751 |
+
# Build t_list: priority: --denoise-t-list > --denoise-shift > linspace
|
| 752 |
+
denoise_t_list = None
|
| 753 |
+
if args.denoise_t_list is not None:
|
| 754 |
+
denoise_t_list = torch.tensor([float(x) for x in args.denoise_t_list.split(",")])
|
| 755 |
+
logger.info("Using manual denoise t_list: %s", denoise_t_list.tolist())
|
| 756 |
+
elif args.denoise_shift is not None:
|
| 757 |
+
denoise_t_list = compute_denoise_t_list(
|
| 758 |
+
args.denoise_t, args.denoise_steps, args.denoise_shift, args.denoise_shift_total_steps
|
| 759 |
+
)
|
| 760 |
+
logger.info("Using shift-computed denoise t_list (shift=%.1f, steps=%d): %s",
|
| 761 |
+
args.denoise_shift, args.denoise_steps, denoise_t_list.tolist())
|
| 762 |
+
else:
|
| 763 |
+
logger.info("Running denoise augmentation evaluation for %s (t=%.3f, steps=%d)...",
|
| 764 |
+
model_name, args.denoise_t, args.denoise_steps)
|
| 765 |
+
save_recon_dir = None
|
| 766 |
+
if args.save_recon_images and accelerator.is_main_process:
|
| 767 |
+
save_recon_dir = str(ckpt_output_dir / "recon_images" / f"{model_name}_denoise_t{args.denoise_t}_s{args.denoise_steps}")
|
| 768 |
+
# Use gfid_dataset (per-class train) if --recon-gfid is also set, else eval_dataset
|
| 769 |
+
denoise_dataset = gfid_dataset if (args.recon_gfid and gfid_dataset is not None) else eval_dataset
|
| 770 |
+
denoise_stats = evaluate_denoise_reconstruction(
|
| 771 |
+
pr=eval_mod,
|
| 772 |
+
dit_model=dit_model,
|
| 773 |
+
dataset=denoise_dataset,
|
| 774 |
+
denoise_t=args.denoise_t,
|
| 775 |
+
denoise_steps=args.denoise_steps,
|
| 776 |
+
batch_size=args.batch_size,
|
| 777 |
+
autocast_kwargs=autocast_kwargs,
|
| 778 |
+
reference_npz_path=args.gfid_npz if (args.recon_gfid and gfid_dataset is not None) else reference_npz,
|
| 779 |
+
metrics_to_compute=eval_metrics,
|
| 780 |
+
accelerator=accelerator,
|
| 781 |
+
experiment_dir=output_dir,
|
| 782 |
+
encoder_cos_sim_model=encoder_cos_sim_model,
|
| 783 |
+
psnr_resize=psnr_resize,
|
| 784 |
+
l1_resize=l1_resize,
|
| 785 |
+
num_samples=args.num_samples,
|
| 786 |
+
save_recon_dir=save_recon_dir,
|
| 787 |
+
t_list=denoise_t_list,
|
| 788 |
+
save_latents_dir=os.path.join(output_dir, "denoise_latents") if args.save_denoise_latents else None,
|
| 789 |
+
)
|
| 790 |
+
if denoise_stats:
|
| 791 |
+
model_results[f"denoise_t{args.denoise_t}_s{args.denoise_steps}"] = {
|
| 792 |
+
k: float(v) for k, v in denoise_stats.items()
|
| 793 |
+
}
|
| 794 |
+
if accelerator.is_main_process and denoise_stats:
|
| 795 |
+
logger.info("%s denoise augment metrics: %s", model_name, denoise_stats)
|
| 796 |
+
elif args.recon_gfid and gfid_dataset is not None:
|
| 797 |
+
# ── Recon gFID mode: reconstruct train images, compute gFID vs precomputed stats ──
|
| 798 |
+
logger.info("Running recon gFID evaluation for %s (%d images)...", model_name, len(gfid_dataset))
|
| 799 |
+
save_recon_dir = None
|
| 800 |
+
if args.save_recon_images and accelerator.is_main_process:
|
| 801 |
+
save_recon_dir = str(ckpt_output_dir / "recon_images" / f"{model_name}_recon_gfid")
|
| 802 |
+
gfid_stats = evaluate_recon_gfid(
|
| 803 |
+
model=eval_mod,
|
| 804 |
+
dataset=gfid_dataset,
|
| 805 |
+
gfid_npz_path=args.gfid_npz,
|
| 806 |
+
batch_size=args.batch_size,
|
| 807 |
+
autocast_kwargs=autocast_kwargs,
|
| 808 |
+
accelerator=accelerator,
|
| 809 |
+
experiment_dir=output_dir,
|
| 810 |
+
num_samples=len(gfid_dataset),
|
| 811 |
+
image_features_mode=eval_modes[0] if eval_modes[0] else None,
|
| 812 |
+
save_recon_dir=save_recon_dir,
|
| 813 |
+
)
|
| 814 |
+
if gfid_stats:
|
| 815 |
+
model_results["recon_gfid"] = {k: float(v) for k, v in gfid_stats.items()}
|
| 816 |
+
if accelerator.is_main_process and gfid_stats:
|
| 817 |
+
logger.info("%s recon gFID metrics: %s", model_name, gfid_stats)
|
| 818 |
+
else:
|
| 819 |
+
# ── Normal reconstruction evaluation ──
|
| 820 |
+
logger.info("Running reconstruction evaluation for %s...", model_name)
|
| 821 |
+
for eval_mode in eval_modes:
|
| 822 |
+
print(eval_mode)
|
| 823 |
+
save_recon_dir = None
|
| 824 |
+
if args.save_recon_images and accelerator.is_main_process:
|
| 825 |
+
subdir = model_name if eval_mode is None else f"{model_name}_{eval_mode}"
|
| 826 |
+
save_recon_dir = str(ckpt_output_dir / "recon_images" / subdir)
|
| 827 |
+
eval_stats = evaluate_reconstruction_distributed(
|
| 828 |
+
eval_mod,
|
| 829 |
+
eval_dataset,
|
| 830 |
+
args.num_samples if args.num_samples else len(eval_dataset),
|
| 831 |
+
batch_size=args.batch_size,
|
| 832 |
+
experiment_dir=output_dir,
|
| 833 |
+
global_step=0,
|
| 834 |
+
autocast_kwargs=autocast_kwargs,
|
| 835 |
+
reference_npz_path=reference_npz,
|
| 836 |
+
metrics_to_compute=eval_metrics,
|
| 837 |
+
accelerator=accelerator,
|
| 838 |
+
save_recon_dir=save_recon_dir,
|
| 839 |
+
image_features_mode=eval_mode,
|
| 840 |
+
encoder_cos_sim_model=encoder_cos_sim_model,
|
| 841 |
+
psnr_resize=psnr_resize,
|
| 842 |
+
l1_resize=l1_resize,
|
| 843 |
+
)
|
| 844 |
+
if eval_stats:
|
| 845 |
+
recon_key = "reconstruction" if eval_mode is None else f"reconstruction_{eval_mode}"
|
| 846 |
+
model_results[recon_key] = {k: float(v) for k, v in eval_stats.items()}
|
| 847 |
+
if accelerator.is_main_process and eval_stats:
|
| 848 |
+
mode_tag = "" if eval_mode is None else f" [{eval_mode}]"
|
| 849 |
+
logger.info("%s reconstruction metrics%s: %s", model_name, mode_tag, eval_stats)
|
| 850 |
+
|
| 851 |
+
if do_zeroshot and zs_data_path and accelerator.is_main_process and not use_external_vae:
|
| 852 |
+
enc_cls = eval_mod.encoder.__class__.__name__
|
| 853 |
+
if "siglip" not in enc_cls.lower():
|
| 854 |
+
logger.warning("Zero-shot skipped for %s: encoder %s is not SigLIP-based.", model_name, enc_cls)
|
| 855 |
+
else:
|
| 856 |
+
try:
|
| 857 |
+
from transformers import AutoProcessor, SiglipModel
|
| 858 |
+
sp = full_cfg.get("stage_1")
|
| 859 |
+
params = getattr(sp, "params", None) if sp is not None else None
|
| 860 |
+
enc_params = getattr(params, "encoder_params", None) if params is not None else None
|
| 861 |
+
siglip_path = None
|
| 862 |
+
if enc_params is not None:
|
| 863 |
+
siglip_path = getattr(enc_params, "model_name", None) or getattr(enc_params, "sementic_model_name", None)
|
| 864 |
+
if not siglip_path and params is not None:
|
| 865 |
+
siglip_path = getattr(params, "encoder_config_path", None)
|
| 866 |
+
siglip_path = str(siglip_path) if siglip_path else ""
|
| 867 |
+
if siglip_path:
|
| 868 |
+
if not zs_initialized:
|
| 869 |
+
logger.info("Loading full SigLIP for zero-shot from %s", siglip_path)
|
| 870 |
+
zs_full_siglip = SiglipModel.from_pretrained(siglip_path).to(device).eval()
|
| 871 |
+
zs_processor = AutoProcessor.from_pretrained(siglip_path)
|
| 872 |
+
zs_classifier = build_zero_shot_classifier(
|
| 873 |
+
zs_full_siglip,
|
| 874 |
+
zs_processor,
|
| 875 |
+
classnames=IMAGENET_CLASSNAMES,
|
| 876 |
+
templates=CLIP_PAPER_PROMPT_TEMPLATES,
|
| 877 |
+
num_classes_per_batch=10,
|
| 878 |
+
device=device,
|
| 879 |
+
use_tqdm=True,
|
| 880 |
+
)
|
| 881 |
+
zs_dataloader = create_imagenet_dataloader_patch_reparam(
|
| 882 |
+
zs_data_path,
|
| 883 |
+
eval_mod.encoder_input_size,
|
| 884 |
+
eval_mod.encoder_mean,
|
| 885 |
+
eval_mod.encoder_std,
|
| 886 |
+
batch_size=zs_batch_size,
|
| 887 |
+
num_workers=zs_num_workers,
|
| 888 |
+
)
|
| 889 |
+
zs_initialized = True
|
| 890 |
+
zs_wrapper = PatchReparamZeroShotWrapper(eval_mod.encoder, zs_full_siglip)
|
| 891 |
+
zs_wrapper.eval()
|
| 892 |
+
zs_top1, zs_top5 = evaluate_zero_shot(
|
| 893 |
+
zs_wrapper,
|
| 894 |
+
zs_processor,
|
| 895 |
+
zs_classifier,
|
| 896 |
+
zs_dataloader,
|
| 897 |
+
device=device,
|
| 898 |
+
precision=args.precision,
|
| 899 |
+
)
|
| 900 |
+
model_results["zeroshot"] = {"top1": float(zs_top1), "top5": float(zs_top5)}
|
| 901 |
+
logger.info("%s zero-shot top1: %.2f, top5: %.2f", model_name, zs_top1, zs_top5)
|
| 902 |
+
else:
|
| 903 |
+
logger.warning("Zero-shot disabled: no SigLIP path in config.")
|
| 904 |
+
except Exception as e:
|
| 905 |
+
logger.warning("Zero-shot failed for %s: %s", model_name, e)
|
| 906 |
+
|
| 907 |
+
results[model_name] = model_results
|
| 908 |
+
|
| 909 |
+
if do_zeroshot and not zs_data_path and accelerator.is_main_process:
|
| 910 |
+
logger.warning("Zero-shot enabled but data_path not set; skipping.")
|
| 911 |
+
|
| 912 |
+
accelerator.wait_for_everyone()
|
| 913 |
+
if accelerator.is_main_process and results:
|
| 914 |
+
result_stem = "external_vae" if use_external_vae else ("pretrained" if args.ckpt is None else Path(args.ckpt).stem)
|
| 915 |
+
result_path = ckpt_output_dir / f"{result_stem}.json"
|
| 916 |
+
suffix = 1
|
| 917 |
+
while result_path.exists():
|
| 918 |
+
result_path = ckpt_output_dir / f"{result_stem}_{suffix}.json"
|
| 919 |
+
suffix += 1
|
| 920 |
+
with open(result_path, "w", encoding="utf-8") as f:
|
| 921 |
+
json.dump(results, f, indent=2, ensure_ascii=False)
|
| 922 |
+
logger.info("Evaluation results saved to %s", result_path)
|
| 923 |
+
logger.info("Evaluation done.")
|
| 924 |
+
|
| 925 |
+
|
| 926 |
+
if __name__ == "__main__":
|
| 927 |
+
main()
|
patch-reparameterization/src/sample_ddp.py
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This source code is licensed under the license found in the
|
| 2 |
+
# LICENSE file in the root directory of this source tree.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
Samples a large number of images from a pre-trained stage-2 model using DDP and
|
| 6 |
+
stores results for downstream metrics. For single-device sampling, use sample.py.
|
| 7 |
+
|
| 8 |
+
When --reference-npz is provided, FID is computed after sampling completes.
|
| 9 |
+
Reference NPZ can be either:
|
| 10 |
+
- Precomputed stats (keys: mu, sigma) from Inception features
|
| 11 |
+
- Raw images (key: arr_0) - FID will be computed via torch_fidelity
|
| 12 |
+
"""
|
| 13 |
+
import sys
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import json
|
| 20 |
+
import logging
|
| 21 |
+
import math
|
| 22 |
+
from typing import Callable, Optional
|
| 23 |
+
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
import numpy as np
|
| 27 |
+
import torch
|
| 28 |
+
import torch.distributed as dist
|
| 29 |
+
from PIL import Image
|
| 30 |
+
from torch.cuda.amp import autocast
|
| 31 |
+
from tqdm import tqdm
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
from omegaconf import OmegaConf
|
| 34 |
+
from utils.config_utils import load_config # noqa: F401 (registers oc.env resolver)
|
| 35 |
+
from utils.model_utils import instantiate_from_config
|
| 36 |
+
from stage1 import PatchReparam
|
| 37 |
+
from stage2.models import Stage2ModelProtocol
|
| 38 |
+
from stage2.transport import create_transport, Sampler
|
| 39 |
+
from utils.train_utils import parse_configs, center_crop_arr
|
| 40 |
+
from eval.fid import calculate_gfid, calculate_rfid
|
| 41 |
+
from torchvision.datasets import ImageFolder
|
| 42 |
+
from torchvision import transforms
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _safe_load_checkpoint(path: str, map_location: str = "cpu"):
|
| 46 |
+
try:
|
| 47 |
+
return torch.load(path, map_location=map_location, weights_only=False)
|
| 48 |
+
except TypeError:
|
| 49 |
+
# For older PyTorch versions without weights_only arg
|
| 50 |
+
return torch.load(path, map_location=map_location)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _load_patch_reparam_checkpoint(pr: PatchReparam, ckpt_path: str, use_ema: bool = True) -> None:
|
| 54 |
+
"""
|
| 55 |
+
Load checkpoint weights into PatchReparam, choosing between EMA and model weights.
|
| 56 |
+
Mirrors the logic in eval_inversion_error._load_patch_reparam_checkpoint.
|
| 57 |
+
"""
|
| 58 |
+
logger.info("Loading PatchReparam checkpoint from %s (use_ema=%s)", ckpt_path, use_ema)
|
| 59 |
+
ckpt = _safe_load_checkpoint(ckpt_path, map_location="cpu")
|
| 60 |
+
state_dict = ckpt.get("ema" if use_ema else "model")
|
| 61 |
+
if state_dict is None:
|
| 62 |
+
raise KeyError(f"Checkpoint must contain 'ema' or 'model'. Got keys: {list(ckpt.keys())}")
|
| 63 |
+
|
| 64 |
+
clean_state = {}
|
| 65 |
+
for k, v in state_dict.items():
|
| 66 |
+
clean_k = k
|
| 67 |
+
for prefix in ["module._orig_mod.", "_orig_mod.", "module."]:
|
| 68 |
+
if clean_k.startswith(prefix):
|
| 69 |
+
clean_k = clean_k[len(prefix):]
|
| 70 |
+
break
|
| 71 |
+
clean_state[clean_k] = v
|
| 72 |
+
|
| 73 |
+
missing, unexpected = pr.load_state_dict(clean_state, strict=False)
|
| 74 |
+
if missing:
|
| 75 |
+
logger.warning("PatchReparam checkpoint missing %d keys (first 5): %s...", len(missing), missing[:5])
|
| 76 |
+
if unexpected:
|
| 77 |
+
logger.warning("PatchReparam checkpoint unexpected %d keys (first 5): %s...", len(unexpected), unexpected[:5])
|
| 78 |
+
logger.info("PatchReparam checkpoint loaded successfully.")
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def create_npz_from_sample_folder(sample_dir, num=50_000):
|
| 82 |
+
"""
|
| 83 |
+
Builds a single .npz file from a folder of .png samples.
|
| 84 |
+
"""
|
| 85 |
+
samples = []
|
| 86 |
+
for i in tqdm(range(num), desc="Building .npz file from samples"):
|
| 87 |
+
sample_pil = Image.open(f"{sample_dir}/{i:06d}.png")
|
| 88 |
+
sample_np = np.asarray(sample_pil).astype(np.uint8)
|
| 89 |
+
samples.append(sample_np)
|
| 90 |
+
samples = np.stack(samples)
|
| 91 |
+
assert samples.shape == (num, samples.shape[1], samples.shape[2], 3)
|
| 92 |
+
npz_path = f"{sample_dir}.npz"
|
| 93 |
+
np.savez(npz_path, arr_0=samples)
|
| 94 |
+
logger.info("Saved .npz file to %s [shape=%s].", npz_path, samples.shape)
|
| 95 |
+
return npz_path
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def compute_fid_from_samples(
|
| 99 |
+
gen_npz_path: str,
|
| 100 |
+
reference_npz_path: str,
|
| 101 |
+
device: torch.device,
|
| 102 |
+
batch_size: int = 128,
|
| 103 |
+
) -> float:
|
| 104 |
+
"""
|
| 105 |
+
Compute FID between generated samples and reference.
|
| 106 |
+
Reference NPZ can be:
|
| 107 |
+
- Precomputed stats: keys 'mu', 'sigma' (Inception moments)
|
| 108 |
+
- Raw images: key 'arr_0'
|
| 109 |
+
"""
|
| 110 |
+
gen_data = np.load(gen_npz_path)
|
| 111 |
+
gen_arr = gen_data["arr_0"]
|
| 112 |
+
|
| 113 |
+
ref_data = np.load(reference_npz_path)
|
| 114 |
+
ref_files = list(ref_data.files)
|
| 115 |
+
|
| 116 |
+
device_str = "cuda" if device.type == "cuda" else "cpu"
|
| 117 |
+
|
| 118 |
+
if "mu" in ref_files and "sigma" in ref_files:
|
| 119 |
+
fid = calculate_gfid(gen_arr, ref_data, batch_size=batch_size, device=device_str)
|
| 120 |
+
elif "arr_0" in ref_files:
|
| 121 |
+
ref_arr = ref_data["arr_0"]
|
| 122 |
+
fid = calculate_rfid(gen_arr, ref_arr, bs=batch_size, device=device_str)
|
| 123 |
+
else:
|
| 124 |
+
raise KeyError(
|
| 125 |
+
f"Reference NPZ must have 'mu'/'sigma' (precomputed stats) or 'arr_0' (raw images). "
|
| 126 |
+
f"Got keys: {ref_files}"
|
| 127 |
+
)
|
| 128 |
+
return float(fid)
|
| 129 |
+
|
| 130 |
+
def build_label_sampler(
|
| 131 |
+
sampling_mode: str,
|
| 132 |
+
num_classes: int,
|
| 133 |
+
num_fid_samples: int,
|
| 134 |
+
total_samples: int,
|
| 135 |
+
samples_needed_this_device: int,
|
| 136 |
+
batch_size: int,
|
| 137 |
+
device: torch.device,
|
| 138 |
+
rank: int,
|
| 139 |
+
iterations: int,
|
| 140 |
+
seed: int,
|
| 141 |
+
eval_data: Optional[str] = None,
|
| 142 |
+
image_size: int = 256,
|
| 143 |
+
) -> Callable[[int], torch.Tensor]:
|
| 144 |
+
"""Create a callable that returns a batch of labels for the given step index.
|
| 145 |
+
When mode='dataset', labels follow val dataset order (same as training eval)."""
|
| 146 |
+
|
| 147 |
+
if sampling_mode == "dataset":
|
| 148 |
+
if not eval_data or not os.path.isdir(eval_data):
|
| 149 |
+
raise ValueError(
|
| 150 |
+
"dataset mode requires --eval-data (or config eval.data_path) pointing to ImageFolder val dir."
|
| 151 |
+
)
|
| 152 |
+
dataset = ImageFolder(
|
| 153 |
+
eval_data,
|
| 154 |
+
transform=transforms.Compose([
|
| 155 |
+
transforms.Lambda(lambda x: center_crop_arr(x, image_size)),
|
| 156 |
+
transforms.ToTensor(),
|
| 157 |
+
]),
|
| 158 |
+
)
|
| 159 |
+
N = min(len(dataset), num_fid_samples)
|
| 160 |
+
world_size = total_samples // samples_needed_this_device
|
| 161 |
+
chunk = total_samples // world_size # == samples_needed_this_device
|
| 162 |
+
start = rank * chunk
|
| 163 |
+
end = min(start + chunk, total_samples)
|
| 164 |
+
rank_indices = list(range(start, end))
|
| 165 |
+
device_pool = torch.tensor([dataset.targets[i] for i in rank_indices], dtype=torch.long)
|
| 166 |
+
needed = iterations * batch_size
|
| 167 |
+
if len(device_pool) < needed:
|
| 168 |
+
gen = torch.Generator().manual_seed(seed + rank)
|
| 169 |
+
device_pool = torch.cat([
|
| 170 |
+
device_pool,
|
| 171 |
+
torch.randint(0, num_classes, (needed - len(device_pool),), generator=gen),
|
| 172 |
+
])
|
| 173 |
+
device_pool = device_pool[:needed].view(iterations, batch_size)
|
| 174 |
+
|
| 175 |
+
def dataset_sampler(step_idx: int) -> torch.Tensor:
|
| 176 |
+
return device_pool[step_idx].to(device)
|
| 177 |
+
|
| 178 |
+
return dataset_sampler
|
| 179 |
+
|
| 180 |
+
if sampling_mode == "random":
|
| 181 |
+
def random_sampler(_step_idx: int) -> torch.Tensor:
|
| 182 |
+
return torch.randint(0, num_classes, (batch_size,), device=device)
|
| 183 |
+
|
| 184 |
+
return random_sampler
|
| 185 |
+
|
| 186 |
+
if sampling_mode == "equal":
|
| 187 |
+
if num_fid_samples % num_classes != 0:
|
| 188 |
+
raise ValueError(
|
| 189 |
+
f"Equal label sampling requires num_fid_samples ({num_fid_samples}) to be divisible by num_classes ({num_classes})."
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
labels_per_class = num_fid_samples // num_classes
|
| 193 |
+
base_pool = torch.arange(num_classes, dtype=torch.long).repeat_interleave(labels_per_class)
|
| 194 |
+
|
| 195 |
+
generator = torch.Generator()
|
| 196 |
+
generator.manual_seed(seed)
|
| 197 |
+
permutation = torch.randperm(base_pool.numel(), generator=generator)
|
| 198 |
+
base_pool = base_pool[permutation]
|
| 199 |
+
|
| 200 |
+
if total_samples > num_fid_samples:
|
| 201 |
+
tail = torch.randint(0, num_classes, (total_samples - num_fid_samples,), generator=generator)
|
| 202 |
+
global_pool = torch.cat([base_pool, tail], dim=0)
|
| 203 |
+
else:
|
| 204 |
+
global_pool = base_pool
|
| 205 |
+
|
| 206 |
+
start = rank * samples_needed_this_device
|
| 207 |
+
end = start + samples_needed_this_device
|
| 208 |
+
device_pool = global_pool[start:end]
|
| 209 |
+
device_pool = device_pool.view(iterations, batch_size)
|
| 210 |
+
|
| 211 |
+
def equal_sampler(step_idx: int) -> torch.Tensor:
|
| 212 |
+
labels = device_pool[step_idx]
|
| 213 |
+
return labels.to(device)
|
| 214 |
+
|
| 215 |
+
return equal_sampler
|
| 216 |
+
raise ValueError(f"Unknown label sampling mode: {sampling_mode}")
|
| 217 |
+
|
| 218 |
+
def main(args):
|
| 219 |
+
"""Run sampling with distributed execution."""
|
| 220 |
+
if not torch.cuda.is_available():
|
| 221 |
+
raise RuntimeError("Sampling with DDP requires at least one GPU. Use sample.py for single-device usage.")
|
| 222 |
+
|
| 223 |
+
torch.backends.cuda.matmul.allow_tf32 = args.tf32
|
| 224 |
+
torch.backends.cudnn.allow_tf32 = args.tf32
|
| 225 |
+
torch.set_grad_enabled(False)
|
| 226 |
+
|
| 227 |
+
dist.init_process_group("nccl")
|
| 228 |
+
rank = dist.get_rank()
|
| 229 |
+
world_size = dist.get_world_size()
|
| 230 |
+
device_idx = rank % torch.cuda.device_count()
|
| 231 |
+
torch.cuda.set_device(device_idx)
|
| 232 |
+
device = torch.device("cuda", device_idx)
|
| 233 |
+
|
| 234 |
+
if rank == 0:
|
| 235 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
| 236 |
+
else:
|
| 237 |
+
logger.addHandler(logging.NullHandler())
|
| 238 |
+
logger.propagate = False
|
| 239 |
+
|
| 240 |
+
seed = args.global_seed * world_size + rank
|
| 241 |
+
torch.manual_seed(seed)
|
| 242 |
+
torch.cuda.manual_seed(seed)
|
| 243 |
+
if rank == 0:
|
| 244 |
+
logger.info("Starting rank=%s, seed=%s, world_size=%s.", rank, seed, world_size)
|
| 245 |
+
|
| 246 |
+
use_bf16 = args.precision == "bf16"
|
| 247 |
+
if use_bf16 and not torch.cuda.is_bf16_supported():
|
| 248 |
+
raise ValueError("Requested bf16 precision, but the current CUDA device does not support bfloat16.")
|
| 249 |
+
autocast_kwargs = dict(dtype=torch.bfloat16, enabled=use_bf16)
|
| 250 |
+
cfg = OmegaConf.load(args.config)
|
| 251 |
+
if args.ckpt is not None:
|
| 252 |
+
OmegaConf.update(cfg, "stage_2.ckpt", args.ckpt, force_add=True)
|
| 253 |
+
if args.cfg_scale is not None:
|
| 254 |
+
OmegaConf.update(cfg, "guidance.scale", args.cfg_scale, force_add=True)
|
| 255 |
+
if args.precomputed_latents_dir is not None:
|
| 256 |
+
val = None if args.precomputed_latents_dir.lower() == "none" else args.precomputed_latents_dir
|
| 257 |
+
OmegaConf.update(cfg, "misc.precomputed_latents_dir", val, force_add=True)
|
| 258 |
+
pr_config, model_config, transport_config, sampler_config, guidance_config, misc, _, _ = parse_configs(cfg)
|
| 259 |
+
# ── VAE mode: if vae_decoder is specified, use external VAE instead of PatchReparam ──
|
| 260 |
+
vae_decoder_cfg = cfg.get("vae_decoder", None)
|
| 261 |
+
use_external_vae = vae_decoder_cfg is not None
|
| 262 |
+
|
| 263 |
+
if not use_external_vae and (pr_config is None or model_config is None):
|
| 264 |
+
raise ValueError("Config must provide both stage_1 and stage_2 entries (or use vae_decoder).")
|
| 265 |
+
if use_external_vae and model_config is None:
|
| 266 |
+
raise ValueError("Config must provide stage_2 section.")
|
| 267 |
+
|
| 268 |
+
misc = {} if misc is None else dict(misc)
|
| 269 |
+
|
| 270 |
+
latent_size = tuple(int(dim) for dim in misc.get("latent_size", (768, 16, 16)))
|
| 271 |
+
shift_dim = misc.get("time_dist_shift_dim", math.prod(latent_size))
|
| 272 |
+
shift_base = misc.get("time_dist_shift_base", 4096)
|
| 273 |
+
time_dist_shift = math.sqrt(shift_dim / shift_base)
|
| 274 |
+
if rank == 0:
|
| 275 |
+
logger.info("Using time_dist_shift=%.4f.", time_dist_shift)
|
| 276 |
+
|
| 277 |
+
# ------------------------------------------------------------------
|
| 278 |
+
# Optional DiT latent caching / reuse
|
| 279 |
+
# ------------------------------------------------------------------
|
| 280 |
+
precomputed_latents_dir = None
|
| 281 |
+
save_latents = False
|
| 282 |
+
latents_dir = None
|
| 283 |
+
if misc is not None:
|
| 284 |
+
precomputed_latents_dir = misc.get("precomputed_latents_dir", None)
|
| 285 |
+
save_latents = bool(misc.get("save_latents", False))
|
| 286 |
+
latents_dir = misc.get("latents_dir", None)
|
| 287 |
+
|
| 288 |
+
external_vae = None
|
| 289 |
+
vae_scaling_factor = None
|
| 290 |
+
pr = None
|
| 291 |
+
proj_down_weight = None
|
| 292 |
+
proj_norm_mode = None
|
| 293 |
+
proj_norm_mean = None
|
| 294 |
+
proj_norm_std = None
|
| 295 |
+
|
| 296 |
+
if use_external_vae:
|
| 297 |
+
from diffusers import AutoencoderKL
|
| 298 |
+
vae_path = vae_decoder_cfg.get("path") if hasattr(vae_decoder_cfg, "get") else str(vae_decoder_cfg)
|
| 299 |
+
logger.info(f"Loading external VAE from {vae_path}")
|
| 300 |
+
external_vae = AutoencoderKL.from_pretrained(vae_path).to(device).eval()
|
| 301 |
+
external_vae.requires_grad_(False)
|
| 302 |
+
vae_scaling_factor = external_vae.config.scaling_factor
|
| 303 |
+
logger.info(f"External VAE loaded: scaling_factor={vae_scaling_factor}")
|
| 304 |
+
|
| 305 |
+
# Optional projection (for high-dim experiment decoding)
|
| 306 |
+
proj_cfg = vae_decoder_cfg.get("projection", None) if hasattr(vae_decoder_cfg, "get") else None
|
| 307 |
+
if proj_cfg is not None:
|
| 308 |
+
proj_cfg = OmegaConf.to_container(proj_cfg, resolve=True) if hasattr(proj_cfg, '_dict') or type(proj_cfg).__name__ == 'DictConfig' else dict(proj_cfg)
|
| 309 |
+
weight_path = proj_cfg.get("weight_path", None)
|
| 310 |
+
if not weight_path or not os.path.exists(weight_path):
|
| 311 |
+
raise FileNotFoundError(f"Projection weight_path required for sampling: {weight_path}")
|
| 312 |
+
proj_up_weight = torch.load(weight_path, map_location="cpu").to(device)
|
| 313 |
+
proj_down_weight = proj_up_weight.T.contiguous()
|
| 314 |
+
logger.info(f"Loaded projection matrix from {weight_path}: {proj_up_weight.shape}")
|
| 315 |
+
|
| 316 |
+
proj_norm_mode = proj_cfg.get("norm_mode", "global")
|
| 317 |
+
if proj_norm_mode == "per_dim":
|
| 318 |
+
norm_stat_path = proj_cfg.get("norm_stat_path", None)
|
| 319 |
+
if norm_stat_path and os.path.exists(norm_stat_path):
|
| 320 |
+
stats = torch.load(norm_stat_path, map_location="cpu")
|
| 321 |
+
proj_norm_mean = stats["mean"].to(device)
|
| 322 |
+
proj_norm_std = torch.sqrt(stats["var"].to(device) + 1e-5)
|
| 323 |
+
logger.info(f"Loaded per-dim norm stats from {norm_stat_path}")
|
| 324 |
+
else:
|
| 325 |
+
raise FileNotFoundError(f"per_dim norm requires norm_stat_path: {norm_stat_path}")
|
| 326 |
+
else:
|
| 327 |
+
pr: PatchReparam = instantiate_from_config(pr_config).to(device)
|
| 328 |
+
pr_ckpt_cfg = pr_config.get("checkpoint") if hasattr(pr_config, "get") else None
|
| 329 |
+
if pr_ckpt_cfg:
|
| 330 |
+
if isinstance(pr_ckpt_cfg, (str, Path)):
|
| 331 |
+
ckpt_path = str(pr_ckpt_cfg)
|
| 332 |
+
use_ema = True
|
| 333 |
+
else:
|
| 334 |
+
ckpt_path = pr_ckpt_cfg.get("path") or pr_ckpt_cfg.get("ckpt_path")
|
| 335 |
+
use_ema = pr_ckpt_cfg.get("use_ema", True)
|
| 336 |
+
if ckpt_path and str(ckpt_path).strip():
|
| 337 |
+
_load_patch_reparam_checkpoint(pr, str(ckpt_path), use_ema=use_ema)
|
| 338 |
+
else:
|
| 339 |
+
logger.warning("PatchReparam checkpoint config present but path is empty.")
|
| 340 |
+
else:
|
| 341 |
+
logger.info("No PatchReparam checkpoint specified.")
|
| 342 |
+
pr.eval()
|
| 343 |
+
|
| 344 |
+
model: Stage2ModelProtocol = instantiate_from_config(model_config).to(device)
|
| 345 |
+
model.eval()
|
| 346 |
+
|
| 347 |
+
transport_params = {}
|
| 348 |
+
if transport_config is not None:
|
| 349 |
+
transport_params = dict(transport_config.get("params", {}))
|
| 350 |
+
transport = create_transport(
|
| 351 |
+
**transport_params,
|
| 352 |
+
time_dist_shift=time_dist_shift,
|
| 353 |
+
)
|
| 354 |
+
sampler = Sampler(transport)
|
| 355 |
+
|
| 356 |
+
sampler_config = {} if sampler_config is None else dict(sampler_config)
|
| 357 |
+
sampler_mode = sampler_config.get("mode", "ODE")
|
| 358 |
+
sampler_params = dict(sampler_config.get("params", {}))
|
| 359 |
+
mode = sampler_mode.upper()
|
| 360 |
+
if mode == "ODE":
|
| 361 |
+
sample_fn = sampler.sample_ode(**sampler_params)
|
| 362 |
+
elif mode == "SDE":
|
| 363 |
+
sample_fn = sampler.sample_sde(**sampler_params)
|
| 364 |
+
else:
|
| 365 |
+
raise NotImplementedError(f"Invalid sampling mode {sampler_mode}.")
|
| 366 |
+
|
| 367 |
+
guidance_config = {} if guidance_config is None else dict(guidance_config)
|
| 368 |
+
|
| 369 |
+
def guidance_value(key: str, default: float):
|
| 370 |
+
if key in guidance_config:
|
| 371 |
+
return guidance_config[key]
|
| 372 |
+
dashed_key = key.replace("_", "-")
|
| 373 |
+
return guidance_config.get(dashed_key, default)
|
| 374 |
+
|
| 375 |
+
guidance_scale = guidance_config.get("scale", 1.0)
|
| 376 |
+
guidance_method = guidance_config.get("method", "cfg")
|
| 377 |
+
t_min = guidance_value("t_min", 0.0)
|
| 378 |
+
t_max = guidance_value("t_max", 1.0)
|
| 379 |
+
|
| 380 |
+
guid_model_forward = None
|
| 381 |
+
if guidance_scale > 1.0 and guidance_method == "autoguidance":
|
| 382 |
+
guid_model_config = guidance_config.get("guidance_model")
|
| 383 |
+
if guid_model_config is None:
|
| 384 |
+
raise ValueError("Please provide a guidance model config when using autoguidance.")
|
| 385 |
+
guid_model: Stage2ModelProtocol = instantiate_from_config(guid_model_config).to(device)
|
| 386 |
+
guid_model.eval()
|
| 387 |
+
guid_model_forward = guid_model.forward
|
| 388 |
+
|
| 389 |
+
num_classes = int(misc.get("num_classes", 1000))
|
| 390 |
+
null_label = int(misc.get("null_label", num_classes))
|
| 391 |
+
|
| 392 |
+
model_target = model_config.get("target", "stage2")
|
| 393 |
+
model_string_name = str(model_target).split(".")[-1]
|
| 394 |
+
ckpt_path = model_config.get("ckpt")
|
| 395 |
+
ckpt_string_name = "pretrained" if not ckpt_path else os.path.splitext(os.path.basename(str(ckpt_path)))[0]
|
| 396 |
+
sampling_method = sampler_params.get("sampling_method", "na")
|
| 397 |
+
num_steps = sampler_params.get("num_steps", sampler_params.get("steps", "na"))
|
| 398 |
+
guidance_tag = f"cfg-{guidance_scale:.2f}"
|
| 399 |
+
base_components = [model_string_name, ckpt_string_name, guidance_tag, f"bs{args.per_proc_batch_size}"]
|
| 400 |
+
logger.info(f"args.per_proc_batch_size{args.per_proc_batch_size}")
|
| 401 |
+
if mode == "ODE":
|
| 402 |
+
detail_components = [mode, str(num_steps), str(sampling_method), args.precision]
|
| 403 |
+
else:
|
| 404 |
+
diffusion_form = sampler_params.get("diffusion_form", "na")
|
| 405 |
+
last_step = sampler_params.get("last_step", "na")
|
| 406 |
+
last_step_size = sampler_params.get("last_step_size", "na")
|
| 407 |
+
detail_components = [mode, str(num_steps), str(sampling_method), str(diffusion_form), str(last_step), str(last_step_size), args.precision]
|
| 408 |
+
folder_name = "-".join(component.replace(os.sep, "-") for component in base_components + detail_components)
|
| 409 |
+
possible_folder_name = os.environ.get('SAVE_FOLDER', None)
|
| 410 |
+
if possible_folder_name:
|
| 411 |
+
sample_folder_dir = os.path.join(args.sample_dir, possible_folder_name)
|
| 412 |
+
else:
|
| 413 |
+
sample_folder_dir = os.path.join(args.sample_dir, folder_name)
|
| 414 |
+
# Parent dir for result files (one level above image folder)
|
| 415 |
+
result_parent_dir = Path(sample_folder_dir).parent
|
| 416 |
+
folder_base = Path(sample_folder_dir).name
|
| 417 |
+
|
| 418 |
+
if rank == 0:
|
| 419 |
+
os.makedirs(sample_folder_dir, exist_ok=True)
|
| 420 |
+
logger.info("Saving .png samples at %s", sample_folder_dir)
|
| 421 |
+
|
| 422 |
+
# Determine directory to read/write DiT latents.
|
| 423 |
+
if latents_dir is None:
|
| 424 |
+
latents_dir = os.path.join(sample_folder_dir, "latents")
|
| 425 |
+
|
| 426 |
+
# Save config and startup args for reproducibility (in parent dir, not inside image folder)
|
| 427 |
+
config_save_path = result_parent_dir / f"{folder_base}_sample_config.yaml"
|
| 428 |
+
OmegaConf.save(cfg, config_save_path)
|
| 429 |
+
logger.info("Config saved to %s", config_save_path)
|
| 430 |
+
args_dict = vars(args)
|
| 431 |
+
args_save_path = result_parent_dir / f"{folder_base}_sample_args.json"
|
| 432 |
+
with open(args_save_path, "w", encoding="utf-8") as f:
|
| 433 |
+
json.dump(args_dict, f, indent=2, ensure_ascii=False)
|
| 434 |
+
logger.info("Args saved to %s", args_save_path)
|
| 435 |
+
# Broadcast latents_dir from rank 0 so all ranks agree.
|
| 436 |
+
latents_dir_obj = {"latents_dir": latents_dir}
|
| 437 |
+
obj_list = [latents_dir_obj]
|
| 438 |
+
dist.broadcast_object_list(obj_list, src=0)
|
| 439 |
+
latents_dir = obj_list[0]["latents_dir"]
|
| 440 |
+
if save_latents and latents_dir is not None and rank == 0:
|
| 441 |
+
os.makedirs(latents_dir, exist_ok=True)
|
| 442 |
+
dist.barrier()
|
| 443 |
+
|
| 444 |
+
# If npz already exists with enough samples, skip sampling to avoid redundant computation
|
| 445 |
+
npz_path = f"{sample_folder_dir}.npz"
|
| 446 |
+
skip_sampling = False
|
| 447 |
+
if rank == 0:
|
| 448 |
+
logger.info("NPZ path: %s, isfile: %s", npz_path, os.path.isfile(npz_path))
|
| 449 |
+
if os.path.isfile(npz_path):
|
| 450 |
+
try:
|
| 451 |
+
data = np.load(npz_path)
|
| 452 |
+
arr = data["arr_0"]
|
| 453 |
+
logger.info("NPZ already exists at %s with %d samples.", npz_path, arr.shape[0])
|
| 454 |
+
if arr.shape[0] >= args.num_fid_samples:
|
| 455 |
+
skip_sampling = True
|
| 456 |
+
logger.info("NPZ already exists at %s with %d samples, skipping sampling.", npz_path, arr.shape[0])
|
| 457 |
+
except Exception as e:
|
| 458 |
+
logger.exception("Error loading NPZ: %s", e)
|
| 459 |
+
# Fall back to existing PNG samples if they are complete enough.
|
| 460 |
+
try:
|
| 461 |
+
existing_files = [
|
| 462 |
+
name
|
| 463 |
+
for name in os.listdir(sample_folder_dir)
|
| 464 |
+
if os.path.isfile(os.path.join(sample_folder_dir, name)) and name.endswith(".png")
|
| 465 |
+
]
|
| 466 |
+
existing_set = set(existing_files)
|
| 467 |
+
expected_files = {f"{i:06d}.png" for i in range(args.num_fid_samples)}
|
| 468 |
+
if expected_files.issubset(existing_set):
|
| 469 |
+
logger.info(
|
| 470 |
+
"Found %d PNG samples and a broken NPZ. Rebuilding NPZ from existing images and skipping sampling.",
|
| 471 |
+
len(existing_files),
|
| 472 |
+
)
|
| 473 |
+
create_npz_from_sample_folder(sample_folder_dir, args.num_fid_samples)
|
| 474 |
+
skip_sampling = True
|
| 475 |
+
else:
|
| 476 |
+
logger.info(
|
| 477 |
+
"Existing PNG samples are incomplete (%d found, need at least %d). Proceeding to resample.",
|
| 478 |
+
len(existing_files),
|
| 479 |
+
args.num_fid_samples,
|
| 480 |
+
)
|
| 481 |
+
except Exception as e2:
|
| 482 |
+
logger.exception("Error while checking/rebuilding NPZ from PNGs: %s", e2)
|
| 483 |
+
skip_tensor = torch.tensor(1 if skip_sampling else 0, device=device)
|
| 484 |
+
dist.broadcast(skip_tensor, 0)
|
| 485 |
+
skip_sampling = skip_tensor.item() == 1
|
| 486 |
+
logger.info("Skip sampling (existing images/NPZ): %s", skip_sampling)
|
| 487 |
+
n = args.per_proc_batch_size
|
| 488 |
+
global_batch_size = n * world_size
|
| 489 |
+
existing = [name for name in os.listdir(sample_folder_dir) if (os.path.isfile(os.path.join(sample_folder_dir, name)) and name.endswith(".png"))]
|
| 490 |
+
num_samples = len(existing)
|
| 491 |
+
total_samples = int(math.ceil(args.num_fid_samples / global_batch_size) * global_batch_size)
|
| 492 |
+
if rank == 0:
|
| 493 |
+
logger.info("Total number of images that will be sampled: %d", total_samples)
|
| 494 |
+
if total_samples % world_size != 0:
|
| 495 |
+
raise ValueError("Total samples must be divisible by world size.")
|
| 496 |
+
samples_needed_this_gpu = total_samples // world_size
|
| 497 |
+
if samples_needed_this_gpu % n != 0:
|
| 498 |
+
raise ValueError("Per-rank sample count must be divisible by the per-GPU batch size.")
|
| 499 |
+
iterations = samples_needed_this_gpu // n
|
| 500 |
+
# Resume: align to global_batch_size boundary to preserve label consistency
|
| 501 |
+
total = (num_samples // global_batch_size) * global_batch_size
|
| 502 |
+
start_step = total // global_batch_size
|
| 503 |
+
remaining_iterations = iterations - start_step
|
| 504 |
+
if rank == 0 and start_step > 0:
|
| 505 |
+
logger.info("Resuming from step %d (%d images exist, aligned to %d), %d steps remaining.",
|
| 506 |
+
start_step, num_samples, total, remaining_iterations)
|
| 507 |
+
pbar = tqdm(range(remaining_iterations)) if rank == 0 else range(remaining_iterations)
|
| 508 |
+
|
| 509 |
+
eval_data = args.eval_data
|
| 510 |
+
if eval_data is None and cfg is not None:
|
| 511 |
+
eval_section = cfg.get("eval", None)
|
| 512 |
+
if eval_section is not None:
|
| 513 |
+
eval_data = eval_section.get("data_path", None)
|
| 514 |
+
label_sampler = build_label_sampler(
|
| 515 |
+
args.label_sampling,
|
| 516 |
+
num_classes,
|
| 517 |
+
args.num_fid_samples,
|
| 518 |
+
total_samples,
|
| 519 |
+
samples_needed_this_gpu,
|
| 520 |
+
n,
|
| 521 |
+
device,
|
| 522 |
+
rank,
|
| 523 |
+
iterations,
|
| 524 |
+
args.global_seed,
|
| 525 |
+
eval_data=eval_data if args.label_sampling == "dataset" else None,
|
| 526 |
+
image_size=args.image_size,
|
| 527 |
+
)
|
| 528 |
+
|
| 529 |
+
# Decide whether to reuse precomputed DiT latents (skip DiT forward) or run DiT.
|
| 530 |
+
use_precomputed_latents = False
|
| 531 |
+
# if precomputed_latents_dir is None:
|
| 532 |
+
# # If a dedicated precomputed directory is not provided, reuse latents_dir if present.
|
| 533 |
+
# precomputed_latents_dir = latents_dir
|
| 534 |
+
if (
|
| 535 |
+
not skip_sampling
|
| 536 |
+
and precomputed_latents_dir is not None
|
| 537 |
+
and os.path.isdir(precomputed_latents_dir)
|
| 538 |
+
):
|
| 539 |
+
if rank == 0:
|
| 540 |
+
logger.info("Using precomputed DiT latents from %s.", precomputed_latents_dir)
|
| 541 |
+
use_precomputed_latents = True
|
| 542 |
+
use_precomputed_tensor = torch.tensor(1 if use_precomputed_latents else 0, device=device)
|
| 543 |
+
dist.broadcast(use_precomputed_tensor, 0)
|
| 544 |
+
use_precomputed_latents = use_precomputed_tensor.item() == 1
|
| 545 |
+
if rank == 0:
|
| 546 |
+
logger.info("Use precomputed DiT latents: %s", use_precomputed_latents)
|
| 547 |
+
|
| 548 |
+
using_cfg = guidance_scale > 1.0
|
| 549 |
+
if not skip_sampling:
|
| 550 |
+
for step_idx in pbar:
|
| 551 |
+
actual_step = start_step + step_idx
|
| 552 |
+
with autocast(**autocast_kwargs):
|
| 553 |
+
if use_precomputed_latents:
|
| 554 |
+
# Load precomputed DiT latents and only run the decoder.
|
| 555 |
+
latents = []
|
| 556 |
+
for local_idx in range(n):
|
| 557 |
+
index = local_idx * world_size + rank + total
|
| 558 |
+
latent_path = os.path.join(precomputed_latents_dir, f"{index:06d}.pt")
|
| 559 |
+
if not os.path.isfile(latent_path):
|
| 560 |
+
raise FileNotFoundError(
|
| 561 |
+
f"Expected latent file {latent_path} but it does not exist. "
|
| 562 |
+
"Please ensure precomputed latents cover all required indices."
|
| 563 |
+
)
|
| 564 |
+
latent = torch.load(latent_path, map_location=device)
|
| 565 |
+
latents.append(latent)
|
| 566 |
+
latents = torch.stack(latents, dim=0).to(device=device)
|
| 567 |
+
else:
|
| 568 |
+
# Run DiT to generate latents.
|
| 569 |
+
z = torch.randn(n, *latent_size, device=device)
|
| 570 |
+
y = label_sampler(actual_step)
|
| 571 |
+
|
| 572 |
+
model_kwargs = dict(y=y)
|
| 573 |
+
model_fn = model.forward
|
| 574 |
+
|
| 575 |
+
if using_cfg:
|
| 576 |
+
z = torch.cat([z, z], dim=0)
|
| 577 |
+
y_null = torch.full((n,), null_label, device=device)
|
| 578 |
+
y = torch.cat([y, y_null], dim=0)
|
| 579 |
+
model_kwargs = dict(
|
| 580 |
+
y=y,
|
| 581 |
+
cfg_scale=guidance_scale,
|
| 582 |
+
cfg_interval=(t_min, t_max),
|
| 583 |
+
)
|
| 584 |
+
if guidance_method == "autoguidance":
|
| 585 |
+
if guid_model_forward is None:
|
| 586 |
+
raise RuntimeError("Guidance model forward is not initialized.")
|
| 587 |
+
model_kwargs["additional_model_forward"] = guid_model_forward
|
| 588 |
+
model_fn = model.forward_with_autoguidance
|
| 589 |
+
else:
|
| 590 |
+
model_fn = model.forward_with_cfg
|
| 591 |
+
latents = sample_fn(z, model_fn, **model_kwargs)[-1]
|
| 592 |
+
if using_cfg:
|
| 593 |
+
latents, _ = latents.chunk(2, dim=0)
|
| 594 |
+
|
| 595 |
+
# Optionally cache DiT latents for future runs.
|
| 596 |
+
if save_latents and latents_dir is not None:
|
| 597 |
+
for local_idx, latent in enumerate(latents):
|
| 598 |
+
index = local_idx * world_size + rank + total
|
| 599 |
+
latent_path = os.path.join(latents_dir, f"{index:06d}.pt")
|
| 600 |
+
torch.save(latent.to("cpu"), latent_path)
|
| 601 |
+
|
| 602 |
+
# Decode latents into images.
|
| 603 |
+
if use_external_vae:
|
| 604 |
+
# Reverse projection if used
|
| 605 |
+
if proj_down_weight is not None:
|
| 606 |
+
if proj_norm_mode == "per_dim" and proj_norm_mean is not None:
|
| 607 |
+
latents = latents * proj_norm_std + proj_norm_mean
|
| 608 |
+
latents = torch.nn.functional.conv2d(latents, proj_down_weight[:, :, None, None])
|
| 609 |
+
decoded = external_vae.decode(latents / vae_scaling_factor).sample
|
| 610 |
+
decoded = ((decoded + 1) / 2).clamp(0, 1)
|
| 611 |
+
else:
|
| 612 |
+
decoded = pr.decode(latents)
|
| 613 |
+
mid_x_rec = getattr(decoded, 'mid_x_rec', None)
|
| 614 |
+
if hasattr(decoded, 'x_rec'):
|
| 615 |
+
decoded = decoded.x_rec
|
| 616 |
+
decoded = decoded.clamp(0, 1)
|
| 617 |
+
if mid_x_rec is not None:
|
| 618 |
+
mid_decoded = mid_x_rec.clamp(0, 1)
|
| 619 |
+
mid_decoded = (
|
| 620 |
+
mid_decoded.mul(255)
|
| 621 |
+
.permute(0, 2, 3, 1)
|
| 622 |
+
.to("cpu", dtype=torch.uint8)
|
| 623 |
+
.numpy()
|
| 624 |
+
)
|
| 625 |
+
else:
|
| 626 |
+
mid_decoded = None
|
| 627 |
+
decoded = (
|
| 628 |
+
decoded.mul(255)
|
| 629 |
+
.permute(0, 2, 3, 1)
|
| 630 |
+
.to("cpu", dtype=torch.uint8)
|
| 631 |
+
.numpy()
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
for local_idx, sample in enumerate(decoded):
|
| 635 |
+
index = local_idx * world_size + rank + total
|
| 636 |
+
Image.fromarray(sample).save(f"{sample_folder_dir}/{index:06d}.png")
|
| 637 |
+
if mid_decoded is not None:
|
| 638 |
+
mid_dir = sample_folder_dir + "_mid"
|
| 639 |
+
os.makedirs(mid_dir, exist_ok=True)
|
| 640 |
+
Image.fromarray(mid_decoded[local_idx]).save(f"{mid_dir}/{index:06d}.png")
|
| 641 |
+
|
| 642 |
+
total += global_batch_size
|
| 643 |
+
dist.barrier()
|
| 644 |
+
|
| 645 |
+
dist.barrier()
|
| 646 |
+
if rank == 0:
|
| 647 |
+
if not skip_sampling:
|
| 648 |
+
npz_path = create_npz_from_sample_folder(sample_folder_dir, args.num_fid_samples)
|
| 649 |
+
# else: npz_path already set and file exists
|
| 650 |
+
|
| 651 |
+
reference_npz = args.reference_npz
|
| 652 |
+
if reference_npz is None and cfg is not None:
|
| 653 |
+
eval_section = cfg.get("eval", None)
|
| 654 |
+
if eval_section is not None:
|
| 655 |
+
reference_npz = eval_section.get("reference_npz_path", None)
|
| 656 |
+
|
| 657 |
+
if reference_npz is not None and not args.skip_fid:
|
| 658 |
+
logger.info("Computing FID against reference...")
|
| 659 |
+
fid_value = compute_fid_from_samples(
|
| 660 |
+
npz_path,
|
| 661 |
+
reference_npz,
|
| 662 |
+
device,
|
| 663 |
+
batch_size=args.fid_batch_size,
|
| 664 |
+
)
|
| 665 |
+
logger.info("FID: %.6f", fid_value)
|
| 666 |
+
|
| 667 |
+
result_path = result_parent_dir / f"{folder_base}_fid_result.json"
|
| 668 |
+
with open(result_path, "w", encoding="utf-8") as f:
|
| 669 |
+
json.dump(
|
| 670 |
+
{
|
| 671 |
+
"fid": fid_value,
|
| 672 |
+
"reference_npz": reference_npz,
|
| 673 |
+
"num_samples": args.num_fid_samples,
|
| 674 |
+
"sample_folder": sample_folder_dir,
|
| 675 |
+
},
|
| 676 |
+
f,
|
| 677 |
+
indent=2,
|
| 678 |
+
)
|
| 679 |
+
logger.info("FID result saved to %s", result_path)
|
| 680 |
+
|
| 681 |
+
logger.info("Done.")
|
| 682 |
+
dist.barrier()
|
| 683 |
+
dist.destroy_process_group()
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
if __name__ == "__main__":
|
| 687 |
+
parser = argparse.ArgumentParser()
|
| 688 |
+
parser.add_argument("--config", type=str, required=True, help="Path to the config file.")
|
| 689 |
+
parser.add_argument("--sample-dir", type=str, default="samples")
|
| 690 |
+
parser.add_argument("--per-proc-batch-size", type=int, default=125)
|
| 691 |
+
parser.add_argument("--num-fid-samples", type=int, default=50_000)
|
| 692 |
+
parser.add_argument("--global-seed", type=int, default=0)
|
| 693 |
+
parser.add_argument("--precision", type=str, choices=["fp32", "bf16"], default="fp32")
|
| 694 |
+
parser.add_argument("--tf32", action=argparse.BooleanOptionalAction, default=True,
|
| 695 |
+
help="Enable TF32 matmuls (Ampere+). Disable if deterministic results are required.")
|
| 696 |
+
parser.add_argument(
|
| 697 |
+
"--label-sampling",
|
| 698 |
+
type=str,
|
| 699 |
+
choices=["random", "equal", "dataset"],
|
| 700 |
+
default="equal",
|
| 701 |
+
help="Choose how to sample class labels. Use 'dataset' to match training eval (val order).",
|
| 702 |
+
)
|
| 703 |
+
parser.add_argument(
|
| 704 |
+
"--eval-data",
|
| 705 |
+
type=str,
|
| 706 |
+
default=None,
|
| 707 |
+
help="Path to eval ImageFolder (val dir). Required for --label-sampling dataset. Overrides config eval.data_path.",
|
| 708 |
+
)
|
| 709 |
+
parser.add_argument(
|
| 710 |
+
"--image-size",
|
| 711 |
+
type=int,
|
| 712 |
+
default=256,
|
| 713 |
+
help="Image size for center crop when using dataset mode.",
|
| 714 |
+
)
|
| 715 |
+
parser.add_argument(
|
| 716 |
+
"--reference-npz",
|
| 717 |
+
type=str,
|
| 718 |
+
default=None,
|
| 719 |
+
help="Path to reference NPZ for FID computation. Can be precomputed stats (mu/sigma) or raw images (arr_0). "
|
| 720 |
+
"Overrides config eval.reference_npz_path if set.",
|
| 721 |
+
)
|
| 722 |
+
parser.add_argument(
|
| 723 |
+
"--fid-batch-size",
|
| 724 |
+
type=int,
|
| 725 |
+
default=128,
|
| 726 |
+
help="Batch size for FID computation.",
|
| 727 |
+
)
|
| 728 |
+
parser.add_argument(
|
| 729 |
+
"--skip-fid",
|
| 730 |
+
action="store_true",
|
| 731 |
+
help="Generate PNG/NPZ artifacts without computing FID in this process.",
|
| 732 |
+
)
|
| 733 |
+
parser.add_argument(
|
| 734 |
+
"--ckpt",
|
| 735 |
+
type=str,
|
| 736 |
+
default=None,
|
| 737 |
+
help="Override stage_2.ckpt in config with this checkpoint path.",
|
| 738 |
+
)
|
| 739 |
+
parser.add_argument(
|
| 740 |
+
"--cfg-scale",
|
| 741 |
+
type=float,
|
| 742 |
+
default=None,
|
| 743 |
+
help="Override guidance.scale in config.",
|
| 744 |
+
)
|
| 745 |
+
parser.add_argument(
|
| 746 |
+
"--precomputed-latents-dir",
|
| 747 |
+
type=str,
|
| 748 |
+
default=None,
|
| 749 |
+
help="Override misc.precomputed_latents_dir in config. Pass 'none' to disable precomputed latents.",
|
| 750 |
+
)
|
| 751 |
+
|
| 752 |
+
args = parser.parse_args()
|
| 753 |
+
main(args)
|
patch-reparameterization/src/stage1/encoders/__init__.py
CHANGED
|
@@ -34,7 +34,12 @@ def register_encoder(cls: Optional[Type] = None, *, name: Optional[str] = None)
|
|
| 34 |
# Only encoders used by released configs are included and imported.
|
| 35 |
from . import siglip2
|
| 36 |
from . import siglip2_unified
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
try:
|
| 38 |
from . import qwen3_unified
|
|
|
|
| 39 |
except ImportError:
|
| 40 |
pass
|
|
|
|
| 34 |
# Only encoders used by released configs are included and imported.
|
| 35 |
from . import siglip2
|
| 36 |
from . import siglip2_unified
|
| 37 |
+
try:
|
| 38 |
+
from . import dinov2_unified
|
| 39 |
+
except ImportError:
|
| 40 |
+
pass
|
| 41 |
try:
|
| 42 |
from . import qwen3_unified
|
| 43 |
+
from . import qwen3_frozen
|
| 44 |
except ImportError:
|
| 45 |
pass
|
patch-reparameterization/src/stage1/encoders/dinov2_unified.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import logging
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from torch import nn
|
| 6 |
+
from transformers import Dinov2WithRegistersModel
|
| 7 |
+
|
| 8 |
+
from . import register_encoder
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@register_encoder()
|
| 14 |
+
class DINOv2Unified(nn.Module):
|
| 15 |
+
"""Single-backbone DINOv2-with-registers encoder with two independent embeddings.
|
| 16 |
+
|
| 17 |
+
Architecture:
|
| 18 |
+
image -> sem_embeddings -> [B, 1+R+N, C] --|
|
| 19 |
+
|--> cat [2B, ...] -> encoder -> split
|
| 20 |
+
image -> recon_embeddings -> [B, 1+R+N, C] --|
|
| 21 |
+
|
| 22 |
+
sem_out (patch tokens only) -> sem_ln -> [B, N, C]
|
| 23 |
+
recon_out (patch tokens only) -> recon_proj -> recon_ln -> [B, N, recon_proj_dim]
|
| 24 |
+
|
| 25 |
+
output: cat([sem, recon], dim=-1) [B, N, C + recon_proj_dim]
|
| 26 |
+
|
| 27 |
+
R = num_register_tokens (default 4), N = num_patches (default 256).
|
| 28 |
+
CLS and register tokens are discarded after the encoder.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(self, model_name: str, recon_proj_dim: int = 256, **kwargs):
|
| 32 |
+
super().__init__()
|
| 33 |
+
|
| 34 |
+
backbone = Dinov2WithRegistersModel.from_pretrained(model_name)
|
| 35 |
+
self.hidden_size = backbone.config.hidden_size
|
| 36 |
+
self.patch_size = backbone.config.patch_size
|
| 37 |
+
self.num_register_tokens = backbone.config.num_register_tokens
|
| 38 |
+
self.recon_proj_dim = recon_proj_dim
|
| 39 |
+
|
| 40 |
+
# Two independent embeddings (patch_embeddings + cls_token + register_tokens + position_embeddings)
|
| 41 |
+
self.sem_embeddings = copy.deepcopy(backbone.embeddings)
|
| 42 |
+
self.recon_embeddings = copy.deepcopy(backbone.embeddings)
|
| 43 |
+
# mask_token is only used in MAE pre-training; remove to avoid DDP unused-parameter error
|
| 44 |
+
del self.recon_embeddings.mask_token
|
| 45 |
+
self.recon_embeddings.mask_token = None
|
| 46 |
+
|
| 47 |
+
# Shared encoder + layernorm
|
| 48 |
+
self.encoder = backbone.encoder
|
| 49 |
+
self.layernorm = backbone.layernorm
|
| 50 |
+
|
| 51 |
+
# recon projection + LN
|
| 52 |
+
self.recon_proj = nn.Linear(self.hidden_size, recon_proj_dim, bias=True)
|
| 53 |
+
nn.init.xavier_uniform_(self.recon_proj.weight)
|
| 54 |
+
nn.init.zeros_(self.recon_proj.bias)
|
| 55 |
+
|
| 56 |
+
self.sem_ln = nn.LayerNorm(self.hidden_size, elementwise_affine=False)
|
| 57 |
+
self.recon_ln = nn.LayerNorm(recon_proj_dim, elementwise_affine=False)
|
| 58 |
+
|
| 59 |
+
self.output_dim = self.hidden_size + recon_proj_dim
|
| 60 |
+
self.recon_hidden_size = self.output_dim
|
| 61 |
+
|
| 62 |
+
# skip tokens: 1 CLS + num_register_tokens
|
| 63 |
+
self._skip = 1 + self.num_register_tokens
|
| 64 |
+
|
| 65 |
+
logger.info(
|
| 66 |
+
f"DINOv2Unified: hidden_size={self.hidden_size}, "
|
| 67 |
+
f"recon_proj_dim={recon_proj_dim}, output_dim={self.output_dim}, "
|
| 68 |
+
f"skip_tokens={self._skip}"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
def forward(self, images: torch.Tensor, return_aux: bool = False, **kwargs):
|
| 72 |
+
B = images.shape[0]
|
| 73 |
+
|
| 74 |
+
sem_embeds = self.sem_embeddings(images) # [B, 1+R+N, C]
|
| 75 |
+
recon_embeds = self.recon_embeddings(images) # [B, 1+R+N, C]
|
| 76 |
+
|
| 77 |
+
combined = torch.cat([sem_embeds, recon_embeds], dim=0) # [2B, 1+R+N, C]
|
| 78 |
+
hidden = self.encoder(combined).last_hidden_state # [2B, 1+R+N, C]
|
| 79 |
+
hidden = self.layernorm(hidden)
|
| 80 |
+
|
| 81 |
+
# discard CLS + register tokens
|
| 82 |
+
sem_out = hidden[:B, self._skip:] # [B, N, C]
|
| 83 |
+
recon_out = hidden[B:, self._skip:] # [B, N, C]
|
| 84 |
+
|
| 85 |
+
sem_tokens = self.sem_ln(sem_out)
|
| 86 |
+
recon_tokens = self.recon_ln(self.recon_proj(recon_out))
|
| 87 |
+
|
| 88 |
+
merged = torch.cat([sem_tokens, recon_tokens], dim=-1)
|
| 89 |
+
|
| 90 |
+
if not return_aux:
|
| 91 |
+
return merged
|
| 92 |
+
|
| 93 |
+
return merged, {
|
| 94 |
+
"sementic_tokens": sem_tokens,
|
| 95 |
+
"recon_tokens": recon_tokens,
|
| 96 |
+
"merged_tokens": merged,
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
def set_training_mode(self, mode: str = "full", **kwargs):
|
| 100 |
+
self.requires_grad_(False)
|
| 101 |
+
if mode == "full":
|
| 102 |
+
self.requires_grad_(True)
|
| 103 |
+
elif mode == "merger_recon_patchemb":
|
| 104 |
+
for p in self.recon_embeddings.parameters():
|
| 105 |
+
p.requires_grad = True
|
| 106 |
+
for p in self.recon_proj.parameters():
|
| 107 |
+
p.requires_grad = True
|
| 108 |
+
elif mode == "frozen":
|
| 109 |
+
pass
|
| 110 |
+
else:
|
| 111 |
+
raise ValueError(f"DINOv2Unified: unknown training mode '{mode}'")
|
| 112 |
+
|
| 113 |
+
trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
|
| 114 |
+
total = sum(p.numel() for p in self.parameters())
|
| 115 |
+
logger.info(
|
| 116 |
+
f"DINOv2Unified set_training_mode='{mode}': "
|
| 117 |
+
f"{trainable/1e6:.2f}M / {total/1e6:.2f}M trainable"
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
def get_last_layer(self) -> nn.Parameter:
|
| 121 |
+
return self.recon_proj.weight
|
| 122 |
+
|
| 123 |
+
def get_recon_last_layer(self) -> nn.Parameter:
|
| 124 |
+
return self.recon_proj.weight
|
patch-reparameterization/src/stage1/encoders/qwen3_frozen.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Qwen3Frozen: 完全冻结的 Qwen3-VL vision backbone,只做 semantic encoding。
|
| 3 |
+
继承 Qwen3Unified 的所有辅助方法(RoPE、pos_embed、block_order),
|
| 4 |
+
去掉 recon 分支,输出 [total_tokens, 1152] 直接送给 decoder。
|
| 5 |
+
只训 decoder,encoder 完全不更新。
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import copy
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from torch import nn
|
| 13 |
+
|
| 14 |
+
from . import register_encoder
|
| 15 |
+
from .qwen3_unified import Qwen3Unified
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@register_encoder()
|
| 21 |
+
class Qwen3Frozen(Qwen3Unified):
|
| 22 |
+
"""
|
| 23 |
+
完全冻结的单路 Qwen3-VIT encoder(无 recon 分支)。
|
| 24 |
+
继承 Qwen3Unified 的所有辅助方法,去掉双路设计。
|
| 25 |
+
输出:[total_tokens, hidden_size=1152]
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
def __init__(self, model_name: str, **kwargs):
|
| 29 |
+
# 不调用 Qwen3Unified.__init__,自己初始化
|
| 30 |
+
nn.Module.__init__(self)
|
| 31 |
+
|
| 32 |
+
from transformers.models.qwen3_vl.modeling_qwen3_vl import (
|
| 33 |
+
Qwen3VLForConditionalGeneration,
|
| 34 |
+
)
|
| 35 |
+
from transformers import AutoConfig
|
| 36 |
+
|
| 37 |
+
full_cfg = AutoConfig.from_pretrained(model_name)
|
| 38 |
+
vis_cfg = full_cfg.vision_config
|
| 39 |
+
|
| 40 |
+
full_model = Qwen3VLForConditionalGeneration.from_pretrained(
|
| 41 |
+
model_name,
|
| 42 |
+
torch_dtype=torch.float32,
|
| 43 |
+
device_map="cpu",
|
| 44 |
+
)
|
| 45 |
+
vis_model = full_model.visual
|
| 46 |
+
del full_model.model
|
| 47 |
+
|
| 48 |
+
self.hidden_size = vis_cfg.hidden_size
|
| 49 |
+
self.patch_size = vis_cfg.patch_size
|
| 50 |
+
self.temporal_patch_size = vis_cfg.temporal_patch_size
|
| 51 |
+
self.spatial_merge_size = vis_cfg.spatial_merge_size
|
| 52 |
+
self.recon_proj_dim = 0 # no recon branch
|
| 53 |
+
|
| 54 |
+
# single patch embed + pos embed (no deepcopy needed, frozen anyway)
|
| 55 |
+
self.sem_patch_embed = vis_model.patch_embed
|
| 56 |
+
self.sem_pos_embed = vis_model.pos_embed
|
| 57 |
+
self.num_grid_per_side = vis_model.num_grid_per_side
|
| 58 |
+
self.rotary_pos_emb = vis_model.rotary_pos_emb
|
| 59 |
+
self.blocks = vis_model.blocks
|
| 60 |
+
|
| 61 |
+
self.output_dim = self.hidden_size
|
| 62 |
+
self.recon_hidden_size = self.hidden_size # decoder latent_dim
|
| 63 |
+
|
| 64 |
+
# PatchReparam reads these to skip internal resize/normalize
|
| 65 |
+
self.handles_preprocessing = True
|
| 66 |
+
self.encoder_mean = torch.tensor([0.5, 0.5, 0.5]).view(1, 3, 1, 1)
|
| 67 |
+
self.encoder_std = torch.tensor([0.5, 0.5, 0.5]).view(1, 3, 1, 1)
|
| 68 |
+
self.encoder_input_size = 256
|
| 69 |
+
|
| 70 |
+
# caches (inherited methods use these)
|
| 71 |
+
self._pos_embed_cache: dict = {}
|
| 72 |
+
self._rot_pos_emb_cache: dict = {}
|
| 73 |
+
self._block_idx_cache: dict = {}
|
| 74 |
+
|
| 75 |
+
# 完全冻结
|
| 76 |
+
self.requires_grad_(False)
|
| 77 |
+
|
| 78 |
+
del vis_model
|
| 79 |
+
|
| 80 |
+
logger.info(f"Qwen3Frozen: hidden_size={self.hidden_size}, fully frozen")
|
| 81 |
+
|
| 82 |
+
def forward(
|
| 83 |
+
self,
|
| 84 |
+
pixel_values: torch.Tensor,
|
| 85 |
+
grid_thw: torch.Tensor,
|
| 86 |
+
return_aux: bool = False,
|
| 87 |
+
**kwargs,
|
| 88 |
+
):
|
| 89 |
+
# patch embed + pos embed (single path)
|
| 90 |
+
x = self.sem_patch_embed(pixel_values)
|
| 91 |
+
pos = self._abs_pos_embed(self.sem_pos_embed, grid_thw)
|
| 92 |
+
|
| 93 |
+
# reorder to 2x2 block order
|
| 94 |
+
perm = self._block_order_indices(grid_thw, device=x.device)
|
| 95 |
+
inv_perm = torch.empty_like(perm)
|
| 96 |
+
inv_perm[perm] = torch.arange(perm.numel(), device=perm.device)
|
| 97 |
+
x = (x + pos)[perm]
|
| 98 |
+
|
| 99 |
+
# RoPE
|
| 100 |
+
rotary = self._rot_pos_emb(grid_thw)
|
| 101 |
+
emb = torch.cat((rotary, rotary), dim=-1)
|
| 102 |
+
position_embeddings = (emb.cos(), emb.sin())
|
| 103 |
+
|
| 104 |
+
# cu_seqlens
|
| 105 |
+
cu_seqlens = torch.repeat_interleave(
|
| 106 |
+
grid_thw[:, 1] * grid_thw[:, 2], grid_thw[:, 0]
|
| 107 |
+
).cumsum(0, dtype=torch.int32)
|
| 108 |
+
cu_seqlens = torch.nn.functional.pad(cu_seqlens, (1, 0), value=0)
|
| 109 |
+
|
| 110 |
+
# blocks forward
|
| 111 |
+
for blk in self.blocks:
|
| 112 |
+
x = blk(x, cu_seqlens=cu_seqlens, position_embeddings=position_embeddings)
|
| 113 |
+
|
| 114 |
+
# restore row-major
|
| 115 |
+
x = x[inv_perm]
|
| 116 |
+
|
| 117 |
+
if not return_aux:
|
| 118 |
+
return x
|
| 119 |
+
|
| 120 |
+
return x, {"sementic_tokens": x, "merged_tokens": x}
|
| 121 |
+
|
| 122 |
+
def set_training_mode(self, mode: str = "frozen", **kwargs):
|
| 123 |
+
self.requires_grad_(False)
|
| 124 |
+
logger.info(f"Qwen3Frozen: always frozen, ignoring mode='{mode}'")
|
| 125 |
+
|
| 126 |
+
def get_last_layer(self):
|
| 127 |
+
return None
|
| 128 |
+
|
| 129 |
+
def get_recon_last_layer(self):
|
| 130 |
+
return None
|
patch-reparameterization/src/stage1/encoders/qwen3_unified.py
CHANGED
|
@@ -67,16 +67,22 @@ class Qwen3Unified(nn.Module):
|
|
| 67 |
):
|
| 68 |
super().__init__()
|
| 69 |
|
| 70 |
-
# Load
|
| 71 |
-
# the pr-qwen-vit-tokenizer checkpoint provides all trained weights)
|
| 72 |
from transformers import AutoConfig
|
| 73 |
-
from transformers.models.qwen3_vl.modeling_qwen3_vl import
|
| 74 |
|
| 75 |
full_cfg = AutoConfig.from_pretrained(model_name)
|
| 76 |
vis_cfg: Qwen3VLVisionConfig = full_cfg.vision_config
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
self.hidden_size = vis_cfg.hidden_size # 1152
|
| 82 |
self.patch_size = vis_cfg.patch_size # 16
|
|
|
|
| 67 |
):
|
| 68 |
super().__init__()
|
| 69 |
|
| 70 |
+
# Load full Qwen3VL model to extract the vision backbone, then discard the LLM
|
|
|
|
| 71 |
from transformers import AutoConfig
|
| 72 |
+
from transformers.models.qwen3_vl.modeling_qwen3_vl import Qwen3VLForConditionalGeneration
|
| 73 |
|
| 74 |
full_cfg = AutoConfig.from_pretrained(model_name)
|
| 75 |
vis_cfg: Qwen3VLVisionConfig = full_cfg.vision_config
|
| 76 |
|
| 77 |
+
full_model = Qwen3VLForConditionalGeneration.from_pretrained(
|
| 78 |
+
model_name,
|
| 79 |
+
torch_dtype=torch.float32,
|
| 80 |
+
device_map=f"cuda:{torch.cuda.current_device()}" if torch.cuda.is_available() else "cpu",
|
| 81 |
+
)
|
| 82 |
+
vis_model = full_model.visual
|
| 83 |
+
full_model.model = None # free LLM weights
|
| 84 |
+
del full_model
|
| 85 |
+
torch.cuda.empty_cache()
|
| 86 |
|
| 87 |
self.hidden_size = vis_cfg.hidden_size # 1152
|
| 88 |
self.patch_size = vis_cfg.patch_size # 16
|
patch-reparameterization/src/stage2/__init__.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .models.lightningDiT import LightningDiT
|
| 2 |
+
from .models.DDT import DiTwDDTHead
|
| 3 |
+
from typing import Callable, Dict, Optional, Type, Union
|
| 4 |
+
from typing import Protocol, Any, runtime_checkable
|
| 5 |
+
|
| 6 |
+
@runtime_checkable
|
| 7 |
+
class Stage2ModelProtocol(Protocol):
|
| 8 |
+
def forward(self, *args: Any, **kwargs: Any) -> Any: ...
|
| 9 |
+
def forward_with_cfg(self, *args: Any, **kwargs: Any) -> Any: ...
|
| 10 |
+
def forward_with_autoguidance(self, *args: Any, **kwargs: Any) -> Any: ...
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# STAGE2_ARCHS: Dict[str, Callable] = {}
|
| 15 |
+
# __all__ = ["STAGE2_ARCHS", "register_stage2"]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# def _add_to_registry(name: str, func: Callable) -> Callable:
|
| 19 |
+
# if name in STAGE2_ARCHS and STAGE2_ARCHS[name] is not func:
|
| 20 |
+
# raise ValueError(f"Stage2 func: '{name}' is already registered.")
|
| 21 |
+
# STAGE2_ARCHS[name] = func
|
| 22 |
+
# return func
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# def register_stage2(func: Optional[Callable] = None, *, name: Optional[str] = None) -> Union[Callable[[Callable], Callable], Callable]:
|
| 26 |
+
# """Register a function in ``STAGE2_ARCHS``.
|
| 27 |
+
|
| 28 |
+
# Can be used either as ``@register_stage2()`` (optionally passing ``name``) or
|
| 29 |
+
# via ``register_stage2(my_function)`` after the function definition.
|
| 30 |
+
# """
|
| 31 |
+
|
| 32 |
+
# def decorator(inner_func: Callable) -> Callable:
|
| 33 |
+
# func_name = name or inner_func.__name__
|
| 34 |
+
# return _add_to_registry(func_name, inner_func)
|
| 35 |
+
|
| 36 |
+
# if func is None:
|
| 37 |
+
# return decorator
|
| 38 |
+
|
| 39 |
+
# return decorator(func)
|
| 40 |
+
|
| 41 |
+
# @register_stage2()
|
| 42 |
+
# def DiTXL(token_dim: int, input_size: int) -> LightningDiT:
|
| 43 |
+
# model = LightningDiT(
|
| 44 |
+
# input_size=input_size,
|
| 45 |
+
# patch_size=1,
|
| 46 |
+
# in_channels=token_dim,
|
| 47 |
+
# hidden_size=1152,
|
| 48 |
+
# depth=28,
|
| 49 |
+
# num_heads=16,
|
| 50 |
+
# mlp_ratio=4.0,
|
| 51 |
+
# class_dropout_prob=0.1,
|
| 52 |
+
# num_classes=1000,
|
| 53 |
+
# )
|
| 54 |
+
# return model
|
| 55 |
+
|
| 56 |
+
# @register_stage2()
|
| 57 |
+
# def DDTXL(token_dim: int, input_size: int) -> DiTwDDTHead:
|
| 58 |
+
# model = DiTwDDTHead(
|
| 59 |
+
# input_size=input_size,
|
| 60 |
+
# patch_size=1,
|
| 61 |
+
# in_channels=token_dim,
|
| 62 |
+
# hidden_size=[1152, 2048],
|
| 63 |
+
# depth=[28, 2],
|
| 64 |
+
# num_heads=[16, 16],
|
| 65 |
+
# mlp_ratio=4.0,
|
| 66 |
+
# class_dropout_prob=0.1,
|
| 67 |
+
# num_classes=1000,
|
| 68 |
+
# )
|
| 69 |
+
# return model
|
| 70 |
+
|
| 71 |
+
# @register_stage2()
|
| 72 |
+
# def DDTS(token_dim: int, input_size: int) -> DiTwDDTHead:
|
| 73 |
+
# model = DiTwDDTHead(
|
| 74 |
+
# input_size=input_size,
|
| 75 |
+
# patch_size=1,
|
| 76 |
+
# in_channels=token_dim,
|
| 77 |
+
# hidden_size=[384, 2048],
|
| 78 |
+
# depth=[12, 2],
|
| 79 |
+
# num_heads=[6, 16],
|
| 80 |
+
# mlp_ratio=4.0,
|
| 81 |
+
# class_dropout_prob=0.1,
|
| 82 |
+
# num_classes=1000,
|
| 83 |
+
# )
|
| 84 |
+
# return model
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
|
patch-reparameterization/src/stage2/models/DDT.py
ADDED
|
@@ -0,0 +1,919 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from math import sqrt
|
| 3 |
+
from re import L
|
| 4 |
+
from regex import B
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
|
| 10 |
+
from transformers import SwinModel
|
| 11 |
+
import torch
|
| 12 |
+
from torch import nn
|
| 13 |
+
from .lightningDiT import PatchEmbed, Mlp, NormAttention
|
| 14 |
+
from timm.models.vision_transformer import PatchEmbed, Mlp
|
| 15 |
+
from .model_utils import VisionRotaryEmbeddingFast, RMSNorm, SwiGLUFFN, GaussianFourierEmbedding, LabelEmbedder, NormAttention, get_2d_sincos_pos_embed
|
| 16 |
+
import torch.nn.functional as F
|
| 17 |
+
from typing import *
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def DDTModulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
|
| 21 |
+
"""
|
| 22 |
+
Applies per-segment modulation to x.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
x: Tensor of shape (B, L_x, D)
|
| 26 |
+
shift: Tensor of shape (B, L, D)
|
| 27 |
+
scale: Tensor of shape (B, L, D)
|
| 28 |
+
Returns:
|
| 29 |
+
Tensor of shape (B, L_x, D): x * (1 + scale) + shift,
|
| 30 |
+
with shift and scale repeated to match L_x if necessary.
|
| 31 |
+
"""
|
| 32 |
+
B, Lx, D = x.shape
|
| 33 |
+
_, L, _ = shift.shape
|
| 34 |
+
if Lx % L != 0:
|
| 35 |
+
raise ValueError(f"L_x ({Lx}) must be divisible by L ({L})")
|
| 36 |
+
repeat = Lx // L
|
| 37 |
+
if repeat != 1:
|
| 38 |
+
# repeat each of the L segments 'repeat' times along the length dim
|
| 39 |
+
shift = shift.repeat_interleave(repeat, dim=1)
|
| 40 |
+
scale = scale.repeat_interleave(repeat, dim=1)
|
| 41 |
+
# apply modulation
|
| 42 |
+
return x * (1 + scale) + shift
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def DDTGate(x: torch.Tensor, gate: torch.Tensor) -> torch.Tensor:
|
| 46 |
+
"""
|
| 47 |
+
Applies per-segment modulation to x.
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
x: Tensor of shape (B, L_x, D)
|
| 51 |
+
gate: Tensor of shape (B, L, D)
|
| 52 |
+
Returns:
|
| 53 |
+
Tensor of shape (B, L_x, D): x * gate,
|
| 54 |
+
with gate repeated to match L_x if necessary.
|
| 55 |
+
"""
|
| 56 |
+
B, Lx, D = x.shape
|
| 57 |
+
_, L, _ = gate.shape
|
| 58 |
+
if Lx % L != 0:
|
| 59 |
+
raise ValueError(f"L_x ({Lx}) must be divisible by L ({L})")
|
| 60 |
+
repeat = Lx // L
|
| 61 |
+
if repeat != 1:
|
| 62 |
+
# repeat each of the L segments 'repeat' times along the length dim
|
| 63 |
+
# print(f"gate shape: {gate.shape}, x shape: {x.shape}")
|
| 64 |
+
gate = gate.repeat_interleave(repeat, dim=1)
|
| 65 |
+
# apply modulation
|
| 66 |
+
return x * gate
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class LightningDDTBlock(nn.Module):
|
| 70 |
+
"""
|
| 71 |
+
Lightning DiT Block. We add features including:
|
| 72 |
+
- ROPE
|
| 73 |
+
- QKNorm
|
| 74 |
+
- RMSNorm
|
| 75 |
+
- SwiGLU
|
| 76 |
+
- No shift AdaLN.
|
| 77 |
+
Not all of them are used in the final model, please refer to the paper for more details.
|
| 78 |
+
"""
|
| 79 |
+
|
| 80 |
+
def __init__(
|
| 81 |
+
self,
|
| 82 |
+
hidden_size,
|
| 83 |
+
num_heads,
|
| 84 |
+
mlp_ratio=4.0,
|
| 85 |
+
use_qknorm=False,
|
| 86 |
+
use_swiglu=True,
|
| 87 |
+
use_rmsnorm=True,
|
| 88 |
+
wo_shift=False,
|
| 89 |
+
**block_kwargs
|
| 90 |
+
):
|
| 91 |
+
super().__init__()
|
| 92 |
+
|
| 93 |
+
# Initialize normalization layers
|
| 94 |
+
if not use_rmsnorm:
|
| 95 |
+
self.norm1 = nn.LayerNorm(
|
| 96 |
+
hidden_size, elementwise_affine=False, eps=1e-6)
|
| 97 |
+
self.norm2 = nn.LayerNorm(
|
| 98 |
+
hidden_size, elementwise_affine=False, eps=1e-6)
|
| 99 |
+
else:
|
| 100 |
+
self.norm1 = RMSNorm(hidden_size)
|
| 101 |
+
self.norm2 = RMSNorm(hidden_size)
|
| 102 |
+
|
| 103 |
+
# Initialize attention layer
|
| 104 |
+
self.attn = NormAttention(
|
| 105 |
+
hidden_size,
|
| 106 |
+
num_heads=num_heads,
|
| 107 |
+
qkv_bias=True,
|
| 108 |
+
qk_norm=use_qknorm,
|
| 109 |
+
use_rmsnorm=use_rmsnorm,
|
| 110 |
+
**block_kwargs
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# Initialize MLP layer
|
| 114 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 115 |
+
def approx_gelu(): return nn.GELU(approximate="tanh")
|
| 116 |
+
if use_swiglu:
|
| 117 |
+
# here we did not use SwiGLU from xformers because it is not compatible with torch.compile for now.
|
| 118 |
+
self.mlp = SwiGLUFFN(hidden_size, int(2/3 * mlp_hidden_dim))
|
| 119 |
+
else:
|
| 120 |
+
self.mlp = Mlp(
|
| 121 |
+
in_features=hidden_size,
|
| 122 |
+
hidden_features=mlp_hidden_dim,
|
| 123 |
+
act_layer=approx_gelu,
|
| 124 |
+
drop=0
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# Initialize AdaLN modulation
|
| 128 |
+
if wo_shift:
|
| 129 |
+
self.adaLN_modulation = nn.Sequential(
|
| 130 |
+
nn.SiLU(),
|
| 131 |
+
nn.Linear(hidden_size, 4 * hidden_size, bias=True)
|
| 132 |
+
)
|
| 133 |
+
else:
|
| 134 |
+
self.adaLN_modulation = nn.Sequential(
|
| 135 |
+
nn.SiLU(),
|
| 136 |
+
nn.Linear(hidden_size, 6 * hidden_size, bias=True)
|
| 137 |
+
)
|
| 138 |
+
self.wo_shift = wo_shift
|
| 139 |
+
|
| 140 |
+
def forward(self, x, c, feat_rope=None):
|
| 141 |
+
if len(c.shape) < len(x.shape):
|
| 142 |
+
c = c.unsqueeze(1) # (B, 1, C)
|
| 143 |
+
if self.wo_shift:
|
| 144 |
+
scale_msa, gate_msa, scale_mlp, gate_mlp = self.adaLN_modulation(
|
| 145 |
+
c).chunk(4, dim=-1)
|
| 146 |
+
shift_msa = None
|
| 147 |
+
shift_mlp = None
|
| 148 |
+
else:
|
| 149 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(
|
| 150 |
+
c).chunk(6, dim=-1)
|
| 151 |
+
x = x + DDTGate(self.attn(DDTModulate(self.norm1(x),
|
| 152 |
+
shift_msa, scale_msa), rope=feat_rope), gate_msa)
|
| 153 |
+
x = x + DDTGate(self.mlp(DDTModulate(self.norm2(x),
|
| 154 |
+
shift_mlp, scale_mlp)), gate_mlp)
|
| 155 |
+
return x
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class DDTFinalLayer(nn.Module):
|
| 159 |
+
"""
|
| 160 |
+
The final layer of DDT.
|
| 161 |
+
"""
|
| 162 |
+
|
| 163 |
+
def __init__(self, hidden_size, patch_size, out_channels, use_rmsnorm=False):
|
| 164 |
+
super().__init__()
|
| 165 |
+
if not use_rmsnorm:
|
| 166 |
+
self.norm_final = nn.LayerNorm(
|
| 167 |
+
hidden_size, elementwise_affine=False, eps=1e-6)
|
| 168 |
+
else:
|
| 169 |
+
self.norm_final = RMSNorm(hidden_size)
|
| 170 |
+
self.linear = nn.Linear(
|
| 171 |
+
hidden_size, patch_size * patch_size * out_channels, bias=True)
|
| 172 |
+
self.adaLN_modulation = nn.Sequential(
|
| 173 |
+
nn.SiLU(),
|
| 174 |
+
nn.Linear(hidden_size, 2 * hidden_size, bias=True)
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
def forward(self, x, c):
|
| 178 |
+
if len(c.shape) < len(x.shape):
|
| 179 |
+
c = c.unsqueeze(1)
|
| 180 |
+
shift, scale = self.adaLN_modulation(c).chunk(2, dim=-1)
|
| 181 |
+
x = DDTModulate(self.norm_final(x), shift, scale) # no gate
|
| 182 |
+
x = self.linear(x)
|
| 183 |
+
return x
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class DiTwDDTHead(nn.Module):
|
| 187 |
+
def __init__(
|
| 188 |
+
self,
|
| 189 |
+
input_size: int = 1,
|
| 190 |
+
patch_size: Union[list, int] = 1,
|
| 191 |
+
in_channels: int = 768,
|
| 192 |
+
hidden_size=[1152, 2048],
|
| 193 |
+
depth=[28, 2],
|
| 194 |
+
num_heads: Union[list[int], int] = [16, 16],
|
| 195 |
+
mlp_ratio=4.0,
|
| 196 |
+
class_dropout_prob=0.1,
|
| 197 |
+
num_classes=1000,
|
| 198 |
+
use_qknorm=False,
|
| 199 |
+
use_swiglu=True,
|
| 200 |
+
use_rope=True,
|
| 201 |
+
use_rmsnorm=True,
|
| 202 |
+
wo_shift=False,
|
| 203 |
+
use_pos_embed: bool = True,
|
| 204 |
+
):
|
| 205 |
+
super().__init__()
|
| 206 |
+
self.in_channels = in_channels
|
| 207 |
+
self.out_channels = in_channels
|
| 208 |
+
|
| 209 |
+
self.encoder_hidden_size = hidden_size[0]
|
| 210 |
+
self.decoder_hidden_size = hidden_size[1]
|
| 211 |
+
self.num_heads = [num_heads, num_heads] if isinstance(
|
| 212 |
+
num_heads, int) else list(num_heads)
|
| 213 |
+
self.num_decoder_blocks = depth[1]
|
| 214 |
+
self.num_encoder_blocks = depth[0]
|
| 215 |
+
self.num_blocks = depth[0] + depth[1]
|
| 216 |
+
self.use_rope = use_rope
|
| 217 |
+
# analyze patch size
|
| 218 |
+
if isinstance(patch_size, int) or isinstance(patch_size, float):
|
| 219 |
+
patch_size = [patch_size, patch_size] # patch size for s , x embed
|
| 220 |
+
assert len(
|
| 221 |
+
patch_size) == 2, f"patch size should be a list of two numbers, but got {patch_size}"
|
| 222 |
+
self.patch_size = patch_size
|
| 223 |
+
self.s_patch_size = patch_size[0]
|
| 224 |
+
self.x_patch_size = patch_size[1]
|
| 225 |
+
s_channel_per_token = in_channels * self.s_patch_size * self.s_patch_size
|
| 226 |
+
s_input_size = input_size
|
| 227 |
+
s_patch_size = self.s_patch_size
|
| 228 |
+
x_input_size = input_size
|
| 229 |
+
x_patch_size = self.x_patch_size
|
| 230 |
+
x_channel_per_token = in_channels * self.x_patch_size * self.x_patch_size
|
| 231 |
+
self.x_embedder = PatchEmbed(
|
| 232 |
+
x_input_size, x_patch_size, x_channel_per_token, self.decoder_hidden_size, bias=True)
|
| 233 |
+
self.s_embedder = PatchEmbed(
|
| 234 |
+
s_input_size, s_patch_size, s_channel_per_token, self.encoder_hidden_size, bias=True)
|
| 235 |
+
self.s_channel_per_token = s_channel_per_token
|
| 236 |
+
self.x_channel_per_token = x_channel_per_token
|
| 237 |
+
self.s_projector = nn.Linear(
|
| 238 |
+
self.encoder_hidden_size, self.decoder_hidden_size) if self.encoder_hidden_size != self.decoder_hidden_size else nn.Identity()
|
| 239 |
+
self.t_embedder = GaussianFourierEmbedding(self.encoder_hidden_size)
|
| 240 |
+
self.y_embedder = LabelEmbedder(
|
| 241 |
+
num_classes, self.encoder_hidden_size, class_dropout_prob)
|
| 242 |
+
# print(f"x_channel_per_token: {x_channel_per_token}, s_channel_per_token: {s_channel_per_token}")
|
| 243 |
+
self.final_layer = DDTFinalLayer(
|
| 244 |
+
self.decoder_hidden_size, 1, x_channel_per_token, use_rmsnorm=use_rmsnorm)
|
| 245 |
+
# Will use fixed sin-cos embedding:
|
| 246 |
+
if use_pos_embed:
|
| 247 |
+
num_patches = self.s_embedder.num_patches
|
| 248 |
+
self.pos_embed = nn.Parameter(torch.zeros(
|
| 249 |
+
1, num_patches, self.encoder_hidden_size), requires_grad=False)
|
| 250 |
+
self.x_pos_embed = None
|
| 251 |
+
self.use_pos_embed = use_pos_embed
|
| 252 |
+
enc_num_heads = self.num_heads[0]
|
| 253 |
+
dec_num_heads = self.num_heads[1]
|
| 254 |
+
# use rotary position encoding, borrow from EVA
|
| 255 |
+
if self.use_rope:
|
| 256 |
+
enc_half_head_dim = self.encoder_hidden_size // enc_num_heads // 2
|
| 257 |
+
hw_seq_len = int(sqrt(self.s_embedder.num_patches))
|
| 258 |
+
# print(f"enc_half_head_dim: {enc_half_head_dim}, hw_seq_len: {hw_seq_len}")
|
| 259 |
+
self.enc_feat_rope = VisionRotaryEmbeddingFast(
|
| 260 |
+
dim=enc_half_head_dim,
|
| 261 |
+
pt_seq_len=hw_seq_len,
|
| 262 |
+
)
|
| 263 |
+
dec_half_head_dim = self.decoder_hidden_size // dec_num_heads // 2
|
| 264 |
+
hw_seq_len = int(sqrt(self.x_embedder.num_patches))
|
| 265 |
+
# print(f"dec_half_head_dim: {dec_half_head_dim}, hw_seq_len: {hw_seq_len}")
|
| 266 |
+
self.dec_feat_rope = VisionRotaryEmbeddingFast(
|
| 267 |
+
dim=dec_half_head_dim,
|
| 268 |
+
pt_seq_len=hw_seq_len,
|
| 269 |
+
)
|
| 270 |
+
else:
|
| 271 |
+
self.feat_rope = None
|
| 272 |
+
self.blocks = nn.ModuleList([
|
| 273 |
+
LightningDDTBlock(self.encoder_hidden_size if i < self.num_encoder_blocks else self.decoder_hidden_size,
|
| 274 |
+
enc_num_heads if i < self.num_encoder_blocks else dec_num_heads,
|
| 275 |
+
mlp_ratio=mlp_ratio,
|
| 276 |
+
use_qknorm=use_qknorm,
|
| 277 |
+
use_rmsnorm=use_rmsnorm,
|
| 278 |
+
use_swiglu=use_swiglu,
|
| 279 |
+
wo_shift=wo_shift,
|
| 280 |
+
) for i in range(self.num_blocks)
|
| 281 |
+
])
|
| 282 |
+
self.initialize_weights()
|
| 283 |
+
|
| 284 |
+
def initialize_weights(self, xavier_uniform_init: bool = False):
|
| 285 |
+
if xavier_uniform_init:
|
| 286 |
+
def _basic_init(module):
|
| 287 |
+
if isinstance(module, nn.Linear):
|
| 288 |
+
torch.nn.init.xavier_uniform_(module.weight)
|
| 289 |
+
if module.bias is not None:
|
| 290 |
+
nn.init.constant_(module.bias, 0)
|
| 291 |
+
self.apply(_basic_init)
|
| 292 |
+
# Initialize patch_embed like nn.Linear (instead of nn.Conv2d):
|
| 293 |
+
w = self.x_embedder.proj.weight.data
|
| 294 |
+
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 295 |
+
nn.init.constant_(self.x_embedder.proj.bias, 0)
|
| 296 |
+
|
| 297 |
+
# Initialize patch_embed like nn.Linear (instead of nn.Conv2d):
|
| 298 |
+
w = self.s_embedder.proj.weight.data
|
| 299 |
+
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 300 |
+
nn.init.constant_(self.s_embedder.proj.bias, 0)
|
| 301 |
+
|
| 302 |
+
# Initialize label embedding table:
|
| 303 |
+
nn.init.normal_(self.y_embedder.embedding_table.weight, std=0.02)
|
| 304 |
+
if self.use_pos_embed:
|
| 305 |
+
# Initialize (and freeze) pos_embed by sin-cos embedding:
|
| 306 |
+
pos_embed = get_2d_sincos_pos_embed(
|
| 307 |
+
self.pos_embed.shape[-1], int(self.s_embedder.num_patches ** 0.5))
|
| 308 |
+
self.pos_embed.data.copy_(
|
| 309 |
+
torch.from_numpy(pos_embed).float().unsqueeze(0))
|
| 310 |
+
|
| 311 |
+
# Zero-out adaLN modulation layers in LightningDiT blocks:
|
| 312 |
+
for block in self.blocks:
|
| 313 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 314 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 315 |
+
|
| 316 |
+
# Initialize timestep embedding MLP:
|
| 317 |
+
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
| 318 |
+
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
| 319 |
+
|
| 320 |
+
# Zero-out output layers:
|
| 321 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
|
| 322 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
|
| 323 |
+
nn.init.constant_(self.final_layer.linear.weight, 0)
|
| 324 |
+
nn.init.constant_(self.final_layer.linear.bias, 0)
|
| 325 |
+
|
| 326 |
+
def unpatchify(self, x):
|
| 327 |
+
"""
|
| 328 |
+
x: (N, T, patch_size**2 * C)
|
| 329 |
+
imgs: (N, H, W, C)
|
| 330 |
+
"""
|
| 331 |
+
# c = self.out_channels
|
| 332 |
+
c = self.x_channel_per_token
|
| 333 |
+
p = self.x_embedder.patch_size[0]
|
| 334 |
+
h = w = int(x.shape[1] ** 0.5)
|
| 335 |
+
assert h * w == x.shape[1]
|
| 336 |
+
|
| 337 |
+
x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
|
| 338 |
+
x = torch.einsum('nhwpqc->nchpwq', x)
|
| 339 |
+
imgs = x.reshape(shape=(x.shape[0], c, h * p, h * p))
|
| 340 |
+
return imgs
|
| 341 |
+
|
| 342 |
+
def encoder_forward(self, x, t, y):
|
| 343 |
+
"""Encoder part — safe under bf16 autocast, can be torch.compiled."""
|
| 344 |
+
t = self.t_embedder(t)
|
| 345 |
+
y = self.y_embedder(y, self.training)
|
| 346 |
+
c = nn.functional.silu(t + y)
|
| 347 |
+
s = self.s_embedder(x)
|
| 348 |
+
if self.use_pos_embed:
|
| 349 |
+
s = s + self.pos_embed
|
| 350 |
+
for i in range(self.num_encoder_blocks):
|
| 351 |
+
s = self.blocks[i](s, c, feat_rope=self.enc_feat_rope)
|
| 352 |
+
t = t.unsqueeze(1).repeat(1, s.shape[1], 1)
|
| 353 |
+
s = nn.functional.silu(t + s)
|
| 354 |
+
return s
|
| 355 |
+
|
| 356 |
+
def decoder_forward(self, x, s):
|
| 357 |
+
"""Decoder part — runs in fp32 to prevent bf16 accumulation errors in DDT modulation."""
|
| 358 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 359 |
+
s = self.s_projector(s.float())
|
| 360 |
+
x = self.x_embedder(x.float())
|
| 361 |
+
if self.use_pos_embed and self.x_pos_embed is not None:
|
| 362 |
+
x = x + self.x_pos_embed
|
| 363 |
+
for i in range(self.num_encoder_blocks, self.num_blocks):
|
| 364 |
+
x = self.blocks[i](x, s, feat_rope=self.dec_feat_rope)
|
| 365 |
+
x = self.final_layer(x, s)
|
| 366 |
+
x = self.unpatchify(x)
|
| 367 |
+
return x
|
| 368 |
+
|
| 369 |
+
def forward(self, x, t, y, s=None, mask=None):
|
| 370 |
+
if s is None:
|
| 371 |
+
s = self.encoder_forward(x, t, y)
|
| 372 |
+
return self.decoder_forward(x, s)
|
| 373 |
+
|
| 374 |
+
def forward_with_cfg(self, x, t, y, cfg_scale, cfg_interval=(0, 1)):
|
| 375 |
+
"""
|
| 376 |
+
Forward pass of LightningDiT, but also batches the unconditional forward pass for classifier-free guidance.
|
| 377 |
+
"""
|
| 378 |
+
# https://github.com/openai/glide-text2im/blob/main/notebooks/text2im.ipynb
|
| 379 |
+
half = x[: len(x) // 2]
|
| 380 |
+
combined = torch.cat([half, half], dim=0)
|
| 381 |
+
model_out = self.forward(combined, t, y)
|
| 382 |
+
# For exact reproducibility reasons, we apply classifier-free guidance on only
|
| 383 |
+
# three channels by default. The standard approach to cfg applies it to all channels.
|
| 384 |
+
# This can be done by uncommenting the following line and commenting-out the line following that.
|
| 385 |
+
# eps, rest = model_out[:, :self.in_channels], model_out[:, self.in_channels:]
|
| 386 |
+
eps, rest = model_out[:,
|
| 387 |
+
:self.in_channels], model_out[:, self.in_channels:]
|
| 388 |
+
# eps, rest = model_out[:, :3], model_out[:, 3:]
|
| 389 |
+
cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)
|
| 390 |
+
guid_t_min, guid_t_max = cfg_interval
|
| 391 |
+
assert guid_t_min < guid_t_max, "cfg_interval should be (min, max) with min < max"
|
| 392 |
+
t = t[: len(t) // 2] # get t for the conditional half
|
| 393 |
+
half_eps = torch.where(
|
| 394 |
+
((t >= guid_t_min) & (t <= guid_t_max)
|
| 395 |
+
).view(-1, *[1] * (len(cond_eps.shape) - 1)),
|
| 396 |
+
uncond_eps + cfg_scale * (cond_eps - uncond_eps), cond_eps
|
| 397 |
+
)
|
| 398 |
+
eps = torch.cat([half_eps, half_eps], dim=0)
|
| 399 |
+
return torch.cat([eps, rest], dim=1)
|
| 400 |
+
|
| 401 |
+
def forward_with_autoguidance(self, x, t, y, cfg_scale, additional_model_forward, cfg_interval=(0, 1)):
|
| 402 |
+
"""
|
| 403 |
+
Forward pass of LightningDiT, but also contain the forward pass for the additional model
|
| 404 |
+
"""
|
| 405 |
+
model_out = self.forward(x, t, y)
|
| 406 |
+
ag_model_out = additional_model_forward(x, t, y)
|
| 407 |
+
eps = model_out[:, :self.in_channels]
|
| 408 |
+
ag_eps = ag_model_out[:, :self.in_channels]
|
| 409 |
+
|
| 410 |
+
guid_t_min, guid_t_max = cfg_interval
|
| 411 |
+
assert guid_t_min < guid_t_max, "cfg_interval should be (min, max) with min < max"
|
| 412 |
+
eps = torch.where(
|
| 413 |
+
((t >= guid_t_min) & (t <= guid_t_max)
|
| 414 |
+
).view(-1, *[1] * (len(eps.shape) - 1)),
|
| 415 |
+
ag_eps + cfg_scale * (eps - ag_eps), eps
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
return eps
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
class DiTwDDTHeadDualHead(DiTwDDTHead):
|
| 422 |
+
"""DDT with dual decoder heads: one for semantic, one for recon.
|
| 423 |
+
|
| 424 |
+
Shared encoder (28 blocks) → two independent decoder branches:
|
| 425 |
+
- sem branch: s_projector_sem → x_embedder_sem → decoder_sem_blocks → final_layer_sem → sem_channels
|
| 426 |
+
- recon branch: s_projector_recon → x_embedder_recon → decoder_recon_blocks → final_layer_recon → recon_channels
|
| 427 |
+
Outputs are concatenated back to in_channels.
|
| 428 |
+
"""
|
| 429 |
+
|
| 430 |
+
def __init__(
|
| 431 |
+
self,
|
| 432 |
+
sem_channels: int = 768,
|
| 433 |
+
input_size: int = 1,
|
| 434 |
+
patch_size: Union[list, int] = 1,
|
| 435 |
+
in_channels: int = 768,
|
| 436 |
+
hidden_size=[1152, 2048],
|
| 437 |
+
depth=[28, 2],
|
| 438 |
+
num_heads: Union[list[int], int] = [16, 16],
|
| 439 |
+
mlp_ratio=4.0,
|
| 440 |
+
class_dropout_prob=0.1,
|
| 441 |
+
num_classes=1000,
|
| 442 |
+
use_qknorm=False,
|
| 443 |
+
use_swiglu=True,
|
| 444 |
+
use_rope=True,
|
| 445 |
+
use_rmsnorm=True,
|
| 446 |
+
wo_shift=False,
|
| 447 |
+
use_pos_embed: bool = True,
|
| 448 |
+
):
|
| 449 |
+
# Initialize parent (builds shared encoder + original decoder)
|
| 450 |
+
super().__init__(
|
| 451 |
+
input_size=input_size, patch_size=patch_size, in_channels=in_channels,
|
| 452 |
+
hidden_size=hidden_size, depth=depth, num_heads=num_heads,
|
| 453 |
+
mlp_ratio=mlp_ratio, class_dropout_prob=class_dropout_prob,
|
| 454 |
+
num_classes=num_classes, use_qknorm=use_qknorm, use_swiglu=use_swiglu,
|
| 455 |
+
use_rope=use_rope, use_rmsnorm=use_rmsnorm, wo_shift=wo_shift,
|
| 456 |
+
use_pos_embed=use_pos_embed,
|
| 457 |
+
)
|
| 458 |
+
recon_channels = in_channels - sem_channels
|
| 459 |
+
assert recon_channels > 0, f"sem_channels={sem_channels} >= in_channels={in_channels}"
|
| 460 |
+
self.sem_channels = sem_channels
|
| 461 |
+
self.recon_channels = recon_channels
|
| 462 |
+
|
| 463 |
+
p = self.x_patch_size
|
| 464 |
+
dec_num_heads = self.num_heads[1]
|
| 465 |
+
_mlp_ratio = mlp_ratio
|
| 466 |
+
|
| 467 |
+
# Input size for x_embedder (spatial dim before patchify)
|
| 468 |
+
_x_input_size = int(sqrt(self.x_embedder.num_patches)) * self.x_patch_size
|
| 469 |
+
|
| 470 |
+
# --- Semantic decoder branch ---
|
| 471 |
+
sem_ch_per_token = sem_channels * p * p
|
| 472 |
+
self.x_embedder_sem = PatchEmbed(
|
| 473 |
+
_x_input_size, self.x_patch_size, sem_ch_per_token, self.decoder_hidden_size, bias=True)
|
| 474 |
+
self.s_projector_sem = nn.Linear(
|
| 475 |
+
self.encoder_hidden_size, self.decoder_hidden_size) if self.encoder_hidden_size != self.decoder_hidden_size else nn.Identity()
|
| 476 |
+
self.decoder_blocks_sem = nn.ModuleList([
|
| 477 |
+
LightningDDTBlock(self.decoder_hidden_size, dec_num_heads,
|
| 478 |
+
mlp_ratio=_mlp_ratio,
|
| 479 |
+
use_qknorm=use_qknorm, use_rmsnorm=use_rmsnorm,
|
| 480 |
+
use_swiglu=use_swiglu, wo_shift=wo_shift)
|
| 481 |
+
for _ in range(self.num_decoder_blocks)
|
| 482 |
+
])
|
| 483 |
+
self.final_layer_sem = DDTFinalLayer(
|
| 484 |
+
self.decoder_hidden_size, 1, sem_ch_per_token, use_rmsnorm=use_rmsnorm)
|
| 485 |
+
|
| 486 |
+
# --- Recon decoder branch ---
|
| 487 |
+
recon_ch_per_token = recon_channels * p * p
|
| 488 |
+
self.x_embedder_recon = PatchEmbed(
|
| 489 |
+
_x_input_size, self.x_patch_size, recon_ch_per_token, self.decoder_hidden_size, bias=True)
|
| 490 |
+
self.s_projector_recon = nn.Linear(
|
| 491 |
+
self.encoder_hidden_size, self.decoder_hidden_size) if self.encoder_hidden_size != self.decoder_hidden_size else nn.Identity()
|
| 492 |
+
self.decoder_blocks_recon = nn.ModuleList([
|
| 493 |
+
LightningDDTBlock(self.decoder_hidden_size, dec_num_heads,
|
| 494 |
+
mlp_ratio=_mlp_ratio,
|
| 495 |
+
use_qknorm=use_qknorm, use_rmsnorm=use_rmsnorm,
|
| 496 |
+
use_swiglu=use_swiglu, wo_shift=wo_shift)
|
| 497 |
+
for _ in range(self.num_decoder_blocks)
|
| 498 |
+
])
|
| 499 |
+
self.final_layer_recon = DDTFinalLayer(
|
| 500 |
+
self.decoder_hidden_size, 1, recon_ch_per_token, use_rmsnorm=use_rmsnorm)
|
| 501 |
+
|
| 502 |
+
# RoPE for new decoder branches (shared same config as original dec_feat_rope)
|
| 503 |
+
if self.use_rope:
|
| 504 |
+
self.dec_feat_rope_sem = self.dec_feat_rope
|
| 505 |
+
self.dec_feat_rope_recon = self.dec_feat_rope
|
| 506 |
+
|
| 507 |
+
# Remove parent's original decoder modules (unused, would cause DDP errors)
|
| 508 |
+
del self.x_embedder
|
| 509 |
+
del self.s_projector
|
| 510 |
+
del self.final_layer
|
| 511 |
+
# Remove parent's original decoder blocks (blocks[num_encoder_blocks:])
|
| 512 |
+
original_blocks = list(self.blocks)
|
| 513 |
+
self.blocks = nn.ModuleList(original_blocks[:self.num_encoder_blocks])
|
| 514 |
+
|
| 515 |
+
# Initialize new modules
|
| 516 |
+
self._init_dual_head_weights()
|
| 517 |
+
|
| 518 |
+
logger.info(f"DiTwDDTHeadDualHead: sem_channels={sem_channels}, recon_channels={recon_channels}, "
|
| 519 |
+
f"decoder_blocks={self.num_decoder_blocks} x2, decoder_hidden={self.decoder_hidden_size}")
|
| 520 |
+
|
| 521 |
+
def _init_dual_head_weights(self):
|
| 522 |
+
"""Initialize weights for the dual decoder branches."""
|
| 523 |
+
for embedder in [self.x_embedder_sem, self.x_embedder_recon]:
|
| 524 |
+
w = embedder.proj.weight.data
|
| 525 |
+
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 526 |
+
nn.init.constant_(embedder.proj.bias, 0)
|
| 527 |
+
|
| 528 |
+
for blocks in [self.decoder_blocks_sem, self.decoder_blocks_recon]:
|
| 529 |
+
for block in blocks:
|
| 530 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 531 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 532 |
+
|
| 533 |
+
for fl in [self.final_layer_sem, self.final_layer_recon]:
|
| 534 |
+
nn.init.constant_(fl.adaLN_modulation[-1].weight, 0)
|
| 535 |
+
nn.init.constant_(fl.adaLN_modulation[-1].bias, 0)
|
| 536 |
+
nn.init.constant_(fl.linear.weight, 0)
|
| 537 |
+
nn.init.constant_(fl.linear.bias, 0)
|
| 538 |
+
|
| 539 |
+
def _unpatchify_branch(self, x, channels):
|
| 540 |
+
"""Unpatchify for a branch with specific number of channels."""
|
| 541 |
+
c = channels * self.x_patch_size * self.x_patch_size
|
| 542 |
+
p = self.x_patch_size
|
| 543 |
+
h = w = int(x.shape[1] ** 0.5)
|
| 544 |
+
assert h * w == x.shape[1]
|
| 545 |
+
x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
|
| 546 |
+
x = torch.einsum('nhwpqc->nchpwq', x)
|
| 547 |
+
imgs = x.reshape(shape=(x.shape[0], c, h * p, h * p))
|
| 548 |
+
return imgs
|
| 549 |
+
|
| 550 |
+
def decoder_forward(self, x, s):
|
| 551 |
+
"""Dual decoder: sem and recon branches run independently, outputs concatenated."""
|
| 552 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 553 |
+
# Split input x along channel dim: [B, C, H, W] → sem [B, sem_ch, H, W] + recon [B, recon_ch, H, W]
|
| 554 |
+
x_float = x.float()
|
| 555 |
+
x_sem_input = x_float[:, :self.sem_channels, :, :]
|
| 556 |
+
x_recon_input = x_float[:, self.sem_channels:, :, :]
|
| 557 |
+
|
| 558 |
+
s_float = s.float()
|
| 559 |
+
|
| 560 |
+
# --- Semantic branch ---
|
| 561 |
+
s_sem = self.s_projector_sem(s_float)
|
| 562 |
+
h_sem = self.x_embedder_sem(x_sem_input)
|
| 563 |
+
rope_sem = self.dec_feat_rope_sem if self.use_rope else None
|
| 564 |
+
for block in self.decoder_blocks_sem:
|
| 565 |
+
h_sem = block(h_sem, s_sem, feat_rope=rope_sem)
|
| 566 |
+
h_sem = self.final_layer_sem(h_sem, s_sem)
|
| 567 |
+
out_sem = self._unpatchify_branch(h_sem, self.sem_channels) # [B, sem_ch, H, W]
|
| 568 |
+
|
| 569 |
+
# --- Recon branch ---
|
| 570 |
+
s_recon = self.s_projector_recon(s_float)
|
| 571 |
+
h_recon = self.x_embedder_recon(x_recon_input)
|
| 572 |
+
rope_recon = self.dec_feat_rope_recon if self.use_rope else None
|
| 573 |
+
for block in self.decoder_blocks_recon:
|
| 574 |
+
h_recon = block(h_recon, s_recon, feat_rope=rope_recon)
|
| 575 |
+
h_recon = self.final_layer_recon(h_recon, s_recon)
|
| 576 |
+
out_recon = self._unpatchify_branch(h_recon, self.recon_channels) # [B, recon_ch, H, W]
|
| 577 |
+
|
| 578 |
+
# Concatenate: [B, sem_ch + recon_ch, H, W] = [B, in_channels, H, W]
|
| 579 |
+
out = torch.cat([out_sem, out_recon], dim=1)
|
| 580 |
+
return out
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
class PartialRoPE(nn.Module):
|
| 584 |
+
"""
|
| 585 |
+
A RoPE wrapper that only applies rotation to the last `num_spatial` tokens
|
| 586 |
+
in the sequence, leaving the leading `num_globaltoken` tokens unchanged.
|
| 587 |
+
|
| 588 |
+
The underlying `feat_rope` (VisionRotaryEmbeddingFast) expects input of shape
|
| 589 |
+
[B, num_heads, num_spatial, head_dim] and returns the same shape with RoPE applied.
|
| 590 |
+
This wrapper slices q/k, applies RoPE to the spatial slice, then stitches back.
|
| 591 |
+
|
| 592 |
+
Args:
|
| 593 |
+
feat_rope: a VisionRotaryEmbeddingFast instance sized for num_spatial tokens.
|
| 594 |
+
num_globaltoken: number of leading global tokens to skip.
|
| 595 |
+
"""
|
| 596 |
+
def __init__(self, feat_rope, num_globaltoken: int):
|
| 597 |
+
super().__init__()
|
| 598 |
+
self.feat_rope = feat_rope
|
| 599 |
+
self.num_globaltoken = num_globaltoken
|
| 600 |
+
|
| 601 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 602 |
+
"""
|
| 603 |
+
Args:
|
| 604 |
+
x: [B, num_heads, N, head_dim] where N = num_globaltoken + num_spatial
|
| 605 |
+
Returns:
|
| 606 |
+
x: same shape, with RoPE applied only to x[:, :, num_globaltoken:, :]
|
| 607 |
+
"""
|
| 608 |
+
g = self.num_globaltoken
|
| 609 |
+
x_global = x[:, :, :g, :] # (B, H, num_globaltoken, D) — untouched
|
| 610 |
+
x_spatial = x[:, :, g:, :] # (B, H, num_spatial, D) — apply RoPE
|
| 611 |
+
x_spatial = self.feat_rope(x_spatial)
|
| 612 |
+
return torch.cat([x_global, x_spatial], dim=2)
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
class DiTwDDTHeadTokenInput(nn.Module):
|
| 616 |
+
"""
|
| 617 |
+
A variant of DiTwDDTHead that accepts pre-tokenized input x of shape [B, N, D],
|
| 618 |
+
where N = num_globaltoken + H * W (global tokens come first).
|
| 619 |
+
|
| 620 |
+
Key differences from DiTwDDTHead:
|
| 621 |
+
- Input x is already tokenized: no PatchEmbed needed; a linear projector maps D -> hidden_size.
|
| 622 |
+
- Position encoding:
|
| 623 |
+
* spatial tokens (H*W) use fixed 2D sin-cos positional embedding.
|
| 624 |
+
* global tokens use learnable positional embedding.
|
| 625 |
+
- Encoder (DDT-style): all N tokens pass through encoder blocks together with AdaLN condition c.
|
| 626 |
+
After encoding, timestep t is broadcast and fused into the token sequence as the condition s.
|
| 627 |
+
- Decoder: all N tokens are denoised conditioned on s from the encoder via AdaLN.
|
| 628 |
+
- Output: [B, N, D], same shape as input (both global and spatial tokens are denoised).
|
| 629 |
+
"""
|
| 630 |
+
|
| 631 |
+
def __init__(
|
| 632 |
+
self,
|
| 633 |
+
in_channels: int = 768, # D: input/output token dimension
|
| 634 |
+
hw_size: int = 16, # spatial grid side length, num spatial tokens = hw_size^2
|
| 635 |
+
num_globaltoken: int = 8, # number of global tokens (prepended before spatial tokens)
|
| 636 |
+
hidden_size: Union[list, int] = [1152, 2048], # [encoder_hidden, decoder_hidden]
|
| 637 |
+
depth: list = [28, 2], # [num_encoder_blocks, num_decoder_blocks]
|
| 638 |
+
num_heads: Union[list, int] = [16, 16],
|
| 639 |
+
mlp_ratio: float = 4.0,
|
| 640 |
+
class_dropout_prob: float = 0.1,
|
| 641 |
+
num_classes: int = 1000,
|
| 642 |
+
use_qknorm: bool = False,
|
| 643 |
+
use_swiglu: bool = True,
|
| 644 |
+
use_rope: bool = False, # RoPE is disabled by default for mixed token sequences
|
| 645 |
+
use_rmsnorm: bool = True,
|
| 646 |
+
wo_shift: bool = False,
|
| 647 |
+
):
|
| 648 |
+
super().__init__()
|
| 649 |
+
self.in_channels = in_channels
|
| 650 |
+
self.out_channels = in_channels
|
| 651 |
+
self.hw_size = hw_size
|
| 652 |
+
self.num_spatial = hw_size * hw_size
|
| 653 |
+
self.num_globaltoken = num_globaltoken
|
| 654 |
+
self.num_tokens = num_globaltoken + self.num_spatial # total N
|
| 655 |
+
|
| 656 |
+
# hidden sizes
|
| 657 |
+
if isinstance(hidden_size, int):
|
| 658 |
+
hidden_size = [hidden_size, hidden_size]
|
| 659 |
+
self.encoder_hidden_size = hidden_size[0]
|
| 660 |
+
self.decoder_hidden_size = hidden_size[1]
|
| 661 |
+
|
| 662 |
+
# num heads
|
| 663 |
+
if isinstance(num_heads, int):
|
| 664 |
+
num_heads = [num_heads, num_heads]
|
| 665 |
+
self.num_heads = list(num_heads)
|
| 666 |
+
enc_num_heads = self.num_heads[0]
|
| 667 |
+
dec_num_heads = self.num_heads[1]
|
| 668 |
+
|
| 669 |
+
# block depths
|
| 670 |
+
self.num_encoder_blocks = depth[0]
|
| 671 |
+
self.num_decoder_blocks = depth[1]
|
| 672 |
+
self.num_blocks = depth[0] + depth[1]
|
| 673 |
+
self.use_rope = use_rope
|
| 674 |
+
|
| 675 |
+
# ---------- input projectors ----------
|
| 676 |
+
# project D -> encoder_hidden_size for the encoder stage
|
| 677 |
+
self.enc_input_proj = nn.Linear(in_channels, self.encoder_hidden_size, bias=True)
|
| 678 |
+
# project D -> decoder_hidden_size for the decoder stage
|
| 679 |
+
self.dec_input_proj = nn.Linear(in_channels, self.decoder_hidden_size, bias=True)
|
| 680 |
+
|
| 681 |
+
# ---------- position embeddings ----------
|
| 682 |
+
# spatial tokens: fixed 2D sin-cos, shape (1, num_spatial, encoder_hidden_size)
|
| 683 |
+
self.enc_spatial_pos_embed = nn.Parameter(
|
| 684 |
+
torch.zeros(1, self.num_spatial, self.encoder_hidden_size), requires_grad=False)
|
| 685 |
+
self.dec_spatial_pos_embed = nn.Parameter(
|
| 686 |
+
torch.zeros(1, self.num_spatial, self.decoder_hidden_size), requires_grad=False)
|
| 687 |
+
|
| 688 |
+
# global tokens: learnable, shape (1, num_globaltoken, hidden_size)
|
| 689 |
+
self.enc_global_pos_embed = nn.Parameter(
|
| 690 |
+
torch.zeros(1, num_globaltoken, self.encoder_hidden_size), requires_grad=True)
|
| 691 |
+
self.dec_global_pos_embed = nn.Parameter(
|
| 692 |
+
torch.zeros(1, num_globaltoken, self.decoder_hidden_size), requires_grad=True)
|
| 693 |
+
|
| 694 |
+
# ---------- condition embedders ----------
|
| 695 |
+
self.t_embedder = GaussianFourierEmbedding(self.encoder_hidden_size)
|
| 696 |
+
self.y_embedder = LabelEmbedder(num_classes, self.encoder_hidden_size, class_dropout_prob)
|
| 697 |
+
|
| 698 |
+
# project encoder hidden -> decoder hidden for condition s
|
| 699 |
+
self.s_projector = (
|
| 700 |
+
nn.Linear(self.encoder_hidden_size, self.decoder_hidden_size)
|
| 701 |
+
if self.encoder_hidden_size != self.decoder_hidden_size
|
| 702 |
+
else nn.Identity()
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
# ---------- RoPE (optional, only applied to spatial tokens) ----------
|
| 706 |
+
# global tokens are skipped; PartialRoPE wraps VisionRotaryEmbeddingFast
|
| 707 |
+
# so that only the trailing num_spatial positions are rotated.
|
| 708 |
+
if self.use_rope:
|
| 709 |
+
enc_half_head_dim = self.encoder_hidden_size // enc_num_heads // 2
|
| 710 |
+
self.enc_feat_rope = PartialRoPE(
|
| 711 |
+
feat_rope=VisionRotaryEmbeddingFast(
|
| 712 |
+
dim=enc_half_head_dim,
|
| 713 |
+
pt_seq_len=hw_size,
|
| 714 |
+
),
|
| 715 |
+
num_globaltoken=num_globaltoken,
|
| 716 |
+
)
|
| 717 |
+
dec_half_head_dim = self.decoder_hidden_size // dec_num_heads // 2
|
| 718 |
+
self.dec_feat_rope = PartialRoPE(
|
| 719 |
+
feat_rope=VisionRotaryEmbeddingFast(
|
| 720 |
+
dim=dec_half_head_dim,
|
| 721 |
+
pt_seq_len=hw_size,
|
| 722 |
+
),
|
| 723 |
+
num_globaltoken=num_globaltoken,
|
| 724 |
+
)
|
| 725 |
+
else:
|
| 726 |
+
self.enc_feat_rope = None
|
| 727 |
+
self.dec_feat_rope = None
|
| 728 |
+
|
| 729 |
+
# ---------- transformer blocks ----------
|
| 730 |
+
self.blocks = nn.ModuleList([
|
| 731 |
+
LightningDDTBlock(
|
| 732 |
+
self.encoder_hidden_size if i < self.num_encoder_blocks else self.decoder_hidden_size,
|
| 733 |
+
enc_num_heads if i < self.num_encoder_blocks else dec_num_heads,
|
| 734 |
+
mlp_ratio=mlp_ratio,
|
| 735 |
+
use_qknorm=use_qknorm,
|
| 736 |
+
use_rmsnorm=use_rmsnorm,
|
| 737 |
+
use_swiglu=use_swiglu,
|
| 738 |
+
wo_shift=wo_shift,
|
| 739 |
+
) for i in range(self.num_blocks)
|
| 740 |
+
])
|
| 741 |
+
|
| 742 |
+
# ---------- final layer ----------
|
| 743 |
+
# outputs in_channels per token (no patch expansion)
|
| 744 |
+
self.final_layer = DDTFinalLayer(
|
| 745 |
+
self.decoder_hidden_size, 1, in_channels, use_rmsnorm=use_rmsnorm)
|
| 746 |
+
|
| 747 |
+
# ---------- output projector ----------
|
| 748 |
+
# map decoder_hidden_size back to in_channels
|
| 749 |
+
# (DDTFinalLayer already does linear(hidden -> 1*1*in_channels), so this is identity-like)
|
| 750 |
+
# We keep it explicit for clarity; DDTFinalLayer.linear outputs in_channels directly.
|
| 751 |
+
|
| 752 |
+
self.initialize_weights()
|
| 753 |
+
|
| 754 |
+
# ------------------------------------------------------------------
|
| 755 |
+
def initialize_weights(self):
|
| 756 |
+
# sin-cos 2D pos embed for spatial tokens (encoder)
|
| 757 |
+
enc_pos = get_2d_sincos_pos_embed(self.encoder_hidden_size, self.hw_size)
|
| 758 |
+
self.enc_spatial_pos_embed.data.copy_(
|
| 759 |
+
torch.from_numpy(enc_pos).float().unsqueeze(0))
|
| 760 |
+
|
| 761 |
+
# sin-cos 2D pos embed for spatial tokens (decoder)
|
| 762 |
+
dec_pos = get_2d_sincos_pos_embed(self.decoder_hidden_size, self.hw_size)
|
| 763 |
+
self.dec_spatial_pos_embed.data.copy_(
|
| 764 |
+
torch.from_numpy(dec_pos).float().unsqueeze(0))
|
| 765 |
+
|
| 766 |
+
# learnable global pos embed: small normal init
|
| 767 |
+
nn.init.normal_(self.enc_global_pos_embed, std=0.02)
|
| 768 |
+
nn.init.normal_(self.dec_global_pos_embed, std=0.02)
|
| 769 |
+
|
| 770 |
+
# input projectors
|
| 771 |
+
nn.init.xavier_uniform_(self.enc_input_proj.weight)
|
| 772 |
+
nn.init.constant_(self.enc_input_proj.bias, 0)
|
| 773 |
+
nn.init.xavier_uniform_(self.dec_input_proj.weight)
|
| 774 |
+
nn.init.constant_(self.dec_input_proj.bias, 0)
|
| 775 |
+
|
| 776 |
+
# label embedding
|
| 777 |
+
nn.init.normal_(self.y_embedder.embedding_table.weight, std=0.02)
|
| 778 |
+
|
| 779 |
+
# timestep embedding MLP
|
| 780 |
+
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
| 781 |
+
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
| 782 |
+
|
| 783 |
+
# zero-out adaLN modulation layers
|
| 784 |
+
for block in self.blocks:
|
| 785 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 786 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 787 |
+
|
| 788 |
+
# zero-out final layer
|
| 789 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
|
| 790 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
|
| 791 |
+
nn.init.constant_(self.final_layer.linear.weight, 0)
|
| 792 |
+
nn.init.constant_(self.final_layer.linear.bias, 0)
|
| 793 |
+
|
| 794 |
+
# ------------------------------------------------------------------
|
| 795 |
+
def _build_pos_embed(self, hidden_size, global_pos_embed, spatial_pos_embed):
|
| 796 |
+
"""
|
| 797 |
+
Concatenate [global_pos_embed, spatial_pos_embed] along dim=1.
|
| 798 |
+
global_pos_embed: (1, num_globaltoken, hidden_size) — learnable
|
| 799 |
+
spatial_pos_embed: (1, num_spatial, hidden_size) — fixed sin-cos
|
| 800 |
+
Returns: (1, num_tokens, hidden_size)
|
| 801 |
+
"""
|
| 802 |
+
return torch.cat([global_pos_embed, spatial_pos_embed], dim=1)
|
| 803 |
+
|
| 804 |
+
# ------------------------------------------------------------------
|
| 805 |
+
def forward(self, x, t, y, s=None):
|
| 806 |
+
"""
|
| 807 |
+
Args:
|
| 808 |
+
x: [B, N, D] noisy token sequence, N = num_globaltoken + H*W
|
| 809 |
+
t: [B] diffusion timestep
|
| 810 |
+
y: [B] class label (int)
|
| 811 |
+
s: optional pre-computed encoder condition [B, N, encoder_hidden_size].
|
| 812 |
+
If provided, the encoder stage is skipped.
|
| 813 |
+
Returns:
|
| 814 |
+
out: [B, N, D] denoised token sequence (same shape as input)
|
| 815 |
+
"""
|
| 816 |
+
B, N, D = x.shape
|
| 817 |
+
assert N == self.num_tokens, (
|
| 818 |
+
f"Expected N={self.num_tokens} (num_globaltoken={self.num_globaltoken} + "
|
| 819 |
+
f"num_spatial={self.num_spatial}), got {N}"
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
# ---- condition embeddings ----
|
| 823 |
+
t_emb = self.t_embedder(t) # (B, encoder_hidden_size)
|
| 824 |
+
y_emb = self.y_embedder(y, self.training) # (B, encoder_hidden_size)
|
| 825 |
+
c = nn.functional.silu(t_emb + y_emb) # (B, encoder_hidden_size)
|
| 826 |
+
|
| 827 |
+
# ---- encoder stage ----
|
| 828 |
+
if s is None:
|
| 829 |
+
# project input to encoder hidden size
|
| 830 |
+
xe = self.enc_input_proj(x) # (B, N, encoder_hidden_size)
|
| 831 |
+
|
| 832 |
+
# add position embeddings:
|
| 833 |
+
# global tokens (front): learnable enc_global_pos_embed
|
| 834 |
+
# spatial tokens (back): fixed sin-cos enc_spatial_pos_embed
|
| 835 |
+
pos = self._build_pos_embed(
|
| 836 |
+
self.encoder_hidden_size,
|
| 837 |
+
self.enc_global_pos_embed, # (1, num_globaltoken, enc_h)
|
| 838 |
+
self.enc_spatial_pos_embed, # (1, num_spatial, enc_h)
|
| 839 |
+
) # (1, N, encoder_hidden_size)
|
| 840 |
+
xe = xe + pos
|
| 841 |
+
|
| 842 |
+
# pass through encoder blocks (self-attention conditioned on c)
|
| 843 |
+
# PartialRoPE ensures only spatial tokens (trailing hw_size^2) are rotated
|
| 844 |
+
for i in range(self.num_encoder_blocks):
|
| 845 |
+
xe = self.blocks[i](xe, c, feat_rope=self.enc_feat_rope)
|
| 846 |
+
|
| 847 |
+
# fuse timestep into encoder output to form condition s
|
| 848 |
+
t_broadcast = t_emb.unsqueeze(1).expand(-1, N, -1) # (B, N, enc_h)
|
| 849 |
+
s = nn.functional.silu(t_broadcast + xe) # (B, N, encoder_hidden_size)
|
| 850 |
+
|
| 851 |
+
# project condition s to decoder hidden size
|
| 852 |
+
s = self.s_projector(s) # (B, N, decoder_hidden_size)
|
| 853 |
+
|
| 854 |
+
# ---- decoder stage ----
|
| 855 |
+
# project input to decoder hidden size
|
| 856 |
+
xd = self.dec_input_proj(x) # (B, N, decoder_hidden_size)
|
| 857 |
+
|
| 858 |
+
# add decoder position embeddings
|
| 859 |
+
dec_pos = self._build_pos_embed(
|
| 860 |
+
self.decoder_hidden_size,
|
| 861 |
+
self.dec_global_pos_embed, # (1, num_globaltoken, dec_h)
|
| 862 |
+
self.dec_spatial_pos_embed, # (1, num_spatial, dec_h)
|
| 863 |
+
) # (1, N, decoder_hidden_size)
|
| 864 |
+
xd = xd + dec_pos
|
| 865 |
+
|
| 866 |
+
# pass through decoder blocks (conditioned on s)
|
| 867 |
+
# PartialRoPE ensures only spatial tokens (trailing hw_size^2) are rotated
|
| 868 |
+
for i in range(self.num_encoder_blocks, self.num_blocks):
|
| 869 |
+
xd = self.blocks[i](xd, s, feat_rope=self.dec_feat_rope)
|
| 870 |
+
|
| 871 |
+
# final layer: outputs (B, N, in_channels)
|
| 872 |
+
out = self.final_layer(xd, s) # (B, N, in_channels)
|
| 873 |
+
return out
|
| 874 |
+
|
| 875 |
+
# ------------------------------------------------------------------
|
| 876 |
+
def forward_with_cfg(self, x, t, y, cfg_scale, cfg_interval=(0, 1)):
|
| 877 |
+
"""
|
| 878 |
+
Classifier-free guidance forward pass.
|
| 879 |
+
Assumes x is already doubled: first half conditional, second half unconditional.
|
| 880 |
+
"""
|
| 881 |
+
half = x[: len(x) // 2]
|
| 882 |
+
combined = torch.cat([half, half], dim=0)
|
| 883 |
+
model_out = self.forward(combined, t, y) # (2B, N, D)
|
| 884 |
+
|
| 885 |
+
eps, rest = model_out[:, :, :self.in_channels], model_out[:, :, self.in_channels:]
|
| 886 |
+
cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)
|
| 887 |
+
|
| 888 |
+
guid_t_min, guid_t_max = cfg_interval
|
| 889 |
+
assert guid_t_min < guid_t_max
|
| 890 |
+
t_half = t[: len(t) // 2]
|
| 891 |
+
half_eps = torch.where(
|
| 892 |
+
((t_half >= guid_t_min) & (t_half <= guid_t_max)
|
| 893 |
+
).view(-1, *[1] * (len(cond_eps.shape) - 1)),
|
| 894 |
+
uncond_eps + cfg_scale * (cond_eps - uncond_eps),
|
| 895 |
+
cond_eps,
|
| 896 |
+
)
|
| 897 |
+
eps = torch.cat([half_eps, half_eps], dim=0)
|
| 898 |
+
return torch.cat([eps, rest], dim=-1)
|
| 899 |
+
|
| 900 |
+
# ------------------------------------------------------------------
|
| 901 |
+
def forward_with_autoguidance(self, x, t, y, cfg_scale, additional_model_forward, cfg_interval=(0, 1)):
|
| 902 |
+
"""
|
| 903 |
+
Auto-guidance forward pass using an additional (weaker) model.
|
| 904 |
+
"""
|
| 905 |
+
model_out = self.forward(x, t, y)
|
| 906 |
+
ag_model_out = additional_model_forward(x, t, y)
|
| 907 |
+
|
| 908 |
+
eps = model_out[:, :, :self.in_channels]
|
| 909 |
+
ag_eps = ag_model_out[:, :, :self.in_channels]
|
| 910 |
+
|
| 911 |
+
guid_t_min, guid_t_max = cfg_interval
|
| 912 |
+
assert guid_t_min < guid_t_max
|
| 913 |
+
eps = torch.where(
|
| 914 |
+
((t >= guid_t_min) & (t <= guid_t_max)
|
| 915 |
+
).view(-1, *[1] * (len(eps.shape) - 1)),
|
| 916 |
+
ag_eps + cfg_scale * (eps - ag_eps),
|
| 917 |
+
eps,
|
| 918 |
+
)
|
| 919 |
+
return eps
|
patch-reparameterization/src/stage2/models/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Encoder registry for stage 2 models."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Import modules that perform registration on import.
|
| 6 |
+
from typing import Callable, Dict, Optional, Type, Union
|
| 7 |
+
from typing import Protocol, Any, runtime_checkable
|
| 8 |
+
|
| 9 |
+
@runtime_checkable
|
| 10 |
+
class Stage2ModelProtocol(Protocol):
|
| 11 |
+
def forward(self, *args: Any, **kwargs: Any) -> Any: ...
|
| 12 |
+
def forward_with_cfg(self, *args: Any, **kwargs: Any) -> Any: ...
|
| 13 |
+
def forward_with_autoguidance(self, *args: Any, **kwargs: Any) -> Any: ...
|
| 14 |
+
def train(self, mode: bool = True) -> Any: ...
|
| 15 |
+
def eval(self) -> Any: ...
|
patch-reparameterization/src/stage2/models/lightningDiT.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from timm.models.vision_transformer import PatchEmbed, Mlp
|
| 4 |
+
import torch
|
| 5 |
+
from torch import nn
|
| 6 |
+
|
| 7 |
+
from .model_utils import VisionRotaryEmbeddingFast, SwiGLUFFN, RMSNorm, NormAttention, LabelEmbedder, get_2d_sincos_pos_embed, GaussianFourierEmbedding, modulate
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class LightningDiTBlock(nn.Module):
|
| 13 |
+
"""
|
| 14 |
+
Lightning DiT Block. We add features including:
|
| 15 |
+
- ROPE
|
| 16 |
+
- QKNorm
|
| 17 |
+
- RMSNorm
|
| 18 |
+
- SwiGLU
|
| 19 |
+
- No shift AdaLN.
|
| 20 |
+
Not all of them are used in the final model, please refer to the paper for more details.
|
| 21 |
+
"""
|
| 22 |
+
def __init__(
|
| 23 |
+
self,
|
| 24 |
+
hidden_size,
|
| 25 |
+
num_heads,
|
| 26 |
+
mlp_ratio=4.0,
|
| 27 |
+
use_qknorm=False,
|
| 28 |
+
use_swiglu=True,
|
| 29 |
+
use_rmsnorm=True,
|
| 30 |
+
wo_shift=False,
|
| 31 |
+
**block_kwargs
|
| 32 |
+
):
|
| 33 |
+
super().__init__()
|
| 34 |
+
|
| 35 |
+
# Initialize normalization layers
|
| 36 |
+
if not use_rmsnorm:
|
| 37 |
+
self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 38 |
+
self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 39 |
+
else:
|
| 40 |
+
self.norm1 = RMSNorm(hidden_size)
|
| 41 |
+
self.norm2 = RMSNorm(hidden_size)
|
| 42 |
+
|
| 43 |
+
# Initialize attention layer
|
| 44 |
+
self.attn = NormAttention(
|
| 45 |
+
hidden_size,
|
| 46 |
+
num_heads=num_heads,
|
| 47 |
+
qkv_bias=True,
|
| 48 |
+
qk_norm=use_qknorm,
|
| 49 |
+
use_rmsnorm=use_rmsnorm,
|
| 50 |
+
**block_kwargs
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# Initialize MLP layer
|
| 54 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 55 |
+
approx_gelu = lambda: nn.GELU(approximate="tanh")
|
| 56 |
+
if use_swiglu:
|
| 57 |
+
# here we did not use SwiGLU from xformers because it is not compatible with torch.compile for now.
|
| 58 |
+
self.mlp = SwiGLUFFN(hidden_size, int(2/3 * mlp_hidden_dim))
|
| 59 |
+
else:
|
| 60 |
+
self.mlp = Mlp(
|
| 61 |
+
in_features=hidden_size,
|
| 62 |
+
hidden_features=mlp_hidden_dim,
|
| 63 |
+
act_layer=approx_gelu,
|
| 64 |
+
drop=0
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# Initialize AdaLN modulation
|
| 68 |
+
if wo_shift:
|
| 69 |
+
self.adaLN_modulation = nn.Sequential(
|
| 70 |
+
nn.SiLU(),
|
| 71 |
+
nn.Linear(hidden_size, 4 * hidden_size, bias=True)
|
| 72 |
+
)
|
| 73 |
+
else:
|
| 74 |
+
self.adaLN_modulation = nn.Sequential(
|
| 75 |
+
nn.SiLU(),
|
| 76 |
+
nn.Linear(hidden_size, 6 * hidden_size, bias=True)
|
| 77 |
+
)
|
| 78 |
+
self.wo_shift = wo_shift
|
| 79 |
+
|
| 80 |
+
def forward(self, x, c, feat_rope=None):
|
| 81 |
+
if self.wo_shift:
|
| 82 |
+
scale_msa, gate_msa, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(4, dim=1)
|
| 83 |
+
shift_msa = None
|
| 84 |
+
shift_mlp = None
|
| 85 |
+
else:
|
| 86 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=1)
|
| 87 |
+
x = x + gate_msa.unsqueeze(1) * self.attn(modulate(self.norm1(x), shift_msa, scale_msa), rope=feat_rope)
|
| 88 |
+
x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp))
|
| 89 |
+
return x
|
| 90 |
+
class LightningFinalLayer(nn.Module):
|
| 91 |
+
"""
|
| 92 |
+
The final layer of LightningDiT.
|
| 93 |
+
"""
|
| 94 |
+
def __init__(self, hidden_size, patch_size, out_channels, use_rmsnorm=False):
|
| 95 |
+
super().__init__()
|
| 96 |
+
if not use_rmsnorm:
|
| 97 |
+
self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 98 |
+
else:
|
| 99 |
+
self.norm_final = RMSNorm(hidden_size)
|
| 100 |
+
self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)
|
| 101 |
+
self.adaLN_modulation = nn.Sequential(
|
| 102 |
+
nn.SiLU(),
|
| 103 |
+
nn.Linear(hidden_size, 2 * hidden_size, bias=True)
|
| 104 |
+
)
|
| 105 |
+
def forward(self, x, c):
|
| 106 |
+
shift, scale = self.adaLN_modulation(c).chunk(2, dim=1)
|
| 107 |
+
x = modulate(self.norm_final(x), shift, scale)
|
| 108 |
+
x = self.linear(x)
|
| 109 |
+
return x
|
| 110 |
+
|
| 111 |
+
class LightningDiT(nn.Module):
|
| 112 |
+
"""
|
| 113 |
+
Diffusion model with a Transformer backbone.
|
| 114 |
+
"""
|
| 115 |
+
def __init__(
|
| 116 |
+
self,
|
| 117 |
+
input_size=16,
|
| 118 |
+
patch_size=1,
|
| 119 |
+
in_channels=768,
|
| 120 |
+
hidden_size=1152,
|
| 121 |
+
depth=28,
|
| 122 |
+
num_heads=16,
|
| 123 |
+
mlp_ratio=4.0,
|
| 124 |
+
class_dropout_prob=0.1,
|
| 125 |
+
num_classes=1000,
|
| 126 |
+
learn_sigma=False,
|
| 127 |
+
use_qknorm=False,
|
| 128 |
+
use_swiglu=True,
|
| 129 |
+
use_rope=True,
|
| 130 |
+
use_rmsnorm=True,
|
| 131 |
+
wo_shift=False,
|
| 132 |
+
use_gembed: bool = True,
|
| 133 |
+
):
|
| 134 |
+
super().__init__()
|
| 135 |
+
self.learn_sigma = learn_sigma
|
| 136 |
+
self.in_channels = in_channels
|
| 137 |
+
self.out_channels = in_channels if not learn_sigma else in_channels * 2
|
| 138 |
+
self.patch_size = patch_size
|
| 139 |
+
self.num_heads = num_heads
|
| 140 |
+
self.use_rope = use_rope
|
| 141 |
+
self.use_rmsnorm = use_rmsnorm
|
| 142 |
+
self.depth = depth
|
| 143 |
+
self.hidden_size = hidden_size
|
| 144 |
+
self.use_gembed = use_gembed
|
| 145 |
+
self.x_embedder = PatchEmbed(input_size, patch_size, in_channels, hidden_size, bias=True)
|
| 146 |
+
self.t_embedder = GaussianFourierEmbedding(hidden_size)
|
| 147 |
+
self.y_embedder = LabelEmbedder(num_classes, hidden_size, class_dropout_prob)
|
| 148 |
+
self.ssl_supervise = False
|
| 149 |
+
num_patches = self.x_embedder.num_patches
|
| 150 |
+
# Will use fixed sin-cos embedding:
|
| 151 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches, hidden_size), requires_grad=False)
|
| 152 |
+
|
| 153 |
+
# use rotary position encoding, borrow from EVA
|
| 154 |
+
if self.use_rope:
|
| 155 |
+
half_head_dim = hidden_size // num_heads // 2
|
| 156 |
+
hw_seq_len = input_size // patch_size
|
| 157 |
+
self.feat_rope = VisionRotaryEmbeddingFast(
|
| 158 |
+
dim=half_head_dim,
|
| 159 |
+
pt_seq_len=hw_seq_len,
|
| 160 |
+
)
|
| 161 |
+
else:
|
| 162 |
+
self.feat_rope = None
|
| 163 |
+
|
| 164 |
+
self.blocks = nn.ModuleList([
|
| 165 |
+
LightningDiTBlock(hidden_size,
|
| 166 |
+
num_heads,
|
| 167 |
+
mlp_ratio=mlp_ratio,
|
| 168 |
+
use_qknorm=use_qknorm,
|
| 169 |
+
use_swiglu=use_swiglu,
|
| 170 |
+
use_rmsnorm=use_rmsnorm,
|
| 171 |
+
wo_shift=wo_shift,
|
| 172 |
+
) for _ in range(depth)
|
| 173 |
+
])
|
| 174 |
+
self.final_layer = LightningFinalLayer(hidden_size, patch_size, self.out_channels, use_rmsnorm=use_rmsnorm)
|
| 175 |
+
self.initialize_weights()
|
| 176 |
+
|
| 177 |
+
def initialize_weights(self):
|
| 178 |
+
# Initialize transformer layers:
|
| 179 |
+
def _basic_init(module):
|
| 180 |
+
if isinstance(module, nn.Linear):
|
| 181 |
+
torch.nn.init.xavier_uniform_(module.weight)
|
| 182 |
+
if module.bias is not None:
|
| 183 |
+
nn.init.constant_(module.bias, 0)
|
| 184 |
+
self.apply(_basic_init)
|
| 185 |
+
|
| 186 |
+
# Initialize (and freeze) pos_embed by sin-cos embedding:
|
| 187 |
+
pos_embed = get_2d_sincos_pos_embed(self.pos_embed.shape[-1], int(self.x_embedder.num_patches ** 0.5))
|
| 188 |
+
self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float().unsqueeze(0))
|
| 189 |
+
|
| 190 |
+
# Initialize patch_embed like nn.Linear (instead of nn.Conv2d):
|
| 191 |
+
w = self.x_embedder.proj.weight.data
|
| 192 |
+
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 193 |
+
nn.init.constant_(self.x_embedder.proj.bias, 0)
|
| 194 |
+
|
| 195 |
+
# Initialize label embedding table:
|
| 196 |
+
nn.init.normal_(self.y_embedder.embedding_table.weight, std=0.02)
|
| 197 |
+
|
| 198 |
+
# Initialize timestep embedding MLP:
|
| 199 |
+
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
| 200 |
+
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
| 201 |
+
|
| 202 |
+
# Zero-out adaLN modulation layers in LightningDiT blocks:
|
| 203 |
+
for block in self.blocks:
|
| 204 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 205 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 206 |
+
|
| 207 |
+
# Zero-out output layers:
|
| 208 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
|
| 209 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
|
| 210 |
+
nn.init.constant_(self.final_layer.linear.weight, 0)
|
| 211 |
+
nn.init.constant_(self.final_layer.linear.bias, 0)
|
| 212 |
+
|
| 213 |
+
def unpatchify(self, x):
|
| 214 |
+
"""
|
| 215 |
+
x: (N, T, patch_size**2 * C)
|
| 216 |
+
imgs: (N, H, W, C)
|
| 217 |
+
"""
|
| 218 |
+
c = self.out_channels
|
| 219 |
+
p = self.x_embedder.patch_size[0]
|
| 220 |
+
h = w = int(x.shape[1] ** 0.5)
|
| 221 |
+
assert h * w == x.shape[1]
|
| 222 |
+
|
| 223 |
+
x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
|
| 224 |
+
x = torch.einsum('nhwpqc->nchpwq', x)
|
| 225 |
+
imgs = x.reshape(shape=(x.shape[0], c, h * p, h * p))
|
| 226 |
+
return imgs
|
| 227 |
+
|
| 228 |
+
def forward(self, x, t=None, y=None):
|
| 229 |
+
"""
|
| 230 |
+
Forward pass of LightningDiT.
|
| 231 |
+
x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
| 232 |
+
t: (N,) tensor of diffusion timesteps
|
| 233 |
+
y: (N,) tensor of class labels
|
| 234 |
+
use_checkpoint: boolean to toggle checkpointing
|
| 235 |
+
"""
|
| 236 |
+
x = self.x_embedder(x) + self.pos_embed # (N, T, D), where T = H * W / patch_size ** 2
|
| 237 |
+
t = self.t_embedder(t) # (N, D)
|
| 238 |
+
y = self.y_embedder(y, self.training) # (N, D)
|
| 239 |
+
c = t + y # (N, D)
|
| 240 |
+
|
| 241 |
+
for block in self.blocks:
|
| 242 |
+
x = block(x, c, feat_rope=self.feat_rope)
|
| 243 |
+
|
| 244 |
+
x = self.final_layer(x, c) # (N, T, patch_size ** 2 * out_channels)
|
| 245 |
+
x = self.unpatchify(x) # (N, out_channels, H, W)
|
| 246 |
+
|
| 247 |
+
if self.learn_sigma:
|
| 248 |
+
x, _ = x.chunk(2, dim=1)
|
| 249 |
+
return x
|
| 250 |
+
|
| 251 |
+
def forward_with_cfg(self, x, t, y, cfg_scale, cfg_interval=(-1e4, -1e4), interval_cfg: float = 0.0):
|
| 252 |
+
"""
|
| 253 |
+
Forward pass of LightningDiT, but also batches the unconditional forward pass for classifier-free guidance.
|
| 254 |
+
"""
|
| 255 |
+
# https://github.com/openai/glide-text2im/blob/main/notebooks/text2im.ipynb
|
| 256 |
+
half = x[: len(x) // 2]
|
| 257 |
+
combined = torch.cat([half, half], dim=0)
|
| 258 |
+
model_out = self.forward(combined, t, y)
|
| 259 |
+
if self.ssl_supervise:
|
| 260 |
+
model_out = model_out[0] # take the output only
|
| 261 |
+
# For exact reproducibility reasons, we apply classifier-free guidance on only
|
| 262 |
+
# three channels by default. The standard approach to cfg applies it to all channels.
|
| 263 |
+
# This can be done by uncommenting the following line and commenting-out the line following that.
|
| 264 |
+
# eps, rest = model_out[:, :self.in_channels], model_out[:, self.in_channels:]
|
| 265 |
+
eps, rest = model_out[:, :self.in_channels], model_out[:, self.in_channels:]
|
| 266 |
+
#eps, rest = model_out[:, :3], model_out[:, 3:]
|
| 267 |
+
cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)
|
| 268 |
+
t = t[0] # check if t < cfg_interval
|
| 269 |
+
in_interval = False
|
| 270 |
+
for i in range(len(cfg_interval)):
|
| 271 |
+
if t >= cfg_interval[i][0] and t < cfg_interval[i][1]:
|
| 272 |
+
#print(f't:{t}, cfg_interval: {cfg_interval[i]}')
|
| 273 |
+
if interval_cfg > 1.0:
|
| 274 |
+
half_eps = uncond_eps + interval_cfg * (cond_eps - uncond_eps)
|
| 275 |
+
else:
|
| 276 |
+
half_eps = cond_eps # only use conditional generation
|
| 277 |
+
in_interval = True
|
| 278 |
+
break
|
| 279 |
+
if not in_interval:
|
| 280 |
+
#print(f't:{t} not in cfg_interval')
|
| 281 |
+
half_eps = uncond_eps + cfg_scale * (cond_eps - uncond_eps)
|
| 282 |
+
eps = torch.cat([half_eps, half_eps], dim=0)
|
| 283 |
+
return torch.cat([eps, rest], dim=1)
|
| 284 |
+
def forward_with_autoguidance(self, x, t, y, cfg_scale, additional_model_forward, cfg_interval=(-1e4, -1e4), interval_cfg: float = 0.0):
|
| 285 |
+
"""
|
| 286 |
+
Forward pass of LightningDiT, but also contain the forward pass for the additional model
|
| 287 |
+
"""
|
| 288 |
+
half = x[: len(x) // 2] # cut the x by half, autoguidance does not need repeated input
|
| 289 |
+
t = t[: len(t) // 2]
|
| 290 |
+
y = y[: len(y) // 2]
|
| 291 |
+
model_out = self.forward(half, t, y)
|
| 292 |
+
ag_model_out = additional_model_forward(half, t, y)
|
| 293 |
+
eps = model_out[:, :self.in_channels]
|
| 294 |
+
ag_eps = ag_model_out[:, :self.in_channels]
|
| 295 |
+
t = t[0]
|
| 296 |
+
in_interval = False
|
| 297 |
+
for i in range(len(cfg_interval)):
|
| 298 |
+
if t >= cfg_interval[i][0] and t < cfg_interval[i][1]:
|
| 299 |
+
if interval_cfg > 1.0:
|
| 300 |
+
eps = ag_eps + interval_cfg * (eps - ag_eps)
|
| 301 |
+
in_interval = True
|
| 302 |
+
break
|
| 303 |
+
if not in_interval:
|
| 304 |
+
eps = ag_eps + cfg_scale * (eps - ag_eps)
|
| 305 |
+
return torch.cat([eps, eps], dim=0)
|
patch-reparameterization/src/stage2/models/model_utils.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy.random as random
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
from einops import rearrange, repeat
|
| 8 |
+
from typing import *
|
| 9 |
+
from math import pi
|
| 10 |
+
from timm.models.vision_transformer import Attention
|
| 11 |
+
from functools import partial
|
| 12 |
+
from einops import rearrange, repeat
|
| 13 |
+
from collections.abc import Callable
|
| 14 |
+
import numpy as np
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def modulate(x, shift, scale):
|
| 18 |
+
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
| 19 |
+
|
| 20 |
+
def broadcat(tensors, dim=-1):
|
| 21 |
+
num_tensors = len(tensors)
|
| 22 |
+
shape_lens = set(list(map(lambda t: len(t.shape), tensors)))
|
| 23 |
+
assert len(shape_lens) == 1, 'tensors must all have the same number of dimensions'
|
| 24 |
+
shape_len = list(shape_lens)[0]
|
| 25 |
+
dim = (dim + shape_len) if dim < 0 else dim
|
| 26 |
+
dims = list(zip(*map(lambda t: list(t.shape), tensors)))
|
| 27 |
+
expandable_dims = [(i, val) for i, val in enumerate(dims) if i != dim]
|
| 28 |
+
assert all([*map(lambda t: len(set(t[1])) <= 2, expandable_dims)]
|
| 29 |
+
), 'invalid dimensions for broadcastable concatentation'
|
| 30 |
+
max_dims = list(map(lambda t: (t[0], max(t[1])), expandable_dims))
|
| 31 |
+
expanded_dims = list(map(lambda t: (t[0], (t[1],) * num_tensors), max_dims))
|
| 32 |
+
expanded_dims.insert(dim, (dim, dims[dim]))
|
| 33 |
+
expandable_shapes = list(zip(*map(lambda t: t[1], expanded_dims)))
|
| 34 |
+
tensors = list(map(lambda t: t[0].expand(*t[1]), zip(tensors, expandable_shapes)))
|
| 35 |
+
return torch.cat(tensors, dim=dim)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def rotate_half(x):
|
| 39 |
+
x = rearrange(x, '... (d r) -> ... d r', r=2)
|
| 40 |
+
x1, x2 = x.unbind(dim=-1)
|
| 41 |
+
x = torch.stack((-x2, x1), dim=-1)
|
| 42 |
+
return rearrange(x, '... d r -> ... (d r)')
|
| 43 |
+
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
| 44 |
+
"""
|
| 45 |
+
embed_dim: output dimension for each position
|
| 46 |
+
pos: a list of positions to be encoded: size (M,)
|
| 47 |
+
out: (M, D)
|
| 48 |
+
"""
|
| 49 |
+
assert embed_dim % 2 == 0
|
| 50 |
+
omega = np.arange(embed_dim // 2, dtype=np.float64)
|
| 51 |
+
omega /= embed_dim / 2.
|
| 52 |
+
omega = 1. / 10000**omega # (D/2,)
|
| 53 |
+
|
| 54 |
+
pos = pos.reshape(-1) # (M,)
|
| 55 |
+
out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
|
| 56 |
+
|
| 57 |
+
emb_sin = np.sin(out) # (M, D/2)
|
| 58 |
+
emb_cos = np.cos(out) # (M, D/2)
|
| 59 |
+
|
| 60 |
+
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
|
| 61 |
+
return emb
|
| 62 |
+
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
| 63 |
+
assert embed_dim % 2 == 0
|
| 64 |
+
|
| 65 |
+
# use half of dimensions to encode grid_h
|
| 66 |
+
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
|
| 67 |
+
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
|
| 68 |
+
|
| 69 |
+
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
|
| 70 |
+
return emb
|
| 71 |
+
|
| 72 |
+
def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0):
|
| 73 |
+
"""
|
| 74 |
+
grid_size: int of the grid height and width
|
| 75 |
+
return:
|
| 76 |
+
pos_embed: [grid_size*grid_size, embed_dim] or [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token)
|
| 77 |
+
"""
|
| 78 |
+
grid_h = np.arange(grid_size, dtype=np.float32)
|
| 79 |
+
grid_w = np.arange(grid_size, dtype=np.float32)
|
| 80 |
+
grid = np.meshgrid(grid_w, grid_h) # here w goes first
|
| 81 |
+
grid = np.stack(grid, axis=0)
|
| 82 |
+
|
| 83 |
+
grid = grid.reshape([2, 1, grid_size, grid_size])
|
| 84 |
+
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
| 85 |
+
if cls_token and extra_tokens > 0:
|
| 86 |
+
pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0)
|
| 87 |
+
return pos_embed
|
| 88 |
+
|
| 89 |
+
class VisionRotaryEmbedding(nn.Module):
|
| 90 |
+
def __init__(
|
| 91 |
+
self,
|
| 92 |
+
dim,
|
| 93 |
+
pt_seq_len,
|
| 94 |
+
ft_seq_len=None,
|
| 95 |
+
custom_freqs=None,
|
| 96 |
+
freqs_for='lang',
|
| 97 |
+
theta=10000,
|
| 98 |
+
max_freq=10,
|
| 99 |
+
num_freqs=1,
|
| 100 |
+
):
|
| 101 |
+
super().__init__()
|
| 102 |
+
if custom_freqs:
|
| 103 |
+
freqs = custom_freqs
|
| 104 |
+
elif freqs_for == 'lang':
|
| 105 |
+
freqs = 1. / (theta ** (torch.arange(0, dim, 2)[:(dim // 2)].float() / dim))
|
| 106 |
+
elif freqs_for == 'pixel':
|
| 107 |
+
freqs = torch.linspace(1., max_freq / 2, dim // 2) * pi
|
| 108 |
+
elif freqs_for == 'constant':
|
| 109 |
+
freqs = torch.ones(num_freqs).float()
|
| 110 |
+
else:
|
| 111 |
+
raise ValueError(f'unknown modality {freqs_for}')
|
| 112 |
+
|
| 113 |
+
if ft_seq_len is None:
|
| 114 |
+
ft_seq_len = pt_seq_len
|
| 115 |
+
t = torch.arange(ft_seq_len) / ft_seq_len * pt_seq_len
|
| 116 |
+
|
| 117 |
+
freqs_h = torch.einsum('..., f -> ... f', t, freqs)
|
| 118 |
+
freqs_h = repeat(freqs_h, '... n -> ... (n r)', r=2)
|
| 119 |
+
|
| 120 |
+
freqs_w = torch.einsum('..., f -> ... f', t, freqs)
|
| 121 |
+
freqs_w = repeat(freqs_w, '... n -> ... (n r)', r=2)
|
| 122 |
+
|
| 123 |
+
freqs = broadcat((freqs_h[:, None, :], freqs_w[None, :, :]), dim=-1)
|
| 124 |
+
|
| 125 |
+
self.register_buffer("freqs_cos", freqs.cos())
|
| 126 |
+
self.register_buffer("freqs_sin", freqs.sin())
|
| 127 |
+
|
| 128 |
+
# print('======== shape of rope freq', self.freqs_cos.shape, '========')
|
| 129 |
+
|
| 130 |
+
def forward(self, t, start_index=0):
|
| 131 |
+
rot_dim = self.freqs_cos.shape[-1]
|
| 132 |
+
end_index = start_index + rot_dim
|
| 133 |
+
assert rot_dim <= t.shape[-1], f'feature dimension {t.shape[-1]} is not of sufficient size to rotate in all the positions {rot_dim}'
|
| 134 |
+
t_left, t, t_right = t[..., :start_index], t[..., start_index:end_index], t[..., end_index:]
|
| 135 |
+
t = (t * self.freqs_cos) + (rotate_half(t) * self.freqs_sin)
|
| 136 |
+
return torch.cat((t_left, t, t_right), dim=-1)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class VisionRotaryEmbeddingFast(nn.Module):
|
| 140 |
+
def __init__(
|
| 141 |
+
self,
|
| 142 |
+
dim,
|
| 143 |
+
pt_seq_len=16,
|
| 144 |
+
ft_seq_len=None,
|
| 145 |
+
custom_freqs=None,
|
| 146 |
+
freqs_for='lang',
|
| 147 |
+
theta=10000,
|
| 148 |
+
max_freq=10,
|
| 149 |
+
num_freqs=1,
|
| 150 |
+
):
|
| 151 |
+
super().__init__()
|
| 152 |
+
if custom_freqs:
|
| 153 |
+
freqs = custom_freqs
|
| 154 |
+
elif freqs_for == 'lang':
|
| 155 |
+
freqs = 1. / (theta ** (torch.arange(0, dim, 2)[:(dim // 2)].float() / dim))
|
| 156 |
+
elif freqs_for == 'pixel':
|
| 157 |
+
freqs = torch.linspace(1., max_freq / 2, dim // 2) * pi
|
| 158 |
+
elif freqs_for == 'constant':
|
| 159 |
+
freqs = torch.ones(num_freqs).float()
|
| 160 |
+
else:
|
| 161 |
+
raise ValueError(f'unknown modality {freqs_for}')
|
| 162 |
+
|
| 163 |
+
if ft_seq_len is None:
|
| 164 |
+
ft_seq_len = pt_seq_len
|
| 165 |
+
t = torch.arange(ft_seq_len) / ft_seq_len * pt_seq_len
|
| 166 |
+
|
| 167 |
+
freqs = torch.einsum('..., f -> ... f', t, freqs)
|
| 168 |
+
freqs = repeat(freqs, '... n -> ... (n r)', r=2)
|
| 169 |
+
freqs = broadcat((freqs[:, None, :], freqs[None, :, :]), dim=-1)
|
| 170 |
+
|
| 171 |
+
freqs_cos = freqs.cos().view(-1, freqs.shape[-1])
|
| 172 |
+
freqs_sin = freqs.sin().view(-1, freqs.shape[-1])
|
| 173 |
+
|
| 174 |
+
self.register_buffer("freqs_cos", freqs_cos)
|
| 175 |
+
self.register_buffer("freqs_sin", freqs_sin)
|
| 176 |
+
|
| 177 |
+
# print('======== shape of rope freq', self.freqs_cos.shape,freqs_sin.shape, '========')
|
| 178 |
+
|
| 179 |
+
def forward(self, t):
|
| 180 |
+
# print('======== shape of t', t.shape, '========')
|
| 181 |
+
_, _, Lt, _ = t.shape # B, num_heads, L, dim
|
| 182 |
+
L, _ = self.freqs_cos.shape # L, dim
|
| 183 |
+
repeat = Lt // L
|
| 184 |
+
freqs_cos, freqs_sin = self.freqs_cos, self.freqs_sin
|
| 185 |
+
if repeat != 1:
|
| 186 |
+
freqs_cos = freqs_cos.repeat_interleave(repeat, dim=0)
|
| 187 |
+
freqs_sin = freqs_sin.repeat_interleave(repeat, dim=0)
|
| 188 |
+
# print('======== shape of rope freq', freqs_cos.shape,freqs_sin.shape, '========')
|
| 189 |
+
# print(f'======== repeat {repeat} times ========')
|
| 190 |
+
# print(f'======== shape of t {t.shape} ========')
|
| 191 |
+
# # assert the repeat is twice
|
| 192 |
+
# #assert repeat == 2, f'repeat should be 2, but got {repeat}'
|
| 193 |
+
# # check the content of the repeated freqs
|
| 194 |
+
# # the content at odd index should be the same as the content at even index
|
| 195 |
+
# assert torch.allclose(freqs_cos[::2], freqs_cos[1::2]), 'repeated freqs_cos are not the same'
|
| 196 |
+
# assert torch.allclose(freqs_sin[::2], freqs_sin[1::2]), 'repeated freqs_sin are not the same'
|
| 197 |
+
# apply repeated freqs
|
| 198 |
+
return t * freqs_cos + rotate_half(t) * freqs_sin
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class RelativePositionBias2D(nn.Module):
|
| 202 |
+
"""
|
| 203 |
+
2D relative positional bias for full self-attention.
|
| 204 |
+
Creates a learnable bias table of size (2*H-1) (2*W-1) per head,
|
| 205 |
+
and a fixed index map to look up bias for any pair of token positions.
|
| 206 |
+
"""
|
| 207 |
+
|
| 208 |
+
def __init__(self, height: int, width: int, num_heads: int):
|
| 209 |
+
super().__init__()
|
| 210 |
+
self.height = height
|
| 211 |
+
self.width = width
|
| 212 |
+
self.num_heads = num_heads
|
| 213 |
+
|
| 214 |
+
# Create a bias table: one bias for every possible relative offset
|
| 215 |
+
# in y ∈ [-(H-1)..(H-1)] and x ∈ [-(W-1)..(W-1)]
|
| 216 |
+
self.relative_bias_table = nn.Parameter(
|
| 217 |
+
torch.zeros((2 * height - 1) * (2 * width - 1), num_heads)
|
| 218 |
+
)
|
| 219 |
+
# Precompute a (H*W)×(H*W) index matrix of which bias entry each pair (i,j) uses
|
| 220 |
+
coords_h = torch.arange(height)
|
| 221 |
+
coords_w = torch.arange(width)
|
| 222 |
+
# meshgrid of absolute coords, shape (H*W, 2)
|
| 223 |
+
coords = torch.stack(torch.meshgrid(coords_h, coords_w, indexing='ij'), dim=-1).view(-1, 2)
|
| 224 |
+
|
| 225 |
+
# Compute all pairwise relative coords
|
| 226 |
+
relative_coords = coords[:, None, :] - coords[None, :, :] # shape (HW, HW, 2)
|
| 227 |
+
# shift to positive
|
| 228 |
+
relative_coords[..., 0] += height - 1 # y
|
| 229 |
+
relative_coords[..., 1] += width - 1 # x
|
| 230 |
+
|
| 231 |
+
# flatten 2D index into single index: idx = y*(2W-1) + x
|
| 232 |
+
relative_index = relative_coords[..., 0] * (2 * width - 1) + relative_coords[..., 1]
|
| 233 |
+
# register as buffer so it’s on the right device / dtype
|
| 234 |
+
self.register_buffer("relative_index", relative_index.long())
|
| 235 |
+
|
| 236 |
+
def forward(self):
|
| 237 |
+
"""
|
| 238 |
+
Returns:
|
| 239 |
+
bias: Tensor of shape (1, num_heads, HW, HW)
|
| 240 |
+
to be added to the raw attention logits before softmax.
|
| 241 |
+
"""
|
| 242 |
+
# Lookup and reshape to (HW, HW, num_heads)
|
| 243 |
+
bias = self.relative_bias_table[self.relative_index.view(-1)] # (HW*HW, num_heads)
|
| 244 |
+
bias = bias.view(self.height * self.width,
|
| 245 |
+
self.height * self.width,
|
| 246 |
+
self.num_heads) # (HW, HW, heads)
|
| 247 |
+
# permute to (heads, HW, HW) and add batch-dim
|
| 248 |
+
bias = bias.permute(2, 0, 1).unsqueeze(0) # (1, heads, HW, HW)
|
| 249 |
+
return bias
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
class SwiGLUFFN(nn.Module):
|
| 253 |
+
def __init__(
|
| 254 |
+
self,
|
| 255 |
+
in_features: int,
|
| 256 |
+
hidden_features: Optional[int] = None,
|
| 257 |
+
out_features: Optional[int] = None,
|
| 258 |
+
act_layer: Callable[..., nn.Module] = None,
|
| 259 |
+
drop: float = 0.0,
|
| 260 |
+
bias: bool = True,
|
| 261 |
+
) -> None:
|
| 262 |
+
super().__init__()
|
| 263 |
+
out_features = out_features or in_features
|
| 264 |
+
hidden_features = hidden_features or in_features
|
| 265 |
+
self.w12 = nn.Linear(in_features, 2 * hidden_features, bias=bias)
|
| 266 |
+
self.w3 = nn.Linear(hidden_features, out_features, bias=bias)
|
| 267 |
+
|
| 268 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 269 |
+
x12 = self.w12(x)
|
| 270 |
+
x1, x2 = x12.chunk(2, dim=-1)
|
| 271 |
+
hidden = F.silu(x1) * x2
|
| 272 |
+
return self.w3(hidden)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
class RMSNorm(torch.nn.Module):
|
| 276 |
+
def __init__(self, dim: int, eps: float = 1e-6):
|
| 277 |
+
"""
|
| 278 |
+
Initialize the RMSNorm normalization layer.
|
| 279 |
+
|
| 280 |
+
Args:
|
| 281 |
+
dim (int): The dimension of the input tensor.
|
| 282 |
+
eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-6.
|
| 283 |
+
|
| 284 |
+
Attributes:
|
| 285 |
+
eps (float): A small value added to the denominator for numerical stability.
|
| 286 |
+
weight (nn.Parameter): Learnable scaling parameter.
|
| 287 |
+
|
| 288 |
+
"""
|
| 289 |
+
super().__init__()
|
| 290 |
+
self.eps = eps
|
| 291 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 292 |
+
|
| 293 |
+
def _norm(self, x):
|
| 294 |
+
"""
|
| 295 |
+
Apply the RMSNorm normalization to the input tensor.
|
| 296 |
+
|
| 297 |
+
Args:
|
| 298 |
+
x (torch.Tensor): The input tensor.
|
| 299 |
+
|
| 300 |
+
Returns:
|
| 301 |
+
torch.Tensor: The normalized tensor.
|
| 302 |
+
|
| 303 |
+
"""
|
| 304 |
+
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
| 305 |
+
|
| 306 |
+
def forward(self, x):
|
| 307 |
+
"""
|
| 308 |
+
Forward pass through the RMSNorm layer.
|
| 309 |
+
|
| 310 |
+
Args:
|
| 311 |
+
x (torch.Tensor): The input tensor.
|
| 312 |
+
|
| 313 |
+
Returns:
|
| 314 |
+
torch.Tensor: The output tensor after applying RMSNorm.
|
| 315 |
+
|
| 316 |
+
"""
|
| 317 |
+
output = self._norm(x.float()).type_as(x)
|
| 318 |
+
return output * self.weight
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
class NormAttention(nn.Module):
|
| 322 |
+
"""
|
| 323 |
+
Attention module of LightningDiT.
|
| 324 |
+
"""
|
| 325 |
+
def __init__(
|
| 326 |
+
self,
|
| 327 |
+
dim: int,
|
| 328 |
+
num_heads: int = 8,
|
| 329 |
+
qkv_bias: bool = False,
|
| 330 |
+
qk_norm: bool = False,
|
| 331 |
+
attn_drop: float = 0.,
|
| 332 |
+
proj_drop: float = 0.,
|
| 333 |
+
norm_layer: nn.Module = nn.LayerNorm,
|
| 334 |
+
fused_attn: bool = True,
|
| 335 |
+
use_rmsnorm: bool = False,
|
| 336 |
+
) -> None:
|
| 337 |
+
super().__init__()
|
| 338 |
+
assert dim % num_heads == 0, 'dim should be divisible by num_heads'
|
| 339 |
+
|
| 340 |
+
self.num_heads = num_heads
|
| 341 |
+
self.head_dim = dim // num_heads
|
| 342 |
+
self.scale = self.head_dim ** -0.5
|
| 343 |
+
self.fused_attn = fused_attn
|
| 344 |
+
|
| 345 |
+
if use_rmsnorm:
|
| 346 |
+
norm_layer = RMSNorm
|
| 347 |
+
|
| 348 |
+
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
|
| 349 |
+
self.q_norm = norm_layer(self.head_dim) if qk_norm else nn.Identity()
|
| 350 |
+
self.k_norm = norm_layer(self.head_dim) if qk_norm else nn.Identity()
|
| 351 |
+
self.attn_drop = nn.Dropout(attn_drop)
|
| 352 |
+
self.proj = nn.Linear(dim, dim)
|
| 353 |
+
self.proj_drop = nn.Dropout(proj_drop)
|
| 354 |
+
|
| 355 |
+
def forward(self, x: torch.Tensor, rope=None) -> torch.Tensor:
|
| 356 |
+
B, N, C = x.shape
|
| 357 |
+
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
|
| 358 |
+
q, k, v = qkv.unbind(0)
|
| 359 |
+
q, k = self.q_norm(q), self.k_norm(k)
|
| 360 |
+
|
| 361 |
+
if rope is not None:
|
| 362 |
+
q = rope(q)
|
| 363 |
+
k = rope(k)
|
| 364 |
+
|
| 365 |
+
if self.fused_attn:
|
| 366 |
+
q = q.to(v.dtype)
|
| 367 |
+
k = k.to(v.dtype) # rope may change the q,k's dtype
|
| 368 |
+
x = F.scaled_dot_product_attention(
|
| 369 |
+
q, k, v,
|
| 370 |
+
dropout_p=self.attn_drop.p if self.training else 0.,
|
| 371 |
+
)
|
| 372 |
+
else:
|
| 373 |
+
q = q * self.scale
|
| 374 |
+
attn = q @ k.transpose(-2, -1)
|
| 375 |
+
attn = attn.softmax(dim=-1)
|
| 376 |
+
attn = self.attn_drop(attn)
|
| 377 |
+
x = attn @ v
|
| 378 |
+
|
| 379 |
+
x = x.transpose(1, 2).reshape(B, N, C)
|
| 380 |
+
x = self.proj(x)
|
| 381 |
+
x = self.proj_drop(x)
|
| 382 |
+
return x
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
class GaussianFourierEmbedding(nn.Module):
|
| 386 |
+
"""
|
| 387 |
+
Gaussian Fourier Embedding for timesteps.
|
| 388 |
+
"""
|
| 389 |
+
embedding_size: int = 256
|
| 390 |
+
scale: float = 1.0
|
| 391 |
+
def __init__(self, hidden_size: int, embedding_size: int = 256, scale: float = 1.0):
|
| 392 |
+
super().__init__()
|
| 393 |
+
self.embedding_size = embedding_size
|
| 394 |
+
self.scale = scale
|
| 395 |
+
self.register_buffer('W', torch.normal(0, self.scale, (embedding_size,)))
|
| 396 |
+
self.mlp = nn.Sequential(
|
| 397 |
+
nn.Linear(embedding_size * 2, hidden_size, bias=True),
|
| 398 |
+
nn.SiLU(),
|
| 399 |
+
nn.Linear(hidden_size, hidden_size, bias=True),
|
| 400 |
+
)
|
| 401 |
+
def forward(self, t):
|
| 402 |
+
with torch.no_grad():
|
| 403 |
+
W = self.W # stop gradient manually
|
| 404 |
+
t = t[:, None] * W[None, :] * 2 * torch.pi
|
| 405 |
+
# Concatenate sine and cosine transformations
|
| 406 |
+
t_embed = torch.cat([torch.sin(t), torch.cos(t)], dim=-1)
|
| 407 |
+
t_embed = self.mlp(t_embed)
|
| 408 |
+
return t_embed
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
class LabelEmbedder(nn.Module):
|
| 412 |
+
"""
|
| 413 |
+
Embeds class labels into vector representations. Also handles label dropout for classifier-free guidance.
|
| 414 |
+
"""
|
| 415 |
+
def __init__(self, num_classes, hidden_size, dropout_prob):
|
| 416 |
+
super().__init__()
|
| 417 |
+
use_cfg_embedding = dropout_prob > 0
|
| 418 |
+
self.embedding_table = nn.Embedding(num_classes + use_cfg_embedding, hidden_size)
|
| 419 |
+
self.num_classes = num_classes
|
| 420 |
+
self.dropout_prob = dropout_prob
|
| 421 |
+
|
| 422 |
+
def token_drop(self, labels, force_drop_ids=None):
|
| 423 |
+
"""
|
| 424 |
+
Drops labels to enable classifier-free guidance.
|
| 425 |
+
"""
|
| 426 |
+
if force_drop_ids is None:
|
| 427 |
+
drop_ids = torch.rand(labels.shape[0], device=labels.device) < self.dropout_prob
|
| 428 |
+
else:
|
| 429 |
+
drop_ids = force_drop_ids == 1
|
| 430 |
+
labels = torch.where(drop_ids, self.num_classes, labels)
|
| 431 |
+
return labels
|
| 432 |
+
|
| 433 |
+
def forward(self, labels, train, force_drop_ids=None):
|
| 434 |
+
use_dropout = self.dropout_prob > 0
|
| 435 |
+
if (train and use_dropout) or (force_drop_ids is not None):
|
| 436 |
+
labels = self.token_drop(labels, force_drop_ids)
|
| 437 |
+
embeddings = self.embedding_table(labels)
|
| 438 |
+
return embeddings
|
patch-reparameterization/src/stage2/transport/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .transport import Transport, ModelType, WeightType, PathType, Sampler
|
| 2 |
+
|
| 3 |
+
def create_transport(
|
| 4 |
+
path_type='Linear',
|
| 5 |
+
prediction="velocity",
|
| 6 |
+
loss_weight=None,
|
| 7 |
+
train_eps=None,
|
| 8 |
+
sample_eps=None,
|
| 9 |
+
time_dist_type="uniform",
|
| 10 |
+
time_dist_shift=1.0,
|
| 11 |
+
sem_dim=None,
|
| 12 |
+
recon_loss_weight=1.0,
|
| 13 |
+
normalize_channel_weight=False,
|
| 14 |
+
**kwargs
|
| 15 |
+
):
|
| 16 |
+
"""function for creating Transport object
|
| 17 |
+
**Note**: model prediction defaults to velocity
|
| 18 |
+
Args:
|
| 19 |
+
- path_type: type of path to use; default to linear
|
| 20 |
+
- learn_score: set model prediction to score
|
| 21 |
+
- learn_noise: set model prediction to noise
|
| 22 |
+
- velocity_weighted: weight loss by velocity weight
|
| 23 |
+
- likelihood_weighted: weight loss by likelihood weight
|
| 24 |
+
- train_eps: small epsilon for avoiding instability during training
|
| 25 |
+
- sample_eps: small epsilon for avoiding instability during sampling
|
| 26 |
+
- time_dist_type: type of time distribution to use; default to uniform
|
| 27 |
+
- time_dist_shift: shift for time distribution; default to 1.0
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
if prediction == "noise":
|
| 31 |
+
model_type = ModelType.NOISE
|
| 32 |
+
elif prediction == "score":
|
| 33 |
+
model_type = ModelType.SCORE
|
| 34 |
+
else:
|
| 35 |
+
model_type = ModelType.VELOCITY
|
| 36 |
+
|
| 37 |
+
if loss_weight == "velocity":
|
| 38 |
+
loss_type = WeightType.VELOCITY
|
| 39 |
+
elif loss_weight == "likelihood":
|
| 40 |
+
loss_type = WeightType.LIKELIHOOD
|
| 41 |
+
else:
|
| 42 |
+
loss_type = WeightType.NONE
|
| 43 |
+
|
| 44 |
+
path_choice = {
|
| 45 |
+
"Linear": PathType.LINEAR,
|
| 46 |
+
"GVP": PathType.GVP,
|
| 47 |
+
"VP": PathType.VP,
|
| 48 |
+
"SemanticRecon": PathType.SEMANTIC_RECON,
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
path_type = path_choice[path_type]
|
| 52 |
+
|
| 53 |
+
if (path_type in [PathType.VP]):
|
| 54 |
+
train_eps = 1e-5 if train_eps is None else train_eps
|
| 55 |
+
sample_eps = 1e-3 if train_eps is None else sample_eps
|
| 56 |
+
elif (path_type in [PathType.GVP, PathType.LINEAR, PathType.SEMANTIC_RECON] and model_type != ModelType.VELOCITY):
|
| 57 |
+
train_eps = 1e-3 if train_eps is None else train_eps
|
| 58 |
+
sample_eps = 1e-3 if train_eps is None else sample_eps
|
| 59 |
+
else: # velocity & [GVP, LINEAR, SEMANTIC_RECON] is stable everywhere
|
| 60 |
+
train_eps = 0
|
| 61 |
+
sample_eps = 0
|
| 62 |
+
|
| 63 |
+
use_time_shift = kwargs.get("use_time_shift", True)
|
| 64 |
+
# create flow state
|
| 65 |
+
state = Transport(
|
| 66 |
+
model_type=model_type,
|
| 67 |
+
path_type=path_type,
|
| 68 |
+
loss_type=loss_type,
|
| 69 |
+
time_dist_type=time_dist_type,
|
| 70 |
+
time_dist_shift=time_dist_shift,
|
| 71 |
+
train_eps=train_eps,
|
| 72 |
+
sample_eps=sample_eps,
|
| 73 |
+
use_time_shift=use_time_shift,
|
| 74 |
+
sem_dim=sem_dim,
|
| 75 |
+
recon_loss_weight=recon_loss_weight,
|
| 76 |
+
normalize_channel_weight=normalize_channel_weight,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
return state
|
patch-reparameterization/src/stage2/transport/integrators.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch as th
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torchdiffeq import odeint
|
| 5 |
+
from functools import partial
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
import logging
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
class sde:
|
| 10 |
+
"""SDE solver class"""
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
drift,
|
| 14 |
+
diffusion,
|
| 15 |
+
*,
|
| 16 |
+
t0,
|
| 17 |
+
t1,
|
| 18 |
+
num_steps,
|
| 19 |
+
sampler_type,
|
| 20 |
+
time_dist_shift,
|
| 21 |
+
):
|
| 22 |
+
assert t0 < t1, "SDE sampler has to be in forward time"
|
| 23 |
+
|
| 24 |
+
self.num_timesteps = num_steps
|
| 25 |
+
self.t = 1 - th.linspace(t0, t1, num_steps)
|
| 26 |
+
self.t = time_dist_shift * self.t / (1 + (time_dist_shift - 1) * self.t)
|
| 27 |
+
self.drift = drift
|
| 28 |
+
self.diffusion = diffusion
|
| 29 |
+
self.sampler_type = sampler_type
|
| 30 |
+
self.time_dist_shift = time_dist_shift
|
| 31 |
+
|
| 32 |
+
def __Euler_Maruyama_step(self, x, mean_x, t_curr, t_next, model, **model_kwargs):
|
| 33 |
+
w_cur = th.randn(x.size()).to(x)
|
| 34 |
+
t = th.ones(x.size(0)).to(x) * t_curr
|
| 35 |
+
dw = w_cur * th.sqrt(t_curr - t_next)
|
| 36 |
+
drift = self.drift(x, t, model, **model_kwargs)
|
| 37 |
+
diffusion = self.diffusion(x, t)
|
| 38 |
+
mean_x = x - drift * (t_curr - t_next)
|
| 39 |
+
x = mean_x + th.sqrt(2 * diffusion) * dw
|
| 40 |
+
return x, mean_x
|
| 41 |
+
|
| 42 |
+
def __Heun_step(self, x, _, t_curr, t_next, model, **model_kwargs):
|
| 43 |
+
w_cur = th.randn(x.size()).to(x)
|
| 44 |
+
dw = w_cur * th.sqrt(t_curr - t_next)
|
| 45 |
+
diffusion = self.diffusion(x, th.ones(x.size(0)).to(x) * t_curr)
|
| 46 |
+
xhat = x + th.sqrt(2 * diffusion) * dw
|
| 47 |
+
K1 = self.drift(
|
| 48 |
+
xhat, th.ones(x.size(0)).to(x) * t_curr, model, **model_kwargs
|
| 49 |
+
)
|
| 50 |
+
xp = xhat - (t_curr - t_next) * K1
|
| 51 |
+
K2 = self.drift(
|
| 52 |
+
xp, th.ones(x.size(0)).to(x) * t_next, model, **model_kwargs
|
| 53 |
+
)
|
| 54 |
+
return xhat - 0.5 * (t_curr - t_next) * (K1 + K2), xhat # at last time point we do not perform the heun step
|
| 55 |
+
|
| 56 |
+
def __forward_fn(self):
|
| 57 |
+
"""TODO: generalize here by adding all private functions ending with steps to it"""
|
| 58 |
+
sampler_dict = {
|
| 59 |
+
"euler": self.__Euler_Maruyama_step,
|
| 60 |
+
"heun": self.__Heun_step,
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
sampler = sampler_dict[self.sampler_type]
|
| 65 |
+
except:
|
| 66 |
+
raise NotImplementedError("Smapler type not implemented.")
|
| 67 |
+
|
| 68 |
+
return sampler
|
| 69 |
+
|
| 70 |
+
def sample(self, init, model, **model_kwargs) -> tuple[th.Tensor]:
|
| 71 |
+
"""forward loop of sde"""
|
| 72 |
+
x = init
|
| 73 |
+
mean_x = init
|
| 74 |
+
samples = []
|
| 75 |
+
sampler = self.__forward_fn()
|
| 76 |
+
for t_curr, t_next in zip(self.t[:-1], self.t[1:]):
|
| 77 |
+
with th.no_grad():
|
| 78 |
+
x, mean_x = sampler(x, mean_x, t_curr, t_next, model, **model_kwargs)
|
| 79 |
+
samples.append(x)
|
| 80 |
+
|
| 81 |
+
return samples
|
| 82 |
+
|
| 83 |
+
class ode:
|
| 84 |
+
"""ODE solver class"""
|
| 85 |
+
def __init__(
|
| 86 |
+
self,
|
| 87 |
+
drift,
|
| 88 |
+
*,
|
| 89 |
+
t0,
|
| 90 |
+
t1,
|
| 91 |
+
sampler_type,
|
| 92 |
+
num_steps,
|
| 93 |
+
atol,
|
| 94 |
+
rtol,
|
| 95 |
+
time_dist_shift,
|
| 96 |
+
use_time_shift=True
|
| 97 |
+
):
|
| 98 |
+
# Allow t0 > t1 for reverse (data -> noise) integration
|
| 99 |
+
assert t0 != t1, "ODE sampler needs distinct endpoints"
|
| 100 |
+
if t0 > t1:
|
| 101 |
+
t0, t1 = t1, t0 # linspace(t0, t1) must go low -> high
|
| 102 |
+
self.drift = drift
|
| 103 |
+
# num_steps=1 means 1 euler step, need 2 time points (start + end)
|
| 104 |
+
num_t_points = max(num_steps, 2)
|
| 105 |
+
self.t = 1 - th.linspace(t0, t1, num_t_points)
|
| 106 |
+
if use_time_shift:
|
| 107 |
+
self.t = time_dist_shift * self.t / (1 + (time_dist_shift - 1) * self.t)
|
| 108 |
+
else:
|
| 109 |
+
logger.warning("Sampling Time shift is disabled.")
|
| 110 |
+
|
| 111 |
+
self.atol = atol
|
| 112 |
+
self.rtol = rtol
|
| 113 |
+
self.sampler_type = sampler_type
|
| 114 |
+
|
| 115 |
+
def sample(self, x, model, **model_kwargs) -> tuple[th.Tensor]:
|
| 116 |
+
|
| 117 |
+
device = x[0].device if isinstance(x, tuple) else x.device
|
| 118 |
+
def _fn(t, x):
|
| 119 |
+
t = th.ones(x[0].size(0)).to(device) * t if isinstance(x, tuple) else th.ones(x.size(0)).to(device) * t
|
| 120 |
+
model_output = self.drift(x, t, model, **model_kwargs)
|
| 121 |
+
return model_output
|
| 122 |
+
|
| 123 |
+
t = self.t.to(device)
|
| 124 |
+
atol = [self.atol] * len(x) if isinstance(x, tuple) else [self.atol]
|
| 125 |
+
rtol = [self.rtol] * len(x) if isinstance(x, tuple) else [self.rtol]
|
| 126 |
+
samples = odeint(
|
| 127 |
+
_fn,
|
| 128 |
+
x,
|
| 129 |
+
t,
|
| 130 |
+
method=self.sampler_type,
|
| 131 |
+
atol=atol,
|
| 132 |
+
rtol=rtol
|
| 133 |
+
)
|
| 134 |
+
return samples
|
patch-reparameterization/src/stage2/transport/path.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch as th
|
| 2 |
+
import numpy as np
|
| 3 |
+
from functools import partial
|
| 4 |
+
import logging
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
def expand_t_like_x(t, x):
|
| 7 |
+
"""Function to reshape time t to broadcastable dimension of x
|
| 8 |
+
Args:
|
| 9 |
+
t: [batch_dim,], time vector
|
| 10 |
+
x: [batch_dim,...], data point
|
| 11 |
+
"""
|
| 12 |
+
dims = [1] * (len(x.size()) - 1)
|
| 13 |
+
t = t.view(t.size(0), *dims)
|
| 14 |
+
return t
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
#################### Coupling Plans ####################
|
| 18 |
+
|
| 19 |
+
class ICPlan:
|
| 20 |
+
"""Linear Coupling Plan"""
|
| 21 |
+
def __init__(self, sigma=0.0):
|
| 22 |
+
self.sigma = sigma
|
| 23 |
+
|
| 24 |
+
def compute_alpha_t(self, t):
|
| 25 |
+
"""Compute the data coefficient along the path"""
|
| 26 |
+
return 1 - t, -1
|
| 27 |
+
|
| 28 |
+
def compute_sigma_t(self, t):
|
| 29 |
+
"""Compute the noise coefficient along the path"""
|
| 30 |
+
return t, 1
|
| 31 |
+
|
| 32 |
+
def compute_d_alpha_alpha_ratio_t(self, t):
|
| 33 |
+
"""Compute the ratio between d_alpha and alpha"""
|
| 34 |
+
return -1 / (1 - t)
|
| 35 |
+
|
| 36 |
+
def compute_drift(self, x, t):
|
| 37 |
+
"""We always output sde according to score parametrization; """
|
| 38 |
+
t = expand_t_like_x(t, x)
|
| 39 |
+
alpha_ratio = self.compute_d_alpha_alpha_ratio_t(t)
|
| 40 |
+
sigma_t, d_sigma_t = self.compute_sigma_t(t)
|
| 41 |
+
drift = alpha_ratio * x
|
| 42 |
+
diffusion = alpha_ratio * (sigma_t ** 2) - sigma_t * d_sigma_t
|
| 43 |
+
|
| 44 |
+
return -drift, diffusion
|
| 45 |
+
|
| 46 |
+
def compute_diffusion(self, x, t, form="constant", norm=1.0):
|
| 47 |
+
"""Compute the diffusion term of the SDE
|
| 48 |
+
Args:
|
| 49 |
+
x: [batch_dim, ...], data point
|
| 50 |
+
t: [batch_dim,], time vector
|
| 51 |
+
form: str, form of the diffusion term
|
| 52 |
+
norm: float, norm of the diffusion term
|
| 53 |
+
"""
|
| 54 |
+
t = expand_t_like_x(t, x)
|
| 55 |
+
choices = {
|
| 56 |
+
"constant": norm,
|
| 57 |
+
"SBDM": norm * self.compute_drift(x, t)[1],
|
| 58 |
+
"sigma": norm * self.compute_sigma_t(t)[0],
|
| 59 |
+
# "linear": norm * (1 - t),
|
| 60 |
+
"linear": norm * t,
|
| 61 |
+
"decreasing": 0.25 * (norm * th.cos(np.pi * (1 - t)) + 1) ** 2,
|
| 62 |
+
"inccreasing-decreasing": norm * th.sin(np.pi * (1 - t)) ** 2,
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
diffusion = choices[form]
|
| 67 |
+
except KeyError:
|
| 68 |
+
raise NotImplementedError(f"Diffusion form {form} not implemented")
|
| 69 |
+
|
| 70 |
+
return diffusion
|
| 71 |
+
|
| 72 |
+
def get_score_from_velocity(self, velocity, x, t):
|
| 73 |
+
"""Wrapper function: transfrom velocity prediction model to score
|
| 74 |
+
Args:
|
| 75 |
+
velocity: [batch_dim, ...] shaped tensor; velocity model output
|
| 76 |
+
x: [batch_dim, ...] shaped tensor; x_t data point
|
| 77 |
+
t: [batch_dim,] time tensor
|
| 78 |
+
"""
|
| 79 |
+
t = expand_t_like_x(t, x)
|
| 80 |
+
alpha_t, d_alpha_t = self.compute_alpha_t(t)
|
| 81 |
+
sigma_t, d_sigma_t = self.compute_sigma_t(t)
|
| 82 |
+
mean = x
|
| 83 |
+
reverse_alpha_ratio = alpha_t / d_alpha_t
|
| 84 |
+
var = sigma_t**2 - reverse_alpha_ratio * d_sigma_t * sigma_t
|
| 85 |
+
score = (reverse_alpha_ratio * velocity - mean) / var
|
| 86 |
+
return score
|
| 87 |
+
|
| 88 |
+
def get_noise_from_velocity(self, velocity, x, t):
|
| 89 |
+
"""Wrapper function: transfrom velocity prediction model to denoiser
|
| 90 |
+
Args:
|
| 91 |
+
velocity: [batch_dim, ...] shaped tensor; velocity model output
|
| 92 |
+
x: [batch_dim, ...] shaped tensor; x_t data point
|
| 93 |
+
t: [batch_dim,] time tensor
|
| 94 |
+
"""
|
| 95 |
+
t = expand_t_like_x(t, x)
|
| 96 |
+
alpha_t, d_alpha_t = self.compute_alpha_t(t)
|
| 97 |
+
sigma_t, d_sigma_t = self.compute_sigma_t(t)
|
| 98 |
+
mean = x
|
| 99 |
+
reverse_alpha_ratio = alpha_t / d_alpha_t
|
| 100 |
+
var = reverse_alpha_ratio * d_sigma_t - sigma_t
|
| 101 |
+
noise = (reverse_alpha_ratio * velocity - mean) / var
|
| 102 |
+
return noise
|
| 103 |
+
|
| 104 |
+
def get_velocity_from_score(self, score, x, t):
|
| 105 |
+
"""Wrapper function: transfrom score prediction model to velocity
|
| 106 |
+
Args:
|
| 107 |
+
score: [batch_dim, ...] shaped tensor; score model output
|
| 108 |
+
x: [batch_dim, ...] shaped tensor; x_t data point
|
| 109 |
+
t: [batch_dim,] time tensor
|
| 110 |
+
"""
|
| 111 |
+
t = expand_t_like_x(t, x)
|
| 112 |
+
drift, var = self.compute_drift(x, t)
|
| 113 |
+
velocity = var * score - drift
|
| 114 |
+
return velocity
|
| 115 |
+
|
| 116 |
+
def compute_mu_t(self, t, x0, x1):
|
| 117 |
+
"""Compute the mean of time-dependent density p_t"""
|
| 118 |
+
t = expand_t_like_x(t, x1)
|
| 119 |
+
alpha_t, _ = self.compute_alpha_t(t)
|
| 120 |
+
sigma_t, _ = self.compute_sigma_t(t)
|
| 121 |
+
return alpha_t * x1 + sigma_t * x0
|
| 122 |
+
|
| 123 |
+
def compute_xt(self, t, x0, x1):
|
| 124 |
+
"""Sample xt from time-dependent density p_t; rng is required"""
|
| 125 |
+
xt = self.compute_mu_t(t, x0, x1)
|
| 126 |
+
return xt
|
| 127 |
+
|
| 128 |
+
def compute_ut(self, t, x0, x1, xt):
|
| 129 |
+
"""Compute the vector field corresponding to p_t"""
|
| 130 |
+
t = expand_t_like_x(t, x1)
|
| 131 |
+
_, d_alpha_t = self.compute_alpha_t(t)
|
| 132 |
+
_, d_sigma_t = self.compute_sigma_t(t)
|
| 133 |
+
return d_alpha_t * x1 + d_sigma_t * x0
|
| 134 |
+
|
| 135 |
+
def plan(self, t, x0, x1, **kwargs):
|
| 136 |
+
"""Compute (t, xt, ut). Extra kwargs are accepted but ignored so that
|
| 137 |
+
callers can unconditionally pass semantic=/recon= without branching."""
|
| 138 |
+
xt = self.compute_xt(t, x0, x1)
|
| 139 |
+
ut = self.compute_ut(t, x0, x1, xt)
|
| 140 |
+
return t, xt, ut
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class VPCPlan(ICPlan):
|
| 144 |
+
"""class for VP path flow matching"""
|
| 145 |
+
|
| 146 |
+
def __init__(self, sigma_min=0.1, sigma_max=20.0):
|
| 147 |
+
self.sigma_min = sigma_min
|
| 148 |
+
self.sigma_max = sigma_max
|
| 149 |
+
self.log_mean_coeff = lambda t: -0.25 * ((1 - t) ** 2) * (self.sigma_max - self.sigma_min) - 0.5 * (1 - t) * self.sigma_min
|
| 150 |
+
self.d_log_mean_coeff = lambda t: 0.5 * (1 - t) * (self.sigma_max - self.sigma_min) + 0.5 * self.sigma_min
|
| 151 |
+
|
| 152 |
+
def compute_alpha_t(self, t):
|
| 153 |
+
"""Compute coefficient of x0"""
|
| 154 |
+
p_alpha_t = 2 * self.log_mean_coeff(t)
|
| 155 |
+
alpha_t = th.sqrt(1 - th.exp(p_alpha_t))
|
| 156 |
+
d_alpha_t = th.exp(p_alpha_t) * (2 * self.d_log_mean_coeff(t)) / (-2 * alpha_t)
|
| 157 |
+
return alpha_t, d_alpha_t
|
| 158 |
+
|
| 159 |
+
def compute_sigma_t(self, t):
|
| 160 |
+
"""Compute coefficient of x1"""
|
| 161 |
+
sigma_t = self.log_mean_coeff(t)
|
| 162 |
+
sigma_t = th.exp(sigma_t)
|
| 163 |
+
d_sigma_t = sigma_t * self.d_log_mean_coeff(t)
|
| 164 |
+
return sigma_t, d_sigma_t
|
| 165 |
+
|
| 166 |
+
def compute_d_alpha_alpha_ratio_t(self, t):
|
| 167 |
+
"""Special purposed function for computing numerical stabled d_alpha_t / alpha_t"""
|
| 168 |
+
alpha_t, d_alpha_t = self.compute_alpha_t(t)
|
| 169 |
+
return d_alpha_t / alpha_t
|
| 170 |
+
|
| 171 |
+
def compute_drift(self, x, t):
|
| 172 |
+
"""Compute the drift term of the SDE"""
|
| 173 |
+
t = expand_t_like_x(t, x)
|
| 174 |
+
beta_t = self.sigma_min + (1 - t) * (self.sigma_max - self.sigma_min)
|
| 175 |
+
return -0.5 * beta_t * x, beta_t / 2
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
class GVPCPlan(ICPlan):
|
| 179 |
+
def __init__(self, sigma=0.0):
|
| 180 |
+
super().__init__(sigma)
|
| 181 |
+
|
| 182 |
+
def compute_alpha_t(self, t):
|
| 183 |
+
"""Compute coefficient of x1"""
|
| 184 |
+
alpha_t = th.cos(t * np.pi / 2)
|
| 185 |
+
d_alpha_t = -np.pi / 2 * th.sin(t * np.pi / 2)
|
| 186 |
+
return alpha_t, d_alpha_t
|
| 187 |
+
|
| 188 |
+
def compute_sigma_t(self, t):
|
| 189 |
+
"""Compute coefficient of x0"""
|
| 190 |
+
sigma_t = th.sin(t * np.pi / 2)
|
| 191 |
+
d_sigma_t = np.pi / 2 * th.cos(t * np.pi / 2)
|
| 192 |
+
return sigma_t, d_sigma_t
|
| 193 |
+
|
| 194 |
+
def compute_d_alpha_alpha_ratio_t(self, t):
|
| 195 |
+
"""Special purposed function for computing numerical stabled d_alpha_t / alpha_t"""
|
| 196 |
+
return -np.pi / 2 * th.tan(t * np.pi / 2)
|
| 197 |
+
|
| 198 |
+
class SemanticReconPlan(ICPlan):
|
| 199 |
+
"""Flow Matching plan with a time-dependent endpoint.
|
| 200 |
+
|
| 201 |
+
Instead of a fixed x1, the target endpoint varies with time:
|
| 202 |
+
x1(t) = semantic + (1 - t) * recon
|
| 203 |
+
|
| 204 |
+
where ``semantic`` (s) and ``recon`` (z) are the normalised semantic and
|
| 205 |
+
reconstruction components of the latent, satisfying s + z = x1_standard.
|
| 206 |
+
|
| 207 |
+
Derived interpolant and velocity field:
|
| 208 |
+
xt = (1-t) * x1(t) + t * x0
|
| 209 |
+
= (1-t) * [s + (1-t)*z] + t * x0
|
| 210 |
+
|
| 211 |
+
ut = d(xt)/dt
|
| 212 |
+
= x0 - s - 2*(1-t)*z
|
| 213 |
+
|
| 214 |
+
When ``semantic`` or ``recon`` is None (e.g. at inference time or when the
|
| 215 |
+
feature is unavailable), the plan falls back to the standard ICPlan
|
| 216 |
+
behaviour so that existing code paths are not broken.
|
| 217 |
+
"""
|
| 218 |
+
|
| 219 |
+
def plan(self, t, x0, x1, semantic=None, recon=None):
|
| 220 |
+
"""Compute (t, xt, ut) for training.
|
| 221 |
+
|
| 222 |
+
Args:
|
| 223 |
+
t: [B] time samples in [0, 1]
|
| 224 |
+
x0: [B, ...] noise samples ~ N(0, I)
|
| 225 |
+
x1: [B, ...] data samples (= semantic + recon, normalised)
|
| 226 |
+
semantic: [B, ...] normalised semantic component s (optional)
|
| 227 |
+
recon: [B, ...] normalised recon component z (optional)
|
| 228 |
+
|
| 229 |
+
Returns:
|
| 230 |
+
t, xt, ut – all [B, ...]
|
| 231 |
+
"""
|
| 232 |
+
# logger.debug(f"sementic: {semantic}")
|
| 233 |
+
# logger.debug(f"recon: {recon}")
|
| 234 |
+
if semantic is None or recon is None:
|
| 235 |
+
# Graceful fallback: standard linear interpolant
|
| 236 |
+
return super().plan(t, x0, x1)
|
| 237 |
+
|
| 238 |
+
t_exp = expand_t_like_x(t, semantic) # [B, 1, 1, ...] broadcastable
|
| 239 |
+
x1_t = semantic + (1 - t_exp) * recon # time-dependent endpoint
|
| 240 |
+
xt = (1 - t_exp) * x1_t + t_exp * x0 # interpolant
|
| 241 |
+
ut = x0 - semantic - 2 * (1 - t_exp) * recon # analytic velocity field
|
| 242 |
+
return t, xt, ut
|
patch-reparameterization/src/stage2/transport/transport.py
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch as th
|
| 2 |
+
import numpy as np
|
| 3 |
+
import logging
|
| 4 |
+
|
| 5 |
+
import enum
|
| 6 |
+
|
| 7 |
+
from . import path
|
| 8 |
+
from .utils import EasyDict, log_state, mean_flat
|
| 9 |
+
from .integrators import ode, sde
|
| 10 |
+
import logging
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
class ModelType(enum.Enum):
|
| 14 |
+
"""
|
| 15 |
+
Which type of output the model predicts.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
NOISE = enum.auto() # the model predicts epsilon
|
| 19 |
+
SCORE = enum.auto() # the model predicts \nabla \log p(x)
|
| 20 |
+
VELOCITY = enum.auto() # the model predicts v(x)
|
| 21 |
+
|
| 22 |
+
class PathType(enum.Enum):
|
| 23 |
+
"""
|
| 24 |
+
Which type of path to use.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
LINEAR = enum.auto()
|
| 28 |
+
GVP = enum.auto()
|
| 29 |
+
VP = enum.auto()
|
| 30 |
+
SEMANTIC_RECON = enum.auto() # time-dependent endpoint: x1(t) = s + (1-t)*z
|
| 31 |
+
|
| 32 |
+
class WeightType(enum.Enum):
|
| 33 |
+
"""
|
| 34 |
+
Which type of weighting to use.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
NONE = enum.auto()
|
| 38 |
+
VELOCITY = enum.auto()
|
| 39 |
+
LIKELIHOOD = enum.auto()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def truncated_logitnormal_sample(
|
| 43 |
+
shape, mu, sigma, low=0.0, high=1.0
|
| 44 |
+
):
|
| 45 |
+
"""
|
| 46 |
+
Samples X in (0,1) with Z = logit(X) ~ Normal(mu, sigma^2), truncated so X in [low, high].
|
| 47 |
+
Works for scalars or tensors mu/sigma/low/high with broadcasting.
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
shape: output batch shape (e.g., (N,) or (N,M)). Leave () to broadcast to mu.shape.
|
| 51 |
+
mu, sigma: tensors or floats (sigma > 0).
|
| 52 |
+
low, high: truncation bounds in [0,1]. (low can be 0, high can be 1).
|
| 53 |
+
device, dtype: optional overrides.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
Tensor of samples with shape = broadcast(shape, mu.shape, ...)
|
| 57 |
+
"""
|
| 58 |
+
mu = th.as_tensor(mu)
|
| 59 |
+
sigma= th.as_tensor(sigma)
|
| 60 |
+
low = th.as_tensor(low)
|
| 61 |
+
high = th.as_tensor(high)
|
| 62 |
+
|
| 63 |
+
# Map truncation bounds to logit space; handles 0/1 → ±inf automatically.
|
| 64 |
+
z_low = th.logit(low) # = -inf if low==0
|
| 65 |
+
z_high = th.logit(high) # = +inf if high==1
|
| 66 |
+
|
| 67 |
+
# Standardize bounds for the base Normal(0,1)
|
| 68 |
+
base = th.distributions.Normal(th.zeros_like(mu), th.ones_like(sigma))
|
| 69 |
+
alpha = (z_low - mu) / sigma
|
| 70 |
+
beta = (z_high - mu) / sigma
|
| 71 |
+
|
| 72 |
+
# Truncated-normal inverse CDF sampling:
|
| 73 |
+
# U ~ Uniform(Φ(alpha), Φ(beta)); Z = mu + sigma * Φ^{-1}(U); X = sigmoid(Z)
|
| 74 |
+
cdf_alpha = base.cdf(alpha)
|
| 75 |
+
cdf_beta = base.cdf(beta)
|
| 76 |
+
|
| 77 |
+
# Draw uniforms on the truncated interval
|
| 78 |
+
out_shape = th.broadcast_shapes(shape, mu.shape, sigma.shape, low.shape, high.shape)
|
| 79 |
+
U = th.rand(out_shape, device=mu.device, dtype=mu.dtype)
|
| 80 |
+
U = cdf_alpha + (cdf_beta - cdf_alpha) * U.clamp_(0, 1)
|
| 81 |
+
|
| 82 |
+
Z = mu + sigma * base.icdf(U)
|
| 83 |
+
X = th.sigmoid(Z)
|
| 84 |
+
|
| 85 |
+
# Numerical safety when low/high are extremely close; clamp back into [low, high].
|
| 86 |
+
return X.clamp(low, high)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class Transport:
|
| 90 |
+
|
| 91 |
+
def __init__(
|
| 92 |
+
self,
|
| 93 |
+
*,
|
| 94 |
+
model_type,
|
| 95 |
+
path_type,
|
| 96 |
+
loss_type,
|
| 97 |
+
time_dist_type,
|
| 98 |
+
time_dist_shift,
|
| 99 |
+
train_eps,
|
| 100 |
+
sample_eps,
|
| 101 |
+
use_time_shift,
|
| 102 |
+
sem_dim=None,
|
| 103 |
+
recon_loss_weight=1.0,
|
| 104 |
+
normalize_channel_weight=False,
|
| 105 |
+
):
|
| 106 |
+
path_options = {
|
| 107 |
+
PathType.LINEAR: path.ICPlan,
|
| 108 |
+
PathType.GVP: path.GVPCPlan,
|
| 109 |
+
PathType.VP: path.VPCPlan,
|
| 110 |
+
PathType.SEMANTIC_RECON: path.SemanticReconPlan,
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
self.loss_type = loss_type
|
| 114 |
+
self.model_type = model_type
|
| 115 |
+
self.time_dist_type = time_dist_type
|
| 116 |
+
self.time_dist_shift = time_dist_shift
|
| 117 |
+
assert self.time_dist_shift >= 1.0, "time distribution shift must be >= 1.0."
|
| 118 |
+
self.path_sampler = path_options[path_type]()
|
| 119 |
+
self.train_eps = train_eps
|
| 120 |
+
self.sample_eps = sample_eps
|
| 121 |
+
self.use_time_shift = use_time_shift
|
| 122 |
+
self.sem_dim = sem_dim
|
| 123 |
+
self.recon_loss_weight = float(recon_loss_weight)
|
| 124 |
+
self.normalize_channel_weight = normalize_channel_weight
|
| 125 |
+
if self.sem_dim is not None:
|
| 126 |
+
logger.info(f"Channel-wise loss: sem_dim={self.sem_dim}, recon_loss_weight={self.recon_loss_weight}, normalize={self.normalize_channel_weight}")
|
| 127 |
+
|
| 128 |
+
def prior_logp(self, z):
|
| 129 |
+
'''
|
| 130 |
+
Standard multivariate normal prior
|
| 131 |
+
Assume z is batched
|
| 132 |
+
'''
|
| 133 |
+
shape = th.tensor(z.size())
|
| 134 |
+
N = th.prod(shape[1:])
|
| 135 |
+
_fn = lambda x: -N / 2. * np.log(2 * np.pi) - th.sum(x ** 2) / 2.
|
| 136 |
+
return th.vmap(_fn)(z)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def check_interval(
|
| 140 |
+
self,
|
| 141 |
+
train_eps,
|
| 142 |
+
sample_eps,
|
| 143 |
+
*,
|
| 144 |
+
diffusion_form="SBDM",
|
| 145 |
+
sde=False,
|
| 146 |
+
reverse=False,
|
| 147 |
+
eval=False,
|
| 148 |
+
last_step_size=0.0,
|
| 149 |
+
):
|
| 150 |
+
t0 = 0
|
| 151 |
+
t1 = 1 - 1 / 1000
|
| 152 |
+
eps = train_eps if not eval else sample_eps
|
| 153 |
+
if (type(self.path_sampler) in [path.VPCPlan]):
|
| 154 |
+
|
| 155 |
+
t1 = 1 - eps if (not sde or last_step_size == 0) else 1 - last_step_size
|
| 156 |
+
|
| 157 |
+
elif (type(self.path_sampler) in [path.ICPlan, path.GVPCPlan, path.SemanticReconPlan]) \
|
| 158 |
+
and (self.model_type != ModelType.VELOCITY or sde): # avoid numerical issue by taking a first semi-implicit step
|
| 159 |
+
|
| 160 |
+
t0 = eps if (diffusion_form == "SBDM" and sde) or self.model_type != ModelType.VELOCITY else 0
|
| 161 |
+
t1 = 1 - eps if (not sde or last_step_size == 0) else 1 - last_step_size
|
| 162 |
+
|
| 163 |
+
if reverse:
|
| 164 |
+
t0, t1 = 1 - t0, 1 - t1
|
| 165 |
+
|
| 166 |
+
return t0, t1
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def sample(self, x1):
|
| 170 |
+
"""Sampling x0 & t based on shape of x1 (if needed)
|
| 171 |
+
Args:
|
| 172 |
+
x1 - data point; [batch, *dim]
|
| 173 |
+
"""
|
| 174 |
+
|
| 175 |
+
x0 = th.randn_like(x1)
|
| 176 |
+
dist_options = self.time_dist_type.split("_")
|
| 177 |
+
t0, t1 = self.check_interval(self.train_eps, self.sample_eps)
|
| 178 |
+
if dist_options[0] == "uniform":
|
| 179 |
+
t = th.rand((x1.shape[0],)) * (t1 - t0) + t0
|
| 180 |
+
# print('UNIFORM IS CALLED')
|
| 181 |
+
elif dist_options[0] == "logit-normal":
|
| 182 |
+
assert len(dist_options) == 3, "Logit-normal distribution must specify the mean and variance."
|
| 183 |
+
mu, sigma = float(dist_options[1]), float(dist_options[2])
|
| 184 |
+
assert sigma > 0, "Logit-normal distribution must have positive variance."
|
| 185 |
+
t = truncated_logitnormal_sample(
|
| 186 |
+
(x1.shape[0],), mu=mu, sigma=sigma, low=t0, high=t1
|
| 187 |
+
)
|
| 188 |
+
# print('LOGITNORMAL IS CALLED')
|
| 189 |
+
elif dist_options[0] == "fixed":
|
| 190 |
+
# Fixed t for 1-step training (e.g., "fixed_1.0")
|
| 191 |
+
fixed_t = float(dist_options[1]) if len(dist_options) > 1 else 1.0
|
| 192 |
+
t = th.full((x1.shape[0],), fixed_t)
|
| 193 |
+
else:
|
| 194 |
+
raise NotImplementedError(f"Unknown time distribution type {self.time_dist_type}")
|
| 195 |
+
|
| 196 |
+
t = t.to(x1)
|
| 197 |
+
# logger.debug(f"t before shift: {t}")
|
| 198 |
+
#sqrt_size_ratio = 1 / self.time_dist_shift # already sqrted
|
| 199 |
+
if self.use_time_shift:
|
| 200 |
+
t = self.time_dist_shift * t / (1 + (self.time_dist_shift - 1) * t)
|
| 201 |
+
# logger.debug(f"t after shift: {t}")
|
| 202 |
+
return t, x0, x1
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def training_losses(
|
| 206 |
+
self,
|
| 207 |
+
model,
|
| 208 |
+
x1,
|
| 209 |
+
model_kwargs=None
|
| 210 |
+
):
|
| 211 |
+
"""Loss for training the score model
|
| 212 |
+
Args:
|
| 213 |
+
- model: backbone model; could be score, noise, or velocity
|
| 214 |
+
- x1: datapoint
|
| 215 |
+
- model_kwargs: additional arguments for the model
|
| 216 |
+
"""
|
| 217 |
+
if model_kwargs == None:
|
| 218 |
+
model_kwargs = {}
|
| 219 |
+
|
| 220 |
+
# Extract auxiliary loss parameters so they aren't passed to the model's forward pass
|
| 221 |
+
aux_losses_cfg = model_kwargs.pop('aux_losses_cfg', {})
|
| 222 |
+
latent_var = model_kwargs.pop('latent_var', None)
|
| 223 |
+
|
| 224 |
+
# Pre-emptively pop all potential projection modules to avoid passing them to the model
|
| 225 |
+
proj_modules = {}
|
| 226 |
+
keys_to_pop = [k for k in model_kwargs.keys() if k.endswith('_proj')]
|
| 227 |
+
for k in keys_to_pop:
|
| 228 |
+
proj_name = k.replace('_proj', '')
|
| 229 |
+
proj_modules[proj_name] = model_kwargs.pop(k)
|
| 230 |
+
|
| 231 |
+
# Handle legacy 'semetic_proj' key if present
|
| 232 |
+
if 'semetic_proj' in model_kwargs:
|
| 233 |
+
proj_modules['semantic'] = model_kwargs.pop('semetic_proj')
|
| 234 |
+
|
| 235 |
+
# Pop semantic/recon components for SemanticReconPlan path construction.
|
| 236 |
+
# These must NOT be forwarded to the model's forward().
|
| 237 |
+
path_semantic = model_kwargs.pop('path_semantic', None)
|
| 238 |
+
path_recon = model_kwargs.pop('path_recon', None)
|
| 239 |
+
|
| 240 |
+
logger.debug(f"x1 shape{x1.shape}")
|
| 241 |
+
t, x0, x1 = self.sample(x1)
|
| 242 |
+
t, xt, ut = self.path_sampler.plan(t, x0, x1, semantic=path_semantic, recon=path_recon)
|
| 243 |
+
logger.debug(f"xt shape{xt.shape}")
|
| 244 |
+
model_output = model(xt, t, **model_kwargs)
|
| 245 |
+
logger.debug(f"model output shape{model_output.shape}")
|
| 246 |
+
B, *_, C = xt.shape
|
| 247 |
+
assert model_output.size() == (B, *xt.size()[1:-1], C)
|
| 248 |
+
|
| 249 |
+
terms = {}
|
| 250 |
+
terms['pred'] = model_output
|
| 251 |
+
terms['xt'] = xt
|
| 252 |
+
terms['t'] = t
|
| 253 |
+
terms['x0'] = x0
|
| 254 |
+
if self.model_type == ModelType.VELOCITY:
|
| 255 |
+
mse = (model_output - ut) ** 2
|
| 256 |
+
if self.sem_dim is not None and self.sem_dim > 0:
|
| 257 |
+
# Channel dim is dim=1 for [B,C,H,W] or dim=-1 for [B,N,C]
|
| 258 |
+
if mse.dim() == 4: # [B, C, H, W]
|
| 259 |
+
terms['loss_sem'] = mean_flat(mse[:, :self.sem_dim])
|
| 260 |
+
terms['loss_recon'] = mean_flat(mse[:, self.sem_dim:])
|
| 261 |
+
if self.recon_loss_weight != 1.0:
|
| 262 |
+
C = mse.shape[1]
|
| 263 |
+
recon_dim = C - self.sem_dim
|
| 264 |
+
sem_w = 1.0
|
| 265 |
+
recon_w = self.recon_loss_weight
|
| 266 |
+
if self.normalize_channel_weight:
|
| 267 |
+
norm_factor = C / (self.sem_dim * sem_w + recon_dim * recon_w)
|
| 268 |
+
sem_w *= norm_factor
|
| 269 |
+
recon_w *= norm_factor
|
| 270 |
+
weight = th.ones(1, C, 1, 1, device=mse.device)
|
| 271 |
+
weight[:, :self.sem_dim] = sem_w
|
| 272 |
+
weight[:, self.sem_dim:] = recon_w
|
| 273 |
+
mse = mse * weight
|
| 274 |
+
else: # [B, N, C]
|
| 275 |
+
terms['loss_sem'] = mean_flat(mse[..., :self.sem_dim])
|
| 276 |
+
terms['loss_recon'] = mean_flat(mse[..., self.sem_dim:])
|
| 277 |
+
if self.recon_loss_weight != 1.0:
|
| 278 |
+
C = mse.shape[-1]
|
| 279 |
+
recon_dim = C - self.sem_dim
|
| 280 |
+
sem_w = 1.0
|
| 281 |
+
recon_w = self.recon_loss_weight
|
| 282 |
+
if self.normalize_channel_weight:
|
| 283 |
+
norm_factor = C / (self.sem_dim * sem_w + recon_dim * recon_w)
|
| 284 |
+
sem_w *= norm_factor
|
| 285 |
+
recon_w *= norm_factor
|
| 286 |
+
weight = th.ones(1, 1, C, device=mse.device)
|
| 287 |
+
weight[..., :self.sem_dim] = sem_w
|
| 288 |
+
weight[..., self.sem_dim:] = recon_w
|
| 289 |
+
mse = mse * weight
|
| 290 |
+
terms['loss'] = mean_flat(mse)
|
| 291 |
+
else:
|
| 292 |
+
_, drift_var = self.path_sampler.compute_drift(xt, t)
|
| 293 |
+
sigma_t, _ = self.path_sampler.compute_sigma_t(path.expand_t_like_x(t, xt))
|
| 294 |
+
if self.loss_type in [WeightType.VELOCITY]:
|
| 295 |
+
weight = (drift_var / sigma_t) ** 2
|
| 296 |
+
elif self.loss_type in [WeightType.LIKELIHOOD]:
|
| 297 |
+
weight = drift_var / (sigma_t ** 2)
|
| 298 |
+
elif self.loss_type in [WeightType.NONE]:
|
| 299 |
+
weight = 1
|
| 300 |
+
else:
|
| 301 |
+
raise NotImplementedError()
|
| 302 |
+
|
| 303 |
+
if self.model_type == ModelType.NOISE:
|
| 304 |
+
terms['loss'] = mean_flat(weight * ((model_output - x0) ** 2))
|
| 305 |
+
else:
|
| 306 |
+
terms['loss'] = mean_flat(weight * ((model_output * sigma_t + x0) ** 2))
|
| 307 |
+
|
| 308 |
+
# --- Compute Auxiliary Losses (e.g., Semantic Loss) ---
|
| 309 |
+
if self.model_type == ModelType.VELOCITY and aux_losses_cfg:
|
| 310 |
+
# Apply base weight to the original loss
|
| 311 |
+
base_weight = aux_losses_cfg.get('base_weight', 1.0)
|
| 312 |
+
logger.debug(f"flow loss: {terms['loss'].detach().mean().item()}")
|
| 313 |
+
terms['loss'] = terms['loss'] * base_weight
|
| 314 |
+
|
| 315 |
+
# Prepare unnormalized velocity if any projection loss is enabled
|
| 316 |
+
# We check if any proj loss is enabled to avoid unnecessary computation
|
| 317 |
+
proj_losses_cfg = aux_losses_cfg.get('proj_losses', {})
|
| 318 |
+
need_unnorm = any(
|
| 319 |
+
isinstance(cfg, dict) and cfg.get('enabled', False)
|
| 320 |
+
for cfg in proj_losses_cfg.values()
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
v_pred_unnorm, v_gt_unnorm = None, None
|
| 324 |
+
if need_unnorm and latent_var is not None:
|
| 325 |
+
logger.debug(f"model_output shape: {model_output.shape}")
|
| 326 |
+
logger.debug(f"latent_var shape: {latent_var.shape}")
|
| 327 |
+
|
| 328 |
+
# 1. Convert model_output and ut to [B, N, C] if they are [B, C, H, W]
|
| 329 |
+
if model_output.dim() == 4:
|
| 330 |
+
B, C, H, W = model_output.shape
|
| 331 |
+
N = H * W
|
| 332 |
+
# [B, C, H, W] -> [B, C, N] -> [B, N, C]
|
| 333 |
+
v_pred_flat = model_output.view(B, C, N).transpose(1, 2)
|
| 334 |
+
v_gt_flat = ut.view(B, C, N).transpose(1, 2)
|
| 335 |
+
else:
|
| 336 |
+
v_pred_flat = model_output
|
| 337 |
+
v_gt_flat = ut
|
| 338 |
+
B, N, C = v_pred_flat.shape
|
| 339 |
+
|
| 340 |
+
# 2. Reshape latent_var to [1, N, C] or [1, 1, C]
|
| 341 |
+
if latent_var.dim() == 1:
|
| 342 |
+
# [C] -> [1, 1, C]
|
| 343 |
+
scale = th.sqrt(latent_var + 1e-5).view(1, 1, -1)
|
| 344 |
+
elif latent_var.dim() == 2:
|
| 345 |
+
# [N, C] -> [1, N, C]
|
| 346 |
+
scale = th.sqrt(latent_var + 1e-5).unsqueeze(0)
|
| 347 |
+
elif latent_var.dim() == 3:
|
| 348 |
+
# [C, H, W] -> [C, N] -> [N, C] -> [1, N, C]
|
| 349 |
+
scale = th.sqrt(latent_var + 1e-5).view(latent_var.shape[0], -1).transpose(0, 1).unsqueeze(0)
|
| 350 |
+
else:
|
| 351 |
+
scale = th.sqrt(latent_var + 1e-5)
|
| 352 |
+
|
| 353 |
+
# 3. Unnormalize
|
| 354 |
+
v_pred_unnorm = v_pred_flat * scale
|
| 355 |
+
v_gt_unnorm = v_gt_flat * scale
|
| 356 |
+
|
| 357 |
+
# --- Loop over projection losses ---
|
| 358 |
+
# This makes it easy to add more projection losses in the future
|
| 359 |
+
for proj_name, proj_cfg in proj_losses_cfg.items():
|
| 360 |
+
if isinstance(proj_cfg, dict) and proj_cfg.get('enabled', False):
|
| 361 |
+
# Get the projection module from our pre-popped dict
|
| 362 |
+
proj_module = proj_modules.get(proj_name)
|
| 363 |
+
|
| 364 |
+
if proj_module is not None:
|
| 365 |
+
if v_pred_unnorm is None:
|
| 366 |
+
logger.warning(f"{proj_name} loss enabled but latent_var is missing.")
|
| 367 |
+
continue
|
| 368 |
+
|
| 369 |
+
# Map to projection space
|
| 370 |
+
proj_v_pred = proj_module(v_pred_unnorm)
|
| 371 |
+
proj_v_gt = proj_module(v_gt_unnorm)
|
| 372 |
+
|
| 373 |
+
# Compute MSE
|
| 374 |
+
proj_loss = mean_flat(((proj_v_pred - proj_v_gt) ** 2))
|
| 375 |
+
|
| 376 |
+
logger.debug(f"{proj_name}_loss: {proj_loss.detach().mean().item()}")
|
| 377 |
+
weight = proj_cfg.get('weight', 1.0)
|
| 378 |
+
|
| 379 |
+
# Accumulate to total loss
|
| 380 |
+
terms['loss'] = terms['loss'] + weight * proj_loss
|
| 381 |
+
terms[f'{proj_name}_loss'] = proj_loss.detach()
|
| 382 |
+
else:
|
| 383 |
+
logger.warning(f"{proj_name} loss enabled but {proj_name}_proj not found in model_kwargs")
|
| 384 |
+
# logger.debug(terms)
|
| 385 |
+
return terms
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def get_drift(
|
| 389 |
+
self
|
| 390 |
+
):
|
| 391 |
+
"""member function for obtaining the drift of the probability flow ODE"""
|
| 392 |
+
def score_ode(x, t, model, **model_kwargs):
|
| 393 |
+
drift_mean, drift_var = self.path_sampler.compute_drift(x, t)
|
| 394 |
+
model_output = model(x, t, **model_kwargs)
|
| 395 |
+
return (-drift_mean + drift_var * model_output) # by change of variable
|
| 396 |
+
|
| 397 |
+
def noise_ode(x, t, model, **model_kwargs):
|
| 398 |
+
drift_mean, drift_var = self.path_sampler.compute_drift(x, t)
|
| 399 |
+
sigma_t, _ = self.path_sampler.compute_sigma_t(path.expand_t_like_x(t, x))
|
| 400 |
+
model_output = model(x, t, **model_kwargs)
|
| 401 |
+
score = model_output / -sigma_t
|
| 402 |
+
return (-drift_mean + drift_var * score)
|
| 403 |
+
|
| 404 |
+
def velocity_ode(x, t, model, **model_kwargs):
|
| 405 |
+
model_output = model(x, t, **model_kwargs)
|
| 406 |
+
return model_output
|
| 407 |
+
|
| 408 |
+
if self.model_type == ModelType.NOISE:
|
| 409 |
+
drift_fn = noise_ode
|
| 410 |
+
elif self.model_type == ModelType.SCORE:
|
| 411 |
+
drift_fn = score_ode
|
| 412 |
+
else:
|
| 413 |
+
drift_fn = velocity_ode
|
| 414 |
+
|
| 415 |
+
def body_fn(x, t, model, **model_kwargs):
|
| 416 |
+
model_output = drift_fn(x, t, model, **model_kwargs)
|
| 417 |
+
assert model_output.shape == x.shape, "Output shape from ODE solver must match input shape"
|
| 418 |
+
return model_output
|
| 419 |
+
|
| 420 |
+
return body_fn
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
def get_score(
|
| 424 |
+
self,
|
| 425 |
+
):
|
| 426 |
+
"""member function for obtaining score of
|
| 427 |
+
x_t = alpha_t * x + sigma_t * eps"""
|
| 428 |
+
if self.model_type == ModelType.NOISE:
|
| 429 |
+
score_fn = lambda x, t, model, **kwargs: model(x, t, **kwargs) / -self.path_sampler.compute_sigma_t(path.expand_t_like_x(t, x))[0]
|
| 430 |
+
elif self.model_type == ModelType.SCORE:
|
| 431 |
+
score_fn = lambda x, t, model, **kwagrs: model(x, t, **kwagrs)
|
| 432 |
+
elif self.model_type == ModelType.VELOCITY:
|
| 433 |
+
score_fn = lambda x, t, model, **kwargs: self.path_sampler.get_score_from_velocity(model(x, t, **kwargs), x, t)
|
| 434 |
+
else:
|
| 435 |
+
raise NotImplementedError()
|
| 436 |
+
|
| 437 |
+
return score_fn
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
class Sampler:
|
| 441 |
+
"""Sampler class for the transport model"""
|
| 442 |
+
def __init__(
|
| 443 |
+
self,
|
| 444 |
+
transport,
|
| 445 |
+
):
|
| 446 |
+
"""Constructor for a general sampler; supporting different sampling methods
|
| 447 |
+
Args:
|
| 448 |
+
- transport: an tranport object specify model prediction & interpolant type
|
| 449 |
+
"""
|
| 450 |
+
|
| 451 |
+
self.transport = transport
|
| 452 |
+
self.drift = self.transport.get_drift()
|
| 453 |
+
self.score = self.transport.get_score()
|
| 454 |
+
|
| 455 |
+
def __get_sde_diffusion_and_drift(
|
| 456 |
+
self,
|
| 457 |
+
*,
|
| 458 |
+
diffusion_form="SBDM",
|
| 459 |
+
diffusion_norm=1.0,
|
| 460 |
+
):
|
| 461 |
+
|
| 462 |
+
def sde_diffusion_fn(x, t):
|
| 463 |
+
diffusion = self.transport.path_sampler.compute_diffusion(x, t, form=diffusion_form, norm=diffusion_norm)
|
| 464 |
+
return diffusion
|
| 465 |
+
|
| 466 |
+
def sde_drift_fn(x, t, model, **kwargs):
|
| 467 |
+
drift_mean = self.drift(x, t, model, **kwargs) - sde_diffusion_fn(x, t) * self.score(x, t, model, **kwargs)
|
| 468 |
+
return drift_mean
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
return sde_drift_fn, sde_diffusion_fn
|
| 472 |
+
|
| 473 |
+
def __get_last_step(
|
| 474 |
+
self,
|
| 475 |
+
sde_drift,
|
| 476 |
+
*,
|
| 477 |
+
last_step,
|
| 478 |
+
last_step_size,
|
| 479 |
+
):
|
| 480 |
+
"""Get the last step function of the SDE solver"""
|
| 481 |
+
|
| 482 |
+
if last_step is None:
|
| 483 |
+
last_step_fn = \
|
| 484 |
+
lambda x, t, model, **model_kwargs: \
|
| 485 |
+
x
|
| 486 |
+
elif last_step == "Mean":
|
| 487 |
+
last_step_fn = \
|
| 488 |
+
lambda x, t, model, **model_kwargs: \
|
| 489 |
+
x - sde_drift(x, t, model, **model_kwargs) * last_step_size
|
| 490 |
+
elif last_step == "Tweedie":
|
| 491 |
+
alpha = self.transport.path_sampler.compute_alpha_t # simple aliasing; the original name was too long
|
| 492 |
+
sigma = self.transport.path_sampler.compute_sigma_t
|
| 493 |
+
last_step_fn = \
|
| 494 |
+
lambda x, t, model, **model_kwargs: \
|
| 495 |
+
x / alpha(t)[0][0] + (sigma(t)[0][0] ** 2) / alpha(t)[0][0] * self.score(x, t, model, **model_kwargs)
|
| 496 |
+
elif last_step == "Euler":
|
| 497 |
+
last_step_fn = \
|
| 498 |
+
lambda x, t, model, **model_kwargs: \
|
| 499 |
+
x - self.drift(x, t, model, **model_kwargs) * last_step_size
|
| 500 |
+
else:
|
| 501 |
+
raise NotImplementedError()
|
| 502 |
+
|
| 503 |
+
return last_step_fn
|
| 504 |
+
|
| 505 |
+
def sample_sde(
|
| 506 |
+
self,
|
| 507 |
+
*,
|
| 508 |
+
sampling_method="Euler",
|
| 509 |
+
diffusion_form="SBDM",
|
| 510 |
+
diffusion_norm=1.0,
|
| 511 |
+
last_step="Mean",
|
| 512 |
+
last_step_size=0.04,
|
| 513 |
+
num_steps=250,
|
| 514 |
+
):
|
| 515 |
+
"""returns a sampling function with given SDE settings
|
| 516 |
+
Args:
|
| 517 |
+
- sampling_method: type of sampler used in solving the SDE; default to be Euler-Maruyama
|
| 518 |
+
- diffusion_form: function form of diffusion coefficient; default to be matching SBDM
|
| 519 |
+
- diffusion_norm: function magnitude of diffusion coefficient; default to 1
|
| 520 |
+
- last_step: type of the last step; default to identity
|
| 521 |
+
- last_step_size: size of the last step; default to match the stride of 250 steps over [0,1]
|
| 522 |
+
- num_steps: total integration step of SDE
|
| 523 |
+
"""
|
| 524 |
+
|
| 525 |
+
if last_step is None:
|
| 526 |
+
last_step_size = 0.0
|
| 527 |
+
|
| 528 |
+
sde_drift, sde_diffusion = self.__get_sde_diffusion_and_drift(
|
| 529 |
+
diffusion_form=diffusion_form,
|
| 530 |
+
diffusion_norm=diffusion_norm,
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
t0, t1 = self.transport.check_interval(
|
| 534 |
+
self.transport.train_eps,
|
| 535 |
+
self.transport.sample_eps,
|
| 536 |
+
diffusion_form=diffusion_form,
|
| 537 |
+
sde=True,
|
| 538 |
+
eval=True,
|
| 539 |
+
reverse=False,
|
| 540 |
+
last_step_size=last_step_size,
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
_sde = sde(
|
| 544 |
+
sde_drift,
|
| 545 |
+
sde_diffusion,
|
| 546 |
+
t0=t0,
|
| 547 |
+
t1=t1,
|
| 548 |
+
num_steps=num_steps,
|
| 549 |
+
sampler_type=sampling_method,
|
| 550 |
+
time_dist_shift=self.transport.time_dist_shift,
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
last_step_fn = self.__get_last_step(sde_drift, last_step=last_step, last_step_size=last_step_size)
|
| 554 |
+
|
| 555 |
+
|
| 556 |
+
def _sample(init, model, **model_kwargs):
|
| 557 |
+
xs = _sde.sample(init, model, **model_kwargs)
|
| 558 |
+
ts = th.ones(init.size(0), device=init.device) * (1 - t1)
|
| 559 |
+
x = last_step_fn(xs[-1], ts, model, **model_kwargs)
|
| 560 |
+
xs.append(x)
|
| 561 |
+
|
| 562 |
+
assert len(xs) == num_steps, "Samples does not match the number of steps"
|
| 563 |
+
|
| 564 |
+
return xs
|
| 565 |
+
|
| 566 |
+
return _sample
|
| 567 |
+
|
| 568 |
+
def sample_ode(
|
| 569 |
+
self,
|
| 570 |
+
*,
|
| 571 |
+
sampling_method="dopri5",
|
| 572 |
+
num_steps=50,
|
| 573 |
+
atol=1e-6,
|
| 574 |
+
rtol=1e-3,
|
| 575 |
+
reverse=False,
|
| 576 |
+
**kwargs
|
| 577 |
+
):
|
| 578 |
+
"""returns a sampling function with given ODE settings
|
| 579 |
+
Args:
|
| 580 |
+
- sampling_method: type of sampler used in solving the ODE; default to be Dopri5
|
| 581 |
+
- num_steps:
|
| 582 |
+
- fixed solver (Euler, Heun): the actual number of integration steps performed
|
| 583 |
+
- adaptive solver (Dopri5): the number of datapoints saved during integration; produced by interpolation
|
| 584 |
+
- atol: absolute error tolerance for the solver
|
| 585 |
+
- rtol: relative error tolerance for the solver
|
| 586 |
+
- reverse: whether solving the ODE in reverse (data to noise); default to False
|
| 587 |
+
"""
|
| 588 |
+
if reverse:
|
| 589 |
+
drift = lambda x, t, model, **kwargs: self.drift(x, th.ones_like(t) * (1 - t), model, **kwargs)
|
| 590 |
+
else:
|
| 591 |
+
drift = self.drift
|
| 592 |
+
|
| 593 |
+
t0, t1 = self.transport.check_interval(
|
| 594 |
+
self.transport.train_eps,
|
| 595 |
+
self.transport.sample_eps,
|
| 596 |
+
sde=False,
|
| 597 |
+
eval=True,
|
| 598 |
+
reverse=reverse,
|
| 599 |
+
last_step_size=0.0,
|
| 600 |
+
)
|
| 601 |
+
# print(t0, t1)
|
| 602 |
+
_ode = ode(
|
| 603 |
+
drift=drift,
|
| 604 |
+
t0=t0,
|
| 605 |
+
t1=t1,
|
| 606 |
+
sampler_type=sampling_method,
|
| 607 |
+
num_steps=num_steps,
|
| 608 |
+
atol=atol,
|
| 609 |
+
rtol=rtol,
|
| 610 |
+
time_dist_shift=self.transport.time_dist_shift,
|
| 611 |
+
use_time_shift=kwargs.get("use_time_shift", True),
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
return _ode.sample
|
patch-reparameterization/src/stage2/transport/utils.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch as th
|
| 2 |
+
|
| 3 |
+
class EasyDict:
|
| 4 |
+
|
| 5 |
+
def __init__(self, sub_dict):
|
| 6 |
+
for k, v in sub_dict.items():
|
| 7 |
+
setattr(self, k, v)
|
| 8 |
+
|
| 9 |
+
def __getitem__(self, key):
|
| 10 |
+
return getattr(self, key)
|
| 11 |
+
|
| 12 |
+
def mean_flat(x):
|
| 13 |
+
"""
|
| 14 |
+
Take the mean over all non-batch dimensions.
|
| 15 |
+
"""
|
| 16 |
+
return th.mean(x, dim=list(range(1, len(x.size()))))
|
| 17 |
+
|
| 18 |
+
def log_state(state):
|
| 19 |
+
result = []
|
| 20 |
+
|
| 21 |
+
sorted_state = dict(sorted(state.items()))
|
| 22 |
+
for key, value in sorted_state.items():
|
| 23 |
+
# Check if the value is an instance of a class
|
| 24 |
+
if "<object" in str(value) or "object at" in str(value):
|
| 25 |
+
result.append(f"{key}: [{value.__class__.__name__}]")
|
| 26 |
+
else:
|
| 27 |
+
result.append(f"{key}: {value}")
|
| 28 |
+
|
| 29 |
+
return '\n'.join(result)
|
patch-reparameterization/src/stage2/transport/xpred.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def make_xpred_sampler(num_steps: int, sampling_method: str = "euler", t_eps: float = 1e-3):
|
| 11 |
+
"""
|
| 12 |
+
Create a JiT-style x-pred sampler operating in latent space.
|
| 13 |
+
|
| 14 |
+
The underlying model is assumed to output x_pred given (z_t, t, **kwargs),
|
| 15 |
+
and we convert it to a velocity field via:
|
| 16 |
+
|
| 17 |
+
v_pred = (x_pred - z_t) / (1 - t)
|
| 18 |
+
|
| 19 |
+
The returned object has the same callable interface as the existing
|
| 20 |
+
Transport Sampler ODE/SDE samplers:
|
| 21 |
+
|
| 22 |
+
xs = sampler(z_init, model_fn, **model_kwargs)
|
| 23 |
+
final_z = xs[-1]
|
| 24 |
+
"""
|
| 25 |
+
method = sampling_method.lower()
|
| 26 |
+
if method not in ("euler", "heun"):
|
| 27 |
+
raise NotImplementedError(f"x-pred sampler only supports euler/heun, got {sampling_method}")
|
| 28 |
+
|
| 29 |
+
def _forward_velocity(z: torch.Tensor, t: torch.Tensor, model_fn, model_kwargs):
|
| 30 |
+
"""
|
| 31 |
+
Map x-pred model output to velocity:
|
| 32 |
+
|
| 33 |
+
v_pred = (x_pred - z) / (1 - t)
|
| 34 |
+
"""
|
| 35 |
+
bsz = z.size(0)
|
| 36 |
+
if t.ndim == 0:
|
| 37 |
+
t = t.expand(bsz)
|
| 38 |
+
t = t.to(z.device)
|
| 39 |
+
t_view = t.view(bsz, *([1] * (z.ndim - 1)))
|
| 40 |
+
x_pred = model_fn(z, t, **model_kwargs)
|
| 41 |
+
v_pred = (x_pred - z) / (1.0 - t_view).clamp_min(t_eps)
|
| 42 |
+
return v_pred
|
| 43 |
+
|
| 44 |
+
def _sample(init: torch.Tensor, model_fn, **model_kwargs):
|
| 45 |
+
"""
|
| 46 |
+
Fixed-step ODE sampler (Euler / Heun) for JiT-style x-pred velocity.
|
| 47 |
+
|
| 48 |
+
Args:
|
| 49 |
+
init: initial latent z_0
|
| 50 |
+
model_fn: callable taking (z_t, t, **model_kwargs) and returning x_pred
|
| 51 |
+
model_kwargs: passed through to model_fn
|
| 52 |
+
"""
|
| 53 |
+
z = init
|
| 54 |
+
xs = []
|
| 55 |
+
bsz = z.size(0)
|
| 56 |
+
device = z.device
|
| 57 |
+
ts = torch.linspace(0.0, 1.0, num_steps + 1, device=device)
|
| 58 |
+
|
| 59 |
+
for i in range(num_steps):
|
| 60 |
+
t = ts[i].expand(bsz)
|
| 61 |
+
dt = ts[i + 1] - ts[i]
|
| 62 |
+
|
| 63 |
+
if method == "euler":
|
| 64 |
+
v_pred = _forward_velocity(z, t, model_fn, model_kwargs)
|
| 65 |
+
z = z + dt * v_pred
|
| 66 |
+
else: # heun
|
| 67 |
+
v_t = _forward_velocity(z, t, model_fn, model_kwargs)
|
| 68 |
+
z_euler = z + dt * v_t
|
| 69 |
+
t_next = ts[i + 1].expand(bsz)
|
| 70 |
+
v_next = _forward_velocity(z_euler, t_next, model_fn, model_kwargs)
|
| 71 |
+
v_pred = 0.5 * (v_t + v_next)
|
| 72 |
+
z = z + dt * v_pred
|
| 73 |
+
|
| 74 |
+
xs.append(z)
|
| 75 |
+
|
| 76 |
+
return xs
|
| 77 |
+
|
| 78 |
+
return _sample
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def compute_xpred_loss(
|
| 82 |
+
model: nn.Module,
|
| 83 |
+
x: torch.Tensor,
|
| 84 |
+
labels: torch.Tensor,
|
| 85 |
+
*,
|
| 86 |
+
transport=None,
|
| 87 |
+
t_eps: float = 1e-3,
|
| 88 |
+
sample_t_method: str = "transport",
|
| 89 |
+
) -> torch.Tensor:
|
| 90 |
+
"""
|
| 91 |
+
JiT-style x-pred loss in latent space.
|
| 92 |
+
|
| 93 |
+
Given latent "clean" target x, we construct:
|
| 94 |
+
|
| 95 |
+
z_t = t * x + (1 - t) * e
|
| 96 |
+
v = (x - z_t) / (1 - t)
|
| 97 |
+
x̂ = model(z_t, t, y=labels)
|
| 98 |
+
v̂ = (x_hat - z_t) / (1 - t)
|
| 99 |
+
L = E[ ||v - v̂||^2 ]
|
| 100 |
+
"""
|
| 101 |
+
device = x.device
|
| 102 |
+
bsz = x.size(0)
|
| 103 |
+
|
| 104 |
+
# If a Transport object is provided, reuse its time sampler so that
|
| 105 |
+
# x-pred shares the same time distribution (uniform/logit-normal,
|
| 106 |
+
# eps truncation, time_dist_shift, etc.) as the underlying FM setup.
|
| 107 |
+
# Linear plan is parameterized data->noise; JiT is noise->data,
|
| 108 |
+
# so we flip t -> 1 - t to keep the *geometric* end with higher
|
| 109 |
+
# sampling density consistent.
|
| 110 |
+
if sample_t_method == "transport" and transport is not None:
|
| 111 |
+
# transport.sample(x1) -> (t, x0, x1)
|
| 112 |
+
t, _, _ = transport.sample(x)
|
| 113 |
+
t = 1.0 - t
|
| 114 |
+
elif sample_t_method == "normal_sigmoid":
|
| 115 |
+
t = torch.randn(bsz, device=device)*0.8 - 0.8
|
| 116 |
+
t = torch.sigmoid(t)
|
| 117 |
+
else:
|
| 118 |
+
raise NotImplementedError(f"Invalid sample_t_method: {sample_t_method}")
|
| 119 |
+
logger.debug(f"t: {t}")
|
| 120 |
+
t_view = t.view(bsz, *([1] * (x.ndim - 1)))
|
| 121 |
+
e = torch.randn_like(x)
|
| 122 |
+
z_t = t_view * x + (1.0 - t_view) * e
|
| 123 |
+
|
| 124 |
+
v = (x - z_t) / (1.0 - t_view).clamp_min(t_eps)
|
| 125 |
+
x_pred = model(z_t, t, y=labels)
|
| 126 |
+
v_pred = (x_pred - z_t) / (1.0 - t_view).clamp_min(t_eps)
|
| 127 |
+
|
| 128 |
+
loss = (v - v_pred) ** 2
|
| 129 |
+
logger.debug(f"xpred loss: {loss}")
|
| 130 |
+
return loss.mean()
|
| 131 |
+
|
requirements.txt
CHANGED
|
@@ -1,18 +1,15 @@
|
|
| 1 |
-
# UniSpace
|
| 2 |
-
#
|
| 3 |
-
torch
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
omegaconf
|
| 9 |
-
|
| 10 |
-
einops
|
| 11 |
-
|
| 12 |
-
scipy
|
| 13 |
-
scikit-image
|
| 14 |
-
pillow
|
| 15 |
sentencepiece
|
| 16 |
-
PyYAML>=6.0
|
| 17 |
tqdm
|
| 18 |
-
|
|
|
|
|
|
| 1 |
+
# Matches the verified UniSpace environment (unispace/requirements.txt):
|
| 2 |
+
# the repo vendors custom Qwen3 / Qwen3-VL modeling code that tracks
|
| 3 |
+
# transformers 4.57.x internals, so both torch and transformers are pinned.
|
| 4 |
+
torch==2.8.0
|
| 5 |
+
torchvision==0.23.0
|
| 6 |
+
transformers==4.57.3
|
| 7 |
+
accelerate==1.12.0
|
| 8 |
+
omegaconf==2.3.0
|
| 9 |
+
safetensors>=0.4.3
|
| 10 |
+
einops>=0.8.1
|
| 11 |
+
timm>=1.0.13
|
|
|
|
|
|
|
|
|
|
| 12 |
sentencepiece
|
|
|
|
| 13 |
tqdm
|
| 14 |
+
numpy>=1.26,<3
|
| 15 |
+
pillow
|
unispace/LICENSE-APACHE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright 2025 Bytedance Ltd. and/or its affiliates
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 200 |
+
implied. See the License for the specific language governing
|
| 201 |
+
permissions and limitations under the License.
|
unispace/NOTICE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
unispace (unified multimodal model)
|
| 2 |
+
====================================
|
| 3 |
+
|
| 4 |
+
This subdirectory contains code originally extracted and refactored from
|
| 5 |
+
the ByteDance Bagel project (https://github.com/ByteDance-Seed/Bagel).
|
| 6 |
+
|
| 7 |
+
Copyright 2025 Bytedance Ltd. and/or its affiliates.
|
| 8 |
+
|
| 9 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 10 |
+
you may not use this file except in compliance with the License.
|
| 11 |
+
You may obtain a copy of the License at
|
| 12 |
+
|
| 13 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 14 |
+
|
| 15 |
+
Unless required by applicable law or agreed to in writing, software
|
| 16 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 17 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 18 |
+
See the License for the specific language governing permissions and
|
| 19 |
+
limitations under the License.
|
| 20 |
+
|
| 21 |
+
The original Apache-2.0 license and copyright notices in the source
|
| 22 |
+
files (header lines `Copyright 2025 Bytedance Ltd. and/or its affiliates`
|
| 23 |
+
/ `SPDX-License-Identifier: Apache-2.0`) are retained as required by the
|
| 24 |
+
license.
|
| 25 |
+
|
| 26 |
+
Modifications and additions made in this subdirectory, as well as all
|
| 27 |
+
other components of this repository (including the `patch-reparameterization/`
|
| 28 |
+
subdirectory), are licensed under the MIT License — see the repository
|
| 29 |
+
root LICENSE file.
|
unispace/modeling/__init__.py
CHANGED
|
@@ -1 +1,4 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 Bytedance Ltd. and/or its affiliates.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
from . import unimm, siglip, autoencoder
|
unispace/modeling/autoencoder.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2024 Black Forest Labs.
|
| 2 |
+
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates.
|
| 3 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
#
|
| 5 |
+
# This file has been modified by ByteDance Ltd. and/or its affiliates. on 2025-05-20.
|
| 6 |
+
#
|
| 7 |
+
# Original file was released under Apache-2.0, with the full license text
|
| 8 |
+
# available at https://github.com/black-forest-labs/flux/blob/main/LICENSE.
|
| 9 |
+
#
|
| 10 |
+
# This modified file is released under the same license.
|
| 11 |
+
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
from einops import rearrange
|
| 16 |
+
from torch import Tensor, nn
|
| 17 |
+
from safetensors.torch import load_file as load_sft
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class AutoEncoderParams:
|
| 22 |
+
resolution: int
|
| 23 |
+
in_channels: int
|
| 24 |
+
downsample: int
|
| 25 |
+
ch: int
|
| 26 |
+
out_ch: int
|
| 27 |
+
ch_mult: list[int]
|
| 28 |
+
num_res_blocks: int
|
| 29 |
+
z_channels: int
|
| 30 |
+
scale_factor: float
|
| 31 |
+
shift_factor: float
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def swish(x: Tensor) -> Tensor:
|
| 35 |
+
return x * torch.sigmoid(x)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class AttnBlock(nn.Module):
|
| 39 |
+
def __init__(self, in_channels: int):
|
| 40 |
+
super().__init__()
|
| 41 |
+
self.in_channels = in_channels
|
| 42 |
+
|
| 43 |
+
self.norm = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 44 |
+
|
| 45 |
+
self.q = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 46 |
+
self.k = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 47 |
+
self.v = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 48 |
+
self.proj_out = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 49 |
+
|
| 50 |
+
def attention(self, h_: Tensor) -> Tensor:
|
| 51 |
+
h_ = self.norm(h_)
|
| 52 |
+
q = self.q(h_)
|
| 53 |
+
k = self.k(h_)
|
| 54 |
+
v = self.v(h_)
|
| 55 |
+
|
| 56 |
+
b, c, h, w = q.shape
|
| 57 |
+
q = rearrange(q, "b c h w -> b 1 (h w) c").contiguous()
|
| 58 |
+
k = rearrange(k, "b c h w -> b 1 (h w) c").contiguous()
|
| 59 |
+
v = rearrange(v, "b c h w -> b 1 (h w) c").contiguous()
|
| 60 |
+
h_ = nn.functional.scaled_dot_product_attention(q, k, v)
|
| 61 |
+
|
| 62 |
+
return rearrange(h_, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b)
|
| 63 |
+
|
| 64 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 65 |
+
return x + self.proj_out(self.attention(x))
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class ResnetBlock(nn.Module):
|
| 69 |
+
def __init__(self, in_channels: int, out_channels: int):
|
| 70 |
+
super().__init__()
|
| 71 |
+
self.in_channels = in_channels
|
| 72 |
+
out_channels = in_channels if out_channels is None else out_channels
|
| 73 |
+
self.out_channels = out_channels
|
| 74 |
+
|
| 75 |
+
self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 76 |
+
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 77 |
+
self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
|
| 78 |
+
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 79 |
+
if self.in_channels != self.out_channels:
|
| 80 |
+
self.nin_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
| 81 |
+
|
| 82 |
+
def forward(self, x):
|
| 83 |
+
h = x
|
| 84 |
+
h = self.norm1(h)
|
| 85 |
+
h = swish(h)
|
| 86 |
+
h = self.conv1(h)
|
| 87 |
+
|
| 88 |
+
h = self.norm2(h)
|
| 89 |
+
h = swish(h)
|
| 90 |
+
h = self.conv2(h)
|
| 91 |
+
|
| 92 |
+
if self.in_channels != self.out_channels:
|
| 93 |
+
x = self.nin_shortcut(x)
|
| 94 |
+
|
| 95 |
+
return x + h
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class Downsample(nn.Module):
|
| 99 |
+
def __init__(self, in_channels: int):
|
| 100 |
+
super().__init__()
|
| 101 |
+
# no asymmetric padding in torch conv, must do it ourselves
|
| 102 |
+
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0)
|
| 103 |
+
|
| 104 |
+
def forward(self, x: Tensor):
|
| 105 |
+
pad = (0, 1, 0, 1)
|
| 106 |
+
x = nn.functional.pad(x, pad, mode="constant", value=0)
|
| 107 |
+
x = self.conv(x)
|
| 108 |
+
return x
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class Upsample(nn.Module):
|
| 112 |
+
def __init__(self, in_channels: int):
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
|
| 115 |
+
|
| 116 |
+
def forward(self, x: Tensor):
|
| 117 |
+
x = nn.functional.interpolate(x, scale_factor=2.0, mode="nearest")
|
| 118 |
+
x = self.conv(x)
|
| 119 |
+
return x
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
class Encoder(nn.Module):
|
| 123 |
+
def __init__(
|
| 124 |
+
self,
|
| 125 |
+
resolution: int,
|
| 126 |
+
in_channels: int,
|
| 127 |
+
ch: int,
|
| 128 |
+
ch_mult: list[int],
|
| 129 |
+
num_res_blocks: int,
|
| 130 |
+
z_channels: int,
|
| 131 |
+
):
|
| 132 |
+
super().__init__()
|
| 133 |
+
self.ch = ch
|
| 134 |
+
self.num_resolutions = len(ch_mult)
|
| 135 |
+
self.num_res_blocks = num_res_blocks
|
| 136 |
+
self.resolution = resolution
|
| 137 |
+
self.in_channels = in_channels
|
| 138 |
+
# downsampling
|
| 139 |
+
self.conv_in = nn.Conv2d(in_channels, self.ch, kernel_size=3, stride=1, padding=1)
|
| 140 |
+
|
| 141 |
+
curr_res = resolution
|
| 142 |
+
in_ch_mult = (1,) + tuple(ch_mult)
|
| 143 |
+
self.in_ch_mult = in_ch_mult
|
| 144 |
+
self.down = nn.ModuleList()
|
| 145 |
+
block_in = self.ch
|
| 146 |
+
for i_level in range(self.num_resolutions):
|
| 147 |
+
block = nn.ModuleList()
|
| 148 |
+
attn = nn.ModuleList()
|
| 149 |
+
block_in = ch * in_ch_mult[i_level]
|
| 150 |
+
block_out = ch * ch_mult[i_level]
|
| 151 |
+
for _ in range(self.num_res_blocks):
|
| 152 |
+
block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
|
| 153 |
+
block_in = block_out
|
| 154 |
+
down = nn.Module()
|
| 155 |
+
down.block = block
|
| 156 |
+
down.attn = attn
|
| 157 |
+
if i_level != self.num_resolutions - 1:
|
| 158 |
+
down.downsample = Downsample(block_in)
|
| 159 |
+
curr_res = curr_res // 2
|
| 160 |
+
self.down.append(down)
|
| 161 |
+
|
| 162 |
+
# middle
|
| 163 |
+
self.mid = nn.Module()
|
| 164 |
+
self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
|
| 165 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 166 |
+
self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
|
| 167 |
+
|
| 168 |
+
# end
|
| 169 |
+
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
|
| 170 |
+
self.conv_out = nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1)
|
| 171 |
+
|
| 172 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 173 |
+
# downsampling
|
| 174 |
+
hs = [self.conv_in(x)]
|
| 175 |
+
for i_level in range(self.num_resolutions):
|
| 176 |
+
for i_block in range(self.num_res_blocks):
|
| 177 |
+
h = self.down[i_level].block[i_block](hs[-1])
|
| 178 |
+
if len(self.down[i_level].attn) > 0:
|
| 179 |
+
h = self.down[i_level].attn[i_block](h)
|
| 180 |
+
hs.append(h)
|
| 181 |
+
if i_level != self.num_resolutions - 1:
|
| 182 |
+
hs.append(self.down[i_level].downsample(hs[-1]))
|
| 183 |
+
|
| 184 |
+
# middle
|
| 185 |
+
h = hs[-1]
|
| 186 |
+
h = self.mid.block_1(h)
|
| 187 |
+
h = self.mid.attn_1(h)
|
| 188 |
+
h = self.mid.block_2(h)
|
| 189 |
+
# end
|
| 190 |
+
h = self.norm_out(h)
|
| 191 |
+
h = swish(h)
|
| 192 |
+
h = self.conv_out(h)
|
| 193 |
+
return h
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
class Decoder(nn.Module):
|
| 197 |
+
def __init__(
|
| 198 |
+
self,
|
| 199 |
+
ch: int,
|
| 200 |
+
out_ch: int,
|
| 201 |
+
ch_mult: list[int],
|
| 202 |
+
num_res_blocks: int,
|
| 203 |
+
in_channels: int,
|
| 204 |
+
resolution: int,
|
| 205 |
+
z_channels: int,
|
| 206 |
+
):
|
| 207 |
+
super().__init__()
|
| 208 |
+
self.ch = ch
|
| 209 |
+
self.num_resolutions = len(ch_mult)
|
| 210 |
+
self.num_res_blocks = num_res_blocks
|
| 211 |
+
self.resolution = resolution
|
| 212 |
+
self.in_channels = in_channels
|
| 213 |
+
self.ffactor = 2 ** (self.num_resolutions - 1)
|
| 214 |
+
|
| 215 |
+
# compute in_ch_mult, block_in and curr_res at lowest res
|
| 216 |
+
block_in = ch * ch_mult[self.num_resolutions - 1]
|
| 217 |
+
curr_res = resolution // 2 ** (self.num_resolutions - 1)
|
| 218 |
+
self.z_shape = (1, z_channels, curr_res, curr_res)
|
| 219 |
+
|
| 220 |
+
# z to block_in
|
| 221 |
+
self.conv_in = nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1)
|
| 222 |
+
|
| 223 |
+
# middle
|
| 224 |
+
self.mid = nn.Module()
|
| 225 |
+
self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
|
| 226 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 227 |
+
self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
|
| 228 |
+
|
| 229 |
+
# upsampling
|
| 230 |
+
self.up = nn.ModuleList()
|
| 231 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 232 |
+
block = nn.ModuleList()
|
| 233 |
+
attn = nn.ModuleList()
|
| 234 |
+
block_out = ch * ch_mult[i_level]
|
| 235 |
+
for _ in range(self.num_res_blocks + 1):
|
| 236 |
+
block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
|
| 237 |
+
block_in = block_out
|
| 238 |
+
up = nn.Module()
|
| 239 |
+
up.block = block
|
| 240 |
+
up.attn = attn
|
| 241 |
+
if i_level != 0:
|
| 242 |
+
up.upsample = Upsample(block_in)
|
| 243 |
+
curr_res = curr_res * 2
|
| 244 |
+
self.up.insert(0, up) # prepend to get consistent order
|
| 245 |
+
|
| 246 |
+
# end
|
| 247 |
+
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
|
| 248 |
+
self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1)
|
| 249 |
+
|
| 250 |
+
def forward(self, z: Tensor) -> Tensor:
|
| 251 |
+
# z to block_in
|
| 252 |
+
h = self.conv_in(z)
|
| 253 |
+
|
| 254 |
+
# middle
|
| 255 |
+
h = self.mid.block_1(h)
|
| 256 |
+
h = self.mid.attn_1(h)
|
| 257 |
+
h = self.mid.block_2(h)
|
| 258 |
+
|
| 259 |
+
# upsampling
|
| 260 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 261 |
+
for i_block in range(self.num_res_blocks + 1):
|
| 262 |
+
h = self.up[i_level].block[i_block](h)
|
| 263 |
+
if len(self.up[i_level].attn) > 0:
|
| 264 |
+
h = self.up[i_level].attn[i_block](h)
|
| 265 |
+
if i_level != 0:
|
| 266 |
+
h = self.up[i_level].upsample(h)
|
| 267 |
+
|
| 268 |
+
# end
|
| 269 |
+
h = self.norm_out(h)
|
| 270 |
+
h = swish(h)
|
| 271 |
+
h = self.conv_out(h)
|
| 272 |
+
return h
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
class DiagonalGaussian(nn.Module):
|
| 276 |
+
def __init__(self, sample: bool = True, chunk_dim: int = 1):
|
| 277 |
+
super().__init__()
|
| 278 |
+
self.sample = sample
|
| 279 |
+
self.chunk_dim = chunk_dim
|
| 280 |
+
|
| 281 |
+
def forward(self, z: Tensor) -> Tensor:
|
| 282 |
+
mean, logvar = torch.chunk(z, 2, dim=self.chunk_dim)
|
| 283 |
+
if self.sample:
|
| 284 |
+
std = torch.exp(0.5 * logvar)
|
| 285 |
+
return mean + std * torch.randn_like(mean)
|
| 286 |
+
else:
|
| 287 |
+
return mean
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
class AutoEncoder(nn.Module):
|
| 291 |
+
def __init__(self, params: AutoEncoderParams):
|
| 292 |
+
super().__init__()
|
| 293 |
+
self.encoder = Encoder(
|
| 294 |
+
resolution=params.resolution,
|
| 295 |
+
in_channels=params.in_channels,
|
| 296 |
+
ch=params.ch,
|
| 297 |
+
ch_mult=params.ch_mult,
|
| 298 |
+
num_res_blocks=params.num_res_blocks,
|
| 299 |
+
z_channels=params.z_channels,
|
| 300 |
+
)
|
| 301 |
+
self.decoder = Decoder(
|
| 302 |
+
resolution=params.resolution,
|
| 303 |
+
in_channels=params.in_channels,
|
| 304 |
+
ch=params.ch,
|
| 305 |
+
out_ch=params.out_ch,
|
| 306 |
+
ch_mult=params.ch_mult,
|
| 307 |
+
num_res_blocks=params.num_res_blocks,
|
| 308 |
+
z_channels=params.z_channels,
|
| 309 |
+
)
|
| 310 |
+
self.reg = DiagonalGaussian()
|
| 311 |
+
|
| 312 |
+
self.scale_factor = params.scale_factor
|
| 313 |
+
self.shift_factor = params.shift_factor
|
| 314 |
+
|
| 315 |
+
def encode(self, x: Tensor) -> Tensor:
|
| 316 |
+
z = self.reg(self.encoder(x))
|
| 317 |
+
z = self.scale_factor * (z - self.shift_factor)
|
| 318 |
+
return z
|
| 319 |
+
|
| 320 |
+
def decode(self, z: Tensor) -> Tensor:
|
| 321 |
+
z = z / self.scale_factor + self.shift_factor
|
| 322 |
+
return self.decoder(z)
|
| 323 |
+
|
| 324 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 325 |
+
return self.decode(self.encode(x))
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def print_load_warning(missing: list[str], unexpected: list[str]) -> None:
|
| 329 |
+
if len(missing) > 0 and len(unexpected) > 0:
|
| 330 |
+
print(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))
|
| 331 |
+
print("\n" + "-" * 79 + "\n")
|
| 332 |
+
print(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))
|
| 333 |
+
elif len(missing) > 0:
|
| 334 |
+
print(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))
|
| 335 |
+
elif len(unexpected) > 0:
|
| 336 |
+
print(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def load_ae(local_path: str, vae_type: str = "flux1-vae", rae_config_path: str = None, **kwargs):
|
| 340 |
+
"""
|
| 341 |
+
Load VAE model.
|
| 342 |
+
|
| 343 |
+
Args:
|
| 344 |
+
local_path: Path to the VAE checkpoint file or directory.
|
| 345 |
+
vae_type: 'flux1-vae' (z=16), 'flux2-vae' (z=32), 'rae' (z=768),
|
| 346 |
+
or 'qwen3_unified' (Qwen3Unified encoder, z=1280).
|
| 347 |
+
rae_config_path: Path to RAE config yaml (used when vae_type='rae' or 'qwen3_unified').
|
| 348 |
+
|
| 349 |
+
Returns:
|
| 350 |
+
(model, config) tuple. Config has .z_channels and .downsample attributes.
|
| 351 |
+
"""
|
| 352 |
+
if vae_type == "flux2-vae":
|
| 353 |
+
from modeling.autoencoder_flux2 import load_flux2_ae
|
| 354 |
+
return load_flux2_ae(local_path)
|
| 355 |
+
|
| 356 |
+
if vae_type in ("rae", "qwen3_unified"):
|
| 357 |
+
from modeling.autoencoder_rae import load_rae
|
| 358 |
+
return load_rae(local_path, rae_config_path=rae_config_path,
|
| 359 |
+
rae_accelerate_root=kwargs.get('rae_accelerate_root', None),
|
| 360 |
+
encoder_only=kwargs.get('encoder_only', False))
|
| 361 |
+
|
| 362 |
+
ae_params = AutoEncoderParams(
|
| 363 |
+
resolution=256,
|
| 364 |
+
in_channels=3,
|
| 365 |
+
downsample=8,
|
| 366 |
+
ch=128,
|
| 367 |
+
out_ch=3,
|
| 368 |
+
ch_mult=[1, 2, 4, 4],
|
| 369 |
+
num_res_blocks=2,
|
| 370 |
+
z_channels=16,
|
| 371 |
+
scale_factor=0.3611,
|
| 372 |
+
shift_factor=0.1159,
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
# Loading the autoencoder
|
| 376 |
+
ae = AutoEncoder(ae_params)
|
| 377 |
+
|
| 378 |
+
if local_path is not None:
|
| 379 |
+
sd = load_sft(local_path)
|
| 380 |
+
missing, unexpected = ae.load_state_dict(sd, strict=False, assign=True)
|
| 381 |
+
print_load_warning(missing, unexpected)
|
| 382 |
+
return ae, ae_params
|
unispace/modeling/autoencoder_flux2.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The HuggingFace Team. All rights reserved.
|
| 2 |
+
# Adapted from diffusers 0.36 AutoencoderKLFlux2, standalone implementation.
|
| 3 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
#
|
| 5 |
+
# Self-contained Flux2 VAE implementation that loads official BFL weights
|
| 6 |
+
# (diffusion_pytorch_model.safetensors) without requiring diffusers >= 0.36.
|
| 7 |
+
# Key names match diffusers convention exactly.
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import math
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
import torch.nn.functional as F
|
| 17 |
+
from safetensors.torch import load_file as load_sft
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ---------------------------------------------------------------------------
|
| 21 |
+
# Config dataclass (mirrors AutoEncoderParams from autoencoder.py for Flux1)
|
| 22 |
+
# ---------------------------------------------------------------------------
|
| 23 |
+
@dataclass
|
| 24 |
+
class Flux2VAEConfig:
|
| 25 |
+
in_channels: int = 3
|
| 26 |
+
out_channels: int = 3
|
| 27 |
+
block_out_channels: tuple = (128, 256, 512, 512)
|
| 28 |
+
layers_per_block: int = 2
|
| 29 |
+
latent_channels: int = 32
|
| 30 |
+
norm_num_groups: int = 32
|
| 31 |
+
downsample: int = 8 # 2^(num_down_blocks - 1) = 2^3 = 8
|
| 32 |
+
z_channels: int = 32 # alias for latent_channels, compat with Flux1 config
|
| 33 |
+
batch_norm_eps: float = 1e-4
|
| 34 |
+
batch_norm_momentum: float = 0.1
|
| 35 |
+
patch_size: tuple = (2, 2)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ---------------------------------------------------------------------------
|
| 39 |
+
# Building blocks
|
| 40 |
+
# ---------------------------------------------------------------------------
|
| 41 |
+
class ResnetBlock2D(nn.Module):
|
| 42 |
+
"""Standard ResNet block: GroupNorm -> SiLU -> Conv -> GroupNorm -> SiLU -> Conv + shortcut."""
|
| 43 |
+
|
| 44 |
+
def __init__(self, in_channels: int, out_channels: int, groups: int = 32):
|
| 45 |
+
super().__init__()
|
| 46 |
+
self.norm1 = nn.GroupNorm(groups, in_channels, eps=1e-6)
|
| 47 |
+
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1)
|
| 48 |
+
self.norm2 = nn.GroupNorm(groups, out_channels, eps=1e-6)
|
| 49 |
+
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
|
| 50 |
+
self.nonlinearity = nn.SiLU()
|
| 51 |
+
if in_channels != out_channels:
|
| 52 |
+
self.conv_shortcut = nn.Conv2d(in_channels, out_channels, 1)
|
| 53 |
+
else:
|
| 54 |
+
self.conv_shortcut = None
|
| 55 |
+
|
| 56 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 57 |
+
h = self.norm1(x)
|
| 58 |
+
h = self.nonlinearity(h)
|
| 59 |
+
h = self.conv1(h)
|
| 60 |
+
h = self.norm2(h)
|
| 61 |
+
h = self.nonlinearity(h)
|
| 62 |
+
h = self.conv2(h)
|
| 63 |
+
if self.conv_shortcut is not None:
|
| 64 |
+
x = self.conv_shortcut(x)
|
| 65 |
+
return x + h
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class AttnBlock(nn.Module):
|
| 69 |
+
"""Self-attention block matching diffusers Attention with _from_deprecated_attn_block=True."""
|
| 70 |
+
|
| 71 |
+
def __init__(self, channels: int, num_groups: int = 32):
|
| 72 |
+
super().__init__()
|
| 73 |
+
self.group_norm = nn.GroupNorm(num_groups, channels, eps=1e-6)
|
| 74 |
+
self.to_q = nn.Linear(channels, channels)
|
| 75 |
+
self.to_k = nn.Linear(channels, channels)
|
| 76 |
+
self.to_v = nn.Linear(channels, channels)
|
| 77 |
+
self.to_out = nn.ModuleList([nn.Linear(channels, channels)])
|
| 78 |
+
|
| 79 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 80 |
+
residual = x
|
| 81 |
+
b, c, h, w = x.shape
|
| 82 |
+
x = self.group_norm(x)
|
| 83 |
+
x = x.reshape(b, c, h * w).permute(0, 2, 1) # (B, HW, C)
|
| 84 |
+
|
| 85 |
+
q = self.to_q(x)
|
| 86 |
+
k = self.to_k(x)
|
| 87 |
+
v = self.to_v(x)
|
| 88 |
+
|
| 89 |
+
# scaled_dot_product_attention
|
| 90 |
+
x = F.scaled_dot_product_attention(q, k, v)
|
| 91 |
+
x = self.to_out[0](x)
|
| 92 |
+
|
| 93 |
+
x = x.permute(0, 2, 1).reshape(b, c, h, w)
|
| 94 |
+
return x + residual
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class Downsample2D(nn.Module):
|
| 98 |
+
"""Spatial downsample with padding + stride-2 conv."""
|
| 99 |
+
|
| 100 |
+
def __init__(self, channels: int):
|
| 101 |
+
super().__init__()
|
| 102 |
+
self.conv = nn.Conv2d(channels, channels, 3, stride=2, padding=0)
|
| 103 |
+
|
| 104 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 105 |
+
x = F.pad(x, (0, 1, 0, 1), mode="constant", value=0)
|
| 106 |
+
return self.conv(x)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class Upsample2D(nn.Module):
|
| 110 |
+
"""Spatial upsample with nearest interpolation + conv."""
|
| 111 |
+
|
| 112 |
+
def __init__(self, channels: int):
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.conv = nn.Conv2d(channels, channels, 3, padding=1)
|
| 115 |
+
|
| 116 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 117 |
+
x = F.interpolate(x, scale_factor=2.0, mode="nearest")
|
| 118 |
+
return self.conv(x)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
# ---------------------------------------------------------------------------
|
| 122 |
+
# Encoder / Decoder blocks (key names: down_blocks.{i}.resnets.{j} / downsamplers.0)
|
| 123 |
+
# ---------------------------------------------------------------------------
|
| 124 |
+
class DownEncoderBlock2D(nn.Module):
|
| 125 |
+
def __init__(self, in_ch: int, out_ch: int, num_layers: int = 2,
|
| 126 |
+
add_downsample: bool = True, groups: int = 32):
|
| 127 |
+
super().__init__()
|
| 128 |
+
self.resnets = nn.ModuleList()
|
| 129 |
+
for i in range(num_layers):
|
| 130 |
+
self.resnets.append(ResnetBlock2D(in_ch if i == 0 else out_ch, out_ch, groups))
|
| 131 |
+
self.downsamplers = nn.ModuleList()
|
| 132 |
+
if add_downsample:
|
| 133 |
+
self.downsamplers.append(Downsample2D(out_ch))
|
| 134 |
+
|
| 135 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 136 |
+
for resnet in self.resnets:
|
| 137 |
+
x = resnet(x)
|
| 138 |
+
for ds in self.downsamplers:
|
| 139 |
+
x = ds(x)
|
| 140 |
+
return x
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class UpDecoderBlock2D(nn.Module):
|
| 144 |
+
def __init__(self, in_ch: int, out_ch: int, num_layers: int = 3,
|
| 145 |
+
add_upsample: bool = True, groups: int = 32):
|
| 146 |
+
super().__init__()
|
| 147 |
+
self.resnets = nn.ModuleList()
|
| 148 |
+
for i in range(num_layers):
|
| 149 |
+
self.resnets.append(ResnetBlock2D(in_ch if i == 0 else out_ch, out_ch, groups))
|
| 150 |
+
self.upsamplers = nn.ModuleList()
|
| 151 |
+
if add_upsample:
|
| 152 |
+
self.upsamplers.append(Upsample2D(out_ch))
|
| 153 |
+
|
| 154 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 155 |
+
for resnet in self.resnets:
|
| 156 |
+
x = resnet(x)
|
| 157 |
+
for us in self.upsamplers:
|
| 158 |
+
x = us(x)
|
| 159 |
+
return x
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class MidBlock(nn.Module):
|
| 163 |
+
"""Mid block: resnet -> attention -> resnet."""
|
| 164 |
+
|
| 165 |
+
def __init__(self, channels: int, groups: int = 32):
|
| 166 |
+
super().__init__()
|
| 167 |
+
self.resnets = nn.ModuleList([
|
| 168 |
+
ResnetBlock2D(channels, channels, groups),
|
| 169 |
+
ResnetBlock2D(channels, channels, groups),
|
| 170 |
+
])
|
| 171 |
+
self.attentions = nn.ModuleList([AttnBlock(channels, groups)])
|
| 172 |
+
|
| 173 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 174 |
+
x = self.resnets[0](x)
|
| 175 |
+
x = self.attentions[0](x)
|
| 176 |
+
x = self.resnets[1](x)
|
| 177 |
+
return x
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
# ---------------------------------------------------------------------------
|
| 181 |
+
# Encoder & Decoder
|
| 182 |
+
# ---------------------------------------------------------------------------
|
| 183 |
+
class Flux2Encoder(nn.Module):
|
| 184 |
+
def __init__(self, cfg: Flux2VAEConfig):
|
| 185 |
+
super().__init__()
|
| 186 |
+
ch = cfg.block_out_channels
|
| 187 |
+
self.conv_in = nn.Conv2d(cfg.in_channels, ch[0], 3, padding=1)
|
| 188 |
+
|
| 189 |
+
self.down_blocks = nn.ModuleList()
|
| 190 |
+
in_c = ch[0]
|
| 191 |
+
for i, out_c in enumerate(ch):
|
| 192 |
+
is_last = (i == len(ch) - 1)
|
| 193 |
+
self.down_blocks.append(
|
| 194 |
+
DownEncoderBlock2D(in_c, out_c, cfg.layers_per_block,
|
| 195 |
+
add_downsample=not is_last, groups=cfg.norm_num_groups)
|
| 196 |
+
)
|
| 197 |
+
in_c = out_c
|
| 198 |
+
|
| 199 |
+
self.mid_block = MidBlock(ch[-1], cfg.norm_num_groups)
|
| 200 |
+
|
| 201 |
+
self.conv_norm_out = nn.GroupNorm(cfg.norm_num_groups, ch[-1], eps=1e-6)
|
| 202 |
+
self.conv_act = nn.SiLU()
|
| 203 |
+
self.conv_out = nn.Conv2d(ch[-1], 2 * cfg.latent_channels, 3, padding=1)
|
| 204 |
+
|
| 205 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 206 |
+
x = self.conv_in(x)
|
| 207 |
+
for block in self.down_blocks:
|
| 208 |
+
x = block(x)
|
| 209 |
+
x = self.mid_block(x)
|
| 210 |
+
x = self.conv_norm_out(x)
|
| 211 |
+
x = self.conv_act(x)
|
| 212 |
+
x = self.conv_out(x)
|
| 213 |
+
return x
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class Flux2Decoder(nn.Module):
|
| 217 |
+
def __init__(self, cfg: Flux2VAEConfig):
|
| 218 |
+
super().__init__()
|
| 219 |
+
ch = cfg.block_out_channels
|
| 220 |
+
rev_ch = list(reversed(ch))
|
| 221 |
+
|
| 222 |
+
self.conv_in = nn.Conv2d(cfg.latent_channels, ch[-1], 3, padding=1)
|
| 223 |
+
self.mid_block = MidBlock(ch[-1], cfg.norm_num_groups)
|
| 224 |
+
|
| 225 |
+
self.up_blocks = nn.ModuleList()
|
| 226 |
+
in_c = rev_ch[0]
|
| 227 |
+
for i, out_c in enumerate(rev_ch):
|
| 228 |
+
is_last = (i == len(rev_ch) - 1)
|
| 229 |
+
# Decoder has layers_per_block + 1 resnets per block
|
| 230 |
+
self.up_blocks.append(
|
| 231 |
+
UpDecoderBlock2D(in_c, out_c, cfg.layers_per_block + 1,
|
| 232 |
+
add_upsample=not is_last, groups=cfg.norm_num_groups)
|
| 233 |
+
)
|
| 234 |
+
in_c = out_c
|
| 235 |
+
|
| 236 |
+
self.conv_norm_out = nn.GroupNorm(cfg.norm_num_groups, ch[0], eps=1e-6)
|
| 237 |
+
self.conv_act = nn.SiLU()
|
| 238 |
+
self.conv_out = nn.Conv2d(ch[0], cfg.out_channels, 3, padding=1)
|
| 239 |
+
|
| 240 |
+
def forward(self, z: torch.Tensor) -> torch.Tensor:
|
| 241 |
+
z = self.conv_in(z)
|
| 242 |
+
z = self.mid_block(z)
|
| 243 |
+
for block in self.up_blocks:
|
| 244 |
+
z = block(z)
|
| 245 |
+
z = self.conv_norm_out(z)
|
| 246 |
+
z = self.conv_act(z)
|
| 247 |
+
z = self.conv_out(z)
|
| 248 |
+
return z
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# ---------------------------------------------------------------------------
|
| 252 |
+
# DiagonalGaussian (sample or mode)
|
| 253 |
+
# ---------------------------------------------------------------------------
|
| 254 |
+
class DiagonalGaussian(nn.Module):
|
| 255 |
+
def __init__(self, sample: bool = True):
|
| 256 |
+
super().__init__()
|
| 257 |
+
self.sample = sample
|
| 258 |
+
|
| 259 |
+
def forward(self, z: torch.Tensor) -> torch.Tensor:
|
| 260 |
+
mean, logvar = torch.chunk(z, 2, dim=1)
|
| 261 |
+
logvar = torch.clamp(logvar, -30.0, 20.0)
|
| 262 |
+
if self.sample:
|
| 263 |
+
std = torch.exp(0.5 * logvar)
|
| 264 |
+
return mean + std * torch.randn_like(mean)
|
| 265 |
+
return mean
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
# ---------------------------------------------------------------------------
|
| 269 |
+
# Main model: AutoEncoderFlux2
|
| 270 |
+
# ---------------------------------------------------------------------------
|
| 271 |
+
class AutoEncoderFlux2(nn.Module):
|
| 272 |
+
"""
|
| 273 |
+
Standalone Flux2 VAE (AutoencoderKLFlux2) implementation.
|
| 274 |
+
|
| 275 |
+
Key differences from Flux1 VAE (autoencoder.py):
|
| 276 |
+
- latent_channels = 32 (vs 16)
|
| 277 |
+
- Has quant_conv / post_quant_conv layers
|
| 278 |
+
- Uses BatchNorm-based normalization instead of scale_factor/shift_factor
|
| 279 |
+
- Architecture follows diffusers standard (DownEncoderBlock2D / UpDecoderBlock2D)
|
| 280 |
+
"""
|
| 281 |
+
|
| 282 |
+
def __init__(self, config: Flux2VAEConfig | None = None):
|
| 283 |
+
super().__init__()
|
| 284 |
+
if config is None:
|
| 285 |
+
config = Flux2VAEConfig()
|
| 286 |
+
self.config = config
|
| 287 |
+
|
| 288 |
+
self.encoder = Flux2Encoder(config)
|
| 289 |
+
self.decoder = Flux2Decoder(config)
|
| 290 |
+
self.quant_conv = nn.Conv2d(2 * config.latent_channels, 2 * config.latent_channels, 1)
|
| 291 |
+
self.post_quant_conv = nn.Conv2d(config.latent_channels, config.latent_channels, 1)
|
| 292 |
+
self.reg = DiagonalGaussian(sample=False) # default: mode (deterministic)
|
| 293 |
+
|
| 294 |
+
# BatchNorm for latent normalization (patchified space)
|
| 295 |
+
bn_channels = math.prod(config.patch_size) * config.latent_channels # 2*2*32=128
|
| 296 |
+
self.bn = nn.BatchNorm2d(
|
| 297 |
+
bn_channels,
|
| 298 |
+
eps=config.batch_norm_eps,
|
| 299 |
+
momentum=config.batch_norm_momentum,
|
| 300 |
+
affine=False,
|
| 301 |
+
track_running_stats=True,
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
def encode(self, x: torch.Tensor) -> torch.Tensor:
|
| 305 |
+
"""Encode image to normalized latent (BN in patchified space, then un-patchify)."""
|
| 306 |
+
h = self.encoder(x)
|
| 307 |
+
h = self.quant_conv(h)
|
| 308 |
+
z = self.reg(h) # (B, C=32, H, W)
|
| 309 |
+
|
| 310 |
+
# Patchify → BN normalize → un-patchify to keep API compatible with Flux1
|
| 311 |
+
B, C, H, W = z.shape
|
| 312 |
+
pH, pW = self.config.patch_size # (2, 2)
|
| 313 |
+
# (B, C, H, W) → (B, C*pH*pW, H//pH, W//pW)
|
| 314 |
+
z_p = z.reshape(B, C, H // pH, pH, W // pW, pW)
|
| 315 |
+
z_p = z_p.permute(0, 1, 3, 5, 2, 4).reshape(B, C * pH * pW, H // pH, W // pW)
|
| 316 |
+
# BN normalize
|
| 317 |
+
bn_mean = self.bn.running_mean.view(1, -1, 1, 1).to(z_p.device, z_p.dtype)
|
| 318 |
+
bn_std = torch.sqrt(
|
| 319 |
+
self.bn.running_var.view(1, -1, 1, 1).to(z_p.device, z_p.dtype)
|
| 320 |
+
+ self.config.batch_norm_eps
|
| 321 |
+
)
|
| 322 |
+
z_p = (z_p - bn_mean) / bn_std
|
| 323 |
+
# Un-patchify: (B, C*pH*pW, H', W') → (B, C, H, W)
|
| 324 |
+
z = z_p.reshape(B, C, pH, pW, H // pH, W // pW)
|
| 325 |
+
z = z.permute(0, 1, 4, 2, 5, 3).reshape(B, C, H, W)
|
| 326 |
+
return z
|
| 327 |
+
|
| 328 |
+
def decode(self, z: torch.Tensor) -> torch.Tensor:
|
| 329 |
+
"""Decode normalized latent to image (symmetric with encode).
|
| 330 |
+
|
| 331 |
+
Pipeline: patchify → BN denormalize → unpatchify → post_quant_conv → decoder.
|
| 332 |
+
This mirrors the encode path which does: encoder → patchify → BN normalize → unpatchify.
|
| 333 |
+
"""
|
| 334 |
+
B, C, H, W = z.shape
|
| 335 |
+
pH, pW = self.config.patch_size # (2, 2)
|
| 336 |
+
|
| 337 |
+
# Patchify: (B, C, H, W) → (B, C*pH*pW, H//pH, W//pW)
|
| 338 |
+
z_p = z.reshape(B, C, H // pH, pH, W // pW, pW)
|
| 339 |
+
z_p = z_p.permute(0, 1, 3, 5, 2, 4).reshape(B, C * pH * pW, H // pH, W // pW)
|
| 340 |
+
|
| 341 |
+
# BN denormalize
|
| 342 |
+
bn_mean = self.bn.running_mean.view(1, -1, 1, 1).to(z_p.device, z_p.dtype)
|
| 343 |
+
bn_std = torch.sqrt(
|
| 344 |
+
self.bn.running_var.view(1, -1, 1, 1).to(z_p.device, z_p.dtype)
|
| 345 |
+
+ self.config.batch_norm_eps
|
| 346 |
+
)
|
| 347 |
+
z_p = z_p * bn_std + bn_mean
|
| 348 |
+
|
| 349 |
+
# Un-patchify: (B, C*pH*pW, H', W') → (B, C, H, W)
|
| 350 |
+
z = z_p.reshape(B, C, pH, pW, H // pH, W // pW)
|
| 351 |
+
z = z.permute(0, 1, 4, 2, 5, 3).reshape(B, C, H, W)
|
| 352 |
+
|
| 353 |
+
z = self.post_quant_conv(z)
|
| 354 |
+
return self.decoder(z)
|
| 355 |
+
|
| 356 |
+
def normalize_latents(self, latents: torch.Tensor) -> torch.Tensor:
|
| 357 |
+
"""Apply BN normalization to patchified latents (as in Flux2Pipeline)."""
|
| 358 |
+
bn_mean = self.bn.running_mean.view(1, -1, 1, 1).to(latents.device, latents.dtype)
|
| 359 |
+
bn_std = torch.sqrt(
|
| 360 |
+
self.bn.running_var.view(1, -1, 1, 1).to(latents.device, latents.dtype)
|
| 361 |
+
+ self.config.batch_norm_eps
|
| 362 |
+
)
|
| 363 |
+
return (latents - bn_mean) / bn_std
|
| 364 |
+
|
| 365 |
+
def denormalize_latents(self, latents: torch.Tensor) -> torch.Tensor:
|
| 366 |
+
"""Reverse BN normalization for decoding."""
|
| 367 |
+
bn_mean = self.bn.running_mean.view(1, -1, 1, 1).to(latents.device, latents.dtype)
|
| 368 |
+
bn_std = torch.sqrt(
|
| 369 |
+
self.bn.running_var.view(1, -1, 1, 1).to(latents.device, latents.dtype)
|
| 370 |
+
+ self.config.batch_norm_eps
|
| 371 |
+
)
|
| 372 |
+
return latents * bn_std + bn_mean
|
| 373 |
+
|
| 374 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 375 |
+
return self.decode(self.encode(x))
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
# ---------------------------------------------------------------------------
|
| 379 |
+
# Loading helper
|
| 380 |
+
# ---------------------------------------------------------------------------
|
| 381 |
+
def _print_load_warning(missing: list[str], unexpected: list[str]) -> None:
|
| 382 |
+
if missing:
|
| 383 |
+
print(f"[Flux2-VAE] {len(missing)} missing keys:\n\t" + "\n\t".join(missing[:20]))
|
| 384 |
+
if unexpected:
|
| 385 |
+
print(f"[Flux2-VAE] {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected[:20]))
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def load_flux2_ae(local_path: str) -> tuple[AutoEncoderFlux2, Flux2VAEConfig]:
|
| 389 |
+
"""
|
| 390 |
+
Load Flux2 VAE from a safetensors checkpoint directory or file.
|
| 391 |
+
|
| 392 |
+
Args:
|
| 393 |
+
local_path: Path to the safetensors file, or directory containing
|
| 394 |
+
diffusion_pytorch_model.safetensors.
|
| 395 |
+
|
| 396 |
+
Returns:
|
| 397 |
+
(model, config) tuple.
|
| 398 |
+
"""
|
| 399 |
+
import os
|
| 400 |
+
if os.path.isdir(local_path):
|
| 401 |
+
sf_path = os.path.join(local_path, "diffusion_pytorch_model.safetensors")
|
| 402 |
+
else:
|
| 403 |
+
sf_path = local_path
|
| 404 |
+
|
| 405 |
+
config = Flux2VAEConfig()
|
| 406 |
+
model = AutoEncoderFlux2(config)
|
| 407 |
+
|
| 408 |
+
sd = load_sft(sf_path)
|
| 409 |
+
missing, unexpected = model.load_state_dict(sd, strict=False, assign=True)
|
| 410 |
+
_print_load_warning(missing, unexpected)
|
| 411 |
+
|
| 412 |
+
return model, config
|
unispace/modeling/siglip/__init__.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The HuggingFace Inc. team.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
from typing import TYPE_CHECKING
|
| 5 |
+
|
| 6 |
+
from transformers.utils import (
|
| 7 |
+
OptionalDependencyNotAvailable,
|
| 8 |
+
_LazyModule,
|
| 9 |
+
is_sentencepiece_available,
|
| 10 |
+
is_torch_available,
|
| 11 |
+
is_vision_available,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
_import_structure = {
|
| 16 |
+
"configuration_siglip": [
|
| 17 |
+
"SiglipConfig",
|
| 18 |
+
"SiglipTextConfig",
|
| 19 |
+
"SiglipVisionConfig",
|
| 20 |
+
],
|
| 21 |
+
"processing_siglip": ["SiglipProcessor"],
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
if not is_sentencepiece_available():
|
| 26 |
+
raise OptionalDependencyNotAvailable()
|
| 27 |
+
except OptionalDependencyNotAvailable:
|
| 28 |
+
pass
|
| 29 |
+
else:
|
| 30 |
+
_import_structure["tokenization_siglip"] = ["SiglipTokenizer"]
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
if not is_vision_available():
|
| 35 |
+
raise OptionalDependencyNotAvailable()
|
| 36 |
+
except OptionalDependencyNotAvailable:
|
| 37 |
+
pass
|
| 38 |
+
else:
|
| 39 |
+
_import_structure["image_processing_siglip"] = ["SiglipImageProcessor"]
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
if not is_torch_available():
|
| 43 |
+
raise OptionalDependencyNotAvailable()
|
| 44 |
+
except OptionalDependencyNotAvailable:
|
| 45 |
+
pass
|
| 46 |
+
else:
|
| 47 |
+
_import_structure["modeling_siglip"] = [
|
| 48 |
+
"SiglipModel",
|
| 49 |
+
"SiglipPreTrainedModel",
|
| 50 |
+
"SiglipTextModel",
|
| 51 |
+
"SiglipVisionModel",
|
| 52 |
+
"SiglipForImageClassification",
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if TYPE_CHECKING:
|
| 57 |
+
from .configuration_siglip import (
|
| 58 |
+
SiglipConfig,
|
| 59 |
+
SiglipTextConfig,
|
| 60 |
+
SiglipVisionConfig,
|
| 61 |
+
)
|
| 62 |
+
from .processing_siglip import SiglipProcessor
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
if not is_sentencepiece_available():
|
| 66 |
+
raise OptionalDependencyNotAvailable()
|
| 67 |
+
except OptionalDependencyNotAvailable:
|
| 68 |
+
pass
|
| 69 |
+
else:
|
| 70 |
+
from .tokenization_siglip import SiglipTokenizer
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
if not is_vision_available():
|
| 74 |
+
raise OptionalDependencyNotAvailable()
|
| 75 |
+
except OptionalDependencyNotAvailable:
|
| 76 |
+
pass
|
| 77 |
+
else:
|
| 78 |
+
from .image_processing_siglip import SiglipImageProcessor
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
if not is_torch_available():
|
| 82 |
+
raise OptionalDependencyNotAvailable()
|
| 83 |
+
except OptionalDependencyNotAvailable:
|
| 84 |
+
pass
|
| 85 |
+
else:
|
| 86 |
+
from .modeling_siglip import (
|
| 87 |
+
SiglipForImageClassification,
|
| 88 |
+
SiglipModel,
|
| 89 |
+
SiglipPreTrainedModel,
|
| 90 |
+
SiglipTextModel,
|
| 91 |
+
SiglipVisionModel,
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
else:
|
| 96 |
+
import sys
|
| 97 |
+
|
| 98 |
+
sys.modules[__name__] = _LazyModule(__name__, globals()["__file__"], _import_structure, module_spec=__spec__)
|
unispace/modeling/siglip/configuration_siglip.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The HuggingFace Inc. team.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""Siglip model configuration"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from typing import Union
|
| 8 |
+
|
| 9 |
+
from transformers.configuration_utils import PretrainedConfig
|
| 10 |
+
from transformers.utils import logging
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
logger = logging.get_logger(__name__)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class SiglipTextConfig(PretrainedConfig):
|
| 17 |
+
r"""
|
| 18 |
+
This is the configuration class to store the configuration of a [`SiglipTextModel`]. It is used to instantiate a
|
| 19 |
+
Siglip text encoder according to the specified arguments, defining the model architecture. Instantiating a
|
| 20 |
+
configuration with the defaults will yield a similar configuration to that of the text encoder of the Siglip
|
| 21 |
+
[google/siglip-base-patch16-224](https://huggingface.co/google/siglip-base-patch16-224) architecture.
|
| 22 |
+
|
| 23 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 24 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
vocab_size (`int`, *optional*, defaults to 32000):
|
| 28 |
+
Vocabulary size of the Siglip text model. Defines the number of different tokens that can be represented by
|
| 29 |
+
the `inputs_ids` passed when calling [`SiglipModel`].
|
| 30 |
+
hidden_size (`int`, *optional*, defaults to 768):
|
| 31 |
+
Dimensionality of the encoder layers and the pooler layer.
|
| 32 |
+
intermediate_size (`int`, *optional*, defaults to 3072):
|
| 33 |
+
Dimensionality of the "intermediate" (i.e., feed-forward) layer in the Transformer encoder.
|
| 34 |
+
num_hidden_layers (`int`, *optional*, defaults to 12):
|
| 35 |
+
Number of hidden layers in the Transformer encoder.
|
| 36 |
+
num_attention_heads (`int`, *optional*, defaults to 12):
|
| 37 |
+
Number of attention heads for each attention layer in the Transformer encoder.
|
| 38 |
+
max_position_embeddings (`int`, *optional*, defaults to 64):
|
| 39 |
+
The maximum sequence length that this model might ever be used with. Typically set this to something large
|
| 40 |
+
just in case (e.g., 512 or 1024 or 2048).
|
| 41 |
+
hidden_act (`str` or `function`, *optional*, defaults to `"gelu_pytorch_tanh"`):
|
| 42 |
+
The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
|
| 43 |
+
`"relu"`, `"selu"` and `"gelu_new"` `"quick_gelu"` are supported.
|
| 44 |
+
layer_norm_eps (`float`, *optional*, defaults to 1e-06):
|
| 45 |
+
The epsilon used by the layer normalization layers.
|
| 46 |
+
attention_dropout (`float`, *optional*, defaults to 0.0):
|
| 47 |
+
The dropout ratio for the attention probabilities.
|
| 48 |
+
pad_token_id (`int`, *optional*, defaults to 1):
|
| 49 |
+
The id of the padding token in the vocabulary.
|
| 50 |
+
bos_token_id (`int`, *optional*, defaults to 49406):
|
| 51 |
+
The id of the beginning-of-sequence token in the vocabulary.
|
| 52 |
+
eos_token_id (`int`, *optional*, defaults to 49407):
|
| 53 |
+
The id of the end-of-sequence token in the vocabulary.
|
| 54 |
+
|
| 55 |
+
Example:
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
>>> from transformers import SiglipTextConfig, SiglipTextModel
|
| 59 |
+
|
| 60 |
+
>>> # Initializing a SiglipTextConfig with google/siglip-base-patch16-224 style configuration
|
| 61 |
+
>>> configuration = SiglipTextConfig()
|
| 62 |
+
|
| 63 |
+
>>> # Initializing a SiglipTextModel (with random weights) from the google/siglip-base-patch16-224 style configuration
|
| 64 |
+
>>> model = SiglipTextModel(configuration)
|
| 65 |
+
|
| 66 |
+
>>> # Accessing the model configuration
|
| 67 |
+
>>> configuration = model.config
|
| 68 |
+
```"""
|
| 69 |
+
|
| 70 |
+
model_type = "siglip_text_model"
|
| 71 |
+
|
| 72 |
+
def __init__(
|
| 73 |
+
self,
|
| 74 |
+
vocab_size=32000,
|
| 75 |
+
hidden_size=768,
|
| 76 |
+
intermediate_size=3072,
|
| 77 |
+
num_hidden_layers=12,
|
| 78 |
+
num_attention_heads=12,
|
| 79 |
+
max_position_embeddings=64,
|
| 80 |
+
hidden_act="gelu_pytorch_tanh",
|
| 81 |
+
layer_norm_eps=1e-6,
|
| 82 |
+
attention_dropout=0.0,
|
| 83 |
+
# This differs from `CLIPTokenizer`'s default and from openai/siglip
|
| 84 |
+
# See https://github.com/huggingface/transformers/pull/24773#issuecomment-1632287538
|
| 85 |
+
pad_token_id=1,
|
| 86 |
+
bos_token_id=49406,
|
| 87 |
+
eos_token_id=49407,
|
| 88 |
+
**kwargs,
|
| 89 |
+
):
|
| 90 |
+
super().__init__(pad_token_id=pad_token_id, bos_token_id=bos_token_id, eos_token_id=eos_token_id, **kwargs)
|
| 91 |
+
|
| 92 |
+
self.vocab_size = vocab_size
|
| 93 |
+
self.hidden_size = hidden_size
|
| 94 |
+
self.intermediate_size = intermediate_size
|
| 95 |
+
self.num_hidden_layers = num_hidden_layers
|
| 96 |
+
self.num_attention_heads = num_attention_heads
|
| 97 |
+
self.max_position_embeddings = max_position_embeddings
|
| 98 |
+
self.layer_norm_eps = layer_norm_eps
|
| 99 |
+
self.hidden_act = hidden_act
|
| 100 |
+
self.attention_dropout = attention_dropout
|
| 101 |
+
|
| 102 |
+
@classmethod
|
| 103 |
+
def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig":
|
| 104 |
+
cls._set_token_in_kwargs(kwargs)
|
| 105 |
+
|
| 106 |
+
config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
|
| 107 |
+
|
| 108 |
+
# get the text config dict if we are loading from SiglipConfig
|
| 109 |
+
if config_dict.get("model_type") == "siglip":
|
| 110 |
+
config_dict = config_dict["text_config"]
|
| 111 |
+
|
| 112 |
+
if "model_type" in config_dict and hasattr(cls, "model_type") and config_dict["model_type"] != cls.model_type:
|
| 113 |
+
logger.warning(
|
| 114 |
+
f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
|
| 115 |
+
f"{cls.model_type}. This is not supported for all configurations of models and can yield errors."
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
return cls.from_dict(config_dict, **kwargs)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class SiglipVisionConfig(PretrainedConfig):
|
| 122 |
+
r"""
|
| 123 |
+
This is the configuration class to store the configuration of a [`SiglipVisionModel`]. It is used to instantiate a
|
| 124 |
+
Siglip vision encoder according to the specified arguments, defining the model architecture. Instantiating a
|
| 125 |
+
configuration with the defaults will yield a similar configuration to that of the vision encoder of the Siglip
|
| 126 |
+
[google/siglip-base-patch16-224](https://huggingface.co/google/siglip-base-patch16-224) architecture.
|
| 127 |
+
|
| 128 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 129 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
hidden_size (`int`, *optional*, defaults to 768):
|
| 133 |
+
Dimensionality of the encoder layers and the pooler layer.
|
| 134 |
+
intermediate_size (`int`, *optional*, defaults to 3072):
|
| 135 |
+
Dimensionality of the "intermediate" (i.e., feed-forward) layer in the Transformer encoder.
|
| 136 |
+
num_hidden_layers (`int`, *optional*, defaults to 12):
|
| 137 |
+
Number of hidden layers in the Transformer encoder.
|
| 138 |
+
num_attention_heads (`int`, *optional*, defaults to 12):
|
| 139 |
+
Number of attention heads for each attention layer in the Transformer encoder.
|
| 140 |
+
num_channels (`int`, *optional*, defaults to 3):
|
| 141 |
+
Number of channels in the input images.
|
| 142 |
+
image_size (`int`, *optional*, defaults to 224):
|
| 143 |
+
The size (resolution) of each image.
|
| 144 |
+
patch_size (`int`, *optional*, defaults to 16):
|
| 145 |
+
The size (resolution) of each patch.
|
| 146 |
+
hidden_act (`str` or `function`, *optional*, defaults to `"gelu_pytorch_tanh"`):
|
| 147 |
+
The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
|
| 148 |
+
`"relu"`, `"selu"` and `"gelu_new"` `"quick_gelu"` are supported.
|
| 149 |
+
layer_norm_eps (`float`, *optional*, defaults to 1e-06):
|
| 150 |
+
The epsilon used by the layer normalization layers.
|
| 151 |
+
attention_dropout (`float`, *optional*, defaults to 0.0):
|
| 152 |
+
The dropout ratio for the attention probabilities.
|
| 153 |
+
|
| 154 |
+
Example:
|
| 155 |
+
|
| 156 |
+
```python
|
| 157 |
+
>>> from transformers import SiglipVisionConfig, SiglipVisionModel
|
| 158 |
+
|
| 159 |
+
>>> # Initializing a SiglipVisionConfig with google/siglip-base-patch16-224 style configuration
|
| 160 |
+
>>> configuration = SiglipVisionConfig()
|
| 161 |
+
|
| 162 |
+
>>> # Initializing a SiglipVisionModel (with random weights) from the google/siglip-base-patch16-224 style configuration
|
| 163 |
+
>>> model = SiglipVisionModel(configuration)
|
| 164 |
+
|
| 165 |
+
>>> # Accessing the model configuration
|
| 166 |
+
>>> configuration = model.config
|
| 167 |
+
```"""
|
| 168 |
+
|
| 169 |
+
model_type = "siglip_vision_model"
|
| 170 |
+
|
| 171 |
+
def __init__(
|
| 172 |
+
self,
|
| 173 |
+
hidden_size=768,
|
| 174 |
+
intermediate_size=3072,
|
| 175 |
+
num_hidden_layers=12,
|
| 176 |
+
num_attention_heads=12,
|
| 177 |
+
num_channels=3,
|
| 178 |
+
image_size=224,
|
| 179 |
+
patch_size=16,
|
| 180 |
+
hidden_act="gelu_pytorch_tanh",
|
| 181 |
+
layer_norm_eps=1e-6,
|
| 182 |
+
attention_dropout=0.0,
|
| 183 |
+
**kwargs,
|
| 184 |
+
):
|
| 185 |
+
super().__init__(**kwargs)
|
| 186 |
+
|
| 187 |
+
self.hidden_size = hidden_size
|
| 188 |
+
self.intermediate_size = intermediate_size
|
| 189 |
+
self.num_hidden_layers = num_hidden_layers
|
| 190 |
+
self.num_attention_heads = num_attention_heads
|
| 191 |
+
self.num_channels = num_channels
|
| 192 |
+
self.patch_size = patch_size
|
| 193 |
+
self.image_size = image_size
|
| 194 |
+
self.attention_dropout = attention_dropout
|
| 195 |
+
self.layer_norm_eps = layer_norm_eps
|
| 196 |
+
self.hidden_act = hidden_act
|
| 197 |
+
|
| 198 |
+
@classmethod
|
| 199 |
+
def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig":
|
| 200 |
+
cls._set_token_in_kwargs(kwargs)
|
| 201 |
+
|
| 202 |
+
config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
|
| 203 |
+
|
| 204 |
+
# get the vision config dict if we are loading from SiglipConfig
|
| 205 |
+
if config_dict.get("model_type") == "siglip":
|
| 206 |
+
config_dict = config_dict["vision_config"]
|
| 207 |
+
|
| 208 |
+
if "model_type" in config_dict and hasattr(cls, "model_type") and config_dict["model_type"] != cls.model_type:
|
| 209 |
+
logger.warning(
|
| 210 |
+
f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
|
| 211 |
+
f"{cls.model_type}. This is not supported for all configurations of models and can yield errors."
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
return cls.from_dict(config_dict, **kwargs)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
class SiglipConfig(PretrainedConfig):
|
| 218 |
+
r"""
|
| 219 |
+
[`SiglipConfig`] is the configuration class to store the configuration of a [`SiglipModel`]. It is used to
|
| 220 |
+
instantiate a Siglip model according to the specified arguments, defining the text model and vision model configs.
|
| 221 |
+
Instantiating a configuration with the defaults will yield a similar configuration to that of the Siglip
|
| 222 |
+
[google/siglip-base-patch16-224](https://huggingface.co/google/siglip-base-patch16-224) architecture.
|
| 223 |
+
|
| 224 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 225 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 226 |
+
|
| 227 |
+
Args:
|
| 228 |
+
text_config (`dict`, *optional*):
|
| 229 |
+
Dictionary of configuration options used to initialize [`SiglipTextConfig`].
|
| 230 |
+
vision_config (`dict`, *optional*):
|
| 231 |
+
Dictionary of configuration options used to initialize [`SiglipVisionConfig`].
|
| 232 |
+
kwargs (*optional*):
|
| 233 |
+
Dictionary of keyword arguments.
|
| 234 |
+
|
| 235 |
+
Example:
|
| 236 |
+
|
| 237 |
+
```python
|
| 238 |
+
>>> from transformers import SiglipConfig, SiglipModel
|
| 239 |
+
|
| 240 |
+
>>> # Initializing a SiglipConfig with google/siglip-base-patch16-224 style configuration
|
| 241 |
+
>>> configuration = SiglipConfig()
|
| 242 |
+
|
| 243 |
+
>>> # Initializing a SiglipModel (with random weights) from the google/siglip-base-patch16-224 style configuration
|
| 244 |
+
>>> model = SiglipModel(configuration)
|
| 245 |
+
|
| 246 |
+
>>> # Accessing the model configuration
|
| 247 |
+
>>> configuration = model.config
|
| 248 |
+
|
| 249 |
+
>>> # We can also initialize a SiglipConfig from a SiglipTextConfig and a SiglipVisionConfig
|
| 250 |
+
>>> from transformers import SiglipTextConfig, SiglipVisionConfig
|
| 251 |
+
|
| 252 |
+
>>> # Initializing a SiglipText and SiglipVision configuration
|
| 253 |
+
>>> config_text = SiglipTextConfig()
|
| 254 |
+
>>> config_vision = SiglipVisionConfig()
|
| 255 |
+
|
| 256 |
+
>>> config = SiglipConfig.from_text_vision_configs(config_text, config_vision)
|
| 257 |
+
```"""
|
| 258 |
+
|
| 259 |
+
model_type = "siglip"
|
| 260 |
+
|
| 261 |
+
def __init__(self, text_config=None, vision_config=None, **kwargs):
|
| 262 |
+
super().__init__(**kwargs)
|
| 263 |
+
|
| 264 |
+
if text_config is None:
|
| 265 |
+
text_config = {}
|
| 266 |
+
logger.info("`text_config` is `None`. Initializing the `SiglipTextConfig` with default values.")
|
| 267 |
+
|
| 268 |
+
if vision_config is None:
|
| 269 |
+
vision_config = {}
|
| 270 |
+
logger.info("`vision_config` is `None`. initializing the `SiglipVisionConfig` with default values.")
|
| 271 |
+
|
| 272 |
+
self.text_config = SiglipTextConfig(**text_config)
|
| 273 |
+
self.vision_config = SiglipVisionConfig(**vision_config)
|
| 274 |
+
|
| 275 |
+
self.initializer_factor = 1.0
|
| 276 |
+
|
| 277 |
+
@classmethod
|
| 278 |
+
def from_text_vision_configs(cls, text_config: SiglipTextConfig, vision_config: SiglipVisionConfig, **kwargs):
|
| 279 |
+
r"""
|
| 280 |
+
Instantiate a [`SiglipConfig`] (or a derived class) from siglip text model configuration and siglip vision
|
| 281 |
+
model configuration.
|
| 282 |
+
|
| 283 |
+
Returns:
|
| 284 |
+
[`SiglipConfig`]: An instance of a configuration object
|
| 285 |
+
"""
|
| 286 |
+
|
| 287 |
+
return cls(text_config=text_config.to_dict(), vision_config=vision_config.to_dict(), **kwargs)
|
unispace/modeling/siglip/convert_siglip_to_hf.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The HuggingFace Inc. team.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""Convert SigLIP checkpoints from the original repository.
|
| 5 |
+
|
| 6 |
+
URL: https://github.com/google-research/big_vision/tree/main
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import collections
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import requests
|
| 15 |
+
import torch
|
| 16 |
+
from huggingface_hub import hf_hub_download
|
| 17 |
+
from numpy import load
|
| 18 |
+
from PIL import Image
|
| 19 |
+
|
| 20 |
+
from transformers import SiglipConfig, SiglipImageProcessor, SiglipModel, SiglipProcessor, SiglipTokenizer
|
| 21 |
+
from transformers.utils import logging
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
logging.set_verbosity_info()
|
| 25 |
+
logger = logging.get_logger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
model_name_to_checkpoint = {
|
| 29 |
+
# base checkpoints
|
| 30 |
+
"siglip-base-patch16-224": "/Users/nielsrogge/Documents/SigLIP/webli_en_b16_224_63724782.npz",
|
| 31 |
+
"siglip-base-patch16-256": "/Users/nielsrogge/Documents/SigLIP/webli_en_b16_256_60500360.npz",
|
| 32 |
+
"siglip-base-patch16-384": "/Users/nielsrogge/Documents/SigLIP/webli_en_b16_384_68578854.npz",
|
| 33 |
+
"siglip-base-patch16-512": "/Users/nielsrogge/Documents/SigLIP/webli_en_b16_512_68580893.npz",
|
| 34 |
+
# large checkpoints
|
| 35 |
+
"siglip-large-patch16-256": "/Users/nielsrogge/Documents/SigLIP/webli_en_l16_256_60552751.npz",
|
| 36 |
+
"siglip-large-patch16-384": "/Users/nielsrogge/Documents/SigLIP/webli_en_l16_384_63634585.npz",
|
| 37 |
+
# multilingual checkpoint
|
| 38 |
+
"siglip-base-patch16-256-i18n": "/Users/nielsrogge/Documents/SigLIP/webli_i18n_b16_256_66117334.npz",
|
| 39 |
+
# so400m checkpoints
|
| 40 |
+
"siglip-so400m-patch14-384": "/Users/nielsrogge/Documents/SigLIP/webli_en_so400m_384_58765454.npz",
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
model_name_to_image_size = {
|
| 44 |
+
"siglip-base-patch16-224": 224,
|
| 45 |
+
"siglip-base-patch16-256": 256,
|
| 46 |
+
"siglip-base-patch16-384": 384,
|
| 47 |
+
"siglip-base-patch16-512": 512,
|
| 48 |
+
"siglip-large-patch16-256": 256,
|
| 49 |
+
"siglip-large-patch16-384": 384,
|
| 50 |
+
"siglip-base-patch16-256-i18n": 256,
|
| 51 |
+
"siglip-so400m-patch14-384": 384,
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def get_siglip_config(model_name):
|
| 56 |
+
config = SiglipConfig()
|
| 57 |
+
|
| 58 |
+
vocab_size = 250000 if "i18n" in model_name else 32000
|
| 59 |
+
image_size = model_name_to_image_size[model_name]
|
| 60 |
+
patch_size = 16 if "patch16" in model_name else 14
|
| 61 |
+
|
| 62 |
+
# size of the architecture
|
| 63 |
+
config.vision_config.image_size = image_size
|
| 64 |
+
config.vision_config.patch_size = patch_size
|
| 65 |
+
config.text_config.vocab_size = vocab_size
|
| 66 |
+
|
| 67 |
+
if "base" in model_name:
|
| 68 |
+
pass
|
| 69 |
+
elif "large" in model_name:
|
| 70 |
+
config.text_config.hidden_size = 1024
|
| 71 |
+
config.text_config.intermediate_size = 4096
|
| 72 |
+
config.text_config.num_hidden_layers = 24
|
| 73 |
+
config.text_config.num_attention_heads = 16
|
| 74 |
+
config.vision_config.hidden_size = 1024
|
| 75 |
+
config.vision_config.intermediate_size = 4096
|
| 76 |
+
config.vision_config.num_hidden_layers = 24
|
| 77 |
+
config.vision_config.num_attention_heads = 16
|
| 78 |
+
elif "so400m" in model_name:
|
| 79 |
+
config.text_config.hidden_size = 1152
|
| 80 |
+
config.text_config.intermediate_size = 4304
|
| 81 |
+
config.text_config.num_hidden_layers = 27
|
| 82 |
+
config.text_config.num_attention_heads = 16
|
| 83 |
+
config.vision_config.hidden_size = 1152
|
| 84 |
+
config.vision_config.intermediate_size = 4304
|
| 85 |
+
config.vision_config.num_hidden_layers = 27
|
| 86 |
+
config.vision_config.num_attention_heads = 16
|
| 87 |
+
else:
|
| 88 |
+
raise ValueError("Model not supported")
|
| 89 |
+
|
| 90 |
+
return config
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def create_rename_keys(config):
|
| 94 |
+
rename_keys = []
|
| 95 |
+
# fmt: off
|
| 96 |
+
|
| 97 |
+
# vision encoder
|
| 98 |
+
|
| 99 |
+
rename_keys.append(("params/img/embedding/kernel", "vision_model.embeddings.patch_embedding.weight"))
|
| 100 |
+
rename_keys.append(("params/img/embedding/bias", "vision_model.embeddings.patch_embedding.bias"))
|
| 101 |
+
rename_keys.append(("params/img/pos_embedding", "vision_model.embeddings.position_embedding.weight"))
|
| 102 |
+
|
| 103 |
+
for i in range(config.vision_config.num_hidden_layers):
|
| 104 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/LayerNorm_0/scale", f"vision_model.encoder.layers.{i}.layer_norm1.weight"))
|
| 105 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/LayerNorm_0/bias", f"vision_model.encoder.layers.{i}.layer_norm1.bias"))
|
| 106 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/LayerNorm_1/scale", f"vision_model.encoder.layers.{i}.layer_norm2.weight"))
|
| 107 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/LayerNorm_1/bias", f"vision_model.encoder.layers.{i}.layer_norm2.bias"))
|
| 108 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MlpBlock_0/Dense_0/kernel", f"vision_model.encoder.layers.{i}.mlp.fc1.weight"))
|
| 109 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MlpBlock_0/Dense_0/bias", f"vision_model.encoder.layers.{i}.mlp.fc1.bias"))
|
| 110 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MlpBlock_0/Dense_1/kernel", f"vision_model.encoder.layers.{i}.mlp.fc2.weight"))
|
| 111 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MlpBlock_0/Dense_1/bias", f"vision_model.encoder.layers.{i}.mlp.fc2.bias"))
|
| 112 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/key/kernel", f"vision_model.encoder.layers.{i}.self_attn.k_proj.weight"))
|
| 113 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/key/bias", f"vision_model.encoder.layers.{i}.self_attn.k_proj.bias"))
|
| 114 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/value/kernel", f"vision_model.encoder.layers.{i}.self_attn.v_proj.weight"))
|
| 115 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/value/bias", f"vision_model.encoder.layers.{i}.self_attn.v_proj.bias"))
|
| 116 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/query/kernel", f"vision_model.encoder.layers.{i}.self_attn.q_proj.weight"))
|
| 117 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/query/bias", f"vision_model.encoder.layers.{i}.self_attn.q_proj.bias"))
|
| 118 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/out/kernel", f"vision_model.encoder.layers.{i}.self_attn.out_proj.weight"))
|
| 119 |
+
rename_keys.append((f"params/img/Transformer/encoderblock_{i}/MultiHeadDotProductAttention_0/out/bias", f"vision_model.encoder.layers.{i}.self_attn.out_proj.bias"))
|
| 120 |
+
|
| 121 |
+
rename_keys.append(("params/img/Transformer/encoder_norm/scale", "vision_model.post_layernorm.weight"))
|
| 122 |
+
rename_keys.append(("params/img/Transformer/encoder_norm/bias", "vision_model.post_layernorm.bias"))
|
| 123 |
+
|
| 124 |
+
rename_keys.append(("params/img/MAPHead_0/probe", "vision_model.head.probe"))
|
| 125 |
+
rename_keys.append(("params/img/MAPHead_0/LayerNorm_0/scale", "vision_model.head.layernorm.weight"))
|
| 126 |
+
rename_keys.append(("params/img/MAPHead_0/LayerNorm_0/bias", "vision_model.head.layernorm.bias"))
|
| 127 |
+
rename_keys.append(("params/img/MAPHead_0/MlpBlock_0/Dense_0/kernel", "vision_model.head.mlp.fc1.weight"))
|
| 128 |
+
rename_keys.append(("params/img/MAPHead_0/MlpBlock_0/Dense_0/bias", "vision_model.head.mlp.fc1.bias"))
|
| 129 |
+
rename_keys.append(("params/img/MAPHead_0/MlpBlock_0/Dense_1/kernel", "vision_model.head.mlp.fc2.weight"))
|
| 130 |
+
rename_keys.append(("params/img/MAPHead_0/MlpBlock_0/Dense_1/bias", "vision_model.head.mlp.fc2.bias"))
|
| 131 |
+
rename_keys.append(("params/img/MAPHead_0/MultiHeadDotProductAttention_0/out/kernel", "vision_model.head.attention.out_proj.weight"))
|
| 132 |
+
rename_keys.append(("params/img/MAPHead_0/MultiHeadDotProductAttention_0/out/bias", "vision_model.head.attention.out_proj.bias"))
|
| 133 |
+
|
| 134 |
+
# text encoder
|
| 135 |
+
|
| 136 |
+
rename_keys.append(("params/txt/Embed_0/embedding", "text_model.embeddings.token_embedding.weight"))
|
| 137 |
+
rename_keys.append(("params/txt/pos_embedding", "text_model.embeddings.position_embedding.weight"))
|
| 138 |
+
|
| 139 |
+
for i in range(config.text_config.num_hidden_layers):
|
| 140 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/LayerNorm_0/scale", f"text_model.encoder.layers.{i}.layer_norm1.weight"))
|
| 141 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/LayerNorm_0/bias", f"text_model.encoder.layers.{i}.layer_norm1.bias"))
|
| 142 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/LayerNorm_1/scale", f"text_model.encoder.layers.{i}.layer_norm2.weight"))
|
| 143 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/LayerNorm_1/bias", f"text_model.encoder.layers.{i}.layer_norm2.bias"))
|
| 144 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MlpBlock_0/Dense_0/kernel", f"text_model.encoder.layers.{i}.mlp.fc1.weight"))
|
| 145 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MlpBlock_0/Dense_0/bias", f"text_model.encoder.layers.{i}.mlp.fc1.bias"))
|
| 146 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MlpBlock_0/Dense_1/kernel", f"text_model.encoder.layers.{i}.mlp.fc2.weight"))
|
| 147 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MlpBlock_0/Dense_1/bias", f"text_model.encoder.layers.{i}.mlp.fc2.bias"))
|
| 148 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/key/kernel", f"text_model.encoder.layers.{i}.self_attn.k_proj.weight"))
|
| 149 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/key/bias", f"text_model.encoder.layers.{i}.self_attn.k_proj.bias"))
|
| 150 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/value/kernel", f"text_model.encoder.layers.{i}.self_attn.v_proj.weight"))
|
| 151 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/value/bias", f"text_model.encoder.layers.{i}.self_attn.v_proj.bias"))
|
| 152 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/query/kernel", f"text_model.encoder.layers.{i}.self_attn.q_proj.weight"))
|
| 153 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/query/bias", f"text_model.encoder.layers.{i}.self_attn.q_proj.bias"))
|
| 154 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/out/kernel", f"text_model.encoder.layers.{i}.self_attn.out_proj.weight"))
|
| 155 |
+
rename_keys.append((f"params/txt/Encoder_0/encoderblock_{i}/MultiHeadDotProductAttention_0/out/bias", f"text_model.encoder.layers.{i}.self_attn.out_proj.bias"))
|
| 156 |
+
|
| 157 |
+
rename_keys.append(("params/txt/Encoder_0/encoder_norm/scale", "text_model.final_layer_norm.weight"))
|
| 158 |
+
rename_keys.append(("params/txt/Encoder_0/encoder_norm/bias", "text_model.final_layer_norm.bias"))
|
| 159 |
+
rename_keys.append(("params/txt/head/kernel", "text_model.head.weight"))
|
| 160 |
+
rename_keys.append(("params/txt/head/bias", "text_model.head.bias"))
|
| 161 |
+
|
| 162 |
+
# learned temperature and bias
|
| 163 |
+
rename_keys.append(("params/t", "logit_scale"))
|
| 164 |
+
rename_keys.append(("params/b", "logit_bias"))
|
| 165 |
+
|
| 166 |
+
# fmt: on
|
| 167 |
+
return rename_keys
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def rename_key(dct, old, new, config):
|
| 171 |
+
val = dct.pop(old)
|
| 172 |
+
|
| 173 |
+
if ("out_proj" in new or "v_proj" in new or "k_proj" in new or "q_proj" in new) and "vision" in new:
|
| 174 |
+
val = val.reshape(-1, config.vision_config.hidden_size)
|
| 175 |
+
if ("out_proj" in new or "v_proj" in new or "k_proj" in new or "q_proj" in new) and "text" in new:
|
| 176 |
+
val = val.reshape(-1, config.text_config.hidden_size)
|
| 177 |
+
|
| 178 |
+
if "patch_embedding.weight" in new:
|
| 179 |
+
val = val.transpose(3, 2, 0, 1)
|
| 180 |
+
elif new.endswith("weight") and "position_embedding" not in new and "token_embedding" not in new:
|
| 181 |
+
val = val.T
|
| 182 |
+
|
| 183 |
+
if "position_embedding" in new and "vision" in new:
|
| 184 |
+
val = val.reshape(-1, config.vision_config.hidden_size)
|
| 185 |
+
if "position_embedding" in new and "text" in new:
|
| 186 |
+
val = val.reshape(-1, config.text_config.hidden_size)
|
| 187 |
+
|
| 188 |
+
if new.endswith("bias"):
|
| 189 |
+
val = val.reshape(-1)
|
| 190 |
+
|
| 191 |
+
dct[new] = torch.from_numpy(val)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def read_in_q_k_v_head(state_dict, config):
|
| 195 |
+
# read in individual input projection layers
|
| 196 |
+
key_proj_weight = (
|
| 197 |
+
state_dict.pop("params/img/MAPHead_0/MultiHeadDotProductAttention_0/key/kernel")
|
| 198 |
+
.reshape(-1, config.vision_config.hidden_size)
|
| 199 |
+
.T
|
| 200 |
+
)
|
| 201 |
+
key_proj_bias = state_dict.pop("params/img/MAPHead_0/MultiHeadDotProductAttention_0/key/bias").reshape(-1)
|
| 202 |
+
value_proj_weight = (
|
| 203 |
+
state_dict.pop("params/img/MAPHead_0/MultiHeadDotProductAttention_0/value/kernel")
|
| 204 |
+
.reshape(-1, config.vision_config.hidden_size)
|
| 205 |
+
.T
|
| 206 |
+
)
|
| 207 |
+
value_proj_bias = state_dict.pop("params/img/MAPHead_0/MultiHeadDotProductAttention_0/value/bias").reshape(-1)
|
| 208 |
+
query_proj_weight = (
|
| 209 |
+
state_dict.pop("params/img/MAPHead_0/MultiHeadDotProductAttention_0/query/kernel")
|
| 210 |
+
.reshape(-1, config.vision_config.hidden_size)
|
| 211 |
+
.T
|
| 212 |
+
)
|
| 213 |
+
query_proj_bias = state_dict.pop("params/img/MAPHead_0/MultiHeadDotProductAttention_0/query/bias").reshape(-1)
|
| 214 |
+
|
| 215 |
+
# next, add them to the state dict as a single matrix + vector
|
| 216 |
+
state_dict["vision_model.head.attention.in_proj_weight"] = torch.from_numpy(
|
| 217 |
+
np.concatenate([query_proj_weight, key_proj_weight, value_proj_weight], axis=0)
|
| 218 |
+
)
|
| 219 |
+
state_dict["vision_model.head.attention.in_proj_bias"] = torch.from_numpy(
|
| 220 |
+
np.concatenate([query_proj_bias, key_proj_bias, value_proj_bias], axis=0)
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
# We will verify our results on an image of cute cats
|
| 225 |
+
def prepare_img():
|
| 226 |
+
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
|
| 227 |
+
image = Image.open(requests.get(url, stream=True).raw)
|
| 228 |
+
return image
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def flatten_nested_dict(params, parent_key="", sep="/"):
|
| 232 |
+
items = []
|
| 233 |
+
|
| 234 |
+
for k, v in params.items():
|
| 235 |
+
new_key = parent_key + sep + k if parent_key else k
|
| 236 |
+
|
| 237 |
+
if isinstance(v, collections.abc.MutableMapping):
|
| 238 |
+
items.extend(flatten_nested_dict(v, new_key, sep=sep).items())
|
| 239 |
+
else:
|
| 240 |
+
items.append((new_key, v))
|
| 241 |
+
return dict(items)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@torch.no_grad()
|
| 245 |
+
def convert_siglip_checkpoint(model_name, pytorch_dump_folder_path, verify_logits=True, push_to_hub=False):
|
| 246 |
+
"""
|
| 247 |
+
Copy/paste/tweak model's weights to our SigLIP structure.
|
| 248 |
+
"""
|
| 249 |
+
|
| 250 |
+
# define default SigLIP configuration
|
| 251 |
+
config = get_siglip_config(model_name)
|
| 252 |
+
|
| 253 |
+
# get checkpoint
|
| 254 |
+
checkpoint = model_name_to_checkpoint[model_name]
|
| 255 |
+
|
| 256 |
+
# get vocab file
|
| 257 |
+
if "i18n" in model_name:
|
| 258 |
+
vocab_file = "/Users/nielsrogge/Documents/SigLIP/multilingual_vocab/sentencepiece.model"
|
| 259 |
+
else:
|
| 260 |
+
vocab_file = "/Users/nielsrogge/Documents/SigLIP/english_vocab/sentencepiece.model"
|
| 261 |
+
|
| 262 |
+
# load original state dict
|
| 263 |
+
data = load(checkpoint)
|
| 264 |
+
state_dict = flatten_nested_dict(data)
|
| 265 |
+
|
| 266 |
+
# remove and rename some keys
|
| 267 |
+
rename_keys = create_rename_keys(config)
|
| 268 |
+
for src, dest in rename_keys:
|
| 269 |
+
rename_key(state_dict, src, dest, config)
|
| 270 |
+
|
| 271 |
+
# qkv matrices of attention pooling head need special treatment
|
| 272 |
+
read_in_q_k_v_head(state_dict, config)
|
| 273 |
+
|
| 274 |
+
# load HuggingFace model
|
| 275 |
+
model = SiglipModel(config).eval()
|
| 276 |
+
model.load_state_dict(state_dict)
|
| 277 |
+
|
| 278 |
+
# create processor
|
| 279 |
+
# important: make tokenizer not return attention_mask since original one doesn't require it
|
| 280 |
+
image_size = config.vision_config.image_size
|
| 281 |
+
size = {"height": image_size, "width": image_size}
|
| 282 |
+
image_processor = SiglipImageProcessor(size=size)
|
| 283 |
+
tokenizer = SiglipTokenizer(vocab_file=vocab_file, model_input_names=["input_ids"])
|
| 284 |
+
processor = SiglipProcessor(image_processor=image_processor, tokenizer=tokenizer)
|
| 285 |
+
|
| 286 |
+
# verify on dummy images and texts
|
| 287 |
+
url_1 = "https://cdn.openai.com/multimodal-neurons/assets/apple/apple-ipod.jpg"
|
| 288 |
+
image_1 = Image.open(requests.get(url_1, stream=True).raw).convert("RGB")
|
| 289 |
+
url_2 = "https://cdn.openai.com/multimodal-neurons/assets/apple/apple-blank.jpg"
|
| 290 |
+
image_2 = Image.open(requests.get(url_2, stream=True).raw).convert("RGB")
|
| 291 |
+
texts = ["an apple", "a picture of an apple"]
|
| 292 |
+
|
| 293 |
+
inputs = processor(images=[image_1, image_2], text=texts, return_tensors="pt", padding="max_length")
|
| 294 |
+
|
| 295 |
+
# verify input_ids against original ones
|
| 296 |
+
if image_size == 224:
|
| 297 |
+
filename = "siglip_pixel_values.pt"
|
| 298 |
+
elif image_size == 256:
|
| 299 |
+
filename = "siglip_pixel_values_256.pt"
|
| 300 |
+
elif image_size == 384:
|
| 301 |
+
filename = "siglip_pixel_values_384.pt"
|
| 302 |
+
elif image_size == 512:
|
| 303 |
+
filename = "siglip_pixel_values_512.pt"
|
| 304 |
+
else:
|
| 305 |
+
raise ValueError("Image size not supported")
|
| 306 |
+
|
| 307 |
+
filepath = hf_hub_download(repo_id="nielsr/test-image", filename=filename, repo_type="dataset")
|
| 308 |
+
original_pixel_values = torch.load(filepath)
|
| 309 |
+
filepath = hf_hub_download(repo_id="nielsr/test-image", filename="siglip_input_ids.pt", repo_type="dataset")
|
| 310 |
+
original_input_ids = torch.load(filepath)
|
| 311 |
+
|
| 312 |
+
if "i18n" not in model_name:
|
| 313 |
+
assert inputs.input_ids.tolist() == original_input_ids.tolist()
|
| 314 |
+
|
| 315 |
+
print("Mean of original pixel values:", original_pixel_values.mean())
|
| 316 |
+
print("Mean of new pixel values:", inputs.pixel_values.mean())
|
| 317 |
+
|
| 318 |
+
# note: we're testing with original pixel values here since we don't have exact pixel values
|
| 319 |
+
with torch.no_grad():
|
| 320 |
+
outputs = model(input_ids=inputs.input_ids, pixel_values=original_pixel_values)
|
| 321 |
+
|
| 322 |
+
# with torch.no_grad():
|
| 323 |
+
# outputs = model(input_ids=inputs.input_ids, pixel_values=inputs.pixel_values)
|
| 324 |
+
|
| 325 |
+
print(outputs.logits_per_image[:3, :3])
|
| 326 |
+
|
| 327 |
+
probs = torch.sigmoid(outputs.logits_per_image) # these are the probabilities
|
| 328 |
+
print(f"{probs[0][0]:.1%} that image 0 is '{texts[0]}'")
|
| 329 |
+
print(f"{probs[0][1]:.1%} that image 0 is '{texts[1]}'")
|
| 330 |
+
|
| 331 |
+
if verify_logits:
|
| 332 |
+
if model_name == "siglip-base-patch16-224":
|
| 333 |
+
expected_slice = torch.tensor(
|
| 334 |
+
[[-2.9621, -2.1672], [-0.2713, 0.2910]],
|
| 335 |
+
)
|
| 336 |
+
elif model_name == "siglip-base-patch16-256":
|
| 337 |
+
expected_slice = torch.tensor(
|
| 338 |
+
[[-3.1146, -1.9894], [-0.7312, 0.6387]],
|
| 339 |
+
)
|
| 340 |
+
elif model_name == "siglip-base-patch16-384":
|
| 341 |
+
expected_slice = torch.tensor(
|
| 342 |
+
[[-2.8098, -2.1891], [-0.4242, 0.4102]],
|
| 343 |
+
)
|
| 344 |
+
elif model_name == "siglip-base-patch16-512":
|
| 345 |
+
expected_slice = torch.tensor(
|
| 346 |
+
[[-2.7899, -2.2668], [-0.4295, -0.0735]],
|
| 347 |
+
)
|
| 348 |
+
elif model_name == "siglip-large-patch16-256":
|
| 349 |
+
expected_slice = torch.tensor(
|
| 350 |
+
[[-1.5827, -0.5801], [-0.9153, 0.1363]],
|
| 351 |
+
)
|
| 352 |
+
elif model_name == "siglip-large-patch16-384":
|
| 353 |
+
expected_slice = torch.tensor(
|
| 354 |
+
[[-2.1523, -0.2899], [-0.2959, 0.7884]],
|
| 355 |
+
)
|
| 356 |
+
elif model_name == "siglip-so400m-patch14-384":
|
| 357 |
+
expected_slice = torch.tensor([[-1.2441, -0.6649], [-0.7060, 0.7374]])
|
| 358 |
+
elif model_name == "siglip-base-patch16-256-i18n":
|
| 359 |
+
expected_slice = torch.tensor(
|
| 360 |
+
[[-0.9064, 0.1073], [-0.0299, 0.5304]],
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
assert torch.allclose(outputs.logits_per_image[:3, :3], expected_slice, atol=1e-4)
|
| 364 |
+
print("Looks ok!")
|
| 365 |
+
|
| 366 |
+
if pytorch_dump_folder_path is not None:
|
| 367 |
+
Path(pytorch_dump_folder_path).mkdir(exist_ok=True)
|
| 368 |
+
print(f"Saving model {model_name} to {pytorch_dump_folder_path}")
|
| 369 |
+
model.save_pretrained(pytorch_dump_folder_path)
|
| 370 |
+
print(f"Saving processor to {pytorch_dump_folder_path}")
|
| 371 |
+
processor.save_pretrained(pytorch_dump_folder_path)
|
| 372 |
+
|
| 373 |
+
if push_to_hub:
|
| 374 |
+
model.push_to_hub(f"nielsr/{model_name}")
|
| 375 |
+
processor.push_to_hub(f"nielsr/{model_name}")
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
if __name__ == "__main__":
|
| 379 |
+
parser = argparse.ArgumentParser()
|
| 380 |
+
# Required parameters
|
| 381 |
+
parser.add_argument(
|
| 382 |
+
"--model_name",
|
| 383 |
+
default="siglip-base-patch16-224",
|
| 384 |
+
type=str,
|
| 385 |
+
choices=model_name_to_checkpoint.keys(),
|
| 386 |
+
help="Name of the model you'd like to convert.",
|
| 387 |
+
)
|
| 388 |
+
parser.add_argument(
|
| 389 |
+
"--pytorch_dump_folder_path", default=None, type=str, help="Path to the output PyTorch model directory."
|
| 390 |
+
)
|
| 391 |
+
parser.add_argument(
|
| 392 |
+
"--verify_logits",
|
| 393 |
+
action="store_false",
|
| 394 |
+
help="Whether to verify logits against the original implementation.",
|
| 395 |
+
)
|
| 396 |
+
parser.add_argument(
|
| 397 |
+
"--push_to_hub", action="store_true", help="Whether or not to push the converted model to the 🤗 hub."
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
args = parser.parse_args()
|
| 401 |
+
convert_siglip_checkpoint(args.model_name, args.pytorch_dump_folder_path, args.verify_logits, args.push_to_hub)
|
unispace/modeling/siglip/image_processing_siglip.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The HuggingFace Inc. team.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""Image processor class for SigLIP."""
|
| 5 |
+
|
| 6 |
+
from typing import Dict, List, Optional, Union
|
| 7 |
+
|
| 8 |
+
from transformers.image_processing_utils import BaseImageProcessor, BatchFeature, get_size_dict
|
| 9 |
+
from transformers.image_transforms import (
|
| 10 |
+
convert_to_rgb,
|
| 11 |
+
resize,
|
| 12 |
+
to_channel_dimension_format,
|
| 13 |
+
)
|
| 14 |
+
from transformers.image_utils import (
|
| 15 |
+
IMAGENET_STANDARD_MEAN,
|
| 16 |
+
IMAGENET_STANDARD_STD,
|
| 17 |
+
ChannelDimension,
|
| 18 |
+
ImageInput,
|
| 19 |
+
PILImageResampling,
|
| 20 |
+
infer_channel_dimension_format,
|
| 21 |
+
is_scaled_image,
|
| 22 |
+
make_list_of_images,
|
| 23 |
+
to_numpy_array,
|
| 24 |
+
valid_images,
|
| 25 |
+
validate_preprocess_arguments,
|
| 26 |
+
)
|
| 27 |
+
from transformers.utils import TensorType, filter_out_non_signature_kwargs, is_vision_available, logging
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
logger = logging.get_logger(__name__)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
if is_vision_available():
|
| 34 |
+
import PIL
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class SiglipImageProcessor(BaseImageProcessor):
|
| 38 |
+
r"""
|
| 39 |
+
Constructs a SigLIP image processor.
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
do_resize (`bool`, *optional*, defaults to `True`):
|
| 43 |
+
Whether to resize the image's (height, width) dimensions to the specified `size`. Can be overridden by
|
| 44 |
+
`do_resize` in the `preprocess` method.
|
| 45 |
+
size (`Dict[str, int]` *optional*, defaults to `{"height": 224, "width": 224}`):
|
| 46 |
+
Size of the image after resizing. Can be overridden by `size` in the `preprocess` method.
|
| 47 |
+
resample (`PILImageResampling`, *optional*, defaults to `Resampling.BICUBIC`):
|
| 48 |
+
Resampling filter to use if resizing the image. Can be overridden by `resample` in the `preprocess` method.
|
| 49 |
+
do_rescale (`bool`, *optional*, defaults to `True`):
|
| 50 |
+
Whether to rescale the image by the specified scale `rescale_factor`. Can be overridden by `do_rescale` in
|
| 51 |
+
the `preprocess` method.
|
| 52 |
+
rescale_factor (`int` or `float`, *optional*, defaults to `1/255`):
|
| 53 |
+
Scale factor to use if rescaling the image. Can be overridden by `rescale_factor` in the `preprocess`
|
| 54 |
+
method.
|
| 55 |
+
do_normalize (`bool`, *optional*, defaults to `True`):
|
| 56 |
+
Whether to normalize the image by the specified mean and standard deviation. Can be overridden by
|
| 57 |
+
`do_normalize` in the `preprocess` method.
|
| 58 |
+
image_mean (`float` or `List[float]`, *optional*, defaults to `[0.5, 0.5, 0.5]`):
|
| 59 |
+
Mean to use if normalizing the image. This is a float or list of floats the length of the number of
|
| 60 |
+
channels in the image. Can be overridden by the `image_mean` parameter in the `preprocess` method.
|
| 61 |
+
image_std (`float` or `List[float]`, *optional*, defaults to `[0.5, 0.5, 0.5]`):
|
| 62 |
+
Standard deviation to use if normalizing the image. This is a float or list of floats the length of the
|
| 63 |
+
number of channels in the image. Can be overridden by the `image_std` parameter in the `preprocess` method.
|
| 64 |
+
Can be overridden by the `image_std` parameter in the `preprocess` method.
|
| 65 |
+
do_convert_rgb (`bool`, *optional*, defaults to `True`):
|
| 66 |
+
Whether to convert the image to RGB.
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
model_input_names = ["pixel_values"]
|
| 70 |
+
|
| 71 |
+
def __init__(
|
| 72 |
+
self,
|
| 73 |
+
do_resize: bool = True,
|
| 74 |
+
size: Dict[str, int] = None,
|
| 75 |
+
resample: PILImageResampling = PILImageResampling.BICUBIC,
|
| 76 |
+
do_rescale: bool = True,
|
| 77 |
+
rescale_factor: Union[int, float] = 1 / 255,
|
| 78 |
+
do_normalize: bool = True,
|
| 79 |
+
image_mean: Optional[Union[float, List[float]]] = None,
|
| 80 |
+
image_std: Optional[Union[float, List[float]]] = None,
|
| 81 |
+
do_convert_rgb: bool = None,
|
| 82 |
+
**kwargs,
|
| 83 |
+
) -> None:
|
| 84 |
+
super().__init__(**kwargs)
|
| 85 |
+
size = size if size is not None else {"height": 224, "width": 224}
|
| 86 |
+
image_mean = image_mean if image_mean is not None else IMAGENET_STANDARD_MEAN
|
| 87 |
+
image_std = image_std if image_std is not None else IMAGENET_STANDARD_STD
|
| 88 |
+
|
| 89 |
+
self.do_resize = do_resize
|
| 90 |
+
self.size = size
|
| 91 |
+
self.resample = resample
|
| 92 |
+
self.do_rescale = do_rescale
|
| 93 |
+
self.rescale_factor = rescale_factor
|
| 94 |
+
self.do_normalize = do_normalize
|
| 95 |
+
self.image_mean = image_mean
|
| 96 |
+
self.image_std = image_std
|
| 97 |
+
self.do_convert_rgb = do_convert_rgb
|
| 98 |
+
|
| 99 |
+
@filter_out_non_signature_kwargs()
|
| 100 |
+
def preprocess(
|
| 101 |
+
self,
|
| 102 |
+
images: ImageInput,
|
| 103 |
+
do_resize: bool = None,
|
| 104 |
+
size: Dict[str, int] = None,
|
| 105 |
+
resample: PILImageResampling = None,
|
| 106 |
+
do_rescale: bool = None,
|
| 107 |
+
rescale_factor: float = None,
|
| 108 |
+
do_normalize: bool = None,
|
| 109 |
+
image_mean: Optional[Union[float, List[float]]] = None,
|
| 110 |
+
image_std: Optional[Union[float, List[float]]] = None,
|
| 111 |
+
return_tensors: Optional[Union[str, TensorType]] = None,
|
| 112 |
+
data_format: Optional[ChannelDimension] = ChannelDimension.FIRST,
|
| 113 |
+
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
| 114 |
+
do_convert_rgb: bool = None,
|
| 115 |
+
) -> PIL.Image.Image:
|
| 116 |
+
"""
|
| 117 |
+
Preprocess an image or batch of images.
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
images (`ImageInput`):
|
| 121 |
+
Image to preprocess. Expects a single or batch of images with pixel values ranging from 0 to 255. If
|
| 122 |
+
passing in images with pixel values between 0 and 1, set `do_rescale=False`.
|
| 123 |
+
do_resize (`bool`, *optional*, defaults to `self.do_resize`):
|
| 124 |
+
Whether to resize the image.
|
| 125 |
+
size (`Dict[str, int]`, *optional*, defaults to `self.size`):
|
| 126 |
+
Size of the image after resizing.
|
| 127 |
+
resample (`int`, *optional*, defaults to `self.resample`):
|
| 128 |
+
Resampling filter to use if resizing the image. This can be one of the enum `PILImageResampling`. Only
|
| 129 |
+
has an effect if `do_resize` is set to `True`.
|
| 130 |
+
do_rescale (`bool`, *optional*, defaults to `self.do_rescale`):
|
| 131 |
+
Whether to rescale the image.
|
| 132 |
+
rescale_factor (`float`, *optional*, defaults to `self.rescale_factor`):
|
| 133 |
+
Rescale factor to rescale the image by if `do_rescale` is set to `True`.
|
| 134 |
+
do_normalize (`bool`, *optional*, defaults to `self.do_normalize`):
|
| 135 |
+
Whether to normalize the image.
|
| 136 |
+
image_mean (`float` or `List[float]`, *optional*, defaults to `self.image_mean`):
|
| 137 |
+
Image mean to use for normalization. Only has an effect if `do_normalize` is set to `True`.
|
| 138 |
+
image_std (`float` or `List[float]`, *optional*, defaults to `self.image_std`):
|
| 139 |
+
Image standard deviation to use for normalization. Only has an effect if `do_normalize` is set to
|
| 140 |
+
`True`.
|
| 141 |
+
return_tensors (`str` or `TensorType`, *optional*):
|
| 142 |
+
The type of tensors to return. Can be one of:
|
| 143 |
+
- Unset: Return a list of `np.ndarray`.
|
| 144 |
+
- `TensorType.TENSORFLOW` or `'tf'`: Return a batch of type `tf.Tensor`.
|
| 145 |
+
- `TensorType.PYTORCH` or `'pt'`: Return a batch of type `torch.Tensor`.
|
| 146 |
+
- `TensorType.NUMPY` or `'np'`: Return a batch of type `np.ndarray`.
|
| 147 |
+
- `TensorType.JAX` or `'jax'`: Return a batch of type `jax.numpy.ndarray`.
|
| 148 |
+
data_format (`ChannelDimension` or `str`, *optional*, defaults to `ChannelDimension.FIRST`):
|
| 149 |
+
The channel dimension format for the output image. Can be one of:
|
| 150 |
+
- `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
|
| 151 |
+
- `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
|
| 152 |
+
- Unset: Use the channel dimension format of the input image.
|
| 153 |
+
input_data_format (`ChannelDimension` or `str`, *optional*):
|
| 154 |
+
The channel dimension format for the input image. If unset, the channel dimension format is inferred
|
| 155 |
+
from the input image. Can be one of:
|
| 156 |
+
- `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
|
| 157 |
+
- `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
|
| 158 |
+
- `"none"` or `ChannelDimension.NONE`: image in (height, width) format.
|
| 159 |
+
do_convert_rgb (`bool`, *optional*, defaults to `self.do_convert_rgb`):
|
| 160 |
+
Whether to convert the image to RGB.
|
| 161 |
+
"""
|
| 162 |
+
do_resize = do_resize if do_resize is not None else self.do_resize
|
| 163 |
+
size = size if size is not None else self.size
|
| 164 |
+
size = get_size_dict(size, param_name="size", default_to_square=False)
|
| 165 |
+
resample = resample if resample is not None else self.resample
|
| 166 |
+
do_rescale = do_rescale if do_rescale is not None else self.do_rescale
|
| 167 |
+
rescale_factor = rescale_factor if rescale_factor is not None else self.rescale_factor
|
| 168 |
+
do_normalize = do_normalize if do_normalize is not None else self.do_normalize
|
| 169 |
+
image_mean = image_mean if image_mean is not None else self.image_mean
|
| 170 |
+
image_std = image_std if image_std is not None else self.image_std
|
| 171 |
+
do_convert_rgb = do_convert_rgb if do_convert_rgb is not None else self.do_convert_rgb
|
| 172 |
+
|
| 173 |
+
images = make_list_of_images(images)
|
| 174 |
+
|
| 175 |
+
if not valid_images(images):
|
| 176 |
+
raise ValueError(
|
| 177 |
+
"Invalid image type. Must be of type PIL.Image.Image, numpy.ndarray, "
|
| 178 |
+
"torch.Tensor, tf.Tensor or jax.ndarray."
|
| 179 |
+
)
|
| 180 |
+
validate_preprocess_arguments(
|
| 181 |
+
do_rescale=do_rescale,
|
| 182 |
+
rescale_factor=rescale_factor,
|
| 183 |
+
do_normalize=do_normalize,
|
| 184 |
+
image_mean=image_mean,
|
| 185 |
+
image_std=image_std,
|
| 186 |
+
do_resize=do_resize,
|
| 187 |
+
size=size,
|
| 188 |
+
resample=resample,
|
| 189 |
+
)
|
| 190 |
+
# All transformations expect numpy arrays.
|
| 191 |
+
images = [to_numpy_array(image) for image in images]
|
| 192 |
+
|
| 193 |
+
if do_convert_rgb:
|
| 194 |
+
images = [convert_to_rgb(image) for image in images]
|
| 195 |
+
|
| 196 |
+
if is_scaled_image(images[0]) and do_rescale:
|
| 197 |
+
logger.warning_once(
|
| 198 |
+
"It looks like you are trying to rescale already rescaled images. If the input"
|
| 199 |
+
" images have pixel values between 0 and 1, set `do_rescale=False` to avoid rescaling them again."
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
if input_data_format is None:
|
| 203 |
+
# We assume that all images have the same channel dimension format.
|
| 204 |
+
input_data_format = infer_channel_dimension_format(images[0])
|
| 205 |
+
|
| 206 |
+
if do_resize:
|
| 207 |
+
height, width = size["height"], size["width"]
|
| 208 |
+
images = [
|
| 209 |
+
resize(image=image, size=(height, width), resample=resample, input_data_format=input_data_format)
|
| 210 |
+
for image in images
|
| 211 |
+
]
|
| 212 |
+
|
| 213 |
+
if do_rescale:
|
| 214 |
+
images = [
|
| 215 |
+
self.rescale(image=image, scale=rescale_factor, input_data_format=input_data_format)
|
| 216 |
+
for image in images
|
| 217 |
+
]
|
| 218 |
+
|
| 219 |
+
if do_normalize:
|
| 220 |
+
images = [
|
| 221 |
+
self.normalize(image=image, mean=image_mean, std=image_std, input_data_format=input_data_format)
|
| 222 |
+
for image in images
|
| 223 |
+
]
|
| 224 |
+
|
| 225 |
+
images = [
|
| 226 |
+
to_channel_dimension_format(image, data_format, input_channel_dim=input_data_format) for image in images
|
| 227 |
+
]
|
| 228 |
+
|
| 229 |
+
data = {"pixel_values": images}
|
| 230 |
+
return BatchFeature(data=data, tensor_type=return_tensors)
|