Spaces:
Running on Zero
Running on Zero
liuhuijie03 commited on
Commit ·
f0395ef
1
Parent(s): ac6f904
update
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +3 -21
- lakonlab/__init__.py +20 -0
- lakonlab/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/__pycache__/__init__.cpython-313.pyc +0 -0
- lakonlab/__pycache__/version.cpython-310.pyc +0 -0
- lakonlab/apis/__init__.py +4 -0
- lakonlab/apis/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/apis/__pycache__/__init__.cpython-313.pyc +0 -0
- lakonlab/apis/__pycache__/inference.cpython-310.pyc +0 -0
- lakonlab/apis/__pycache__/train.cpython-310.pyc +0 -0
- lakonlab/apis/__pycache__/train.cpython-313.pyc +0 -0
- lakonlab/apis/inference.py +56 -0
- lakonlab/apis/train.py +166 -0
- lakonlab/datasets/__init__.py +8 -0
- lakonlab/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/datasets/__pycache__/builder.cpython-310.pyc +0 -0
- lakonlab/datasets/__pycache__/checkerboard.cpython-310.pyc +0 -0
- lakonlab/datasets/__pycache__/image_prompts.cpython-310.pyc +0 -0
- lakonlab/datasets/__pycache__/imagenet.cpython-310.pyc +0 -0
- lakonlab/datasets/builder.py +61 -0
- lakonlab/datasets/checkerboard.py +59 -0
- lakonlab/datasets/image_prompts.py +432 -0
- lakonlab/datasets/imagenet.py +155 -0
- lakonlab/datasets/samplers/__init__.py +1 -0
- lakonlab/datasets/samplers/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/datasets/samplers/__pycache__/distributed_sampler.cpython-310.pyc +0 -0
- lakonlab/datasets/samplers/distributed_sampler.py +158 -0
- lakonlab/evaluation/__init__.py +8 -0
- lakonlab/evaluation/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/evaluation/__pycache__/eval_hooks.cpython-310.pyc +0 -0
- lakonlab/evaluation/__pycache__/hpsv3.cpython-310.pyc +0 -0
- lakonlab/evaluation/__pycache__/metrics.cpython-310.pyc +0 -0
- lakonlab/evaluation/__pycache__/vqa_score.cpython-310.pyc +0 -0
- lakonlab/evaluation/eval_hooks.py +318 -0
- lakonlab/evaluation/hpsv3.py +587 -0
- lakonlab/evaluation/metrics.py +1329 -0
- lakonlab/evaluation/vqa_score.py +667 -0
- lakonlab/models/__init__.py +6 -0
- lakonlab/models/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/models/__pycache__/base.cpython-310.pyc +0 -0
- lakonlab/models/__pycache__/base_diffusion.cpython-310.pyc +0 -0
- lakonlab/models/__pycache__/diffusion_2d.cpython-310.pyc +0 -0
- lakonlab/models/__pycache__/latent_diffusion_class_image.cpython-310.pyc +0 -0
- lakonlab/models/__pycache__/latent_diffusion_text_image.cpython-310.pyc +0 -0
- lakonlab/models/architecture/__init__.py +4 -0
- lakonlab/models/architecture/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/models/architecture/__pycache__/utils.cpython-310.pyc +0 -0
- lakonlab/models/architecture/ddpm/__init__.py +6 -0
- lakonlab/models/architecture/ddpm/__pycache__/__init__.cpython-310.pyc +0 -0
- lakonlab/models/architecture/ddpm/__pycache__/denoising.cpython-310.pyc +0 -0
README.md
CHANGED
|
@@ -1,27 +1,7 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: CoTyle
|
| 3 |
-
emoji: 🎨
|
| 4 |
-
colorFrom: gray
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 5.49.1
|
| 8 |
-
app_file: app.py
|
| 9 |
-
python_version: 3.10
|
| 10 |
-
# 移除 license 字段,因为它不是官方支持的字段
|
| 11 |
-
gpu: true
|
| 12 |
-
suggested_hardware: a100-large
|
| 13 |
-
models:
|
| 14 |
-
- Kwai-Kolors/CoTyle
|
| 15 |
-
tags:
|
| 16 |
-
- image-generation
|
| 17 |
-
- code-to-style
|
| 18 |
-
- gradio
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
|
| 22 |
# A Style is Worth One Code: Unlocking Code-to-Style Image Generation with Discrete Style Space
|
| 23 |
<p align="center">
|
| 24 |
-
<a href="
|
| 25 |
<a href="https://Kwai-Kolors.github.io/CoTyle/"><img alt="Build" src="https://img.shields.io/badge/Project%20Page-Homepage-yellow"></a>
|
| 26 |
<a href="https://github.com/Kwai-Kolors/CoTyle"><img alt="Build" src="https://img.shields.io/badge/GitHub-Code-f8f0f0.svg"></a>
|
| 27 |
<a href="https://huggingface.co/spaces/Kwai-Kolors/CoTyle"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-32CD32"></a>
|
|
@@ -78,6 +58,8 @@ git clone https://github.com/Kwai-Kolors/CoTyle
|
|
| 78 |
cd CoTyle
|
| 79 |
conda create -n cotyle python=3.10
|
| 80 |
conda activate cotyle
|
|
|
|
|
|
|
| 81 |
pip install -r requirements.txt
|
| 82 |
```
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
# A Style is Worth One Code: Unlocking Code-to-Style Image Generation with Discrete Style Space
|
| 3 |
<p align="center">
|
| 4 |
+
<a href="https://arxiv.org/abs/2511.10555"><img alt="Build" src="https://img.shields.io/badge/arXiv-Paper-da282a.svg"></a>
|
| 5 |
<a href="https://Kwai-Kolors.github.io/CoTyle/"><img alt="Build" src="https://img.shields.io/badge/Project%20Page-Homepage-yellow"></a>
|
| 6 |
<a href="https://github.com/Kwai-Kolors/CoTyle"><img alt="Build" src="https://img.shields.io/badge/GitHub-Code-f8f0f0.svg"></a>
|
| 7 |
<a href="https://huggingface.co/spaces/Kwai-Kolors/CoTyle"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-32CD32"></a>
|
|
|
|
| 58 |
cd CoTyle
|
| 59 |
conda create -n cotyle python=3.10
|
| 60 |
conda activate cotyle
|
| 61 |
+
pip install torch==2.6.0 torchvision==0.21.0
|
| 62 |
+
pip install -e git+https://github.com/Lakonik/piFlow.git@b1ef16e5e305251bccdfeac2a0e3d0ef339b974a#egg=lakonlab
|
| 63 |
pip install -r requirements.txt
|
| 64 |
```
|
| 65 |
|
lakonlab/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
|
| 3 |
+
# suppress warnings from MMCV about optional dependencies
|
| 4 |
+
warnings.filterwarnings(
|
| 5 |
+
'ignore',
|
| 6 |
+
category=UserWarning,
|
| 7 |
+
message=r'^Fail to import ``MultiScaleDeformableAttention`` from ``mmcv\.ops\.multi_scale_deform_attn``.*',
|
| 8 |
+
module=r'^mmcv\.cnn\.bricks\.transformer$',
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
# import all modules for registration
|
| 12 |
+
from .apis import *
|
| 13 |
+
from .datasets import *
|
| 14 |
+
from .models import *
|
| 15 |
+
from .ops import *
|
| 16 |
+
from .runner import *
|
| 17 |
+
from .evaluation import *
|
| 18 |
+
from .utils import *
|
| 19 |
+
|
| 20 |
+
from .version import __version__
|
lakonlab/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (632 Bytes). View file
|
|
|
lakonlab/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (699 Bytes). View file
|
|
|
lakonlab/__pycache__/version.cpython-310.pyc
ADDED
|
Binary file (226 Bytes). View file
|
|
|
lakonlab/apis/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .train import train_model
|
| 2 |
+
from .inference import init_model
|
| 3 |
+
|
| 4 |
+
__all__ = ['init_model', 'train_model']
|
lakonlab/apis/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (325 Bytes). View file
|
|
|
lakonlab/apis/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (341 Bytes). View file
|
|
|
lakonlab/apis/__pycache__/inference.cpython-310.pyc
ADDED
|
Binary file (1.55 kB). View file
|
|
|
lakonlab/apis/__pycache__/train.cpython-310.pyc
ADDED
|
Binary file (3.63 kB). View file
|
|
|
lakonlab/apis/__pycache__/train.cpython-313.pyc
ADDED
|
Binary file (6.39 kB). View file
|
|
|
lakonlab/apis/inference.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import mmcv
|
| 3 |
+
from mmcv.runner import load_checkpoint
|
| 4 |
+
from mmgen.models import build_model
|
| 5 |
+
from lakonlab.runner.hooks.ema_hook import get_ori_key
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def init_model(
|
| 9 |
+
config, checkpoint=None, device='cuda:0', cfg_options=None,
|
| 10 |
+
ema_only=True, use_fp16=False, use_bf16=False):
|
| 11 |
+
if isinstance(config, str):
|
| 12 |
+
config = mmcv.Config.fromfile(config)
|
| 13 |
+
elif not isinstance(config, mmcv.Config):
|
| 14 |
+
raise TypeError('config must be a filename or Config object, '
|
| 15 |
+
f'but got {type(config)}')
|
| 16 |
+
if cfg_options is not None:
|
| 17 |
+
config.merge_from_dict(cfg_options)
|
| 18 |
+
|
| 19 |
+
model = build_model(
|
| 20 |
+
config.model, train_cfg=config.train_cfg, test_cfg=config.test_cfg)
|
| 21 |
+
|
| 22 |
+
if ema_only:
|
| 23 |
+
module_keys = []
|
| 24 |
+
for hook in config.get('custom_hooks', []):
|
| 25 |
+
if hook['type'] in ('ExponentialMovingAverageHookMod', 'ExponentialMovingAverageHook'):
|
| 26 |
+
if isinstance(hook['module_keys'], str):
|
| 27 |
+
module_keys.append(hook['module_keys'])
|
| 28 |
+
else:
|
| 29 |
+
module_keys.extend(hook['module_keys'])
|
| 30 |
+
for key in module_keys:
|
| 31 |
+
ori_key = get_ori_key(key)
|
| 32 |
+
del model._modules[ori_key]
|
| 33 |
+
|
| 34 |
+
if checkpoint is not None:
|
| 35 |
+
load_checkpoint(model, checkpoint, map_location='cpu')
|
| 36 |
+
|
| 37 |
+
model._cfg = config # save the config in the model for convenience
|
| 38 |
+
|
| 39 |
+
for module in model.modules():
|
| 40 |
+
if hasattr(module, 'bake_lora_weights'):
|
| 41 |
+
module.bake_lora_weights()
|
| 42 |
+
|
| 43 |
+
if use_fp16 or use_bf16:
|
| 44 |
+
for m in model.modules():
|
| 45 |
+
if hasattr(m, 'autocast_dtype'):
|
| 46 |
+
setattr(m, 'autocast_dtype', None)
|
| 47 |
+
if use_fp16:
|
| 48 |
+
assert not use_bf16
|
| 49 |
+
model.to(dtype=torch.float16)
|
| 50 |
+
elif use_bf16:
|
| 51 |
+
model.to(dtype=torch.bfloat16)
|
| 52 |
+
|
| 53 |
+
model.to(device)
|
| 54 |
+
model.eval()
|
| 55 |
+
|
| 56 |
+
return model
|
lakonlab/apis/train.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modified from https://github.com/open-mmlab/mmgeneration
|
| 2 |
+
|
| 3 |
+
import warnings
|
| 4 |
+
import re
|
| 5 |
+
from copy import deepcopy
|
| 6 |
+
|
| 7 |
+
from mmcv.parallel import MMDataParallel
|
| 8 |
+
from mmcv.runner import HOOKS, IterBasedRunner, OptimizerHook, build_runner
|
| 9 |
+
from mmcv.utils import build_from_cfg
|
| 10 |
+
|
| 11 |
+
from mmgen.datasets import build_dataset
|
| 12 |
+
from mmgen.utils import get_root_logger
|
| 13 |
+
|
| 14 |
+
from lakonlab.parallel import apply_module_wrapper
|
| 15 |
+
from lakonlab.runner.optimizer import build_optimizers
|
| 16 |
+
from lakonlab.runner.checkpoint import exists_ckpt
|
| 17 |
+
from lakonlab.datasets import build_dataloader
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def train_model(model,
|
| 21 |
+
dataset,
|
| 22 |
+
cfg,
|
| 23 |
+
distributed=False,
|
| 24 |
+
validate=False,
|
| 25 |
+
timestamp=None,
|
| 26 |
+
meta=None):
|
| 27 |
+
logger = get_root_logger(cfg.log_level)
|
| 28 |
+
|
| 29 |
+
# prepare data loaders
|
| 30 |
+
dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset]
|
| 31 |
+
|
| 32 |
+
# default loader config
|
| 33 |
+
loader_cfg = dict(
|
| 34 |
+
# cfg.gpus will be ignored if distributed
|
| 35 |
+
num_gpus=len(cfg.gpu_ids),
|
| 36 |
+
seed=cfg.seed)
|
| 37 |
+
|
| 38 |
+
# The overall dataloader settings
|
| 39 |
+
loader_cfg.update({
|
| 40 |
+
k: v
|
| 41 |
+
for k, v in cfg.data.items()
|
| 42 |
+
if k not in [
|
| 43 |
+
'train', 'train_dataloader', 'val_dataloader', 'test_dataloader'
|
| 44 |
+
] and not re.fullmatch(r'(val|test)\d*', k)
|
| 45 |
+
})
|
| 46 |
+
|
| 47 |
+
# The specific datalaoder settings
|
| 48 |
+
train_loader_cfg = {**loader_cfg, **cfg.data.get('train_dataloader', {})}
|
| 49 |
+
|
| 50 |
+
data_loaders = [build_dataloader(ds, **train_loader_cfg) for ds in dataset]
|
| 51 |
+
|
| 52 |
+
if cfg.get('apex_amp', None):
|
| 53 |
+
raise NotImplementedError('Apex AMP is no longer supported.')
|
| 54 |
+
|
| 55 |
+
# put model on gpus
|
| 56 |
+
if distributed:
|
| 57 |
+
module_wrapper = cfg.get('module_wrapper', None)
|
| 58 |
+
model = apply_module_wrapper(model, module_wrapper, cfg)
|
| 59 |
+
else:
|
| 60 |
+
model = MMDataParallel(model, device_ids=cfg.gpu_ids)
|
| 61 |
+
|
| 62 |
+
# build optimizer
|
| 63 |
+
if cfg.optimizer:
|
| 64 |
+
optimizer = build_optimizers(model, cfg.optimizer)
|
| 65 |
+
# In GANs, we allow building optimizer in GAN model.
|
| 66 |
+
else:
|
| 67 |
+
optimizer = None
|
| 68 |
+
|
| 69 |
+
# allow users to define the runner
|
| 70 |
+
if cfg.get('runner', None):
|
| 71 |
+
runner = build_runner(
|
| 72 |
+
cfg.runner,
|
| 73 |
+
dict(
|
| 74 |
+
model=model,
|
| 75 |
+
optimizer=optimizer,
|
| 76 |
+
work_dir=cfg.work_dir,
|
| 77 |
+
logger=logger,
|
| 78 |
+
use_apex_amp=False,
|
| 79 |
+
meta=meta))
|
| 80 |
+
else:
|
| 81 |
+
runner = IterBasedRunner(
|
| 82 |
+
model,
|
| 83 |
+
optimizer=optimizer,
|
| 84 |
+
work_dir=cfg.work_dir,
|
| 85 |
+
logger=logger,
|
| 86 |
+
meta=meta)
|
| 87 |
+
# set if use dynamic ddp in training
|
| 88 |
+
# is_dynamic_ddp=cfg.get('is_dynamic_ddp', False))
|
| 89 |
+
# an ugly walkaround to make the .log and .log.json filenames the same
|
| 90 |
+
runner.timestamp = timestamp
|
| 91 |
+
|
| 92 |
+
# fp16 setting
|
| 93 |
+
fp16_cfg = cfg.get('fp16', None)
|
| 94 |
+
|
| 95 |
+
# In GANs, we can directly optimize parameter in `train_step` function.
|
| 96 |
+
if cfg.get('optimizer_cfg', None) is None:
|
| 97 |
+
optimizer_config = None
|
| 98 |
+
elif fp16_cfg is not None:
|
| 99 |
+
raise NotImplementedError('Fp16 has not been supported.')
|
| 100 |
+
# optimizer_config = Fp16OptimizerHook(
|
| 101 |
+
# **cfg.optimizer_config, **fp16_cfg, distributed=distributed)
|
| 102 |
+
# default to use OptimizerHook
|
| 103 |
+
elif distributed and 'type' not in cfg.optimizer_config:
|
| 104 |
+
optimizer_config = OptimizerHook(**cfg.optimizer_config)
|
| 105 |
+
else:
|
| 106 |
+
optimizer_config = cfg.optimizer_config
|
| 107 |
+
|
| 108 |
+
# # update `out_dir` in ckpt hook
|
| 109 |
+
# if cfg.checkpoint_config is not None:
|
| 110 |
+
# cfg.checkpoint_config['out_dir'] = os.path.join(
|
| 111 |
+
# cfg.work_dir, cfg.checkpoint_config.get('out_dir', 'ckpt'))
|
| 112 |
+
|
| 113 |
+
# register hooks
|
| 114 |
+
runner.register_training_hooks(cfg.lr_config, optimizer_config,
|
| 115 |
+
cfg.checkpoint_config, cfg.log_config,
|
| 116 |
+
cfg.get('momentum_config', None))
|
| 117 |
+
|
| 118 |
+
# # DistSamplerSeedHook should be used with EpochBasedRunner
|
| 119 |
+
# if distributed:
|
| 120 |
+
# runner.register_hook(DistSamplerSeedHook())
|
| 121 |
+
|
| 122 |
+
# In general, we do NOT adopt standard evaluation hook in GAN training.
|
| 123 |
+
# Thus, if you want a eval hook, you need further define the key of
|
| 124 |
+
# 'evaluation' in the config.
|
| 125 |
+
# register eval hooks
|
| 126 |
+
if validate and cfg.get('evaluation', None) is not None:
|
| 127 |
+
assert isinstance(cfg.evaluation, list)
|
| 128 |
+
for eval_cfg_ in cfg.evaluation:
|
| 129 |
+
val_dataset = build_dataset(cfg.data[eval_cfg_.data])
|
| 130 |
+
val_loader_cfg = {
|
| 131 |
+
**loader_cfg, 'shuffle': False,
|
| 132 |
+
**cfg.data.get('val_dataloader', {})
|
| 133 |
+
}
|
| 134 |
+
val_dataloader = build_dataloader(val_dataset, **val_loader_cfg)
|
| 135 |
+
eval_cfg = deepcopy(eval_cfg_)
|
| 136 |
+
priority = eval_cfg.pop('priority', 'LOW')
|
| 137 |
+
eval_cfg.update(dict(dist=distributed, dataloader=val_dataloader))
|
| 138 |
+
eval_hook = build_from_cfg(eval_cfg, HOOKS)
|
| 139 |
+
runner.register_hook(eval_hook, priority=priority)
|
| 140 |
+
|
| 141 |
+
# user-defined hooks
|
| 142 |
+
if cfg.get('custom_hooks', None):
|
| 143 |
+
custom_hooks = cfg.custom_hooks
|
| 144 |
+
assert isinstance(custom_hooks, list), \
|
| 145 |
+
f'custom_hooks expect list type, but got {type(custom_hooks)}'
|
| 146 |
+
for hook_cfg in cfg.custom_hooks:
|
| 147 |
+
assert isinstance(hook_cfg, dict), \
|
| 148 |
+
'Each item in custom_hooks expects dict type, but got ' \
|
| 149 |
+
f'{type(hook_cfg)}'
|
| 150 |
+
hook_cfg = hook_cfg.copy()
|
| 151 |
+
priority = hook_cfg.pop('priority', 'NORMAL')
|
| 152 |
+
hook = build_from_cfg(hook_cfg, HOOKS)
|
| 153 |
+
runner.register_hook(hook, priority=priority)
|
| 154 |
+
|
| 155 |
+
ckpt_kwargs = dict()
|
| 156 |
+
if distributed and module_wrapper.lower() in ['fsdp', 'fsdp2']:
|
| 157 |
+
ckpt_kwargs.update(map_location='cpu')
|
| 158 |
+
if exists_ckpt(cfg.resume_from):
|
| 159 |
+
runner.resume(cfg.resume_from, **ckpt_kwargs)
|
| 160 |
+
for data_loader in data_loaders:
|
| 161 |
+
data_loader.sampler.set_epoch(runner.epoch)
|
| 162 |
+
data_loader.sampler.set_iter(runner.iter)
|
| 163 |
+
elif exists_ckpt(cfg.load_from):
|
| 164 |
+
runner.load_checkpoint(cfg.load_from, **ckpt_kwargs)
|
| 165 |
+
|
| 166 |
+
runner.run(data_loaders, cfg.workflow, cfg.total_iters)
|
lakonlab/datasets/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .builder import build_dataloader
|
| 2 |
+
from .imagenet import ImageNet
|
| 3 |
+
from .checkerboard import CheckerboardData
|
| 4 |
+
from .image_prompts import ImagePrompt
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
'build_dataloader', 'ImageNet', 'CheckerboardData', 'ImagePrompt'
|
| 8 |
+
]
|
lakonlab/datasets/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (447 Bytes). View file
|
|
|
lakonlab/datasets/__pycache__/builder.cpython-310.pyc
ADDED
|
Binary file (1.49 kB). View file
|
|
|
lakonlab/datasets/__pycache__/checkerboard.cpython-310.pyc
ADDED
|
Binary file (2.52 kB). View file
|
|
|
lakonlab/datasets/__pycache__/image_prompts.cpython-310.pyc
ADDED
|
Binary file (15.3 kB). View file
|
|
|
lakonlab/datasets/__pycache__/imagenet.cpython-310.pyc
ADDED
|
Binary file (5.01 kB). View file
|
|
|
lakonlab/datasets/builder.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
from functools import partial
|
| 3 |
+
|
| 4 |
+
from mmcv.parallel import collate
|
| 5 |
+
from mmcv.runner import get_dist_info
|
| 6 |
+
from mmcv.utils import TORCH_VERSION, digit_version
|
| 7 |
+
from torch.utils.data import DataLoader
|
| 8 |
+
|
| 9 |
+
from mmgen.datasets.builder import worker_init_fn
|
| 10 |
+
from .samplers import DistributedSampler
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def build_dataloader(dataset,
|
| 14 |
+
samples_per_gpu,
|
| 15 |
+
workers_per_gpu,
|
| 16 |
+
num_gpus=1,
|
| 17 |
+
dist=True,
|
| 18 |
+
shuffle=True,
|
| 19 |
+
seed=None,
|
| 20 |
+
persistent_workers=False,
|
| 21 |
+
sampler=None,
|
| 22 |
+
**kwargs):
|
| 23 |
+
rank, world_size = get_dist_info()
|
| 24 |
+
if dist:
|
| 25 |
+
assert sampler is None, 'sampler is not supported in distributed mode'
|
| 26 |
+
sampler = DistributedSampler(
|
| 27 |
+
dataset,
|
| 28 |
+
world_size,
|
| 29 |
+
rank,
|
| 30 |
+
shuffle=shuffle,
|
| 31 |
+
samples_per_gpu=samples_per_gpu,
|
| 32 |
+
seed=seed)
|
| 33 |
+
shuffle = False
|
| 34 |
+
batch_size = samples_per_gpu
|
| 35 |
+
num_workers = workers_per_gpu
|
| 36 |
+
else:
|
| 37 |
+
batch_size = num_gpus * samples_per_gpu
|
| 38 |
+
num_workers = num_gpus * workers_per_gpu
|
| 39 |
+
|
| 40 |
+
init_fn = partial(
|
| 41 |
+
worker_init_fn, num_workers=num_workers, rank=rank,
|
| 42 |
+
seed=seed) if seed is not None else None
|
| 43 |
+
|
| 44 |
+
if (digit_version(TORCH_VERSION) >= digit_version('1.7.0')
|
| 45 |
+
and TORCH_VERSION != 'parrots'):
|
| 46 |
+
kwargs['persistent_workers'] = persistent_workers
|
| 47 |
+
elif persistent_workers is True:
|
| 48 |
+
warnings.warn('persistent_workers is invalid because your pytorch '
|
| 49 |
+
'version is lower than 1.7.0')
|
| 50 |
+
|
| 51 |
+
data_loader = DataLoader(
|
| 52 |
+
dataset,
|
| 53 |
+
batch_size=batch_size,
|
| 54 |
+
sampler=sampler,
|
| 55 |
+
num_workers=num_workers,
|
| 56 |
+
collate_fn=partial(collate, samples_per_gpu=samples_per_gpu),
|
| 57 |
+
shuffle=shuffle,
|
| 58 |
+
worker_init_fn=init_fn,
|
| 59 |
+
**kwargs)
|
| 60 |
+
|
| 61 |
+
return data_loader
|
lakonlab/datasets/checkerboard.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from torch.utils.data import Dataset
|
| 6 |
+
from mmgen.datasets.builder import DATASETS
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@DATASETS.register_module()
|
| 10 |
+
class CheckerboardData(Dataset):
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
n_rc=4,
|
| 14 |
+
n_samples=1e8,
|
| 15 |
+
thickness=1.0,
|
| 16 |
+
scale=1,
|
| 17 |
+
shift=[0.0, 0.0],
|
| 18 |
+
rotation=0.0,
|
| 19 |
+
test_mode=False):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.n_rc = n_rc
|
| 22 |
+
self.n_samples = int(n_samples)
|
| 23 |
+
self.thickness = thickness
|
| 24 |
+
self.scale = scale
|
| 25 |
+
self.shift = torch.tensor(shift, dtype=torch.float32)
|
| 26 |
+
self.rotation = rotation
|
| 27 |
+
white_squares = [(i, j) for i in range(n_rc) for j in range(n_rc) if (i + j) % 2 == 0]
|
| 28 |
+
self.white_squares = torch.tensor(white_squares, dtype=torch.float32)
|
| 29 |
+
self.n_squares = len(white_squares)
|
| 30 |
+
self.samples = self.draw_samples(self.n_samples)
|
| 31 |
+
|
| 32 |
+
def draw_samples(self, n_samples):
|
| 33 |
+
chosen_indices = torch.randint(0, self.n_squares, size=(n_samples, ))
|
| 34 |
+
chosen_squares = self.white_squares[chosen_indices]
|
| 35 |
+
square_samples = torch.rand(n_samples, 2, dtype=torch.float32)
|
| 36 |
+
if self.thickness < 1:
|
| 37 |
+
square_samples = square_samples - 0.5
|
| 38 |
+
square_samples_r = square_samples.square().sum(dim=-1, keepdims=True)
|
| 39 |
+
square_samples_angle = torch.atan2(square_samples[:, 1], square_samples[:, 0]).unsqueeze(-1)
|
| 40 |
+
max_r = torch.minimum(
|
| 41 |
+
0.5 / square_samples_angle.cos().abs().clamp(min=1e-6),
|
| 42 |
+
0.5 / square_samples_angle.sin().abs().clamp(min=1e-6)).square()
|
| 43 |
+
square_samples_r_scaled = max_r - (max_r - square_samples_r) * self.thickness ** 0.5
|
| 44 |
+
square_samples *= (square_samples_r_scaled / square_samples_r).sqrt()
|
| 45 |
+
square_samples = square_samples + 0.5
|
| 46 |
+
samples = (chosen_squares + square_samples) * (2 / self.n_rc) - 1
|
| 47 |
+
if self.rotation != 0.0:
|
| 48 |
+
angle = torch.tensor(self.rotation, dtype=torch.float32) * torch.pi / 180
|
| 49 |
+
rotation_matrix = torch.tensor([[torch.cos(angle), -torch.sin(angle)],
|
| 50 |
+
[torch.sin(angle), torch.cos(angle)]])
|
| 51 |
+
samples = samples @ rotation_matrix
|
| 52 |
+
return samples * self.scale + self.shift
|
| 53 |
+
|
| 54 |
+
def __len__(self):
|
| 55 |
+
return self.n_samples
|
| 56 |
+
|
| 57 |
+
def __getitem__(self, idx):
|
| 58 |
+
data = dict(x=self.samples[idx])
|
| 59 |
+
return data
|
lakonlab/datasets/image_prompts.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
import zstandard as zstd
|
| 10 |
+
import pickle
|
| 11 |
+
import gzip
|
| 12 |
+
import orjson
|
| 13 |
+
import mmcv
|
| 14 |
+
import torch.storage
|
| 15 |
+
torch.storage.UntypedStorage.dtype = torch.uint8 # hot patch for torch 2.6 deserialization
|
| 16 |
+
|
| 17 |
+
from io import BytesIO
|
| 18 |
+
from typing import Optional, Tuple, Union
|
| 19 |
+
from torch.utils.data import Dataset
|
| 20 |
+
from datasets import load_dataset, DatasetDict, Dataset as HFDataset
|
| 21 |
+
from mmcv.fileio import FileClient
|
| 22 |
+
from mmcv.parallel import DataContainer as DC
|
| 23 |
+
from mmgen.utils import get_root_logger
|
| 24 |
+
from mmgen.datasets.builder import DATASETS
|
| 25 |
+
from lakonlab.utils.io_utils import load_image
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@DATASETS.register_module()
|
| 29 |
+
class ImagePrompt(Dataset):
|
| 30 |
+
"""Initialize an image/prompt dataset that reads either cached pickled records
|
| 31 |
+
(zstd-compressed) or a HuggingFace prompt dataset (optionally paired with images).
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
data_root (str): Root path for IO, resolved via `mmcv.FileClient`.
|
| 35 |
+
cache_dir (Optional[str]): Subdirectory of `data_root` containing `.zst`
|
| 36 |
+
cache shards. Enables cache mode when provided and exists. Caches must
|
| 37 |
+
contain pickled dicts with keys `"prompt"` and `"prompt_embed_kwargs"`,
|
| 38 |
+
and optionally `"latents"` or `"latent_size"`.
|
| 39 |
+
cache_datalist_path (Optional[str]): Optional datalist path for `cache_dir`.
|
| 40 |
+
Supports `.jsonl`, `.jsonl.gz`, or `.json`. If not exists, files are
|
| 41 |
+
discovered by listing the directory.
|
| 42 |
+
ignore_cached_latents (bool): If True, ignores any cached latents and
|
| 43 |
+
prioritizes loading images from `image_dir`. Defaults to False.
|
| 44 |
+
prompt_dataset_kwargs (Optional[dict]): Keyword arguments forwarded to
|
| 45 |
+
`datasets.load_dataset(...)`. Enables prompt-dataset mode when provided.
|
| 46 |
+
If a `DatasetDict` is returned, a split (e.g., "train") is selected
|
| 47 |
+
internally.
|
| 48 |
+
image_dir (Optional[str]): Subdirectory of `data_root` with images to pair
|
| 49 |
+
with prompts (used only in prompt-dataset mode).
|
| 50 |
+
image_datalist_path (Optional[str]): Optional datalist for `image_dir`
|
| 51 |
+
(same formats as above). When `bucketize=True`, JSONL entries must
|
| 52 |
+
include `"size_idx"`.
|
| 53 |
+
image_extension (Optional[str]): Image file extension used to compose
|
| 54 |
+
paths when `image_dir` is set. Defaults to ".png".
|
| 55 |
+
image_scale_factor (float): Scale factor applied to image spatial dimensions
|
| 56 |
+
after loading. Defaults to 1.0 (no scaling).
|
| 57 |
+
negative_prompt_embeds_path (Optional[str]): Path to a `torch.load`-able
|
| 58 |
+
file containing keyward arguments forwarded to the diffusion model
|
| 59 |
+
for negative prompt embeddings. Added to each sample when provided.
|
| 60 |
+
negative_prompt_kwargs (Optional[dict]): Keyword arguments forwarded to
|
| 61 |
+
the text encoder for negative prompts. Added to each sample when provided.
|
| 62 |
+
pad_seq_len (int): If set, pads/truncates `"encoder_hidden_states"` and
|
| 63 |
+
`"encoder_hidden_states_mask"` along the sequence dimension to this
|
| 64 |
+
length. Defaults to None.
|
| 65 |
+
latent_size (Optional[Tuple[int]]): Default latent shape `(C, H, W)` used
|
| 66 |
+
when no cached latents exist and no image size is provided. Defaults to
|
| 67 |
+
`(16, 128, 128)`.
|
| 68 |
+
vae_scale_factor (Optional[Union[int, Tuple[int]]]): Downscale factor(s)
|
| 69 |
+
applied to image dimensions when deriving latent sizes from image size.
|
| 70 |
+
If an `int`, applies to each spatial dim; if a `tuple`, its length must
|
| 71 |
+
match the provided image spatial size (e.g., `(H, W)` or `(T, H, W)` for
|
| 72 |
+
video VAEs).
|
| 73 |
+
repeat (int): Virtual repetition factor for each underlying sample.
|
| 74 |
+
Affects `__len__` and index mapping.
|
| 75 |
+
start_ind (Optional[int]): Start index (inclusive) into the underlying
|
| 76 |
+
dataset. Defaults to 0.
|
| 77 |
+
end_ind (int): End index (exclusive) into the underlying dataset. Defaults
|
| 78 |
+
to dataset length.
|
| 79 |
+
bucketize (bool): If True, enables bucketing in `DistributedSampler` so that
|
| 80 |
+
each rank receives samples of the same size. Expects `"size_idx"` in JSONL
|
| 81 |
+
datalists and collects bucket ids. Defaults to False.
|
| 82 |
+
test_mode (bool): If True, return deterministic noise per sample instead of
|
| 83 |
+
reading/allocating real latents or images.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
PROMPT_KEY_MAPS = {
|
| 87 |
+
'prompt_embeds': 'encoder_hidden_states',
|
| 88 |
+
'prompt_embeds_scale': 'encoder_hidden_states_scale',
|
| 89 |
+
'pooled_prompt_embeds': 'pooled_projections',
|
| 90 |
+
'prompt_embeds_mask': 'encoder_hidden_states_mask'
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
def __init__(self,
|
| 94 |
+
data_root: str,
|
| 95 |
+
cache_dir: Optional[str] = None,
|
| 96 |
+
cache_datalist_path: Optional[str] = None,
|
| 97 |
+
ignore_cached_latents: bool = False,
|
| 98 |
+
prompt_dataset_kwargs: Optional[dict] = None,
|
| 99 |
+
image_dir: Optional[str] = None,
|
| 100 |
+
image_datalist_path: Optional[str] = None,
|
| 101 |
+
image_extension: Optional[str] = '.png',
|
| 102 |
+
image_scale_factor: float = 1.0,
|
| 103 |
+
negative_prompt_embeds_path: Optional[str] = None,
|
| 104 |
+
negative_prompt_kwargs: Optional[dict] = None,
|
| 105 |
+
pad_seq_len: int = None,
|
| 106 |
+
latent_size: Optional[Tuple[int]] = (16, 128, 128),
|
| 107 |
+
vae_scale_factor: Optional[Union[int, Tuple[int]]] = 8,
|
| 108 |
+
repeat: int = 1,
|
| 109 |
+
start_ind: Optional[int] = None,
|
| 110 |
+
end_ind: int = None,
|
| 111 |
+
bucketize: bool = False,
|
| 112 |
+
test_mode: bool = False):
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.data_root = data_root
|
| 115 |
+
self.file_client = FileClient.infer_client(uri=self.data_root)
|
| 116 |
+
|
| 117 |
+
self.pad_seq_len = pad_seq_len
|
| 118 |
+
|
| 119 |
+
self.cache_dir_path = self.cache_datalist_path = None
|
| 120 |
+
self.prompt_dataset = self.image_dir_path = self.image_datalist_path = None
|
| 121 |
+
self.image_extension = image_extension
|
| 122 |
+
self.image_scale_factor = image_scale_factor
|
| 123 |
+
self.ignore_cached_latents = ignore_cached_latents
|
| 124 |
+
self.bucketize = bucketize
|
| 125 |
+
bucket_ids = None
|
| 126 |
+
|
| 127 |
+
if (cache_dir is not None
|
| 128 |
+
and self.file_client.isdir(self.file_client.join_path(data_root, cache_dir))
|
| 129 |
+
and cache_datalist_path is not None
|
| 130 |
+
and FileClient.infer_client(uri=cache_datalist_path).isfile(cache_datalist_path)):
|
| 131 |
+
self.cache_dir_path = self.file_client.join_path(data_root, cache_dir)
|
| 132 |
+
self.cache_datalist, bucket_ids = self.parse_datalist(
|
| 133 |
+
self.cache_dir_path, cache_datalist_path)
|
| 134 |
+
dataset_len = len(self.cache_datalist)
|
| 135 |
+
|
| 136 |
+
elif prompt_dataset_kwargs is not None:
|
| 137 |
+
self.prompt_dataset = load_dataset(**prompt_dataset_kwargs)
|
| 138 |
+
if isinstance(self.prompt_dataset, DatasetDict):
|
| 139 |
+
split = 'train' if 'train' in self.prompt_dataset else list(self.prompt_dataset.keys())[0]
|
| 140 |
+
self.prompt_dataset = self.prompt_dataset[split]
|
| 141 |
+
assert isinstance(self.prompt_dataset, HFDataset), \
|
| 142 |
+
f"Expected HF Dataset/DatasetDict, got {type(self.prompt_dataset)}."
|
| 143 |
+
dataset_len = len(self.prompt_dataset)
|
| 144 |
+
|
| 145 |
+
else:
|
| 146 |
+
raise ValueError('Either `cache_dir` or `prompt_dataset_kwargs` must be provided.')
|
| 147 |
+
|
| 148 |
+
if image_dir is not None and self.file_client.isdir(self.file_client.join_path(data_root, image_dir)):
|
| 149 |
+
self.image_dir_path = self.file_client.join_path(data_root, image_dir)
|
| 150 |
+
self.image_datalist, bucket_ids = self.parse_datalist(
|
| 151 |
+
self.image_dir_path, image_datalist_path, datalist_must_exist=True)
|
| 152 |
+
assert dataset_len == len(self.image_datalist)
|
| 153 |
+
|
| 154 |
+
if bucket_ids is None and self.bucketize:
|
| 155 |
+
assert self.prompt_dataset is not None
|
| 156 |
+
bucket_ids = self.get_bucket_ids_from_prompt_dataset()
|
| 157 |
+
|
| 158 |
+
self.negative_prompt_embed_kwargs = None
|
| 159 |
+
if negative_prompt_embeds_path is not None:
|
| 160 |
+
negative_prompt_embeds_bytesio = BytesIO(
|
| 161 |
+
FileClient.infer_client(uri=negative_prompt_embeds_path).get(negative_prompt_embeds_path))
|
| 162 |
+
self.negative_prompt_embed_kwargs = self.parse_prompt_embeds(
|
| 163 |
+
torch.load(negative_prompt_embeds_bytesio, map_location='cpu'))
|
| 164 |
+
self.negative_prompt_kwargs = negative_prompt_kwargs
|
| 165 |
+
|
| 166 |
+
self.latent_size = latent_size
|
| 167 |
+
self.vae_scale_factor = vae_scale_factor
|
| 168 |
+
|
| 169 |
+
self.repeat = repeat
|
| 170 |
+
if start_ind is not None:
|
| 171 |
+
start_ind = max(min(start_ind, dataset_len - 1), -dataset_len) % dataset_len
|
| 172 |
+
else:
|
| 173 |
+
start_ind = 0
|
| 174 |
+
if end_ind is not None:
|
| 175 |
+
end_ind = max(min(end_ind - 1, dataset_len - 1), -dataset_len) % dataset_len + 1
|
| 176 |
+
else:
|
| 177 |
+
end_ind = dataset_len
|
| 178 |
+
assert start_ind < end_ind, f'Invalid start_ind and end_ind.'
|
| 179 |
+
self.start_ind = start_ind
|
| 180 |
+
self.end_ind = end_ind
|
| 181 |
+
|
| 182 |
+
if self.bucketize:
|
| 183 |
+
assert bucket_ids is not None and len(bucket_ids) == dataset_len
|
| 184 |
+
self.bucket_ids = [bucket_ids[self._map_idx(i)] for i in range(len(self))]
|
| 185 |
+
|
| 186 |
+
self.test_mode = test_mode
|
| 187 |
+
|
| 188 |
+
def get_bucket_ids_from_prompt_dataset(self):
|
| 189 |
+
ds = self.prompt_dataset
|
| 190 |
+
assert 'height' in ds.column_names and 'width' in ds.column_names, \
|
| 191 |
+
'When bucketize=True and no datalist is provided, the prompt dataset ' \
|
| 192 |
+
'must contain `height` and `width` columns.'
|
| 193 |
+
cols = ['height', 'width']
|
| 194 |
+
if 'frames' in ds.column_names:
|
| 195 |
+
cols = ['frames'] + cols
|
| 196 |
+
ds_arrow = ds.with_format('arrow', columns=cols)
|
| 197 |
+
batch = ds_arrow[:]
|
| 198 |
+
|
| 199 |
+
arrs = [batch[c].combine_chunks().to_numpy(zero_copy_only=False) for c in cols]
|
| 200 |
+
arrs = np.stack(arrs, axis=1)
|
| 201 |
+
|
| 202 |
+
_, inv = np.unique(arrs, axis=0, return_inverse=True)
|
| 203 |
+
return inv.tolist()
|
| 204 |
+
|
| 205 |
+
def parse_datalist(self, dir_path, datalist_path=None, datalist_must_exist=False):
|
| 206 |
+
logger = get_root_logger()
|
| 207 |
+
|
| 208 |
+
if datalist_path is not None and FileClient.infer_client(uri=datalist_path).isfile(datalist_path):
|
| 209 |
+
filenames = []
|
| 210 |
+
bucket_ids = []
|
| 211 |
+
|
| 212 |
+
datalist_bytesio = BytesIO(FileClient.infer_client(uri=datalist_path).get(datalist_path))
|
| 213 |
+
if datalist_path.endswith('.jsonl.gz') or datalist_path.endswith('.jsonl'):
|
| 214 |
+
if datalist_path.endswith('.jsonl.gz'):
|
| 215 |
+
with gzip.open(datalist_bytesio, 'rt', encoding='utf-8') as f:
|
| 216 |
+
datalist = f.readlines()
|
| 217 |
+
else:
|
| 218 |
+
datalist = datalist_bytesio.read().decode('utf-8').splitlines()
|
| 219 |
+
for line in datalist:
|
| 220 |
+
data_item = orjson.loads(line)
|
| 221 |
+
if 'filename' in data_item:
|
| 222 |
+
filenames.append(data_item['filename'])
|
| 223 |
+
elif 'image_hash' in data_item:
|
| 224 |
+
filenames.append(data_item['image_hash'])
|
| 225 |
+
else:
|
| 226 |
+
raise ValueError('No valid key to identify data item.')
|
| 227 |
+
if self.bucketize:
|
| 228 |
+
assert 'size_idx' in data_item, 'size_idx must be provided for bucketize.'
|
| 229 |
+
bucket_ids.append(data_item['size_idx'])
|
| 230 |
+
elif datalist_path.endswith('.json'):
|
| 231 |
+
assert not self.bucketize, 'Bucketize not supported for json datalist.'
|
| 232 |
+
datalist = orjson.loads(datalist_bytesio.read())
|
| 233 |
+
for data_item in datalist:
|
| 234 |
+
filenames.append(os.path.splitext(os.path.basename(data_item))[0])
|
| 235 |
+
else:
|
| 236 |
+
raise ValueError('Datalist file must be .jsonl, .jsonl.gz or .json')
|
| 237 |
+
|
| 238 |
+
else:
|
| 239 |
+
assert not datalist_must_exist, f'Datalist file {datalist_path} does not exist.'
|
| 240 |
+
assert not self.bucketize, 'Bucketize not supported when datalist is not provided.'
|
| 241 |
+
mmcv.print_log(
|
| 242 |
+
f'Datalist file {datalist_path} does not exist, directly list all files in the directory.',
|
| 243 |
+
logger=logger,
|
| 244 |
+
level=logging.WARNING)
|
| 245 |
+
# list all files in the directory
|
| 246 |
+
filenames = [os.path.splitext(p)[0] for p in self.file_client.list_dir_or_file(dir_path)]
|
| 247 |
+
filenames.sort()
|
| 248 |
+
bucket_ids = None
|
| 249 |
+
# save the datalist if datalist_path is provided
|
| 250 |
+
if datalist_path is not None:
|
| 251 |
+
if datalist_path.endswith('.jsonl.gz') or datalist_path.endswith('.jsonl'):
|
| 252 |
+
datalist = []
|
| 253 |
+
for filename in filenames:
|
| 254 |
+
datalist.append(orjson.dumps({'filename': filename}).decode('utf-8'))
|
| 255 |
+
datalist_str = '\n'.join(datalist)
|
| 256 |
+
if datalist_path.endswith('.jsonl.gz'):
|
| 257 |
+
datalist_bytesio = BytesIO()
|
| 258 |
+
with gzip.open(datalist_bytesio, 'wt', encoding='utf-8') as f:
|
| 259 |
+
f.write(datalist_str)
|
| 260 |
+
FileClient.infer_client(uri=datalist_path).put(datalist_bytesio.getvalue(), datalist_path)
|
| 261 |
+
else:
|
| 262 |
+
FileClient.infer_client(uri=datalist_path).put_text(datalist_str, datalist_path)
|
| 263 |
+
elif datalist_path.endswith('.json'):
|
| 264 |
+
datalist = filenames
|
| 265 |
+
FileClient.infer_client(uri=datalist_path).put_text(
|
| 266 |
+
orjson.dumps(datalist).decode('utf-8'), datalist_path)
|
| 267 |
+
|
| 268 |
+
mmcv.print_log(f'Loaded {len(filenames)} samples.', logger=logger)
|
| 269 |
+
|
| 270 |
+
return filenames, bucket_ids
|
| 271 |
+
|
| 272 |
+
def pad_prompt_embeds(self, prompt_embeds):
|
| 273 |
+
if self.pad_seq_len is not None:
|
| 274 |
+
if prompt_embeds.size(0) > self.pad_seq_len:
|
| 275 |
+
prompt_embeds = prompt_embeds[:self.pad_seq_len]
|
| 276 |
+
else:
|
| 277 |
+
zeros_size = (self.pad_seq_len - prompt_embeds.size(0),) + prompt_embeds.shape[1:]
|
| 278 |
+
prompt_embeds = torch.cat([prompt_embeds, prompt_embeds.new_zeros(zeros_size)], dim=0)
|
| 279 |
+
return prompt_embeds
|
| 280 |
+
|
| 281 |
+
def parse_prompt_embeds(self, data):
|
| 282 |
+
prompt_embed_kwargs = data.get('prompt_embed_kwargs', {}).copy()
|
| 283 |
+
|
| 284 |
+
# Map legacy keys to new ones if not already present
|
| 285 |
+
for legacy_key, new_key in self.PROMPT_KEY_MAPS.items():
|
| 286 |
+
if legacy_key in data and new_key not in prompt_embed_kwargs:
|
| 287 |
+
prompt_embed_kwargs[new_key] = data[legacy_key]
|
| 288 |
+
|
| 289 |
+
# Common post-processing
|
| 290 |
+
encoder_hidden_states_scale = prompt_embed_kwargs.pop('encoder_hidden_states_scale', None)
|
| 291 |
+
if 'encoder_hidden_states' in prompt_embed_kwargs:
|
| 292 |
+
encoder_hidden_states = prompt_embed_kwargs['encoder_hidden_states'].float()
|
| 293 |
+
if encoder_hidden_states_scale is not None:
|
| 294 |
+
encoder_hidden_states = encoder_hidden_states * encoder_hidden_states_scale
|
| 295 |
+
prompt_embed_kwargs['encoder_hidden_states'] = self.pad_prompt_embeds(encoder_hidden_states)
|
| 296 |
+
|
| 297 |
+
if 'pooled_projections' in prompt_embed_kwargs:
|
| 298 |
+
prompt_embed_kwargs['pooled_projections'] = prompt_embed_kwargs['pooled_projections'].float()
|
| 299 |
+
|
| 300 |
+
if 'encoder_hidden_states_mask' in prompt_embed_kwargs:
|
| 301 |
+
prompt_embed_kwargs['encoder_hidden_states_mask'] = self.pad_prompt_embeds(
|
| 302 |
+
prompt_embed_kwargs['encoder_hidden_states_mask'])
|
| 303 |
+
|
| 304 |
+
return prompt_embed_kwargs
|
| 305 |
+
|
| 306 |
+
def calculate_latent_size(self, image_spatial_size):
|
| 307 |
+
if isinstance(self.vae_scale_factor, int):
|
| 308 |
+
latent_spatial_size = tuple(s // self.vae_scale_factor for s in image_spatial_size)
|
| 309 |
+
else:
|
| 310 |
+
assert len(self.vae_scale_factor) == len(image_spatial_size)
|
| 311 |
+
latent_spatial_size = tuple(
|
| 312 |
+
s // f for s, f in zip(image_spatial_size, self.vae_scale_factor))
|
| 313 |
+
latent_size = (self.latent_size[0],) + latent_spatial_size
|
| 314 |
+
return latent_size
|
| 315 |
+
|
| 316 |
+
def calculate_scaled_image_size(self, image_spatial_size):
|
| 317 |
+
if self.image_scale_factor != 1:
|
| 318 |
+
if len(image_spatial_size) == 2:
|
| 319 |
+
new_spatial_size = (int(round(image_spatial_size[0] * self.image_scale_factor)),
|
| 320 |
+
int(round(image_spatial_size[1] * self.image_scale_factor)))
|
| 321 |
+
elif len(image_spatial_size) == 3:
|
| 322 |
+
new_spatial_size = (image_spatial_size[0],
|
| 323 |
+
int(round(image_spatial_size[1] * self.image_scale_factor)),
|
| 324 |
+
int(round(image_spatial_size[2] * self.image_scale_factor)))
|
| 325 |
+
else:
|
| 326 |
+
raise ValueError(f'Unsupported image spatial size {image_spatial_size}.')
|
| 327 |
+
else:
|
| 328 |
+
new_spatial_size = image_spatial_size
|
| 329 |
+
return new_spatial_size
|
| 330 |
+
|
| 331 |
+
def scale_image(self, image):
|
| 332 |
+
if self.image_scale_factor != 1:
|
| 333 |
+
new_spatial_size = self.calculate_scaled_image_size(image.shape[1:])
|
| 334 |
+
if len(new_spatial_size) == 2:
|
| 335 |
+
image = F.interpolate(
|
| 336 |
+
image[None], size=new_spatial_size, mode='bicubic', align_corners=False, antialias=True
|
| 337 |
+
)[0].clamp(min=0, max=1)
|
| 338 |
+
elif len(new_spatial_size) == 3:
|
| 339 |
+
image = F.interpolate(
|
| 340 |
+
image, size=new_spatial_size[1:], mode='bicubic', align_corners=False, antialias=True
|
| 341 |
+
).clamp(min=0, max=1)
|
| 342 |
+
else:
|
| 343 |
+
raise ValueError(f'Unsupported image spatial size {image.shape[1:]}.')
|
| 344 |
+
return image
|
| 345 |
+
|
| 346 |
+
def _map_idx(self, idx):
|
| 347 |
+
return self.start_ind + (idx // self.repeat)
|
| 348 |
+
|
| 349 |
+
def __len__(self):
|
| 350 |
+
return self.repeat * (self.end_ind - self.start_ind)
|
| 351 |
+
|
| 352 |
+
def __getitem__(self, idx):
|
| 353 |
+
mapped_idx = self._map_idx(idx)
|
| 354 |
+
|
| 355 |
+
prompt_data = None
|
| 356 |
+
|
| 357 |
+
if self.cache_dir_path is not None:
|
| 358 |
+
data_path = self.file_client.join_path(
|
| 359 |
+
self.cache_dir_path, f'{self.cache_datalist[mapped_idx]}.zst')
|
| 360 |
+
data_bytesio = BytesIO(self.file_client.get(data_path))
|
| 361 |
+
with zstd.ZstdDecompressor().stream_reader(data_bytesio) as f:
|
| 362 |
+
raw_data = pickle.load(f)
|
| 363 |
+
data = dict(
|
| 364 |
+
ids=DC(idx, cpu_only=True),
|
| 365 |
+
name=DC(raw_data['prompt'], cpu_only=True),
|
| 366 |
+
prompt_embed_kwargs=self.parse_prompt_embeds(raw_data))
|
| 367 |
+
|
| 368 |
+
if not self.ignore_cached_latents: # load latents
|
| 369 |
+
if 'latents' in raw_data:
|
| 370 |
+
latents = raw_data['latents']
|
| 371 |
+
if self.test_mode:
|
| 372 |
+
data['noise'] = torch.randn(
|
| 373 |
+
latents.size(), dtype=torch.float32, generator=torch.Generator().manual_seed(idx))
|
| 374 |
+
else:
|
| 375 |
+
data['latents'] = latents.float()
|
| 376 |
+
latents_scale = raw_data.get('latents_scale', None)
|
| 377 |
+
if latents_scale is not None:
|
| 378 |
+
data['latents'] = data['latents'] * latents_scale
|
| 379 |
+
else:
|
| 380 |
+
latent_size = raw_data.get('latent_size', self.latent_size)
|
| 381 |
+
if self.test_mode:
|
| 382 |
+
data['noise'] = torch.randn(
|
| 383 |
+
latent_size, dtype=torch.float32, generator=torch.Generator().manual_seed(idx))
|
| 384 |
+
else:
|
| 385 |
+
data['latents'] = torch.empty(latent_size, dtype=torch.float32)
|
| 386 |
+
|
| 387 |
+
else:
|
| 388 |
+
prompt_data = self.prompt_dataset[mapped_idx]
|
| 389 |
+
if 'prompt_kwargs' in prompt_data:
|
| 390 |
+
prompt_kwargs = {k: DC(v, cpu_only=True) for k, v in prompt_data['prompt_kwargs'].items()}
|
| 391 |
+
else:
|
| 392 |
+
prompt_kwargs = dict(prompt=DC(prompt_data['prompt'], cpu_only=True))
|
| 393 |
+
data = dict(
|
| 394 |
+
ids=DC(idx, cpu_only=True),
|
| 395 |
+
name=DC(prompt_data['prompt'], cpu_only=True),
|
| 396 |
+
prompt_kwargs=prompt_kwargs)
|
| 397 |
+
|
| 398 |
+
if self.image_dir_path is not None:
|
| 399 |
+
image_path = self.file_client.join_path(
|
| 400 |
+
self.image_dir_path, self.image_datalist[mapped_idx] + self.image_extension)
|
| 401 |
+
image = load_image(image_path, self.file_client)
|
| 402 |
+
image = np.moveaxis(image, -1, 0) # channel first
|
| 403 |
+
if self.test_mode:
|
| 404 |
+
data['noise'] = torch.randn(
|
| 405 |
+
self.calculate_latent_size(self.calculate_scaled_image_size(image.shape[1:])),
|
| 406 |
+
dtype=torch.float32, generator=torch.Generator().manual_seed(idx))
|
| 407 |
+
else:
|
| 408 |
+
images = torch.from_numpy(image)
|
| 409 |
+
if images.dtype == torch.uint8:
|
| 410 |
+
images = images.float() / 255.0
|
| 411 |
+
assert torch.is_floating_point(images), f'Image dtype {images.dtype} not supported.'
|
| 412 |
+
data['images'] = self.scale_image(images.float())
|
| 413 |
+
elif 'latents' not in data and 'noise' not in data: # allocate latents if not already loaded
|
| 414 |
+
if prompt_data is not None and 'height' in prompt_data and 'width' in prompt_data:
|
| 415 |
+
image_spatial_size = (prompt_data['height'], prompt_data['width'])
|
| 416 |
+
if 'frames' in prompt_data:
|
| 417 |
+
image_spatial_size = (prompt_data['frames'],) + image_spatial_size
|
| 418 |
+
latent_size = self.calculate_latent_size(self.calculate_scaled_image_size(image_spatial_size))
|
| 419 |
+
else:
|
| 420 |
+
latent_size = self.latent_size
|
| 421 |
+
if self.test_mode:
|
| 422 |
+
data['noise'] = torch.randn(
|
| 423 |
+
latent_size, dtype=torch.float32, generator=torch.Generator().manual_seed(idx))
|
| 424 |
+
else:
|
| 425 |
+
data['latents'] = torch.empty(latent_size, dtype=torch.float32)
|
| 426 |
+
|
| 427 |
+
if self.negative_prompt_embed_kwargs is not None:
|
| 428 |
+
data.update(negative_prompt_embed_kwargs=self.negative_prompt_embed_kwargs)
|
| 429 |
+
if self.negative_prompt_kwargs is not None:
|
| 430 |
+
data.update(negative_prompt_kwargs=self.negative_prompt_kwargs)
|
| 431 |
+
|
| 432 |
+
return data
|
lakonlab/datasets/imagenet.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
import mmcv
|
| 8 |
+
|
| 9 |
+
from io import BytesIO
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from torch.utils.data import Dataset
|
| 12 |
+
from mmcv.fileio import FileClient
|
| 13 |
+
from mmcv.parallel import DataContainer as DC
|
| 14 |
+
from mmgen.datasets.builder import DATASETS
|
| 15 |
+
from mmgen.utils import get_root_logger
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def image_preproc(pil_image, image_size, random_flip=False):
|
| 19 |
+
"""
|
| 20 |
+
Center cropping implementation from ADM.
|
| 21 |
+
https://github.com/openai/guided-diffusion/blob/8fb3ad9197f16bbc40620447b2742e13458d2831/guided_diffusion/image_datasets.py#L126
|
| 22 |
+
"""
|
| 23 |
+
while min(*pil_image.size) >= 2 * image_size:
|
| 24 |
+
pil_image = pil_image.resize(tuple(x // 2 for x in pil_image.size), resample=Image.BOX)
|
| 25 |
+
|
| 26 |
+
scale = image_size / min(*pil_image.size)
|
| 27 |
+
pil_image = pil_image.resize(
|
| 28 |
+
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC)
|
| 29 |
+
|
| 30 |
+
arr = np.array(pil_image)
|
| 31 |
+
crop_y = (arr.shape[0] - image_size) // 2
|
| 32 |
+
crop_x = (arr.shape[1] - image_size) // 2
|
| 33 |
+
arr = arr[crop_y: crop_y + image_size, crop_x: crop_x + image_size]
|
| 34 |
+
|
| 35 |
+
if random_flip and np.random.rand() < 0.5:
|
| 36 |
+
arr = np.ascontiguousarray(arr[:, ::-1])
|
| 37 |
+
|
| 38 |
+
if arr.ndim == 2:
|
| 39 |
+
arr = np.stack([arr] * 3, axis=-1)
|
| 40 |
+
elif arr.ndim == 3:
|
| 41 |
+
if arr.shape[2] == 1:
|
| 42 |
+
arr = np.concatenate([arr] * 3, axis=-1)
|
| 43 |
+
elif arr.shape[2] == 4:
|
| 44 |
+
arr = arr[:, :, :3]
|
| 45 |
+
else:
|
| 46 |
+
assert arr.shape[2] == 3
|
| 47 |
+
else:
|
| 48 |
+
raise ValueError(f'Unexpected number of dimensions: {arr.ndim}')
|
| 49 |
+
return arr
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@DATASETS.register_module()
|
| 53 |
+
class ImageNet(Dataset):
|
| 54 |
+
def __init__(
|
| 55 |
+
self,
|
| 56 |
+
data_root='data/imagenet/train',
|
| 57 |
+
datalist_path='data/imagenet/train.txt',
|
| 58 |
+
label2name_path='data/imagenet/imagenet1000_clsidx_to_labels.txt',
|
| 59 |
+
random_flip=True,
|
| 60 |
+
negative_label=1000,
|
| 61 |
+
image_size=256,
|
| 62 |
+
latent_size=(4, 32, 32),
|
| 63 |
+
test_label_repeat=1,
|
| 64 |
+
test_mode=False,
|
| 65 |
+
num_test_images=50000):
|
| 66 |
+
super().__init__()
|
| 67 |
+
self.data_root = data_root
|
| 68 |
+
self.file_client = FileClient.infer_client(uri=self.data_root)
|
| 69 |
+
|
| 70 |
+
self.datalist_path = datalist_path
|
| 71 |
+
self.label2name_path = label2name_path
|
| 72 |
+
self.random_flip = random_flip
|
| 73 |
+
self.negative_label = negative_label
|
| 74 |
+
self.image_size = image_size
|
| 75 |
+
self.latent_size = latent_size
|
| 76 |
+
self.test_label_repeat = test_label_repeat
|
| 77 |
+
self.test_mode = test_mode
|
| 78 |
+
self.num_test_images = num_test_images
|
| 79 |
+
|
| 80 |
+
self.label2name = {}
|
| 81 |
+
label2name_text = FileClient.infer_client(uri=self.label2name_path).get_text(self.label2name_path)
|
| 82 |
+
for line in label2name_text.split('\n'):
|
| 83 |
+
line = line.strip()
|
| 84 |
+
if len(line) == 0:
|
| 85 |
+
continue
|
| 86 |
+
idx, name = line.split(':')
|
| 87 |
+
idx, name = idx.strip(), name.strip()
|
| 88 |
+
if name[-1] == ',':
|
| 89 |
+
name = name[:-1]
|
| 90 |
+
if name[0] == '"' and name[-1] == '"':
|
| 91 |
+
name = name[1:-1]
|
| 92 |
+
if name[0] == "'" and name[-1] == "'":
|
| 93 |
+
name = name[1:-1]
|
| 94 |
+
self.label2name[int(idx)] = name
|
| 95 |
+
|
| 96 |
+
if not test_mode:
|
| 97 |
+
self.all_paths = []
|
| 98 |
+
self.all_labels = []
|
| 99 |
+
datalist_text = FileClient.infer_client(uri=self.datalist_path).get_text(self.datalist_path)
|
| 100 |
+
for line in datalist_text.split('\n'):
|
| 101 |
+
line = line.strip()
|
| 102 |
+
if len(line) == 0:
|
| 103 |
+
continue
|
| 104 |
+
path_label = line.split(' ')
|
| 105 |
+
self.all_paths.append(path_label[0])
|
| 106 |
+
if len(path_label) > 1:
|
| 107 |
+
self.all_labels.append(int(path_label[1]))
|
| 108 |
+
|
| 109 |
+
logger = get_root_logger()
|
| 110 |
+
mmcv.print_log(f'Data root: {self.data_root}', logger=logger)
|
| 111 |
+
mmcv.print_log(f'Data list path: {self.datalist_path}', logger=logger)
|
| 112 |
+
mmcv.print_log(f'Number of images: {len(self.all_paths)}', logger=logger)
|
| 113 |
+
|
| 114 |
+
def __len__(self):
|
| 115 |
+
return self.num_test_images if self.test_mode else len(self.all_paths)
|
| 116 |
+
|
| 117 |
+
def __getitem__(self, idx):
|
| 118 |
+
data = dict(ids=DC(idx, cpu_only=True))
|
| 119 |
+
|
| 120 |
+
if self.test_mode:
|
| 121 |
+
label_generator = torch.Generator().manual_seed(idx // self.test_label_repeat)
|
| 122 |
+
label = torch.randint(0, 1000, (), generator=label_generator).long()
|
| 123 |
+
noise_generator = torch.Generator().manual_seed(idx + 1000)
|
| 124 |
+
noise = torch.randn(self.latent_size, generator=noise_generator)
|
| 125 |
+
data.update(noise=noise)
|
| 126 |
+
|
| 127 |
+
else:
|
| 128 |
+
rel_data_path = self.all_paths[idx]
|
| 129 |
+
data.update(paths=DC(rel_data_path, cpu_only=True))
|
| 130 |
+
data_path = self.file_client.join_path(self.data_root, rel_data_path)
|
| 131 |
+
data_bytesio = BytesIO(self.file_client.get(data_path))
|
| 132 |
+
ext = os.path.splitext(data_path)[-1]
|
| 133 |
+
if ext.lower() in ('.pth', '.pt'):
|
| 134 |
+
torch_data = torch.load(data_bytesio, map_location='cpu')
|
| 135 |
+
label = torch_data['y'].long()
|
| 136 |
+
data.update(latents=torch_data['x'].float())
|
| 137 |
+
elif ext.lower() in ('.jpg', '.jpeg', '.png'):
|
| 138 |
+
label = torch.tensor(self.all_labels[idx], dtype=torch.long)
|
| 139 |
+
img_data = Image.open(data_bytesio)
|
| 140 |
+
data.update(
|
| 141 |
+
images=torch.from_numpy(image_preproc(
|
| 142 |
+
img_data, self.image_size, random_flip=self.random_flip)).float().permute(2, 0, 1) / 255.0)
|
| 143 |
+
else:
|
| 144 |
+
raise ValueError(f'Unsupported file extension: {ext}')
|
| 145 |
+
|
| 146 |
+
name = self.label2name[label.item()]
|
| 147 |
+
data.update(labels=label, name=DC(name, cpu_only=True))
|
| 148 |
+
|
| 149 |
+
if self.negative_label is not None:
|
| 150 |
+
if isinstance(self.negative_label, int):
|
| 151 |
+
data.update(negative_labels=torch.tensor(self.negative_label, dtype=torch.long))
|
| 152 |
+
else:
|
| 153 |
+
raise ValueError(f'Unsupported negative label: {self.negative_label}')
|
| 154 |
+
|
| 155 |
+
return data
|
lakonlab/datasets/samplers/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .distributed_sampler import DistributedSampler
|
lakonlab/datasets/samplers/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (286 Bytes). View file
|
|
|
lakonlab/datasets/samplers/__pycache__/distributed_sampler.cpython-310.pyc
ADDED
|
Binary file (4.21 kB). View file
|
|
|
lakonlab/datasets/samplers/distributed_sampler.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from torch.utils.data import DistributedSampler as _DistributedSampler
|
| 7 |
+
from mmgen.utils import sync_random_seed
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def reverse_index_map(bucket_ids):
|
| 11 |
+
bucket_map = dict()
|
| 12 |
+
for data_id, bucket_id in enumerate(bucket_ids):
|
| 13 |
+
if bucket_id not in bucket_map:
|
| 14 |
+
bucket_map[bucket_id] = []
|
| 15 |
+
bucket_map[bucket_id].append(data_id)
|
| 16 |
+
return bucket_map
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class DistributedSampler(_DistributedSampler):
|
| 20 |
+
|
| 21 |
+
def __init__(self,
|
| 22 |
+
dataset,
|
| 23 |
+
num_replicas=None,
|
| 24 |
+
rank=None,
|
| 25 |
+
shuffle=True,
|
| 26 |
+
samples_per_gpu=1,
|
| 27 |
+
seed=None):
|
| 28 |
+
super().__init__(dataset, num_replicas=num_replicas, rank=rank)
|
| 29 |
+
|
| 30 |
+
self.shuffle = shuffle
|
| 31 |
+
self.samples_per_gpu = samples_per_gpu
|
| 32 |
+
|
| 33 |
+
self.bucket_map = self.total_size_bucketwise = None
|
| 34 |
+
if hasattr(dataset, 'bucket_ids'):
|
| 35 |
+
self._init_bucket_sampler(dataset)
|
| 36 |
+
else:
|
| 37 |
+
self._init_sampler(dataset)
|
| 38 |
+
|
| 39 |
+
self.seed = sync_random_seed(seed)
|
| 40 |
+
self.skip_iter = 0
|
| 41 |
+
|
| 42 |
+
def _init_sampler(self, dataset):
|
| 43 |
+
data_len = len(dataset)
|
| 44 |
+
# to avoid padding bug when meeting too small dataset
|
| 45 |
+
if data_len < self.num_replicas * self.samples_per_gpu:
|
| 46 |
+
raise ValueError(
|
| 47 |
+
'You may use too small dataset and our distributed '
|
| 48 |
+
'sampler cannot pad your dataset correctly. Please '
|
| 49 |
+
'use fewer GPUs or smaller batch sizes per GPU.')
|
| 50 |
+
|
| 51 |
+
num_batches = int(np.ceil(data_len / self.num_replicas / self.samples_per_gpu))
|
| 52 |
+
self.num_samples = num_batches * self.samples_per_gpu
|
| 53 |
+
self.total_size = self.num_samples * self.num_replicas
|
| 54 |
+
|
| 55 |
+
def _init_bucket_sampler(self, dataset):
|
| 56 |
+
self.bucket_map = reverse_index_map(dataset.bucket_ids)
|
| 57 |
+
self.bucket_map = dict(sorted(self.bucket_map.items())) # sort by bucket_id
|
| 58 |
+
|
| 59 |
+
data_len = 0
|
| 60 |
+
self.total_size_bucketwise = {}
|
| 61 |
+
|
| 62 |
+
for bucket_id, data_indices in self.bucket_map.items():
|
| 63 |
+
_data_len = len(data_indices)
|
| 64 |
+
if _data_len < self.samples_per_gpu:
|
| 65 |
+
raise ValueError(
|
| 66 |
+
'You may use too small dataset and our distributed '
|
| 67 |
+
'sampler cannot pad your dataset correctly. Please '
|
| 68 |
+
'use smaller batch sizes per GPU.')
|
| 69 |
+
|
| 70 |
+
_total_num_batches = int(np.ceil(_data_len / self.samples_per_gpu))
|
| 71 |
+
_total_size = _total_num_batches * self.samples_per_gpu
|
| 72 |
+
|
| 73 |
+
data_len += _total_size
|
| 74 |
+
self.total_size_bucketwise[bucket_id] = _total_size
|
| 75 |
+
|
| 76 |
+
if data_len < self.num_replicas * self.samples_per_gpu:
|
| 77 |
+
raise ValueError(
|
| 78 |
+
'You may use too small dataset and our distributed '
|
| 79 |
+
'sampler cannot pad your dataset correctly. Please '
|
| 80 |
+
'use fewer GPUs or smaller batch sizes per GPU.')
|
| 81 |
+
|
| 82 |
+
num_batches = int(np.ceil(data_len / self.num_replicas / self.samples_per_gpu))
|
| 83 |
+
self.num_samples = num_batches * self.samples_per_gpu
|
| 84 |
+
self.total_size = self.num_samples * self.num_replicas
|
| 85 |
+
|
| 86 |
+
def update_sampler(self, dataset, samples_per_gpu=None):
|
| 87 |
+
self.dataset = dataset
|
| 88 |
+
if samples_per_gpu is not None:
|
| 89 |
+
self.samples_per_gpu = samples_per_gpu
|
| 90 |
+
self.bucket_map = self.total_size_bucketwise = None
|
| 91 |
+
if hasattr(dataset, 'bucket_ids'):
|
| 92 |
+
self._init_bucket_sampler(dataset)
|
| 93 |
+
else:
|
| 94 |
+
self._init_sampler(dataset)
|
| 95 |
+
|
| 96 |
+
def set_iter(self, iteration):
|
| 97 |
+
num_batches = self.num_samples // self.samples_per_gpu
|
| 98 |
+
self.skip_iter = iteration % num_batches
|
| 99 |
+
|
| 100 |
+
def __iter__(self):
|
| 101 |
+
if self.bucket_map is None:
|
| 102 |
+
if self.shuffle:
|
| 103 |
+
g = torch.Generator()
|
| 104 |
+
g.manual_seed(self.seed + self.epoch)
|
| 105 |
+
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
| 106 |
+
else:
|
| 107 |
+
indices = torch.arange(len(self.dataset)).tolist()
|
| 108 |
+
# add extra samples to make it evenly divisible
|
| 109 |
+
indices += indices[:(self.total_size - len(indices))]
|
| 110 |
+
assert len(indices) == self.total_size
|
| 111 |
+
# subsample
|
| 112 |
+
indices = indices[self.rank:self.total_size:self.num_replicas]
|
| 113 |
+
|
| 114 |
+
else: # guarantees that batch samples are from the same bucket
|
| 115 |
+
if self.shuffle:
|
| 116 |
+
g = torch.Generator()
|
| 117 |
+
g.manual_seed(self.seed + self.epoch)
|
| 118 |
+
else:
|
| 119 |
+
g = None
|
| 120 |
+
indices = []
|
| 121 |
+
for bucket_id, data_indices in self.bucket_map.items():
|
| 122 |
+
data_indices = torch.tensor(data_indices)
|
| 123 |
+
if g is not None:
|
| 124 |
+
data_indices = data_indices[torch.randperm(len(data_indices), generator=g)]
|
| 125 |
+
pad = self.total_size_bucketwise[bucket_id] - data_indices.numel()
|
| 126 |
+
if pad:
|
| 127 |
+
data_indices = torch.cat([data_indices, data_indices[:pad]], dim=0)
|
| 128 |
+
assert data_indices.numel() == self.total_size_bucketwise[bucket_id]
|
| 129 |
+
_total_num_batches = self.total_size_bucketwise[bucket_id] // self.samples_per_gpu
|
| 130 |
+
_num_batches = _total_num_batches // self.num_replicas
|
| 131 |
+
_total_leftover_batches = _total_num_batches % self.num_replicas
|
| 132 |
+
# data_indices_a: evenly split batches for full round-robins across replicas
|
| 133 |
+
# data_indices_b: the leftover partial round-robin
|
| 134 |
+
data_indices_a = data_indices[:(_num_batches * self.num_replicas * self.samples_per_gpu)].reshape(
|
| 135 |
+
_num_batches, self.samples_per_gpu, self.num_replicas
|
| 136 |
+
).permute(0, 2, 1).reshape(
|
| 137 |
+
_num_batches * self.num_replicas, self.samples_per_gpu)
|
| 138 |
+
data_indices_b = data_indices[(_num_batches * self.num_replicas * self.samples_per_gpu):].reshape(
|
| 139 |
+
self.samples_per_gpu, _total_leftover_batches
|
| 140 |
+
).permute(1, 0)
|
| 141 |
+
indices.extend([data_indices_a, data_indices_b])
|
| 142 |
+
indices = torch.cat(indices, dim=0) # (total_num_batches, samples_per_gpu)
|
| 143 |
+
if g is not None:
|
| 144 |
+
indices = indices[torch.randperm(indices.size(0), generator=g)]
|
| 145 |
+
total_num_batches = self.total_size // self.samples_per_gpu
|
| 146 |
+
pad = total_num_batches - indices.size(0)
|
| 147 |
+
if pad:
|
| 148 |
+
indices = torch.cat([indices, indices[:pad]], dim=0)
|
| 149 |
+
assert indices.numel() == self.total_size
|
| 150 |
+
indices = indices[self.rank:total_num_batches:self.num_replicas].flatten().tolist()
|
| 151 |
+
|
| 152 |
+
assert len(indices) == self.num_samples
|
| 153 |
+
skip_len = self.skip_iter * self.samples_per_gpu
|
| 154 |
+
assert skip_len < self.num_samples
|
| 155 |
+
indices = indices[skip_len:]
|
| 156 |
+
self.skip_iter = 0
|
| 157 |
+
|
| 158 |
+
return iter(indices)
|
lakonlab/evaluation/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .metrics import FIDKID, PR, InceptionMetrics, ColorStats, HPSv2, CLIPSimilarity
|
| 2 |
+
from .vqa_score import VQAScore
|
| 3 |
+
from .hpsv3 import HPSv3
|
| 4 |
+
from .eval_hooks import GenerativeEvalHook
|
| 5 |
+
|
| 6 |
+
__all__ = ['GenerativeEvalHook', 'FIDKID', 'PR',
|
| 7 |
+
'InceptionMetrics', 'ColorStats', 'HPSv2', 'VQAScore', 'CLIPSimilarity',
|
| 8 |
+
'HPSv3']
|
lakonlab/evaluation/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (553 Bytes). View file
|
|
|
lakonlab/evaluation/__pycache__/eval_hooks.cpython-310.pyc
ADDED
|
Binary file (8.93 kB). View file
|
|
|
lakonlab/evaluation/__pycache__/hpsv3.cpython-310.pyc
ADDED
|
Binary file (17 kB). View file
|
|
|
lakonlab/evaluation/__pycache__/metrics.cpython-310.pyc
ADDED
|
Binary file (35.1 kB). View file
|
|
|
lakonlab/evaluation/__pycache__/vqa_score.cpython-310.pyc
ADDED
|
Binary file (21.5 kB). View file
|
|
|
lakonlab/evaluation/eval_hooks.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import unicodedata
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import torch.distributed as dist
|
| 10 |
+
import mmcv
|
| 11 |
+
|
| 12 |
+
from copy import deepcopy
|
| 13 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 14 |
+
from mmcv.runner import HOOKS, get_dist_info
|
| 15 |
+
from mmcv.fileio import FileClient
|
| 16 |
+
from mmgen.models.architectures.common import get_module_device
|
| 17 |
+
from mmgen.core import GenerativeEvalHook as _GenerativeEvalHook
|
| 18 |
+
from lakonlab.utils.io_utils import save_image, save_video, load_images_parallel
|
| 19 |
+
from lakonlab.runner.timer import default_timers
|
| 20 |
+
from lakonlab.utils import gc_context
|
| 21 |
+
from lakonlab.ui.media_viewer import write_html
|
| 22 |
+
|
| 23 |
+
default_timers.add_timer('total time')
|
| 24 |
+
|
| 25 |
+
_reserved = {"CON", "PRN", "AUX", "NUL", *(f"COM{i}" for i in range(1, 10)), *(f"LPT{i}" for i in range(1, 10))}
|
| 26 |
+
_invalid = re.compile(r'[<>:"/\\|?*\:%]+')
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def flatten_list(lst):
|
| 30 |
+
for item in lst:
|
| 31 |
+
if isinstance(item, list):
|
| 32 |
+
yield from flatten_list(item) # recurse into sub-list
|
| 33 |
+
else:
|
| 34 |
+
yield item
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _safe_name(name: str, max_len: int = 160) -> str:
|
| 38 |
+
s = unicodedata.normalize("NFKC", str(name))
|
| 39 |
+
s = "".join(c for c in s if 32 <= ord(c) != 127) # drop control chars
|
| 40 |
+
s = _invalid.sub("", s).strip(" ._") # strip invalid chars
|
| 41 |
+
if s.startswith("."):
|
| 42 |
+
s = s.lstrip(".") # avoid hidden files
|
| 43 |
+
if s.upper() in _reserved:
|
| 44 |
+
s += "_" # avoid reserved names
|
| 45 |
+
s = (s or "untitled")[:max_len].rstrip(" .") # truncate & clean
|
| 46 |
+
return s or "untitled"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def evaluate(model, dataloader, metrics=None,
|
| 50 |
+
feed_batch_size=32, viz_dir=None, viz_num=None, sample_kwargs=dict(),
|
| 51 |
+
fps=16, enable_timers=False, reuse_viz=False):
|
| 52 |
+
has_metrics = metrics is not None and len(metrics) > 0
|
| 53 |
+
if has_metrics:
|
| 54 |
+
for metric in metrics:
|
| 55 |
+
if hasattr(metric, 'load_to_gpu'):
|
| 56 |
+
metric.load_to_gpu()
|
| 57 |
+
|
| 58 |
+
if enable_timers:
|
| 59 |
+
default_timers.enable_all()
|
| 60 |
+
|
| 61 |
+
batch_size = dataloader.batch_size
|
| 62 |
+
rank, ws = get_dist_info()
|
| 63 |
+
total_batch_size = batch_size * ws
|
| 64 |
+
|
| 65 |
+
max_num_fakes = len(dataloader.dataset)
|
| 66 |
+
|
| 67 |
+
if viz_dir is not None:
|
| 68 |
+
html_entries = []
|
| 69 |
+
saved_data_ids = set()
|
| 70 |
+
file_client = FileClient.infer_client(uri=viz_dir)
|
| 71 |
+
executor = ThreadPoolExecutor(max_workers=(os.cpu_count() or 4) * 4)
|
| 72 |
+
else:
|
| 73 |
+
html_entries = saved_data_ids = file_client = executor = None
|
| 74 |
+
|
| 75 |
+
if rank == 0:
|
| 76 |
+
mmcv.print_log(
|
| 77 |
+
f'Generate {max_num_fakes} fake samples for evaluation', 'mmgen')
|
| 78 |
+
pbar = mmcv.ProgressBar(max_num_fakes)
|
| 79 |
+
|
| 80 |
+
log_vars = dict()
|
| 81 |
+
batch_size_list = []
|
| 82 |
+
|
| 83 |
+
for i, data in enumerate(dataloader):
|
| 84 |
+
can_reuse = False
|
| 85 |
+
loaded_imgs_tensor = None
|
| 86 |
+
loaded_html_entries = None
|
| 87 |
+
|
| 88 |
+
if viz_dir is not None and reuse_viz:
|
| 89 |
+
batch_names = list(flatten_list(data['name'].data))
|
| 90 |
+
ids = list(flatten_list(data['ids'].data))
|
| 91 |
+
|
| 92 |
+
reuse_candidates = []
|
| 93 |
+
all_png_exist = True
|
| 94 |
+
|
| 95 |
+
for name, data_id in zip(batch_names, ids):
|
| 96 |
+
safe_name = _safe_name(name)
|
| 97 |
+
filename = f'{data_id:09d}_{safe_name}.png'
|
| 98 |
+
filepath = file_client.join_path(viz_dir, filename)
|
| 99 |
+
if file_client.isfile(filepath):
|
| 100 |
+
reuse_candidates.append((data_id, name, filename, filepath))
|
| 101 |
+
else:
|
| 102 |
+
all_png_exist = False
|
| 103 |
+
break
|
| 104 |
+
|
| 105 |
+
if all_png_exist:
|
| 106 |
+
filepaths = []
|
| 107 |
+
loaded_html_entries = []
|
| 108 |
+
for (data_id, name, filename, filepath) in reuse_candidates:
|
| 109 |
+
filepaths.append(filepath)
|
| 110 |
+
rel_filepath = file_client.join_path(os.path.basename(viz_dir), filename)
|
| 111 |
+
loaded_html_entries.append((data_id, rel_filepath, name))
|
| 112 |
+
try:
|
| 113 |
+
loaded_imgs_tensor = torch.from_numpy(
|
| 114 |
+
np.stack(load_images_parallel(filepaths, file_client), axis=0)
|
| 115 |
+
).permute(0, 3, 1, 2).float() / 255.0
|
| 116 |
+
can_reuse = True
|
| 117 |
+
except Exception as e:
|
| 118 |
+
can_reuse = False
|
| 119 |
+
loaded_imgs_tensor = None
|
| 120 |
+
loaded_html_entries = None
|
| 121 |
+
|
| 122 |
+
if can_reuse:
|
| 123 |
+
outputs_dict = dict(
|
| 124 |
+
pred_imgs=loaded_imgs_tensor, # [0,1] float32
|
| 125 |
+
num_samples=loaded_imgs_tensor.size(0))
|
| 126 |
+
html_entries.extend(loaded_html_entries)
|
| 127 |
+
|
| 128 |
+
else:
|
| 129 |
+
# fall back to generation
|
| 130 |
+
sample_kwargs_ = deepcopy(sample_kwargs)
|
| 131 |
+
|
| 132 |
+
with default_timers['total time']:
|
| 133 |
+
outputs_dict = model.val_step(data, show_pbar=rank == 0, **sample_kwargs_)
|
| 134 |
+
|
| 135 |
+
if viz_dir is not None:
|
| 136 |
+
batch_names = list(flatten_list(data['name'].data))
|
| 137 |
+
for batch_id, data_id in enumerate(flatten_list(data['ids'].data)):
|
| 138 |
+
if (viz_num is not None and data_id >= viz_num) or (data_id in saved_data_ids):
|
| 139 |
+
continue
|
| 140 |
+
name = batch_names[batch_id]
|
| 141 |
+
safe_name = _safe_name(name)
|
| 142 |
+
|
| 143 |
+
image_viz = (outputs_dict['pred_imgs'][batch_id] * 255).round().to(torch.uint8)
|
| 144 |
+
if image_viz.dim() == 3: # image
|
| 145 |
+
image_viz = image_viz.permute(1, 2, 0).cpu().numpy()
|
| 146 |
+
filename = f'{data_id:09d}_{safe_name}.png'
|
| 147 |
+
executor.submit(
|
| 148 |
+
save_image,
|
| 149 |
+
image_viz, file_client.join_path(viz_dir, filename), file_client)
|
| 150 |
+
elif image_viz.dim() == 4: # video
|
| 151 |
+
image_viz = image_viz.permute(1, 2, 3, 0).cpu().numpy() # (t, h, w, c)
|
| 152 |
+
filename = f'{data_id:09d}_{safe_name}.mp4'
|
| 153 |
+
executor.submit(
|
| 154 |
+
save_video,
|
| 155 |
+
image_viz, file_client.join_path(viz_dir, filename), file_client, fps)
|
| 156 |
+
else:
|
| 157 |
+
raise ValueError(f'Unsupported image dimension: {image_viz.dim()}')
|
| 158 |
+
rel_filepath = file_client.join_path(os.path.basename(viz_dir), filename)
|
| 159 |
+
html_entries.append((data_id, rel_filepath, name))
|
| 160 |
+
saved_data_ids.add(data_id)
|
| 161 |
+
|
| 162 |
+
if 'log_vars' in outputs_dict:
|
| 163 |
+
for k, v in outputs_dict['log_vars'].items():
|
| 164 |
+
if k in log_vars:
|
| 165 |
+
log_vars[k].append(outputs_dict['log_vars'][k])
|
| 166 |
+
else:
|
| 167 |
+
log_vars[k] = [outputs_dict['log_vars'][k]]
|
| 168 |
+
batch_size_list.append(outputs_dict['num_samples'])
|
| 169 |
+
|
| 170 |
+
if has_metrics:
|
| 171 |
+
pred_imgs = outputs_dict['pred_imgs'].split(feed_batch_size, dim=0)
|
| 172 |
+
real_imgs = None
|
| 173 |
+
if 'images' in data:
|
| 174 |
+
real_imgs = data['images']
|
| 175 |
+
elif 'target_imgs' in outputs_dict:
|
| 176 |
+
real_imgs = outputs_dict['target_imgs']
|
| 177 |
+
if real_imgs is not None:
|
| 178 |
+
real_imgs = real_imgs.split(feed_batch_size, dim=0)
|
| 179 |
+
requires_prompt = False
|
| 180 |
+
for metric in metrics:
|
| 181 |
+
requires_prompt |= getattr(metric, 'requires_prompt', False)
|
| 182 |
+
if requires_prompt:
|
| 183 |
+
prompts = list(flatten_list(data['name'].data)) # list of prompts
|
| 184 |
+
prompts = [prompts[i:i + feed_batch_size] for i in range(0, len(prompts), feed_batch_size)]
|
| 185 |
+
for metric in metrics:
|
| 186 |
+
for batch_id, batch_imgs in enumerate(pred_imgs):
|
| 187 |
+
if getattr(metric, 'requires_prompt', False):
|
| 188 |
+
metric.feed(
|
| 189 |
+
dict(imgs=batch_imgs * 2 - 1, prompts=prompts[batch_id]), 'fakes')
|
| 190 |
+
if real_imgs is not None:
|
| 191 |
+
metric.feed(
|
| 192 |
+
dict(imgs=real_imgs[batch_id] * 2 - 1, prompts=prompts[batch_id]), 'reals')
|
| 193 |
+
else:
|
| 194 |
+
metric.feed(batch_imgs * 2 - 1, 'fakes')
|
| 195 |
+
if real_imgs is not None:
|
| 196 |
+
metric.feed(real_imgs[batch_id] * 2 - 1, 'reals')
|
| 197 |
+
|
| 198 |
+
if rank == 0:
|
| 199 |
+
pbar.update(total_batch_size)
|
| 200 |
+
|
| 201 |
+
if ws > 1:
|
| 202 |
+
device = get_module_device(model)
|
| 203 |
+
batch_size_list = torch.tensor(batch_size_list, dtype=torch.float, device=device)
|
| 204 |
+
batch_size_sum = torch.sum(batch_size_list)
|
| 205 |
+
dist.all_reduce(batch_size_sum, op=dist.ReduceOp.SUM)
|
| 206 |
+
for k, v in log_vars.items():
|
| 207 |
+
weigted_values = torch.tensor(log_vars[k], dtype=torch.float, device=device) * batch_size_list
|
| 208 |
+
weigted_values_sum = torch.sum(weigted_values)
|
| 209 |
+
dist.all_reduce(weigted_values_sum, op=dist.ReduceOp.SUM)
|
| 210 |
+
log_vars[k] = float(weigted_values_sum / batch_size_sum)
|
| 211 |
+
else:
|
| 212 |
+
for k, v in log_vars.items():
|
| 213 |
+
log_vars[k] = np.average(log_vars[k], weights=batch_size_list)
|
| 214 |
+
|
| 215 |
+
if viz_dir is not None:
|
| 216 |
+
if ws > 1:
|
| 217 |
+
gathered = [None for _ in range(ws)]
|
| 218 |
+
dist.all_gather_object(gathered, html_entries)
|
| 219 |
+
if rank == 0:
|
| 220 |
+
html_entries = [e for sub in gathered for e in (sub or [])]
|
| 221 |
+
if rank == 0:
|
| 222 |
+
unique_entries = dict()
|
| 223 |
+
for entry in html_entries:
|
| 224 |
+
data_id = entry[0]
|
| 225 |
+
if data_id not in unique_entries:
|
| 226 |
+
unique_entries[data_id] = entry
|
| 227 |
+
html_entries = list(unique_entries.values())
|
| 228 |
+
html_entries.sort(key=lambda item: item[0])
|
| 229 |
+
html_path = file_client.join_path(os.path.dirname(viz_dir), os.path.basename(viz_dir) + '.html')
|
| 230 |
+
write_html(html_path, html_entries, file_client)
|
| 231 |
+
executor.shutdown(wait=True)
|
| 232 |
+
|
| 233 |
+
return log_vars
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
@HOOKS.register_module(force=True)
|
| 237 |
+
class GenerativeEvalHook(_GenerativeEvalHook):
|
| 238 |
+
greater_keys = ['acc', 'top', 'AR@', 'auc', 'precision', 'mAP', 'is', 'test_ssim', 'test_psnr']
|
| 239 |
+
less_keys = ['loss', 'fid', 'kid', 'test_lpips']
|
| 240 |
+
_supported_best_metrics = ['fid', 'kid', 'is', 'test_ssim', 'test_psnr', 'test_lpips']
|
| 241 |
+
|
| 242 |
+
def __init__(self,
|
| 243 |
+
*args,
|
| 244 |
+
data='',
|
| 245 |
+
viz_dir=None,
|
| 246 |
+
feed_batch_size=32,
|
| 247 |
+
viz_num=None,
|
| 248 |
+
clear_reals=False,
|
| 249 |
+
prefix='',
|
| 250 |
+
metric_cpu_offload=False,
|
| 251 |
+
**kwargs):
|
| 252 |
+
super(GenerativeEvalHook, self).__init__(*args, **kwargs)
|
| 253 |
+
self.data = data
|
| 254 |
+
self.viz_dir = viz_dir
|
| 255 |
+
self.file_client = FileClient.infer_client(
|
| 256 |
+
uri=viz_dir) if viz_dir is not None else None
|
| 257 |
+
self.feed_batch_size = feed_batch_size
|
| 258 |
+
self.viz_num = viz_num
|
| 259 |
+
self.clear_reals = clear_reals
|
| 260 |
+
self.prefix = prefix
|
| 261 |
+
self.metric_cpu_offload = metric_cpu_offload
|
| 262 |
+
|
| 263 |
+
@torch.no_grad()
|
| 264 |
+
def after_train_iter(self, runner):
|
| 265 |
+
with gc_context(enable=True):
|
| 266 |
+
interval = self.get_current_interval(runner)
|
| 267 |
+
if not self.every_n_iters(runner, interval):
|
| 268 |
+
return
|
| 269 |
+
|
| 270 |
+
runner.model.eval()
|
| 271 |
+
rank, ws = get_dist_info()
|
| 272 |
+
|
| 273 |
+
if self.viz_dir is not None:
|
| 274 |
+
viz_dir = self.file_client.join_path(self.viz_dir, str(runner.iter + 1))
|
| 275 |
+
if rank == 0:
|
| 276 |
+
if self.file_client.exists(viz_dir):
|
| 277 |
+
for name in self.file_client.list_dir_or_file(viz_dir):
|
| 278 |
+
self.file_client.remove(self.file_client.join_path(viz_dir, name))
|
| 279 |
+
if ws > 1:
|
| 280 |
+
dist.barrier()
|
| 281 |
+
else:
|
| 282 |
+
viz_dir = None
|
| 283 |
+
log_vars = evaluate(
|
| 284 |
+
runner.model, self.dataloader, self.metrics, self.feed_batch_size,
|
| 285 |
+
viz_dir, self.viz_num, self.sample_kwargs)
|
| 286 |
+
|
| 287 |
+
if len(runner.log_buffer.output) == 0:
|
| 288 |
+
runner.log_buffer.clear()
|
| 289 |
+
|
| 290 |
+
# a dirty walkround to change the line at the end of pbar
|
| 291 |
+
if rank == 0:
|
| 292 |
+
sys.stdout.write('\n')
|
| 293 |
+
for metric in self.metrics:
|
| 294 |
+
metric.summary()
|
| 295 |
+
for name, val in metric._result_dict.items():
|
| 296 |
+
prefix_name = self.prefix + '_' + name if len(self.prefix) > 0 else name
|
| 297 |
+
runner.log_buffer.output[self.data + '_' + prefix_name] = val
|
| 298 |
+
# record best metric and save the best ckpt
|
| 299 |
+
if self.save_best_ckpt and name in self.best_metric:
|
| 300 |
+
self._save_best_ckpt(runner, val, name)
|
| 301 |
+
for name, val in log_vars.items():
|
| 302 |
+
prefix_name = self.prefix + '_' + name if len(self.prefix) > 0 else name
|
| 303 |
+
# print(self.data + '_' + prefix_name + ' = {}'.format(val))
|
| 304 |
+
runner.log_buffer.output[self.data + '_' + prefix_name] = val
|
| 305 |
+
# record best metric and save the best ckpt
|
| 306 |
+
if self.save_best_ckpt and name in self.best_metric:
|
| 307 |
+
self._save_best_ckpt(runner, val, name)
|
| 308 |
+
runner.log_buffer.ready = True
|
| 309 |
+
|
| 310 |
+
runner.model.train()
|
| 311 |
+
|
| 312 |
+
for metric in self.metrics:
|
| 313 |
+
metric.clear(clear_reals=self.clear_reals)
|
| 314 |
+
if self.metric_cpu_offload:
|
| 315 |
+
if hasattr(metric, 'offload_to_cpu'):
|
| 316 |
+
metric.offload_to_cpu()
|
| 317 |
+
|
| 318 |
+
torch.cuda.empty_cache()
|
lakonlab/evaluation/hpsv3.py
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modified from https://github.com/MizzenAI/HPSv3
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import torch.distributed as dist
|
| 9 |
+
import mmcv
|
| 10 |
+
|
| 11 |
+
from typing import List, Optional, Union
|
| 12 |
+
from torch.distributed.fsdp import MixedPrecision, ShardingStrategy, FullyShardedDataParallel
|
| 13 |
+
from torch.distributed.fsdp.wrap import ModuleWrapPolicy
|
| 14 |
+
from accelerate import init_empty_weights
|
| 15 |
+
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
|
| 16 |
+
from transformers.image_processing_utils import BaseImageProcessor, BatchFeature
|
| 17 |
+
from transformers.image_utils import (
|
| 18 |
+
OPENAI_CLIP_MEAN,
|
| 19 |
+
OPENAI_CLIP_STD,
|
| 20 |
+
)
|
| 21 |
+
from transformers.utils import TensorType
|
| 22 |
+
from transformers.models.qwen2_vl.modeling_qwen2_vl import Qwen2VLVisionBlock, Qwen2VLDecoderLayer
|
| 23 |
+
from mmcv.runner import get_dist_info
|
| 24 |
+
from mmgen.core.registry import METRICS
|
| 25 |
+
from mmgen.core.evaluation.metrics import Metric
|
| 26 |
+
from lakonlab.runner.checkpoint import _load_checkpoint
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
INSTRUCTION = """
|
| 30 |
+
You are tasked with evaluating a generated image based on Visual Quality and Text Alignment and give a overall score to estimate the human preference. Please provide a rating from 0 to 10, with 0 being the worst and 10 being the best.
|
| 31 |
+
|
| 32 |
+
**Visual Quality:**
|
| 33 |
+
Evaluate the overall visual quality of the image. The following sub-dimensions should be considered:
|
| 34 |
+
- **Reasonableness:** The image should not contain any significant biological or logical errors, such as abnormal body structures or nonsensical environmental setups.
|
| 35 |
+
- **Clarity:** Evaluate the sharpness and visibility of the image. The image should be clear and easy to interpret, with no blurring or indistinct areas.
|
| 36 |
+
- **Detail Richness:** Consider the level of detail in textures, materials, lighting, and other visual elements (e.g., hair, clothing, shadows).
|
| 37 |
+
- **Aesthetic and Creativity:** Assess the artistic aspects of the image, including the color scheme, composition, atmosphere, depth of field, and the overall creative appeal. The scene should convey a sense of harmony and balance.
|
| 38 |
+
- **Safety:** The image should not contain harmful or inappropriate content, such as political, violent, or adult material. If such content is present, the image quality and satisfaction score should be the lowest possible.
|
| 39 |
+
|
| 40 |
+
**Text Alignment:**
|
| 41 |
+
Assess how well the image matches the textual prompt across the following sub-dimensions:
|
| 42 |
+
- **Subject Relevance** Evaluate how accurately the subject(s) in the image (e.g., person, animal, object) align with the textual description. The subject should match the description in terms of number, appearance, and behavior.
|
| 43 |
+
- **Style Relevance:** If the prompt specifies a particular artistic or stylistic style, evaluate how well the image adheres to this style.
|
| 44 |
+
- **Contextual Consistency**: Assess whether the background, setting, and surrounding elements in the image logically fit the scenario described in the prompt. The environment should support and enhance the subject without contradictions.
|
| 45 |
+
- **Attribute Fidelity**: Check if specific attributes mentioned in the prompt (e.g., colors, clothing, accessories, expressions, actions) are faithfully represented in the image. Minor deviations may be acceptable, but critical attributes should be preserved.
|
| 46 |
+
- **Semantic Coherence**: Evaluate whether the overall meaning and intent of the prompt are captured in the image. The generated content should not introduce elements that conflict with or distort the original description.
|
| 47 |
+
Textual prompt - {text_prompt}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
prompt_with_special_token = """
|
| 53 |
+
Please provide the overall ratings of this image: <|Reward|>
|
| 54 |
+
|
| 55 |
+
END
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
prompt_without_special_token = """
|
| 59 |
+
Please provide the overall ratings of this image:
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def smart_resize(
|
| 64 |
+
height: int, width: int, factor: int = 28, min_pixels: int = 56 * 56, max_pixels: int = 14 * 14 * 4 * 1280):
|
| 65 |
+
"""Rescales the image so that the following conditions are met:
|
| 66 |
+
|
| 67 |
+
1. Both dimensions (height and width) are divisible by 'factor'.
|
| 68 |
+
|
| 69 |
+
2. The total number of pixels is within the range ['min_pixels', 'max_pixels'].
|
| 70 |
+
|
| 71 |
+
3. The aspect ratio of the image is maintained as closely as possible.
|
| 72 |
+
|
| 73 |
+
"""
|
| 74 |
+
if height < factor or width < factor:
|
| 75 |
+
raise ValueError(f"height:{height} or width:{width} must be larger than factor:{factor}")
|
| 76 |
+
elif max(height, width) / min(height, width) > 200:
|
| 77 |
+
raise ValueError(
|
| 78 |
+
f"absolute aspect ratio must be smaller than 200, got {max(height, width) / min(height, width)}"
|
| 79 |
+
)
|
| 80 |
+
h_bar = round(height / factor) * factor
|
| 81 |
+
w_bar = round(width / factor) * factor
|
| 82 |
+
if h_bar * w_bar > max_pixels:
|
| 83 |
+
beta = math.sqrt((height * width) / max_pixels)
|
| 84 |
+
h_bar = math.floor(height / beta / factor) * factor
|
| 85 |
+
w_bar = math.floor(width / beta / factor) * factor
|
| 86 |
+
elif h_bar * w_bar < min_pixels:
|
| 87 |
+
beta = math.sqrt(min_pixels / (height * width))
|
| 88 |
+
h_bar = math.ceil(height * beta / factor) * factor
|
| 89 |
+
w_bar = math.ceil(width * beta / factor) * factor
|
| 90 |
+
return h_bar, w_bar
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class Qwen2VLImageProcessor(BaseImageProcessor):
|
| 94 |
+
model_input_names = ["pixel_values", "image_grid_thw", "pixel_values_videos", "video_grid_thw"]
|
| 95 |
+
|
| 96 |
+
def __init__(
|
| 97 |
+
self,
|
| 98 |
+
do_resize: bool = True,
|
| 99 |
+
do_normalize: bool = True,
|
| 100 |
+
image_mean: Optional[Union[float, List[float]]] = None,
|
| 101 |
+
image_std: Optional[Union[float, List[float]]] = None,
|
| 102 |
+
min_pixels: int = 256 * 28 * 28,
|
| 103 |
+
max_pixels: int = 256 * 28 * 28,
|
| 104 |
+
patch_size: int = 14,
|
| 105 |
+
temporal_patch_size: int = 2,
|
| 106 |
+
merge_size: int = 2,
|
| 107 |
+
**kwargs):
|
| 108 |
+
super().__init__(**kwargs)
|
| 109 |
+
self.do_resize = do_resize
|
| 110 |
+
self.do_normalize = do_normalize
|
| 111 |
+
self.image_mean = image_mean if image_mean is not None else OPENAI_CLIP_MEAN
|
| 112 |
+
self.image_std = image_std if image_std is not None else OPENAI_CLIP_STD
|
| 113 |
+
self.min_pixels = min_pixels
|
| 114 |
+
self.max_pixels = max_pixels
|
| 115 |
+
self.patch_size = patch_size
|
| 116 |
+
self.temporal_patch_size = temporal_patch_size
|
| 117 |
+
self.merge_size = merge_size
|
| 118 |
+
|
| 119 |
+
def _preprocess(
|
| 120 |
+
self,
|
| 121 |
+
images: torch.Tensor,
|
| 122 |
+
do_resize: bool = None,
|
| 123 |
+
do_normalize: bool = None,
|
| 124 |
+
image_mean: Optional[Union[float, List[float]]] = None,
|
| 125 |
+
image_std: Optional[Union[float, List[float]]] = None):
|
| 126 |
+
batch_size, channel, height, width = images.size()
|
| 127 |
+
if do_resize:
|
| 128 |
+
resized_height, resized_width = smart_resize(
|
| 129 |
+
height,
|
| 130 |
+
width,
|
| 131 |
+
factor=self.patch_size * self.merge_size,
|
| 132 |
+
min_pixels=self.min_pixels,
|
| 133 |
+
max_pixels=self.max_pixels,
|
| 134 |
+
)
|
| 135 |
+
images = F.interpolate(
|
| 136 |
+
images,
|
| 137 |
+
size=(resized_height, resized_width),
|
| 138 |
+
mode='bicubic',
|
| 139 |
+
align_corners=False,
|
| 140 |
+
antialias=True,
|
| 141 |
+
).clamp(min=0, max=1)
|
| 142 |
+
else:
|
| 143 |
+
resized_height, resized_width = height, width
|
| 144 |
+
|
| 145 |
+
if do_normalize:
|
| 146 |
+
mean = torch.tensor(image_mean, device=images.device, dtype=images.dtype).view(-1, 1, 1)
|
| 147 |
+
std = torch.tensor(image_std, device=images.device, dtype=images.dtype).view(-1, 1, 1)
|
| 148 |
+
images = (images - mean) / std
|
| 149 |
+
|
| 150 |
+
patches = images.unsqueeze(1).expand(-1, self.temporal_patch_size, -1, -1, -1)
|
| 151 |
+
|
| 152 |
+
grid_t = 1
|
| 153 |
+
grid_h, grid_w = resized_height // self.patch_size, resized_width // self.patch_size
|
| 154 |
+
|
| 155 |
+
patches = patches.reshape(
|
| 156 |
+
batch_size * grid_t,
|
| 157 |
+
self.temporal_patch_size,
|
| 158 |
+
channel,
|
| 159 |
+
grid_h // self.merge_size,
|
| 160 |
+
self.merge_size,
|
| 161 |
+
self.patch_size,
|
| 162 |
+
grid_w // self.merge_size,
|
| 163 |
+
self.merge_size,
|
| 164 |
+
self.patch_size,
|
| 165 |
+
)
|
| 166 |
+
patches = patches.permute(0, 3, 6, 4, 7, 2, 1, 5, 8)
|
| 167 |
+
flatten_patches = patches.reshape(
|
| 168 |
+
batch_size * grid_t * grid_h * grid_w, channel * self.temporal_patch_size * self.patch_size * self.patch_size
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
return flatten_patches, np.array((grid_t, grid_h, grid_w)).reshape(1, 3).repeat(batch_size, axis=0)
|
| 172 |
+
|
| 173 |
+
def preprocess(
|
| 174 |
+
self,
|
| 175 |
+
images: torch.Tensor,
|
| 176 |
+
return_tensors: Optional[Union[str, TensorType]] = None):
|
| 177 |
+
pixel_values, vision_grid_thws = self._preprocess(
|
| 178 |
+
images,
|
| 179 |
+
do_resize=self.do_resize,
|
| 180 |
+
do_normalize=self.do_normalize,
|
| 181 |
+
image_mean=self.image_mean,
|
| 182 |
+
image_std=self.image_std,
|
| 183 |
+
)
|
| 184 |
+
data = {"pixel_values": pixel_values, "image_grid_thw": vision_grid_thws}
|
| 185 |
+
return BatchFeature(data=data, tensor_type=return_tensors)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class Qwen2VLRewardModelBT(Qwen2VLForConditionalGeneration):
|
| 189 |
+
|
| 190 |
+
def __init__(
|
| 191 |
+
self,
|
| 192 |
+
config,
|
| 193 |
+
output_dim=4,
|
| 194 |
+
reward_token="last",
|
| 195 |
+
special_token_ids=None,
|
| 196 |
+
rm_head_type="default",
|
| 197 |
+
rm_head_kwargs=None,
|
| 198 |
+
):
|
| 199 |
+
super().__init__(config)
|
| 200 |
+
# pdb.set_trace()
|
| 201 |
+
self.output_dim = output_dim
|
| 202 |
+
if rm_head_type == "default":
|
| 203 |
+
self.rm_head = nn.Linear(config.hidden_size, output_dim, bias=False)
|
| 204 |
+
elif rm_head_type == "ranknet":
|
| 205 |
+
if rm_head_kwargs is not None:
|
| 206 |
+
for layer in range(rm_head_kwargs.get("num_layers", 3)):
|
| 207 |
+
if layer == 0:
|
| 208 |
+
self.rm_head = nn.Sequential(
|
| 209 |
+
nn.Linear(config.hidden_size, rm_head_kwargs["hidden_size"]),
|
| 210 |
+
nn.ReLU(),
|
| 211 |
+
nn.Dropout(rm_head_kwargs.get("dropout", 0.1)),
|
| 212 |
+
)
|
| 213 |
+
elif layer < rm_head_kwargs.get("num_layers", 3) - 1:
|
| 214 |
+
self.rm_head.add_module(
|
| 215 |
+
f"layer_{layer}",
|
| 216 |
+
nn.Sequential(
|
| 217 |
+
nn.Linear(rm_head_kwargs["hidden_size"], rm_head_kwargs["hidden_size"]),
|
| 218 |
+
nn.ReLU(),
|
| 219 |
+
nn.Dropout(rm_head_kwargs.get("dropout", 0.1)),
|
| 220 |
+
),
|
| 221 |
+
)
|
| 222 |
+
else:
|
| 223 |
+
self.rm_head.add_module(
|
| 224 |
+
f"output_layer",
|
| 225 |
+
nn.Linear(rm_head_kwargs["hidden_size"], output_dim, bias=rm_head_kwargs.get("bias", False)),
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
else:
|
| 229 |
+
self.rm_head = nn.Sequential(
|
| 230 |
+
nn.Linear(config.hidden_size, 1024),
|
| 231 |
+
nn.ReLU(),
|
| 232 |
+
nn.Dropout(0.05),
|
| 233 |
+
nn.Linear(1024, 16),
|
| 234 |
+
nn.ReLU(),
|
| 235 |
+
nn.Linear(16, output_dim),
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
self.rm_head.to(torch.float32)
|
| 239 |
+
self.reward_token = reward_token
|
| 240 |
+
|
| 241 |
+
self.special_token_ids = special_token_ids
|
| 242 |
+
if self.special_token_ids is not None:
|
| 243 |
+
self.reward_token = "special"
|
| 244 |
+
|
| 245 |
+
def forward(
|
| 246 |
+
self,
|
| 247 |
+
input_ids: torch.LongTensor = None,
|
| 248 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 249 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 250 |
+
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
| 251 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 252 |
+
labels: Optional[torch.LongTensor] = None,
|
| 253 |
+
use_cache: Optional[bool] = None,
|
| 254 |
+
output_attentions: Optional[bool] = None,
|
| 255 |
+
output_hidden_states: Optional[bool] = None,
|
| 256 |
+
return_dict: Optional[bool] = None,
|
| 257 |
+
pixel_values: Optional[torch.Tensor] = None,
|
| 258 |
+
pixel_values_videos: Optional[torch.FloatTensor] = None,
|
| 259 |
+
image_grid_thw: Optional[torch.LongTensor] = None,
|
| 260 |
+
video_grid_thw: Optional[torch.LongTensor] = None,
|
| 261 |
+
rope_deltas: Optional[torch.LongTensor] = None,
|
| 262 |
+
):
|
| 263 |
+
# modified from the origin class Qwen2VLForConditionalGeneration
|
| 264 |
+
output_attentions = (
|
| 265 |
+
output_attentions
|
| 266 |
+
if output_attentions is not None
|
| 267 |
+
else self.config.output_attentions
|
| 268 |
+
)
|
| 269 |
+
output_hidden_states = (
|
| 270 |
+
output_hidden_states
|
| 271 |
+
if output_hidden_states is not None
|
| 272 |
+
else self.config.output_hidden_states
|
| 273 |
+
)
|
| 274 |
+
return_dict = (
|
| 275 |
+
return_dict if return_dict is not None else self.config.use_return_dict
|
| 276 |
+
)
|
| 277 |
+
# pdb.set_trace()
|
| 278 |
+
if inputs_embeds is None:
|
| 279 |
+
inputs_embeds = self.model.language_model.embed_tokens(input_ids)
|
| 280 |
+
if pixel_values is not None:
|
| 281 |
+
pixel_values = pixel_values.type(self.model.visual.get_dtype())
|
| 282 |
+
image_embeds = self.model.visual(pixel_values, grid_thw=image_grid_thw)
|
| 283 |
+
image_mask = (
|
| 284 |
+
(input_ids == self.config.image_token_id)
|
| 285 |
+
.unsqueeze(-1)
|
| 286 |
+
.expand_as(inputs_embeds)
|
| 287 |
+
)
|
| 288 |
+
image_embeds = image_embeds.to(
|
| 289 |
+
inputs_embeds.device, inputs_embeds.dtype
|
| 290 |
+
)
|
| 291 |
+
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
|
| 292 |
+
|
| 293 |
+
if pixel_values_videos is not None:
|
| 294 |
+
pixel_values_videos = pixel_values_videos.type(self.model.visual.get_dtype())
|
| 295 |
+
video_embeds = self.model.visual(pixel_values_videos, grid_thw=video_grid_thw)
|
| 296 |
+
video_mask = (
|
| 297 |
+
(input_ids == self.config.video_token_id)
|
| 298 |
+
.unsqueeze(-1)
|
| 299 |
+
.expand_as(inputs_embeds)
|
| 300 |
+
)
|
| 301 |
+
video_embeds = video_embeds.to(
|
| 302 |
+
inputs_embeds.device, inputs_embeds.dtype
|
| 303 |
+
)
|
| 304 |
+
inputs_embeds = inputs_embeds.masked_scatter(video_mask, video_embeds)
|
| 305 |
+
|
| 306 |
+
if attention_mask is not None:
|
| 307 |
+
attention_mask = attention_mask.to(inputs_embeds.device)
|
| 308 |
+
|
| 309 |
+
outputs = self.model.language_model(
|
| 310 |
+
input_ids=None,
|
| 311 |
+
position_ids=position_ids,
|
| 312 |
+
attention_mask=attention_mask,
|
| 313 |
+
past_key_values=past_key_values,
|
| 314 |
+
inputs_embeds=inputs_embeds,
|
| 315 |
+
use_cache=use_cache,
|
| 316 |
+
output_attentions=output_attentions,
|
| 317 |
+
output_hidden_states=output_hidden_states,
|
| 318 |
+
return_dict=return_dict,
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
hidden_states = outputs[0] # [B, L, D]
|
| 322 |
+
with torch.autocast(device_type='cuda', dtype=torch.float32):
|
| 323 |
+
logits = self.rm_head(hidden_states) # [B, L, N]
|
| 324 |
+
|
| 325 |
+
if input_ids is not None:
|
| 326 |
+
batch_size = input_ids.shape[0]
|
| 327 |
+
else:
|
| 328 |
+
batch_size = inputs_embeds.shape[0]
|
| 329 |
+
|
| 330 |
+
# get sequence length
|
| 331 |
+
if self.config.pad_token_id is None and batch_size != 1:
|
| 332 |
+
raise ValueError(
|
| 333 |
+
"Cannot handle batch sizes > 1 if no padding token is defined."
|
| 334 |
+
)
|
| 335 |
+
if self.config.pad_token_id is None:
|
| 336 |
+
sequence_lengths = -1
|
| 337 |
+
else:
|
| 338 |
+
if input_ids is not None:
|
| 339 |
+
# if no pad token found, use modulo instead of reverse indexing for ONNX compatibility
|
| 340 |
+
sequence_lengths = (
|
| 341 |
+
torch.eq(input_ids, self.config.pad_token_id).int().argmax(-1) - 1
|
| 342 |
+
)
|
| 343 |
+
sequence_lengths = sequence_lengths % input_ids.shape[-1]
|
| 344 |
+
sequence_lengths = sequence_lengths.to(logits.device)
|
| 345 |
+
else:
|
| 346 |
+
sequence_lengths = -1
|
| 347 |
+
|
| 348 |
+
# get the last token's logits
|
| 349 |
+
if self.reward_token == "last":
|
| 350 |
+
pooled_logits = logits[
|
| 351 |
+
torch.arange(batch_size, device=logits.device), sequence_lengths
|
| 352 |
+
]
|
| 353 |
+
elif self.reward_token == "mean":
|
| 354 |
+
# get the mean of all valid tokens' logits
|
| 355 |
+
valid_lengths = torch.clamp(sequence_lengths, min=0, max=logits.size(1) - 1)
|
| 356 |
+
pooled_logits = torch.stack(
|
| 357 |
+
[logits[i, : valid_lengths[i]].mean(dim=0) for i in range(batch_size)]
|
| 358 |
+
)
|
| 359 |
+
elif self.reward_token == "special":
|
| 360 |
+
# special_token_ids = self.tokenizer.convert_tokens_to_ids(self.special_tokens)
|
| 361 |
+
# create a mask for special tokens
|
| 362 |
+
special_token_mask = torch.zeros_like(input_ids, dtype=torch.bool)
|
| 363 |
+
for special_token_id in self.special_token_ids:
|
| 364 |
+
special_token_mask = special_token_mask | (
|
| 365 |
+
input_ids == special_token_id
|
| 366 |
+
)
|
| 367 |
+
pooled_logits = logits[special_token_mask, ...]
|
| 368 |
+
pooled_logits = pooled_logits.view(
|
| 369 |
+
batch_size, 1, -1
|
| 370 |
+
) # [B, 3, N] assert 3 attributes
|
| 371 |
+
pooled_logits = pooled_logits.view(batch_size, -1)
|
| 372 |
+
|
| 373 |
+
# pdb.set_trace()
|
| 374 |
+
else:
|
| 375 |
+
raise ValueError("Invalid reward_token")
|
| 376 |
+
|
| 377 |
+
return {"logits": pooled_logits}
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
_hpsv3_cache = {}
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def load_hpsv3(device, dtype, use_fsdp=True):
|
| 384 |
+
# Create cache key from arguments
|
| 385 |
+
cache_key = f"{device}_{dtype}_{use_fsdp}"
|
| 386 |
+
|
| 387 |
+
# Check if model is already cached
|
| 388 |
+
if cache_key in _hpsv3_cache:
|
| 389 |
+
return _hpsv3_cache[cache_key]
|
| 390 |
+
|
| 391 |
+
processor = AutoProcessor.from_pretrained(
|
| 392 |
+
'Qwen/Qwen2-VL-7B-Instruct', padding_side='right',
|
| 393 |
+
)
|
| 394 |
+
processor.image_processor = Qwen2VLImageProcessor()
|
| 395 |
+
special_tokens = ['<|Reward|>']
|
| 396 |
+
processor.tokenizer.add_special_tokens(
|
| 397 |
+
{'additional_special_tokens': special_tokens}
|
| 398 |
+
)
|
| 399 |
+
special_token_ids = processor.tokenizer.convert_tokens_to_ids(special_tokens)
|
| 400 |
+
|
| 401 |
+
with init_empty_weights():
|
| 402 |
+
config = Qwen2VLRewardModelBT.config_class.from_pretrained(
|
| 403 |
+
'Qwen/Qwen2-VL-7B-Instruct',
|
| 404 |
+
)
|
| 405 |
+
model = Qwen2VLRewardModelBT(
|
| 406 |
+
config,
|
| 407 |
+
output_dim=2,
|
| 408 |
+
reward_token='special',
|
| 409 |
+
special_token_ids=special_token_ids,
|
| 410 |
+
rm_head_type='ranknet',
|
| 411 |
+
)
|
| 412 |
+
model.requires_grad_(False)
|
| 413 |
+
|
| 414 |
+
model.resize_token_embeddings(len(processor.tokenizer))
|
| 415 |
+
|
| 416 |
+
model.config.tokenizer_padding_side = processor.tokenizer.padding_side
|
| 417 |
+
model.config.pad_token_id = processor.tokenizer.pad_token_id
|
| 418 |
+
|
| 419 |
+
state_dict = _load_checkpoint(
|
| 420 |
+
'huggingface://MizzenAI/HPSv3/HPSv3.safetensors', map_location='cpu'
|
| 421 |
+
)
|
| 422 |
+
new_state_dict = dict()
|
| 423 |
+
for k, v in state_dict.items(): # fix transformers version mismatch
|
| 424 |
+
if k.startswith('model.'):
|
| 425 |
+
new_k = 'model.language_model.' + k[len('model.'):]
|
| 426 |
+
elif k.startswith('visual.'):
|
| 427 |
+
new_k = 'model.visual.' + k[len('visual.'):]
|
| 428 |
+
else:
|
| 429 |
+
new_k = k
|
| 430 |
+
new_state_dict[new_k] = v
|
| 431 |
+
model.load_state_dict(new_state_dict, strict=True, assign=True)
|
| 432 |
+
model.rm_head.to(torch.float32)
|
| 433 |
+
|
| 434 |
+
if use_fsdp:
|
| 435 |
+
mmcv.print_log('Wrapping HPSv3 model with FSDP.')
|
| 436 |
+
ignored_states = []
|
| 437 |
+
for p in model.rm_head.parameters():
|
| 438 |
+
p.data = p.data.cuda()
|
| 439 |
+
ignored_states.append(p)
|
| 440 |
+
model = FullyShardedDataParallel(
|
| 441 |
+
model,
|
| 442 |
+
device_id=torch.cuda.current_device(),
|
| 443 |
+
use_orig_params=False,
|
| 444 |
+
mixed_precision=MixedPrecision(
|
| 445 |
+
param_dtype=dtype,
|
| 446 |
+
reduce_dtype=dtype,
|
| 447 |
+
buffer_dtype=dtype,
|
| 448 |
+
cast_root_forward_inputs=False),
|
| 449 |
+
sharding_strategy=ShardingStrategy.HYBRID_SHARD,
|
| 450 |
+
auto_wrap_policy=ModuleWrapPolicy([Qwen2VLVisionBlock, Qwen2VLDecoderLayer]),
|
| 451 |
+
ignored_states=ignored_states
|
| 452 |
+
)
|
| 453 |
+
else:
|
| 454 |
+
model.to(device)
|
| 455 |
+
|
| 456 |
+
result = model, processor
|
| 457 |
+
_hpsv3_cache[cache_key] = result
|
| 458 |
+
return result
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
@METRICS.register_module()
|
| 462 |
+
class HPSv3(Metric):
|
| 463 |
+
name = 'HPSv3'
|
| 464 |
+
requires_prompt = True
|
| 465 |
+
|
| 466 |
+
def __init__(self,
|
| 467 |
+
num_images=None,
|
| 468 |
+
use_fsdp=True):
|
| 469 |
+
super().__init__(num_images)
|
| 470 |
+
use_fsdp = use_fsdp and torch.cuda.is_available() and dist.is_initialized() and dist.get_world_size() > 0
|
| 471 |
+
|
| 472 |
+
self.use_fsdp = use_fsdp
|
| 473 |
+
self.dtype = torch.bfloat16
|
| 474 |
+
self.device = 'cuda' if use_fsdp else 'cpu'
|
| 475 |
+
|
| 476 |
+
self.model, self.processor = load_hpsv3(device=self.device, dtype=self.dtype, use_fsdp=use_fsdp)
|
| 477 |
+
self.model.eval()
|
| 478 |
+
|
| 479 |
+
def prepare(self):
|
| 480 |
+
self.scores = []
|
| 481 |
+
|
| 482 |
+
@torch.no_grad()
|
| 483 |
+
def feed_op(self, batch, mode):
|
| 484 |
+
imgs = batch['imgs']
|
| 485 |
+
prompts = batch['prompts']
|
| 486 |
+
|
| 487 |
+
imgs = (imgs.to(device=self.device, dtype=torch.float32) / 2 + 0.5).clamp(0, 1)
|
| 488 |
+
|
| 489 |
+
message_list = []
|
| 490 |
+
for text in prompts:
|
| 491 |
+
out_message = [
|
| 492 |
+
{
|
| 493 |
+
"role": "user",
|
| 494 |
+
"content": [
|
| 495 |
+
{
|
| 496 |
+
"type": "image",
|
| 497 |
+
"min_pixels": self.processor.image_processor.min_pixels,
|
| 498 |
+
"max_pixels": self.processor.image_processor.max_pixels,
|
| 499 |
+
},
|
| 500 |
+
{
|
| 501 |
+
"type": "text",
|
| 502 |
+
"text": (
|
| 503 |
+
INSTRUCTION.format(text_prompt=text)
|
| 504 |
+
+ prompt_with_special_token
|
| 505 |
+
),
|
| 506 |
+
},
|
| 507 |
+
],
|
| 508 |
+
}
|
| 509 |
+
]
|
| 510 |
+
message_list.append(out_message)
|
| 511 |
+
|
| 512 |
+
batch = self.processor(
|
| 513 |
+
text=self.processor.apply_chat_template(message_list, tokenize=False, add_generation_prompt=True),
|
| 514 |
+
images=imgs,
|
| 515 |
+
padding=True,
|
| 516 |
+
return_tensors="pt",
|
| 517 |
+
videos_kwargs={"do_rescale": True})
|
| 518 |
+
batch = {k: v.to(self.device) for k, v in batch.items()}
|
| 519 |
+
rewards = self.model(
|
| 520 |
+
return_dict=True,
|
| 521 |
+
**batch
|
| 522 |
+
)["logits"][:, 0]
|
| 523 |
+
|
| 524 |
+
if dist.is_initialized():
|
| 525 |
+
ws = dist.get_world_size()
|
| 526 |
+
placeholder = [torch.empty_like(rewards) for _ in range(ws)]
|
| 527 |
+
dist.all_gather(placeholder, rewards)
|
| 528 |
+
rewards = torch.cat(placeholder, dim=0)
|
| 529 |
+
|
| 530 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 531 |
+
self.scores.append(rewards.float().cpu())
|
| 532 |
+
|
| 533 |
+
def feed(self, batch, mode):
|
| 534 |
+
if mode == 'reals':
|
| 535 |
+
return 0
|
| 536 |
+
|
| 537 |
+
if self.num_images is None:
|
| 538 |
+
self.feed_op(batch, mode)
|
| 539 |
+
|
| 540 |
+
else:
|
| 541 |
+
_, ws = get_dist_info()
|
| 542 |
+
|
| 543 |
+
if self.num_fake_feeded == self.num_fake_need:
|
| 544 |
+
return 0
|
| 545 |
+
|
| 546 |
+
if isinstance(batch, dict):
|
| 547 |
+
batch_size = len(list(batch.values())[0])
|
| 548 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 549 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 550 |
+
else:
|
| 551 |
+
batch_size = batch.shape[0]
|
| 552 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 553 |
+
batch_to_feed = batch[:end]
|
| 554 |
+
|
| 555 |
+
global_end = min(batch_size * ws,
|
| 556 |
+
self.num_fake_need - self.num_fake_feeded)
|
| 557 |
+
self.feed_op(batch_to_feed, mode)
|
| 558 |
+
self.num_fake_feeded += global_end
|
| 559 |
+
return end
|
| 560 |
+
|
| 561 |
+
@torch.no_grad()
|
| 562 |
+
def summary(self):
|
| 563 |
+
scores = torch.cat(self.scores, dim=0)
|
| 564 |
+
if self.num_images is not None:
|
| 565 |
+
assert scores.shape[0] >= self.num_images
|
| 566 |
+
scores = scores[:self.num_images]
|
| 567 |
+
mean_score = scores.mean().item()
|
| 568 |
+
self._result_dict = dict(hpsv3=mean_score)
|
| 569 |
+
self._result_str = f'HPSv3: {mean_score:.4f}'
|
| 570 |
+
return mean_score
|
| 571 |
+
|
| 572 |
+
def clear_fake_data(self):
|
| 573 |
+
self.scores = []
|
| 574 |
+
self.num_fake_feeded = 0
|
| 575 |
+
|
| 576 |
+
def clear(self, clear_reals=False):
|
| 577 |
+
self.clear_fake_data()
|
| 578 |
+
|
| 579 |
+
def load_to_gpu(self):
|
| 580 |
+
if torch.cuda.is_available() and not isinstance(self.model, FullyShardedDataParallel):
|
| 581 |
+
self.model.cuda()
|
| 582 |
+
self.device = 'cuda'
|
| 583 |
+
|
| 584 |
+
def offload_to_cpu(self):
|
| 585 |
+
if not isinstance(self.model, FullyShardedDataParallel):
|
| 586 |
+
self.model.cpu()
|
| 587 |
+
self.device = 'cpu'
|
lakonlab/evaluation/metrics.py
ADDED
|
@@ -0,0 +1,1329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Hansheng Chen
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import logging
|
| 6 |
+
import pickle
|
| 7 |
+
import warnings
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import torch.distributed as dist
|
| 11 |
+
import torch.nn.functional as F
|
| 12 |
+
import mmcv
|
| 13 |
+
import hashlib
|
| 14 |
+
|
| 15 |
+
from copy import deepcopy
|
| 16 |
+
from contextlib import contextmanager, redirect_stdout, nullcontext
|
| 17 |
+
from scipy import linalg
|
| 18 |
+
from scipy.stats import entropy
|
| 19 |
+
from torchvision import models
|
| 20 |
+
from mmcv.runner import get_dist_info, load_checkpoint
|
| 21 |
+
from mmgen.utils import get_root_logger
|
| 22 |
+
from mmgen.core.registry import METRICS
|
| 23 |
+
from mmgen.core.evaluation.metrics import (
|
| 24 |
+
Metric, TERO_INCEPTION_URL, _load_inception_torch, MMGEN_CACHE_DIR)
|
| 25 |
+
from mmgen.core.evaluation.metrics import FID as _FID
|
| 26 |
+
from mmgen.core.evaluation.metrics import PR as _PR
|
| 27 |
+
from open_clip import get_tokenizer, create_model
|
| 28 |
+
from lakonlab.utils.io_utils import download_from_huggingface, download_from_url
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Global caches for model loading
|
| 32 |
+
_inception_cache = {}
|
| 33 |
+
_hpsv2_cache = {}
|
| 34 |
+
_clip_cache = {}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _argv_ctx(argv):
|
| 38 |
+
class _Argv:
|
| 39 |
+
|
| 40 |
+
def __enter__(self):
|
| 41 |
+
self._old = sys.argv
|
| 42 |
+
sys.argv = argv
|
| 43 |
+
|
| 44 |
+
def __exit__(self, exc_type, exc, tb):
|
| 45 |
+
sys.argv = self._old
|
| 46 |
+
|
| 47 |
+
return _Argv()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _redirect_stdout(to_buf):
|
| 51 |
+
return redirect_stdout(to_buf) if to_buf is not None else nullcontext()
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@contextmanager
|
| 55 |
+
def _quarantine_openclip_logging():
|
| 56 |
+
"""
|
| 57 |
+
Guard against open_clip (and friends) mutating global logging.
|
| 58 |
+
Snapshots root handlers/level, runs the block, then removes any
|
| 59 |
+
NEW handlers and restores the level. Also disables propagation
|
| 60 |
+
for the open_clip logger so logs don’t bubble to root.
|
| 61 |
+
"""
|
| 62 |
+
root = logging.getLogger()
|
| 63 |
+
before_handlers = tuple(root.handlers) # snapshot by identity
|
| 64 |
+
before_ids = {id(h) for h in before_handlers}
|
| 65 |
+
before_level = root.level
|
| 66 |
+
|
| 67 |
+
try:
|
| 68 |
+
yield
|
| 69 |
+
finally:
|
| 70 |
+
# Remove only handlers that were added during the block
|
| 71 |
+
for h in list(root.handlers):
|
| 72 |
+
if id(h) not in before_ids:
|
| 73 |
+
root.removeHandler(h)
|
| 74 |
+
try:
|
| 75 |
+
h.close()
|
| 76 |
+
except Exception:
|
| 77 |
+
pass
|
| 78 |
+
root.setLevel(before_level)
|
| 79 |
+
|
| 80 |
+
# Clamp open_clip logger so it won’t re-emit to root
|
| 81 |
+
oc = logging.getLogger("open_clip")
|
| 82 |
+
oc.propagate = False
|
| 83 |
+
oc.handlers.clear()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _load_inception_from_path(inception_path, map_location=None):
|
| 87 |
+
mmcv.print_log(
|
| 88 |
+
'Try to load Tero\'s Inception Model from '
|
| 89 |
+
f'\'{inception_path}\'.', 'mmgen')
|
| 90 |
+
try:
|
| 91 |
+
model = torch.jit.load(inception_path, map_location=map_location)
|
| 92 |
+
mmcv.print_log('Load Tero\'s Inception Model successfully.', 'mmgen')
|
| 93 |
+
except Exception as e:
|
| 94 |
+
model = None
|
| 95 |
+
mmcv.print_log(
|
| 96 |
+
'Load Tero\'s Inception Model failed. '
|
| 97 |
+
f'\'{e}\' occurs.', 'mmgen')
|
| 98 |
+
return model
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _load_inception_from_url(inception_url, map_location=None):
|
| 102 |
+
"""
|
| 103 |
+
Fix multi-node downloading issue in MMGen.
|
| 104 |
+
"""
|
| 105 |
+
inception_url = inception_url if inception_url else TERO_INCEPTION_URL
|
| 106 |
+
mmcv.print_log(f'Try to download Inception Model from {inception_url}...',
|
| 107 |
+
'mmgen')
|
| 108 |
+
try:
|
| 109 |
+
path = download_from_url(inception_url, dest_dir=MMGEN_CACHE_DIR)
|
| 110 |
+
mmcv.print_log('Download Finished.')
|
| 111 |
+
return _load_inception_from_path(path, map_location=map_location)
|
| 112 |
+
except Exception as e:
|
| 113 |
+
mmcv.print_log(f'Download Failed. {e} occurs.')
|
| 114 |
+
return None
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def load_inception(inception_args, metric, map_location=None):
|
| 118 |
+
"""
|
| 119 |
+
Fix multi-node downloading issue in MMGen.
|
| 120 |
+
"""
|
| 121 |
+
if not isinstance(inception_args, dict):
|
| 122 |
+
raise TypeError('Receive invalid \'inception_args\': '
|
| 123 |
+
f'\'{inception_args}\'')
|
| 124 |
+
|
| 125 |
+
# Create cache key from arguments
|
| 126 |
+
cache_key = hashlib.md5(str(sorted(inception_args.items())).encode()).hexdigest()
|
| 127 |
+
cache_key += f"_{metric}"
|
| 128 |
+
|
| 129 |
+
# Check if model is already cached
|
| 130 |
+
if cache_key in _inception_cache:
|
| 131 |
+
return _inception_cache[cache_key]
|
| 132 |
+
|
| 133 |
+
_inception_args = deepcopy(inception_args)
|
| 134 |
+
inceptoin_type = _inception_args.pop('type', None)
|
| 135 |
+
|
| 136 |
+
if torch.__version__ < '1.6.0':
|
| 137 |
+
mmcv.print_log(
|
| 138 |
+
'Current Pytorch Version not support script module, load '
|
| 139 |
+
'Inception Model from torch model zoo. If you want to use '
|
| 140 |
+
'Tero\' script model, please update your Pytorch higher '
|
| 141 |
+
f'than \'1.6\' (now is {torch.__version__})', 'mmgen')
|
| 142 |
+
result = _load_inception_torch(_inception_args, metric), 'pytorch'
|
| 143 |
+
_inception_cache[cache_key] = result
|
| 144 |
+
return result
|
| 145 |
+
|
| 146 |
+
# load pytorch version is specific
|
| 147 |
+
if inceptoin_type != 'StyleGAN':
|
| 148 |
+
result = _load_inception_torch(_inception_args, metric), 'pytorch'
|
| 149 |
+
_inception_cache[cache_key] = result
|
| 150 |
+
return result
|
| 151 |
+
|
| 152 |
+
# try to load Tero's version
|
| 153 |
+
path = _inception_args.get('inception_path', TERO_INCEPTION_URL)
|
| 154 |
+
|
| 155 |
+
# try to parse `path` as web url and download
|
| 156 |
+
if 'http' not in path:
|
| 157 |
+
model = _load_inception_from_path(path, map_location=map_location)
|
| 158 |
+
if isinstance(model, torch.nn.Module):
|
| 159 |
+
result = model, 'StyleGAN'
|
| 160 |
+
_inception_cache[cache_key] = result
|
| 161 |
+
return result
|
| 162 |
+
|
| 163 |
+
# try to parse `path` as path on disk
|
| 164 |
+
model = _load_inception_from_url(path, map_location=map_location)
|
| 165 |
+
if isinstance(model, torch.nn.Module):
|
| 166 |
+
result = model, 'StyleGAN'
|
| 167 |
+
_inception_cache[cache_key] = result
|
| 168 |
+
return result
|
| 169 |
+
|
| 170 |
+
raise RuntimeError('Cannot Load Inception Model, please check the input '
|
| 171 |
+
f'`inception_args`: {inception_args}')
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def load_hpsv2(hps_version, device='cpu', precision='fp16'):
|
| 175 |
+
assert hps_version in ['v2', 'v2.1']
|
| 176 |
+
|
| 177 |
+
# Create cache key from arguments
|
| 178 |
+
cache_key = f"{hps_version}_{device}_{precision}"
|
| 179 |
+
|
| 180 |
+
# Check if model is already cached
|
| 181 |
+
if cache_key in _hpsv2_cache:
|
| 182 |
+
return _hpsv2_cache[cache_key]
|
| 183 |
+
|
| 184 |
+
with _quarantine_openclip_logging():
|
| 185 |
+
model = create_model(
|
| 186 |
+
'ViT-H-14-quickgelu',
|
| 187 |
+
precision=precision,
|
| 188 |
+
device=device,
|
| 189 |
+
output_dict=True)
|
| 190 |
+
model.requires_grad_(False)
|
| 191 |
+
tokenizer = get_tokenizer('ViT-H-14')
|
| 192 |
+
load_checkpoint(
|
| 193 |
+
model,
|
| 194 |
+
f'huggingface://xswu/HPSv2/HPS_{hps_version}_compressed.pt',
|
| 195 |
+
map_location='cpu', strict=True)
|
| 196 |
+
|
| 197 |
+
result = model, tokenizer
|
| 198 |
+
_hpsv2_cache[cache_key] = result
|
| 199 |
+
return result
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def load_openclip(
|
| 203 |
+
model_name='ViT-L-14-336-quickgelu',
|
| 204 |
+
pretrained='openai',
|
| 205 |
+
device='cpu',
|
| 206 |
+
precision='fp16'):
|
| 207 |
+
cache_key = f'{model_name}_{pretrained}_{device}_{precision}'
|
| 208 |
+
if cache_key in _clip_cache:
|
| 209 |
+
return _clip_cache[cache_key]
|
| 210 |
+
|
| 211 |
+
with _quarantine_openclip_logging():
|
| 212 |
+
model = create_model(
|
| 213 |
+
model_name,
|
| 214 |
+
pretrained=pretrained,
|
| 215 |
+
precision=precision,
|
| 216 |
+
device=device,
|
| 217 |
+
output_dict=True)
|
| 218 |
+
model.requires_grad_(False)
|
| 219 |
+
tokenizer = get_tokenizer(model_name)
|
| 220 |
+
_clip_cache[cache_key] = (model, tokenizer)
|
| 221 |
+
return _clip_cache[cache_key]
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def compute_pr_distances(row_features,
|
| 225 |
+
col_features,
|
| 226 |
+
col_batch_size=10000):
|
| 227 |
+
dist_batches = []
|
| 228 |
+
for col_batch in col_features.split(col_batch_size):
|
| 229 |
+
dist_batch = torch.cdist(
|
| 230 |
+
row_features.unsqueeze(0), col_batch.unsqueeze(0))[0]
|
| 231 |
+
dist_batches.append(dist_batch.cpu())
|
| 232 |
+
return torch.cat(dist_batches, dim=1)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
@METRICS.register_module(force=True)
|
| 236 |
+
class PR(_PR):
|
| 237 |
+
|
| 238 |
+
def __init__(
|
| 239 |
+
self,
|
| 240 |
+
num_images=None,
|
| 241 |
+
image_shape=None,
|
| 242 |
+
feats_pkl=None,
|
| 243 |
+
k=3,
|
| 244 |
+
bgr2rgb=True,
|
| 245 |
+
vgg16_script=None,
|
| 246 |
+
inception_args=None,
|
| 247 |
+
row_batch_size=10000,
|
| 248 |
+
col_batch_size=10000):
|
| 249 |
+
super(_PR, self).__init__(num_images, image_shape)
|
| 250 |
+
|
| 251 |
+
self.feats_pkl = feats_pkl
|
| 252 |
+
|
| 253 |
+
self.vgg16 = self.inception_net = None
|
| 254 |
+
self.device = 'cpu'
|
| 255 |
+
|
| 256 |
+
if vgg16_script is not None:
|
| 257 |
+
mmcv.print_log('loading vgg16 for improved precision and recall...',
|
| 258 |
+
'mmgen')
|
| 259 |
+
if os.path.isfile(vgg16_script):
|
| 260 |
+
self.vgg16 = torch.jit.load('work_dirs/cache/vgg16.pt', map_location=self.device).eval()
|
| 261 |
+
self.use_tero_scirpt = True
|
| 262 |
+
else:
|
| 263 |
+
mmcv.print_log(
|
| 264 |
+
'Cannot load Tero\'s script module. Use official '
|
| 265 |
+
'vgg16 instead', 'mmgen')
|
| 266 |
+
self.vgg16 = models.vgg16(pretrained=True).eval()
|
| 267 |
+
self.use_tero_scirpt = False
|
| 268 |
+
elif inception_args is not None:
|
| 269 |
+
self.inception_net, self.inception_style = load_inception(
|
| 270 |
+
inception_args, 'FID')
|
| 271 |
+
else:
|
| 272 |
+
raise ValueError('Please provide either vgg16_script or inception_args')
|
| 273 |
+
|
| 274 |
+
self.k = k
|
| 275 |
+
self.bgr2rgb = bgr2rgb
|
| 276 |
+
self.row_batch_size = row_batch_size
|
| 277 |
+
self.col_batch_size = col_batch_size
|
| 278 |
+
|
| 279 |
+
def prepare(self):
|
| 280 |
+
self.features_of_reals = []
|
| 281 |
+
self.features_of_fakes = []
|
| 282 |
+
if self.feats_pkl is not None:
|
| 283 |
+
assert mmcv.is_filepath(self.feats_pkl)
|
| 284 |
+
with open(self.feats_pkl, 'rb') as f:
|
| 285 |
+
reference = pickle.load(f)
|
| 286 |
+
self.features_of_reals = [torch.from_numpy(feat) for feat in reference['features_of_reals']]
|
| 287 |
+
self.num_real_feeded = reference['num_real_feeded']
|
| 288 |
+
mmcv.print_log(
|
| 289 |
+
f'Load reference inception pkl from {self.feats_pkl}',
|
| 290 |
+
'mmgen')
|
| 291 |
+
|
| 292 |
+
def extract_features(self, batch):
|
| 293 |
+
if self.vgg16 is not None:
|
| 294 |
+
if self.use_tero_scirpt:
|
| 295 |
+
batch = (batch * 127.5 + 128).clamp(0, 255).to(torch.uint8)
|
| 296 |
+
feat = self.vgg16(batch, return_features=True)
|
| 297 |
+
else:
|
| 298 |
+
batch = F.interpolate(batch, size=(224, 224))
|
| 299 |
+
before_fc = self.vgg16.features(batch)
|
| 300 |
+
before_fc = before_fc.view(-1, 7 * 7 * 512)
|
| 301 |
+
feat = self.vgg16.classifier[:4](before_fc)
|
| 302 |
+
else:
|
| 303 |
+
if self.inception_style == 'StyleGAN':
|
| 304 |
+
batch = (batch * 127.5 + 128).clamp(0, 255).to(torch.uint8)
|
| 305 |
+
feat = self.inception_net(batch, return_features=True)
|
| 306 |
+
else:
|
| 307 |
+
feat = self.inception_net(batch)[0].view(batch.shape[0], -1)
|
| 308 |
+
return feat
|
| 309 |
+
|
| 310 |
+
@torch.no_grad()
|
| 311 |
+
def feed_op(self, batch, mode):
|
| 312 |
+
batch = batch.to(self.device)
|
| 313 |
+
if self.bgr2rgb:
|
| 314 |
+
batch = batch[:, [2, 1, 0]]
|
| 315 |
+
|
| 316 |
+
feat = self.extract_features(batch)
|
| 317 |
+
|
| 318 |
+
if dist.is_initialized():
|
| 319 |
+
ws = dist.get_world_size()
|
| 320 |
+
placeholder = [torch.zeros_like(feat) for _ in range(ws)]
|
| 321 |
+
dist.all_gather(placeholder, feat)
|
| 322 |
+
feat = torch.stack(placeholder, dim=1).reshape(feat.size(0) * ws, *feat.shape[1:])
|
| 323 |
+
|
| 324 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 325 |
+
if mode == 'reals':
|
| 326 |
+
self.features_of_reals.append(feat)
|
| 327 |
+
elif mode == 'fakes':
|
| 328 |
+
self.features_of_fakes.append(feat)
|
| 329 |
+
else:
|
| 330 |
+
raise ValueError(f'{mode} is not a implemented feed mode.')
|
| 331 |
+
|
| 332 |
+
def feed(self, batch, mode):
|
| 333 |
+
if self.num_images is not None:
|
| 334 |
+
return super().feed(batch, mode)
|
| 335 |
+
else:
|
| 336 |
+
self.feed_op(batch, mode)
|
| 337 |
+
|
| 338 |
+
@torch.no_grad()
|
| 339 |
+
def summary(self):
|
| 340 |
+
gen_features = torch.cat(self.features_of_fakes)
|
| 341 |
+
real_features = torch.cat(self.features_of_reals).to(device=gen_features.device)
|
| 342 |
+
if self.num_images is not None:
|
| 343 |
+
assert gen_features.shape[0] >= self.num_images
|
| 344 |
+
gen_features = gen_features[:self.num_images]
|
| 345 |
+
if self.feats_pkl is None: # real feats not pre-calculated
|
| 346 |
+
assert real_features.shape[0] >= self.num_images
|
| 347 |
+
real_features = real_features[:self.num_images]
|
| 348 |
+
|
| 349 |
+
self._result_dict = {}
|
| 350 |
+
|
| 351 |
+
for name, manifold, probes in [
|
| 352 |
+
('precision', real_features, gen_features),
|
| 353 |
+
('recall', gen_features, real_features)
|
| 354 |
+
]:
|
| 355 |
+
kth = []
|
| 356 |
+
for manifold_batch in manifold.split(self.row_batch_size):
|
| 357 |
+
distance = compute_pr_distances(
|
| 358 |
+
row_features=manifold_batch,
|
| 359 |
+
col_features=manifold,
|
| 360 |
+
col_batch_size=self.col_batch_size)
|
| 361 |
+
kth.append(
|
| 362 |
+
distance.to(torch.float32).kthvalue(self.k + 1).values.to(torch.float16))
|
| 363 |
+
kth = torch.cat(kth)
|
| 364 |
+
pred = []
|
| 365 |
+
for probes_batch in probes.split(self.row_batch_size):
|
| 366 |
+
distance = compute_pr_distances(
|
| 367 |
+
row_features=probes_batch,
|
| 368 |
+
col_features=manifold,
|
| 369 |
+
col_batch_size=self.col_batch_size)
|
| 370 |
+
pred.append((distance <= kth).any(dim=1))
|
| 371 |
+
self._result_dict[name] = float(torch.cat(pred).to(torch.float32).mean())
|
| 372 |
+
|
| 373 |
+
precision = self._result_dict['precision']
|
| 374 |
+
recall = self._result_dict['recall']
|
| 375 |
+
self._result_str = f'precision: {precision}, recall:{recall}'
|
| 376 |
+
return self._result_dict
|
| 377 |
+
|
| 378 |
+
def clear_fake_data(self):
|
| 379 |
+
self.features_of_fakes = []
|
| 380 |
+
self.num_fake_feeded = 0
|
| 381 |
+
|
| 382 |
+
def clear(self, clear_reals=False):
|
| 383 |
+
self.clear_fake_data()
|
| 384 |
+
if clear_reals:
|
| 385 |
+
self.features_of_reals = []
|
| 386 |
+
self.num_real_feeded = 0
|
| 387 |
+
|
| 388 |
+
def load_to_gpu(self):
|
| 389 |
+
"""Move models to GPU."""
|
| 390 |
+
if torch.cuda.is_available():
|
| 391 |
+
if self.vgg16 is not None:
|
| 392 |
+
self.vgg16 = self.vgg16.cuda()
|
| 393 |
+
elif self.inception_net is not None:
|
| 394 |
+
self.inception_net.cuda()
|
| 395 |
+
self.device = 'cuda'
|
| 396 |
+
|
| 397 |
+
def offload_to_cpu(self):
|
| 398 |
+
"""Move models to CPU."""
|
| 399 |
+
if self.vgg16 is not None:
|
| 400 |
+
self.vgg16 = self.vgg16.cpu()
|
| 401 |
+
elif self.inception_net is not None:
|
| 402 |
+
self.inception_net.cpu()
|
| 403 |
+
self.device = 'cpu'
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
@METRICS.register_module(force=True)
|
| 407 |
+
class FID(_FID):
|
| 408 |
+
|
| 409 |
+
def __init__(self,
|
| 410 |
+
num_images=None,
|
| 411 |
+
image_shape=None,
|
| 412 |
+
inception_pkl=None,
|
| 413 |
+
bgr2rgb=True,
|
| 414 |
+
inception_args=dict(normalize_input=False)):
|
| 415 |
+
super().__init__(
|
| 416 |
+
num_images,
|
| 417 |
+
image_shape=image_shape,
|
| 418 |
+
inception_pkl=inception_pkl,
|
| 419 |
+
bgr2rgb=bgr2rgb,
|
| 420 |
+
inception_args=inception_args)
|
| 421 |
+
|
| 422 |
+
def prepare(self):
|
| 423 |
+
if self.inception_pkl is not None:
|
| 424 |
+
assert mmcv.is_filepath(self.inception_pkl)
|
| 425 |
+
if self.inception_pkl.startswith('huggingface://'):
|
| 426 |
+
self.inception_pkl = download_from_huggingface(self.inception_pkl)
|
| 427 |
+
elif self.inception_pkl.startswith(('http://', 'https://')):
|
| 428 |
+
self.inception_pkl = download_from_url(self.inception_pkl)
|
| 429 |
+
with open(self.inception_pkl, 'rb') as f:
|
| 430 |
+
reference = pickle.load(f)
|
| 431 |
+
self.real_mean = reference['mean']
|
| 432 |
+
self.real_cov = reference['cov']
|
| 433 |
+
mmcv.print_log(
|
| 434 |
+
f'Load reference inception pkl from {self.inception_pkl}',
|
| 435 |
+
'mmgen')
|
| 436 |
+
self.num_real_feeded = self.num_images
|
| 437 |
+
|
| 438 |
+
@torch.no_grad()
|
| 439 |
+
def summary(self):
|
| 440 |
+
# calculate reference inception stat
|
| 441 |
+
if self.real_mean is None:
|
| 442 |
+
feats = torch.cat(self.real_feats, dim=0)
|
| 443 |
+
if self.num_images is not None:
|
| 444 |
+
assert feats.shape[0] >= self.num_images
|
| 445 |
+
feats = feats[:self.num_images]
|
| 446 |
+
feats_np = feats.numpy()
|
| 447 |
+
self.real_mean = np.mean(feats_np, 0)
|
| 448 |
+
self.real_cov = np.cov(feats_np, rowvar=False)
|
| 449 |
+
|
| 450 |
+
# calculate fake inception stat
|
| 451 |
+
fake_feats = torch.cat(self.fake_feats, dim=0)
|
| 452 |
+
if self.num_images is not None:
|
| 453 |
+
assert fake_feats.shape[0] >= self.num_images
|
| 454 |
+
fake_feats = fake_feats[:self.num_images]
|
| 455 |
+
fake_feats_np = fake_feats.numpy()
|
| 456 |
+
fake_mean = np.mean(fake_feats_np, 0)
|
| 457 |
+
fake_cov = np.cov(fake_feats_np, rowvar=False)
|
| 458 |
+
|
| 459 |
+
# calculate distance between real and fake statistics
|
| 460 |
+
fid, mean, cov = self._calc_fid(fake_mean, fake_cov, self.real_mean, self.real_cov)
|
| 461 |
+
|
| 462 |
+
# results for print/table
|
| 463 |
+
self._result_str = (f'{fid:.4f} ({mean:.5f}/{cov:.5f})')
|
| 464 |
+
# results for log_buffer
|
| 465 |
+
self._result_dict = dict(fid=fid, fid_mean=mean, fid_cov=cov)
|
| 466 |
+
|
| 467 |
+
return fid, mean, cov
|
| 468 |
+
|
| 469 |
+
def feed(self, batch, mode):
|
| 470 |
+
if self.num_images is not None:
|
| 471 |
+
return super().feed(batch, mode)
|
| 472 |
+
else:
|
| 473 |
+
self.feed_op(batch, mode)
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
@METRICS.register_module()
|
| 477 |
+
class FIDKID(FID):
|
| 478 |
+
name = 'FIDKID'
|
| 479 |
+
|
| 480 |
+
def __init__(self,
|
| 481 |
+
num_images=None,
|
| 482 |
+
num_subsets=100,
|
| 483 |
+
max_subset_size=1000,
|
| 484 |
+
**kwargs):
|
| 485 |
+
super().__init__(num_images=num_images, **kwargs)
|
| 486 |
+
self.num_subsets = num_subsets
|
| 487 |
+
self.max_subset_size = max_subset_size
|
| 488 |
+
self.real_feats_np = None
|
| 489 |
+
|
| 490 |
+
def prepare(self):
|
| 491 |
+
if self.inception_pkl is not None:
|
| 492 |
+
assert mmcv.is_filepath(self.inception_pkl)
|
| 493 |
+
with open(self.inception_pkl, 'rb') as f:
|
| 494 |
+
reference = pickle.load(f)
|
| 495 |
+
self.real_mean = reference['mean']
|
| 496 |
+
self.real_cov = reference['cov']
|
| 497 |
+
self.real_feats_np = reference['feats_np']
|
| 498 |
+
mmcv.print_log(
|
| 499 |
+
f'Load reference inception pkl from {self.inception_pkl}',
|
| 500 |
+
'mmgen')
|
| 501 |
+
self.num_real_feeded = self.num_images
|
| 502 |
+
|
| 503 |
+
@staticmethod
|
| 504 |
+
def _calc_kid(real_feat, fake_feat, num_subsets, max_subset_size):
|
| 505 |
+
"""Refer to the implementation from:
|
| 506 |
+
https://github.com/NVlabs/stylegan2-ada-pytorch/blob/main/metrics/kernel_inception_distance.py#L18 # noqa
|
| 507 |
+
Args:
|
| 508 |
+
real_feat (np.array): Features of the real samples.
|
| 509 |
+
fake_feat (np.array): Features of the fake samples.
|
| 510 |
+
num_subsets (int): Number of subsets to calculate KID.
|
| 511 |
+
max_subset_size (int): The max size of each subset.
|
| 512 |
+
Returns:
|
| 513 |
+
float: The calculated kid metric.
|
| 514 |
+
"""
|
| 515 |
+
n = real_feat.shape[1]
|
| 516 |
+
m = min(min(real_feat.shape[0], fake_feat.shape[0]), max_subset_size)
|
| 517 |
+
t = 0
|
| 518 |
+
for _ in range(num_subsets):
|
| 519 |
+
x = fake_feat[np.random.choice(
|
| 520 |
+
fake_feat.shape[0], m, replace=False)]
|
| 521 |
+
y = real_feat[np.random.choice(
|
| 522 |
+
real_feat.shape[0], m, replace=False)]
|
| 523 |
+
a = (x @ x.T / n + 1)**3 + (y @ y.T / n + 1)**3
|
| 524 |
+
b = (x @ y.T / n + 1)**3
|
| 525 |
+
t += (a.sum() - np.diag(a).sum()) / (m - 1) - b.sum() * 2 / m
|
| 526 |
+
|
| 527 |
+
kid = t / num_subsets / m
|
| 528 |
+
return float(kid)
|
| 529 |
+
|
| 530 |
+
@torch.no_grad()
|
| 531 |
+
def summary(self):
|
| 532 |
+
if self.real_feats_np is None:
|
| 533 |
+
feats = torch.cat(self.real_feats, dim=0)
|
| 534 |
+
if self.num_images is not None:
|
| 535 |
+
assert feats.shape[0] >= self.num_images
|
| 536 |
+
feats = feats[:self.num_images]
|
| 537 |
+
feats_np = feats.numpy()
|
| 538 |
+
self.real_feats_np = feats_np
|
| 539 |
+
self.real_mean = np.mean(feats_np, 0)
|
| 540 |
+
self.real_cov = np.cov(feats_np, rowvar=False)
|
| 541 |
+
|
| 542 |
+
fake_feats = torch.cat(self.fake_feats, dim=0)
|
| 543 |
+
if self.num_images is not None:
|
| 544 |
+
assert fake_feats.shape[0] >= self.num_images
|
| 545 |
+
fake_feats = fake_feats[:self.num_images]
|
| 546 |
+
fake_feats_np = fake_feats.numpy()
|
| 547 |
+
fake_mean = np.mean(fake_feats_np, 0)
|
| 548 |
+
fake_cov = np.cov(fake_feats_np, rowvar=False)
|
| 549 |
+
|
| 550 |
+
fid, mean, cov = self._calc_fid(fake_mean, fake_cov, self.real_mean,
|
| 551 |
+
self.real_cov)
|
| 552 |
+
kid = self._calc_kid(self.real_feats_np, fake_feats_np, self.num_subsets,
|
| 553 |
+
self.max_subset_size) * 1000
|
| 554 |
+
|
| 555 |
+
self._result_str = f'{fid:.4f} ({mean:.5f}/{cov:.5f}), {kid:.4f}'
|
| 556 |
+
self._result_dict = dict(fid=fid, fid_mean=mean, fid_cov=cov, kid=kid)
|
| 557 |
+
|
| 558 |
+
return fid, mean, cov, kid
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
@METRICS.register_module()
|
| 562 |
+
class InceptionMetrics(Metric):
|
| 563 |
+
name = 'InceptionMetrics'
|
| 564 |
+
|
| 565 |
+
def __init__(self,
|
| 566 |
+
num_images=None,
|
| 567 |
+
reference_pkl=None,
|
| 568 |
+
bgr2rgb=False,
|
| 569 |
+
center_crop=False, # SDXL-Lightning patch FID
|
| 570 |
+
resize=True,
|
| 571 |
+
inception_args=dict(
|
| 572 |
+
type='StyleGAN',
|
| 573 |
+
inception_path=TERO_INCEPTION_URL),
|
| 574 |
+
use_kid=False,
|
| 575 |
+
use_pr=True,
|
| 576 |
+
use_is=True,
|
| 577 |
+
kid_num_subsets=100,
|
| 578 |
+
kid_max_subset_size=1000,
|
| 579 |
+
pr_k=3,
|
| 580 |
+
pr_row_batch_size=10000,
|
| 581 |
+
pr_col_batch_size=10000,
|
| 582 |
+
is_splits=10,
|
| 583 |
+
prefix=''):
|
| 584 |
+
super().__init__(num_images)
|
| 585 |
+
self.reference_pkl = reference_pkl
|
| 586 |
+
self.real_feats = []
|
| 587 |
+
self.fake_feats = []
|
| 588 |
+
self.preds = []
|
| 589 |
+
self.real_mean = None
|
| 590 |
+
self.real_cov = None
|
| 591 |
+
self.bgr2rgb = bgr2rgb
|
| 592 |
+
self.center_crop = center_crop
|
| 593 |
+
self.resize = resize
|
| 594 |
+
self.device = 'cpu'
|
| 595 |
+
|
| 596 |
+
if self.center_crop and self.resize:
|
| 597 |
+
warnings.warn('`center_crop` is set to True, `resize` will be ignored.')
|
| 598 |
+
|
| 599 |
+
logger = get_root_logger()
|
| 600 |
+
ori_level = logger.level
|
| 601 |
+
logger.setLevel('ERROR')
|
| 602 |
+
self.inception_net, self.inception_style = load_inception(
|
| 603 |
+
inception_args, 'FID', map_location=self.device)
|
| 604 |
+
logger.setLevel(ori_level)
|
| 605 |
+
|
| 606 |
+
self.inception_net.eval()
|
| 607 |
+
|
| 608 |
+
self.use_kid = use_kid
|
| 609 |
+
self.use_pr = use_pr
|
| 610 |
+
self.use_is = use_is
|
| 611 |
+
self.kid_num_subsets = kid_num_subsets
|
| 612 |
+
self.kid_max_subset_size = kid_max_subset_size
|
| 613 |
+
self.real_feats_np = None
|
| 614 |
+
|
| 615 |
+
self.pr_k = pr_k
|
| 616 |
+
self.pr_row_batch_size = pr_row_batch_size
|
| 617 |
+
self.pr_col_batch_size = pr_col_batch_size
|
| 618 |
+
|
| 619 |
+
self.is_splits = is_splits
|
| 620 |
+
self.prefix = prefix
|
| 621 |
+
|
| 622 |
+
def prepare(self):
|
| 623 |
+
self.real_feats = []
|
| 624 |
+
self.real_feats_np = None
|
| 625 |
+
self.fake_feats = []
|
| 626 |
+
self.preds = []
|
| 627 |
+
if self.reference_pkl is not None:
|
| 628 |
+
assert mmcv.is_filepath(self.reference_pkl)
|
| 629 |
+
if self.reference_pkl.startswith('huggingface://'):
|
| 630 |
+
self.reference_pkl = download_from_huggingface(self.reference_pkl)
|
| 631 |
+
elif self.reference_pkl.startswith(('http://', 'https://')):
|
| 632 |
+
self.reference_pkl = download_from_url(self.reference_pkl)
|
| 633 |
+
with open(self.reference_pkl, 'rb') as f:
|
| 634 |
+
reference = pickle.load(f)
|
| 635 |
+
self.real_mean = reference['mean']
|
| 636 |
+
self.real_cov = reference['cov']
|
| 637 |
+
self.real_feats_np = reference['real_feats_np']
|
| 638 |
+
self.real_feats = [torch.from_numpy(reference['real_feats_np'])]
|
| 639 |
+
self.num_real_feeded = reference['num_real_feeded']
|
| 640 |
+
|
| 641 |
+
@staticmethod
|
| 642 |
+
def _calc_fid(sample_mean, sample_cov, real_mean, real_cov, eps=1e-6):
|
| 643 |
+
"""Refer to the implementation from:
|
| 644 |
+
|
| 645 |
+
https://github.com/rosinality/stylegan2-pytorch/blob/master/fid.py#L34
|
| 646 |
+
"""
|
| 647 |
+
cov_sqrt, _ = linalg.sqrtm(sample_cov @ real_cov, disp=False)
|
| 648 |
+
|
| 649 |
+
if not np.isfinite(cov_sqrt).all():
|
| 650 |
+
print('product of cov matrices is singular')
|
| 651 |
+
offset = np.eye(sample_cov.shape[0]) * eps
|
| 652 |
+
cov_sqrt = linalg.sqrtm(
|
| 653 |
+
(sample_cov + offset) @ (real_cov + offset))
|
| 654 |
+
|
| 655 |
+
if np.iscomplexobj(cov_sqrt):
|
| 656 |
+
if not np.allclose(np.diagonal(cov_sqrt).imag, 0, atol=1e-3):
|
| 657 |
+
m = np.max(np.abs(cov_sqrt.imag))
|
| 658 |
+
|
| 659 |
+
raise ValueError(f'Imaginary component {m}')
|
| 660 |
+
|
| 661 |
+
cov_sqrt = cov_sqrt.real
|
| 662 |
+
|
| 663 |
+
mean_diff = sample_mean - real_mean
|
| 664 |
+
mean_norm = mean_diff @ mean_diff
|
| 665 |
+
|
| 666 |
+
trace = np.trace(sample_cov) + np.trace(
|
| 667 |
+
real_cov) - 2 * np.trace(cov_sqrt)
|
| 668 |
+
|
| 669 |
+
fid = mean_norm + trace
|
| 670 |
+
|
| 671 |
+
return fid, mean_norm, trace
|
| 672 |
+
|
| 673 |
+
@staticmethod
|
| 674 |
+
def _calc_kid(real_feat, fake_feat, num_subsets, max_subset_size):
|
| 675 |
+
"""Refer to the implementation from:
|
| 676 |
+
https://github.com/NVlabs/stylegan2-ada-pytorch/blob/main/metrics/kernel_inception_distance.py#L18 # noqa
|
| 677 |
+
Args:
|
| 678 |
+
real_feat (np.array): Features of the real samples.
|
| 679 |
+
fake_feat (np.array): Features of the fake samples.
|
| 680 |
+
num_subsets (int): Number of subsets to calculate KID.
|
| 681 |
+
max_subset_size (int): The max size of each subset.
|
| 682 |
+
Returns:
|
| 683 |
+
float: The calculated kid metric.
|
| 684 |
+
"""
|
| 685 |
+
n = real_feat.shape[1]
|
| 686 |
+
m = min(min(real_feat.shape[0], fake_feat.shape[0]), max_subset_size)
|
| 687 |
+
t = 0
|
| 688 |
+
for _ in range(num_subsets):
|
| 689 |
+
x = fake_feat[np.random.choice(
|
| 690 |
+
fake_feat.shape[0], m, replace=False)]
|
| 691 |
+
y = real_feat[np.random.choice(
|
| 692 |
+
real_feat.shape[0], m, replace=False)]
|
| 693 |
+
a = (x @ x.T / n + 1)**3 + (y @ y.T / n + 1)**3
|
| 694 |
+
b = (x @ y.T / n + 1)**3
|
| 695 |
+
t += (a.sum() - np.diag(a).sum()) / (m - 1) - b.sum() * 2 / m
|
| 696 |
+
|
| 697 |
+
kid = t / num_subsets / m
|
| 698 |
+
return float(kid)
|
| 699 |
+
|
| 700 |
+
def extract_features(self, batch):
|
| 701 |
+
if self.center_crop:
|
| 702 |
+
crop_size = 299
|
| 703 |
+
h, w = batch.shape[2], batch.shape[3]
|
| 704 |
+
assert h >= crop_size and w >= crop_size
|
| 705 |
+
h_offset = (h - crop_size) // 2
|
| 706 |
+
w_offset = (w - crop_size) // 2
|
| 707 |
+
batch = batch[:, :, h_offset:h_offset + crop_size, w_offset:w_offset + crop_size]
|
| 708 |
+
elif self.resize:
|
| 709 |
+
batch = F.interpolate(
|
| 710 |
+
batch, size=(299, 299), mode='bicubic', align_corners=False, antialias=True).clamp(min=-1, max=1)
|
| 711 |
+
assert self.inception_style == 'StyleGAN'
|
| 712 |
+
batch = (batch * 127.5 + 128).clamp(0, 255).to(torch.uint8)
|
| 713 |
+
feat = self.inception_net(batch, return_features=True)
|
| 714 |
+
pred = F.linear(feat, self.inception_net.output.weight).softmax(dim=1)
|
| 715 |
+
return feat, pred
|
| 716 |
+
|
| 717 |
+
@torch.no_grad()
|
| 718 |
+
def feed_op(self, batch, mode):
|
| 719 |
+
if self.bgr2rgb:
|
| 720 |
+
batch = batch[:, [2, 1, 0]]
|
| 721 |
+
batch = batch.to(self.device)
|
| 722 |
+
|
| 723 |
+
feat, pred = self.extract_features(batch)
|
| 724 |
+
|
| 725 |
+
if dist.is_initialized():
|
| 726 |
+
ws = dist.get_world_size()
|
| 727 |
+
placeholder = [torch.zeros_like(feat) for _ in range(ws)]
|
| 728 |
+
dist.all_gather(placeholder, feat)
|
| 729 |
+
feat = torch.stack(placeholder, dim=1).reshape(feat.size(0) * ws, *feat.shape[1:])
|
| 730 |
+
if mode == 'fakes':
|
| 731 |
+
placeholder = [torch.zeros_like(pred) for _ in range(ws)]
|
| 732 |
+
dist.all_gather(placeholder, pred)
|
| 733 |
+
pred = torch.stack(placeholder, dim=1).reshape(pred.size(0) * ws, *pred.shape[1:])
|
| 734 |
+
|
| 735 |
+
# in distributed training, we only collect features at rank-0.
|
| 736 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 737 |
+
if mode == 'reals':
|
| 738 |
+
self.real_feats.append(feat.cpu())
|
| 739 |
+
elif mode == 'fakes':
|
| 740 |
+
self.fake_feats.append(feat.cpu())
|
| 741 |
+
self.preds.append(pred.cpu().numpy())
|
| 742 |
+
else:
|
| 743 |
+
raise ValueError(
|
| 744 |
+
f"The expected mode should be set to 'reals' or 'fakes,\
|
| 745 |
+
but got '{mode}'")
|
| 746 |
+
|
| 747 |
+
def feed(self, batch, mode):
|
| 748 |
+
if self.num_images is None:
|
| 749 |
+
self.feed_op(batch, mode)
|
| 750 |
+
|
| 751 |
+
else:
|
| 752 |
+
_, ws = get_dist_info()
|
| 753 |
+
if mode == 'reals':
|
| 754 |
+
if self.num_real_feeded == self.num_real_need:
|
| 755 |
+
return 0
|
| 756 |
+
|
| 757 |
+
if isinstance(batch, dict):
|
| 758 |
+
batch_size = len(list(batch.values())[0])
|
| 759 |
+
end = min(batch_size, self.num_real_need - self.num_real_feeded)
|
| 760 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 761 |
+
else:
|
| 762 |
+
batch_size = batch.shape[0]
|
| 763 |
+
end = min(batch_size, self.num_real_need - self.num_real_feeded)
|
| 764 |
+
batch_to_feed = batch[:end]
|
| 765 |
+
|
| 766 |
+
global_end = min(batch_size * ws,
|
| 767 |
+
self.num_real_need - self.num_real_feeded)
|
| 768 |
+
self.feed_op(batch_to_feed, mode)
|
| 769 |
+
self.num_real_feeded += global_end
|
| 770 |
+
return end
|
| 771 |
+
|
| 772 |
+
elif mode == 'fakes':
|
| 773 |
+
if self.num_fake_feeded == self.num_fake_need:
|
| 774 |
+
return 0
|
| 775 |
+
|
| 776 |
+
if isinstance(batch, dict):
|
| 777 |
+
batch_size = len(list(batch.values())[0])
|
| 778 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 779 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 780 |
+
else:
|
| 781 |
+
batch_size = batch.shape[0]
|
| 782 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 783 |
+
batch_to_feed = batch[:end]
|
| 784 |
+
|
| 785 |
+
global_end = min(batch_size * ws,
|
| 786 |
+
self.num_fake_need - self.num_fake_feeded)
|
| 787 |
+
self.feed_op(batch_to_feed, mode)
|
| 788 |
+
self.num_fake_feeded += global_end
|
| 789 |
+
return end
|
| 790 |
+
else:
|
| 791 |
+
raise ValueError(
|
| 792 |
+
'The expected mode should be set to \'reals\' or \'fakes\','
|
| 793 |
+
f'but got \'{mode}\'')
|
| 794 |
+
|
| 795 |
+
@torch.no_grad()
|
| 796 |
+
def summary(self):
|
| 797 |
+
real_feats = torch.cat(self.real_feats, dim=0)
|
| 798 |
+
fake_feats = torch.cat(self.fake_feats, dim=0)
|
| 799 |
+
if self.num_images is not None:
|
| 800 |
+
assert fake_feats.shape[0] >= self.num_images
|
| 801 |
+
fake_feats = fake_feats[:self.num_images]
|
| 802 |
+
if self.reference_pkl is None: # real feats not pre-calculated
|
| 803 |
+
assert real_feats.shape[0] >= self.num_images
|
| 804 |
+
real_feats = real_feats[:self.num_images]
|
| 805 |
+
|
| 806 |
+
if self.real_feats_np is None:
|
| 807 |
+
real_feats_np = real_feats.numpy()
|
| 808 |
+
self.real_feats_np = real_feats_np
|
| 809 |
+
self.real_mean = np.mean(real_feats_np, 0)
|
| 810 |
+
self.real_cov = np.cov(real_feats_np, rowvar=False)
|
| 811 |
+
|
| 812 |
+
self._result_dict = dict()
|
| 813 |
+
|
| 814 |
+
prefix = self.prefix + '_' if len(self.prefix) > 0 else ''
|
| 815 |
+
|
| 816 |
+
# FID
|
| 817 |
+
fake_feats_np = fake_feats.numpy()
|
| 818 |
+
fake_mean = np.mean(fake_feats_np, 0)
|
| 819 |
+
fake_cov = np.cov(fake_feats_np, rowvar=False)
|
| 820 |
+
fid, mean, cov = self._calc_fid(fake_mean, fake_cov, self.real_mean,
|
| 821 |
+
self.real_cov)
|
| 822 |
+
self._result_dict.update({f'{prefix}fid': fid})
|
| 823 |
+
_result_str = f'{prefix}FID: {fid:.4f} ({mean:.4f}/{cov:.4f})'
|
| 824 |
+
|
| 825 |
+
# KID
|
| 826 |
+
if self.use_kid:
|
| 827 |
+
kid = self._calc_kid(self.real_feats_np, fake_feats_np, self.kid_num_subsets,
|
| 828 |
+
self.kid_max_subset_size) * 1000
|
| 829 |
+
self._result_dict.update({f'{prefix}kid': kid})
|
| 830 |
+
_result_str += f', {prefix}KID: {kid:.4f}'
|
| 831 |
+
else:
|
| 832 |
+
kid = None
|
| 833 |
+
|
| 834 |
+
# PR
|
| 835 |
+
if self.use_pr:
|
| 836 |
+
for name, manifold, probes in [
|
| 837 |
+
(f'{prefix}precision', real_feats, fake_feats),
|
| 838 |
+
(f'{prefix}recall', fake_feats, real_feats)
|
| 839 |
+
]:
|
| 840 |
+
kth = []
|
| 841 |
+
for manifold_batch in manifold.split(self.pr_row_batch_size):
|
| 842 |
+
distance = compute_pr_distances(
|
| 843 |
+
row_features=manifold_batch,
|
| 844 |
+
col_features=manifold,
|
| 845 |
+
col_batch_size=self.pr_col_batch_size)
|
| 846 |
+
kth.append(
|
| 847 |
+
distance.to(torch.float32).kthvalue(self.pr_k + 1).values.to(torch.float16))
|
| 848 |
+
kth = torch.cat(kth)
|
| 849 |
+
pred = []
|
| 850 |
+
for probes_batch in probes.split(self.pr_row_batch_size):
|
| 851 |
+
distance = compute_pr_distances(
|
| 852 |
+
row_features=probes_batch,
|
| 853 |
+
col_features=manifold,
|
| 854 |
+
col_batch_size=self.pr_col_batch_size)
|
| 855 |
+
pred.append((distance <= kth).any(dim=1))
|
| 856 |
+
self._result_dict[name] = float(torch.cat(pred).to(torch.float32).mean())
|
| 857 |
+
precision = self._result_dict[f'{prefix}precision']
|
| 858 |
+
recall = self._result_dict[f'{prefix}recall']
|
| 859 |
+
_result_str += f', {prefix}Precision: {precision:.5f}, {prefix}Recall:{recall:.5f}'
|
| 860 |
+
else:
|
| 861 |
+
precision = recall = None
|
| 862 |
+
|
| 863 |
+
# IS
|
| 864 |
+
if self.use_is:
|
| 865 |
+
split_scores = []
|
| 866 |
+
self.preds = np.concatenate(self.preds, axis=0)
|
| 867 |
+
if self.num_images is not None:
|
| 868 |
+
assert self.preds.shape[0] >= self.num_images
|
| 869 |
+
self.preds = self.preds[:self.num_images]
|
| 870 |
+
num_preds = self.preds.shape[0]
|
| 871 |
+
for k in range(self.is_splits):
|
| 872 |
+
part = self.preds[k * (num_preds // self.is_splits):(k + 1) * (num_preds // self.is_splits), :]
|
| 873 |
+
py = np.mean(part, axis=0)
|
| 874 |
+
scores = []
|
| 875 |
+
for i in range(part.shape[0]):
|
| 876 |
+
pyx = part[i, :]
|
| 877 |
+
scores.append(entropy(pyx, py))
|
| 878 |
+
split_scores.append(np.exp(np.mean(scores)))
|
| 879 |
+
is_mean = np.mean(split_scores)
|
| 880 |
+
self._result_dict.update({f'{prefix}is': is_mean})
|
| 881 |
+
_result_str += f', {prefix}IS: {is_mean:.2f}'
|
| 882 |
+
else:
|
| 883 |
+
is_mean = None
|
| 884 |
+
|
| 885 |
+
self._result_str = _result_str
|
| 886 |
+
|
| 887 |
+
return fid, kid, precision, recall, is_mean
|
| 888 |
+
|
| 889 |
+
def clear_fake_data(self):
|
| 890 |
+
self.fake_feats = []
|
| 891 |
+
self.preds = []
|
| 892 |
+
self.num_fake_feeded = 0
|
| 893 |
+
|
| 894 |
+
def clear(self, clear_reals=False):
|
| 895 |
+
self.clear_fake_data()
|
| 896 |
+
if clear_reals:
|
| 897 |
+
self.real_feats = []
|
| 898 |
+
self.real_feats_np = None
|
| 899 |
+
self.num_real_feeded = 0
|
| 900 |
+
|
| 901 |
+
def load_to_gpu(self):
|
| 902 |
+
"""Move models to GPU."""
|
| 903 |
+
if torch.cuda.is_available():
|
| 904 |
+
self.inception_net.cuda()
|
| 905 |
+
self.device = 'cuda'
|
| 906 |
+
|
| 907 |
+
def offload_to_cpu(self):
|
| 908 |
+
"""Move models to CPU."""
|
| 909 |
+
self.inception_net.cpu()
|
| 910 |
+
self.device = 'cpu'
|
| 911 |
+
|
| 912 |
+
|
| 913 |
+
@METRICS.register_module()
|
| 914 |
+
class ColorStats(Metric):
|
| 915 |
+
name = 'ColorStats'
|
| 916 |
+
|
| 917 |
+
def __init__(self,
|
| 918 |
+
num_images=None):
|
| 919 |
+
super().__init__(num_images)
|
| 920 |
+
|
| 921 |
+
def prepare(self):
|
| 922 |
+
self.stats = []
|
| 923 |
+
|
| 924 |
+
@staticmethod
|
| 925 |
+
def srgb_to_linear(c):
|
| 926 |
+
threshold = 0.04045
|
| 927 |
+
below = c <= threshold
|
| 928 |
+
out = torch.where(
|
| 929 |
+
below, c / 12.92, ((c + 0.055) / 1.055) ** 2.4)
|
| 930 |
+
return out
|
| 931 |
+
|
| 932 |
+
@staticmethod
|
| 933 |
+
def linear_to_srgb(c):
|
| 934 |
+
threshold = 0.0031308
|
| 935 |
+
below = c <= threshold
|
| 936 |
+
out = torch.where(
|
| 937 |
+
below, 12.92 * c, 1.055 * c ** (1.0 / 2.4) - 0.055)
|
| 938 |
+
return out
|
| 939 |
+
|
| 940 |
+
def rgb_to_grayscale_srgb(self, img_srgb):
|
| 941 |
+
img_lin = self.srgb_to_linear(img_srgb)
|
| 942 |
+
R_lin, G_lin, B_lin = img_lin.unbind(dim=1)
|
| 943 |
+
Y_lin = 0.2126 * R_lin + 0.7152 * G_lin + 0.0722 * B_lin
|
| 944 |
+
gray_srgb = self.linear_to_srgb(Y_lin)
|
| 945 |
+
return gray_srgb
|
| 946 |
+
|
| 947 |
+
@staticmethod
|
| 948 |
+
def srgb_to_hsv_saturation(img_srgb):
|
| 949 |
+
c_max = torch.amax(img_srgb, dim=1)
|
| 950 |
+
c_min = torch.amin(img_srgb, dim=1)
|
| 951 |
+
delta = c_max - c_min
|
| 952 |
+
sat = delta / c_max.clamp(min=1e-5)
|
| 953 |
+
return sat
|
| 954 |
+
|
| 955 |
+
def compute_stats(self, batch):
|
| 956 |
+
batch = (batch / 2 + 0.5).clamp(0, 1)
|
| 957 |
+
gray = self.rgb_to_grayscale_srgb(batch).flatten(1)
|
| 958 |
+
contrast, brightness = torch.std_mean(gray, dim=1)
|
| 959 |
+
saturation = self.srgb_to_hsv_saturation(batch).flatten(1).mean(dim=1)
|
| 960 |
+
return torch.stack([brightness, contrast, saturation], dim=-1)
|
| 961 |
+
|
| 962 |
+
@torch.no_grad()
|
| 963 |
+
def feed_op(self, batch, mode):
|
| 964 |
+
stats = self.compute_stats(batch)
|
| 965 |
+
|
| 966 |
+
if dist.is_initialized():
|
| 967 |
+
ws = dist.get_world_size()
|
| 968 |
+
placeholder = [torch.zeros_like(stats) for _ in range(ws)]
|
| 969 |
+
dist.all_gather(placeholder, stats)
|
| 970 |
+
stats = torch.stack(placeholder, dim=1).reshape(stats.size(0) * ws, *stats.shape[1:])
|
| 971 |
+
|
| 972 |
+
# in distributed training, we only collect features at rank-0.
|
| 973 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 974 |
+
self.stats.append(stats.cpu())
|
| 975 |
+
|
| 976 |
+
def feed(self, batch, mode):
|
| 977 |
+
if mode == 'reals':
|
| 978 |
+
return 0
|
| 979 |
+
|
| 980 |
+
if self.num_images is None:
|
| 981 |
+
self.feed_op(batch, mode)
|
| 982 |
+
|
| 983 |
+
else:
|
| 984 |
+
_, ws = get_dist_info()
|
| 985 |
+
|
| 986 |
+
if self.num_fake_feeded == self.num_fake_need:
|
| 987 |
+
return 0
|
| 988 |
+
|
| 989 |
+
if isinstance(batch, dict):
|
| 990 |
+
batch_size = len(list(batch.values())[0])
|
| 991 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 992 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 993 |
+
else:
|
| 994 |
+
batch_size = batch.shape[0]
|
| 995 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 996 |
+
batch_to_feed = batch[:end]
|
| 997 |
+
|
| 998 |
+
global_end = min(batch_size * ws,
|
| 999 |
+
self.num_fake_need - self.num_fake_feeded)
|
| 1000 |
+
self.feed_op(batch_to_feed, mode)
|
| 1001 |
+
self.num_fake_feeded += global_end
|
| 1002 |
+
return end
|
| 1003 |
+
|
| 1004 |
+
@torch.no_grad()
|
| 1005 |
+
def summary(self):
|
| 1006 |
+
stats = torch.cat(self.stats, dim=0)
|
| 1007 |
+
if self.num_images is not None:
|
| 1008 |
+
assert stats.shape[0] >= self.num_images
|
| 1009 |
+
stats = stats[:self.num_images]
|
| 1010 |
+
stats = stats.mean(dim=0)
|
| 1011 |
+
brightness, contrast, saturation = stats.tolist()
|
| 1012 |
+
self._result_dict = dict(
|
| 1013 |
+
brightness=brightness, contrast=contrast, saturation=saturation)
|
| 1014 |
+
self._result_str = f'Brightness: {brightness:.4f}, Contrast: {contrast:.4f}, Saturation: {saturation:.4f}'
|
| 1015 |
+
return brightness, contrast, saturation
|
| 1016 |
+
|
| 1017 |
+
def clear_fake_data(self):
|
| 1018 |
+
self.stats = []
|
| 1019 |
+
self.num_fake_feeded = 0
|
| 1020 |
+
|
| 1021 |
+
def clear(self, clear_reals=False):
|
| 1022 |
+
self.clear_fake_data()
|
| 1023 |
+
|
| 1024 |
+
|
| 1025 |
+
@METRICS.register_module()
|
| 1026 |
+
class HPSv2(Metric):
|
| 1027 |
+
name = 'HPSv2'
|
| 1028 |
+
requires_prompt = True
|
| 1029 |
+
|
| 1030 |
+
def __init__(self,
|
| 1031 |
+
num_images=None,
|
| 1032 |
+
hps_version='v2.1'):
|
| 1033 |
+
super().__init__(num_images)
|
| 1034 |
+
self.hps_version = hps_version
|
| 1035 |
+
self.device = 'cpu' # Initialize on CPU
|
| 1036 |
+
self.dtype = torch.float16
|
| 1037 |
+
self.model, self.tokenizer = load_hpsv2(hps_version, device=self.device, precision='fp16')
|
| 1038 |
+
self.model.eval()
|
| 1039 |
+
image_size = self.model.visual.image_size
|
| 1040 |
+
if isinstance(image_size, tuple):
|
| 1041 |
+
assert len(image_size) == 2 and image_size[0] == image_size[1]
|
| 1042 |
+
image_size = image_size[0]
|
| 1043 |
+
self.image_size = image_size
|
| 1044 |
+
self.image_mean = torch.tensor(self.model.visual.image_mean, device=self.device).view(3, 1, 1)
|
| 1045 |
+
self.image_std = torch.tensor(self.model.visual.image_std, device=self.device).view(3, 1, 1)
|
| 1046 |
+
|
| 1047 |
+
def prepare(self):
|
| 1048 |
+
self.scores = []
|
| 1049 |
+
|
| 1050 |
+
def resize(self, imgs):
|
| 1051 |
+
h, w = imgs.shape[2:]
|
| 1052 |
+
scale = self.image_size / float(max(h, w))
|
| 1053 |
+
if scale != 1.0:
|
| 1054 |
+
h = int(round(h * scale))
|
| 1055 |
+
w = int(round(w * scale))
|
| 1056 |
+
imgs = F.interpolate(imgs, size=(h, w), mode='bicubic', align_corners=False, antialias=True).clamp(0, 1)
|
| 1057 |
+
if h != w:
|
| 1058 |
+
pad_h = self.image_size - h
|
| 1059 |
+
pad_w = self.image_size - w
|
| 1060 |
+
imgs = F.pad(
|
| 1061 |
+
imgs, (pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2), mode='constant', value=0)
|
| 1062 |
+
return imgs
|
| 1063 |
+
|
| 1064 |
+
@torch.no_grad()
|
| 1065 |
+
def feed_op(self, batch, mode):
|
| 1066 |
+
imgs = batch['imgs']
|
| 1067 |
+
prompts = batch['prompts']
|
| 1068 |
+
|
| 1069 |
+
imgs = (imgs.to(device=self.device, dtype=torch.float32) / 2 + 0.5).clamp(0, 1)
|
| 1070 |
+
imgs = ((self.resize(imgs) - self.image_mean) / self.image_std).to(dtype=self.dtype)
|
| 1071 |
+
prompts = self.tokenizer(prompts).to(device=self.device)
|
| 1072 |
+
|
| 1073 |
+
outputs = self.model(imgs, prompts)
|
| 1074 |
+
image_features, text_features = outputs['image_features'], outputs['text_features']
|
| 1075 |
+
hps_scores = (image_features * text_features).sum(dim=-1) # (bs, )
|
| 1076 |
+
|
| 1077 |
+
if dist.is_initialized():
|
| 1078 |
+
ws = dist.get_world_size()
|
| 1079 |
+
placeholder = [torch.empty_like(hps_scores) for _ in range(ws)]
|
| 1080 |
+
dist.all_gather(placeholder, hps_scores)
|
| 1081 |
+
hps_scores = torch.stack(placeholder, dim=1).reshape(hps_scores.size(0) * ws)
|
| 1082 |
+
|
| 1083 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 1084 |
+
self.scores.append(hps_scores.float().cpu())
|
| 1085 |
+
|
| 1086 |
+
def feed(self, batch, mode):
|
| 1087 |
+
if mode == 'reals':
|
| 1088 |
+
return 0
|
| 1089 |
+
|
| 1090 |
+
if self.num_images is None:
|
| 1091 |
+
self.feed_op(batch, mode)
|
| 1092 |
+
|
| 1093 |
+
else:
|
| 1094 |
+
_, ws = get_dist_info()
|
| 1095 |
+
|
| 1096 |
+
if self.num_fake_feeded == self.num_fake_need:
|
| 1097 |
+
return 0
|
| 1098 |
+
|
| 1099 |
+
if isinstance(batch, dict):
|
| 1100 |
+
batch_size = len(list(batch.values())[0])
|
| 1101 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 1102 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 1103 |
+
else:
|
| 1104 |
+
batch_size = batch.shape[0]
|
| 1105 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 1106 |
+
batch_to_feed = batch[:end]
|
| 1107 |
+
|
| 1108 |
+
global_end = min(batch_size * ws,
|
| 1109 |
+
self.num_fake_need - self.num_fake_feeded)
|
| 1110 |
+
self.feed_op(batch_to_feed, mode)
|
| 1111 |
+
self.num_fake_feeded += global_end
|
| 1112 |
+
return end
|
| 1113 |
+
|
| 1114 |
+
@torch.no_grad()
|
| 1115 |
+
def summary(self):
|
| 1116 |
+
scores = torch.cat(self.scores, dim=0)
|
| 1117 |
+
if self.num_images is not None:
|
| 1118 |
+
assert scores.shape[0] >= self.num_images
|
| 1119 |
+
scores = scores[:self.num_images]
|
| 1120 |
+
mean_score = scores.mean().item()
|
| 1121 |
+
self._result_dict = dict(hpsv2=mean_score)
|
| 1122 |
+
self._result_str = f'HPSv2: {mean_score:.4f}'
|
| 1123 |
+
return mean_score
|
| 1124 |
+
|
| 1125 |
+
def clear_fake_data(self):
|
| 1126 |
+
self.scores = []
|
| 1127 |
+
self.num_fake_feeded = 0
|
| 1128 |
+
|
| 1129 |
+
def clear(self, clear_reals=False):
|
| 1130 |
+
self.clear_fake_data()
|
| 1131 |
+
|
| 1132 |
+
def load_to_gpu(self):
|
| 1133 |
+
if torch.cuda.is_available():
|
| 1134 |
+
self.model.cuda()
|
| 1135 |
+
self.image_mean = self.image_mean.cuda()
|
| 1136 |
+
self.image_std = self.image_std.cuda()
|
| 1137 |
+
self.device = 'cuda'
|
| 1138 |
+
|
| 1139 |
+
def offload_to_cpu(self):
|
| 1140 |
+
self.model.cpu()
|
| 1141 |
+
self.image_mean = self.image_mean.cpu()
|
| 1142 |
+
self.image_std = self.image_std.cpu()
|
| 1143 |
+
self.device = 'cpu'
|
| 1144 |
+
|
| 1145 |
+
|
| 1146 |
+
@METRICS.register_module()
|
| 1147 |
+
class CLIPSimilarity(Metric):
|
| 1148 |
+
"""
|
| 1149 |
+
Average image–text CLIP cosine similarity (↑ better).
|
| 1150 |
+
Preprocess emulates OpenAI CLIP for ViT-L/14@336:
|
| 1151 |
+
- Resize so min(H, W) = 336 (bicubic, antialias), keep aspect ratio
|
| 1152 |
+
- Center crop to 336x336
|
| 1153 |
+
- Normalize with model.visual.image_mean/std
|
| 1154 |
+
Expects batch = {'imgs': (B,3,H,W) in [-1,1], 'prompts': List[str]}
|
| 1155 |
+
"""
|
| 1156 |
+
name = 'CLIPSimilarity'
|
| 1157 |
+
requires_prompt = True
|
| 1158 |
+
|
| 1159 |
+
def __init__(
|
| 1160 |
+
self,
|
| 1161 |
+
num_images=None,
|
| 1162 |
+
model_name='ViT-L-14-336-quickgelu',
|
| 1163 |
+
pretrained='openai',
|
| 1164 |
+
precision='fp16', # 'fp16' | 'fp32' | 'bf16'
|
| 1165 |
+
):
|
| 1166 |
+
super().__init__(num_images)
|
| 1167 |
+
self.model_name = model_name
|
| 1168 |
+
self.pretrained = pretrained
|
| 1169 |
+
self.precision = precision
|
| 1170 |
+
|
| 1171 |
+
self.device = 'cpu'
|
| 1172 |
+
self.dtype = {
|
| 1173 |
+
'fp16': torch.float16,
|
| 1174 |
+
'bf16': torch.bfloat16,
|
| 1175 |
+
'fp32': torch.float32
|
| 1176 |
+
}.get(precision, torch.float16)
|
| 1177 |
+
|
| 1178 |
+
self.model, self.tokenizer = load_openclip(
|
| 1179 |
+
model_name=model_name,
|
| 1180 |
+
pretrained=pretrained,
|
| 1181 |
+
device=self.device,
|
| 1182 |
+
precision=precision,
|
| 1183 |
+
)
|
| 1184 |
+
self.model.eval()
|
| 1185 |
+
|
| 1186 |
+
# OpenAI ViT-L/14@336 uses square 336 input
|
| 1187 |
+
image_size = self.model.visual.image_size
|
| 1188 |
+
if isinstance(image_size, tuple):
|
| 1189 |
+
assert len(image_size) == 2 and image_size[0] == image_size[1]
|
| 1190 |
+
image_size = image_size[0]
|
| 1191 |
+
self.image_size = int(image_size) # 336
|
| 1192 |
+
|
| 1193 |
+
# Use the model's own stats for normalization
|
| 1194 |
+
self.image_mean = torch.tensor(self.model.visual.image_mean, device=self.device).view(3, 1, 1)
|
| 1195 |
+
self.image_std = torch.tensor(self.model.visual.image_std, device=self.device).view(3, 1, 1)
|
| 1196 |
+
|
| 1197 |
+
def prepare(self):
|
| 1198 |
+
self.scores = []
|
| 1199 |
+
|
| 1200 |
+
def _resize_min_side_then_center_crop(self, imgs):
|
| 1201 |
+
"""
|
| 1202 |
+
imgs: (B,3,H,W) in [0,1], float32, on self.device
|
| 1203 |
+
1) Resize so min(H,W) == self.image_size, preserve AR (bicubic, antialias)
|
| 1204 |
+
2) Center-crop to (self.image_size, self.image_size)
|
| 1205 |
+
3) Normalize with model mean/std
|
| 1206 |
+
4) Cast to self.dtype
|
| 1207 |
+
"""
|
| 1208 |
+
_, _, H, W = imgs.shape
|
| 1209 |
+
target = self.image_size
|
| 1210 |
+
|
| 1211 |
+
# Scale factor so that the shorter side becomes 'target'
|
| 1212 |
+
short, long = (H, W) if H < W else (W, H)
|
| 1213 |
+
if short == 0:
|
| 1214 |
+
raise ValueError("Invalid image with zero dimension.")
|
| 1215 |
+
scale = target / float(short)
|
| 1216 |
+
|
| 1217 |
+
new_h = max(1, int(round(H * scale)))
|
| 1218 |
+
new_w = max(1, int(round(W * scale)))
|
| 1219 |
+
if new_h != H or new_w != W:
|
| 1220 |
+
imgs = F.interpolate(
|
| 1221 |
+
imgs, size=(new_h, new_w),
|
| 1222 |
+
mode='bicubic', align_corners=False, antialias=True
|
| 1223 |
+
).clamp(0, 1)
|
| 1224 |
+
|
| 1225 |
+
# Center crop to target x target
|
| 1226 |
+
top = max(0, (new_h - target) // 2)
|
| 1227 |
+
left = max(0, (new_w - target) // 2)
|
| 1228 |
+
imgs = imgs[:, :, top:top + target, left:left + target]
|
| 1229 |
+
|
| 1230 |
+
imgs = (imgs - self.image_mean) / self.image_std
|
| 1231 |
+
return imgs.to(dtype=self.dtype)
|
| 1232 |
+
|
| 1233 |
+
@torch.no_grad()
|
| 1234 |
+
def feed_op(self, batch, mode):
|
| 1235 |
+
if mode == 'reals':
|
| 1236 |
+
return 0
|
| 1237 |
+
|
| 1238 |
+
imgs = batch['imgs']
|
| 1239 |
+
prompts = batch['prompts']
|
| 1240 |
+
|
| 1241 |
+
# [-1,1] -> [0,1]
|
| 1242 |
+
imgs = (imgs.to(device=self.device, dtype=torch.float32) / 2 + 0.5).clamp(0, 1)
|
| 1243 |
+
imgs = self._resize_min_side_then_center_crop(imgs)
|
| 1244 |
+
|
| 1245 |
+
# Tokenize on device
|
| 1246 |
+
text = self.tokenizer(prompts).to(device=self.device)
|
| 1247 |
+
|
| 1248 |
+
# Forward (create_model(..., output_dict=True)) => dict w/ features
|
| 1249 |
+
out = self.model(imgs, text)
|
| 1250 |
+
if isinstance(out, dict) and ('image_features' in out and 'text_features' in out):
|
| 1251 |
+
img_feat = out['image_features']
|
| 1252 |
+
txt_feat = out['text_features']
|
| 1253 |
+
else:
|
| 1254 |
+
img_feat = self.model.encode_image(imgs)
|
| 1255 |
+
txt_feat = self.model.encode_text(text)
|
| 1256 |
+
|
| 1257 |
+
# Cosine similarity per pair
|
| 1258 |
+
img_feat = F.normalize(img_feat, dim=-1)
|
| 1259 |
+
txt_feat = F.normalize(txt_feat, dim=-1)
|
| 1260 |
+
sim = (img_feat * txt_feat).sum(dim=-1).to(torch.float32) # (B,)
|
| 1261 |
+
|
| 1262 |
+
# DDP gather
|
| 1263 |
+
if dist.is_initialized():
|
| 1264 |
+
ws = dist.get_world_size()
|
| 1265 |
+
bucket = [torch.empty_like(sim) for _ in range(ws)]
|
| 1266 |
+
dist.all_gather(bucket, sim)
|
| 1267 |
+
sim = torch.stack(bucket, dim=1).reshape(sim.size(0) * ws)
|
| 1268 |
+
|
| 1269 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 1270 |
+
self.scores.append(sim.cpu())
|
| 1271 |
+
|
| 1272 |
+
def feed(self, batch, mode):
|
| 1273 |
+
if mode == 'reals':
|
| 1274 |
+
return 0
|
| 1275 |
+
|
| 1276 |
+
if self.num_images is None:
|
| 1277 |
+
self.feed_op(batch, mode)
|
| 1278 |
+
|
| 1279 |
+
else:
|
| 1280 |
+
_, ws = get_dist_info()
|
| 1281 |
+
|
| 1282 |
+
if self.num_fake_feeded == self.num_fake_need:
|
| 1283 |
+
return 0
|
| 1284 |
+
|
| 1285 |
+
if isinstance(batch, dict):
|
| 1286 |
+
batch_size = len(list(batch.values())[0])
|
| 1287 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 1288 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 1289 |
+
else:
|
| 1290 |
+
batch_size = batch.shape[0]
|
| 1291 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 1292 |
+
batch_to_feed = batch[:end]
|
| 1293 |
+
|
| 1294 |
+
global_end = min(batch_size * ws, self.num_fake_need - self.num_fake_feeded)
|
| 1295 |
+
self.feed_op(batch_to_feed, mode)
|
| 1296 |
+
self.num_fake_feeded += global_end
|
| 1297 |
+
return end
|
| 1298 |
+
|
| 1299 |
+
@torch.no_grad()
|
| 1300 |
+
def summary(self):
|
| 1301 |
+
sims = torch.cat(self.scores, dim=0)
|
| 1302 |
+
if self.num_images is not None:
|
| 1303 |
+
assert sims.shape[0] >= self.num_images
|
| 1304 |
+
sims = sims[:self.num_images]
|
| 1305 |
+
mean_sim = sims.mean().item()
|
| 1306 |
+
|
| 1307 |
+
self._result_dict = dict(clipsim=mean_sim) # raw cosine in [-1,1]
|
| 1308 |
+
self._result_str = f'CLIPSim: {mean_sim:.4f}'
|
| 1309 |
+
return mean_sim
|
| 1310 |
+
|
| 1311 |
+
def clear_fake_data(self):
|
| 1312 |
+
self.scores = []
|
| 1313 |
+
self.num_fake_feeded = 0
|
| 1314 |
+
|
| 1315 |
+
def clear(self, clear_reals=False):
|
| 1316 |
+
self.clear_fake_data()
|
| 1317 |
+
|
| 1318 |
+
def load_to_gpu(self):
|
| 1319 |
+
if torch.cuda.is_available():
|
| 1320 |
+
self.model.cuda()
|
| 1321 |
+
self.image_mean = self.image_mean.cuda()
|
| 1322 |
+
self.image_std = self.image_std.cuda()
|
| 1323 |
+
self.device = 'cuda'
|
| 1324 |
+
|
| 1325 |
+
def offload_to_cpu(self):
|
| 1326 |
+
self.model.cpu()
|
| 1327 |
+
self.image_mean = self.image_mean.cpu()
|
| 1328 |
+
self.image_std = self.image_std.cpu()
|
| 1329 |
+
self.device = 'cpu'
|
lakonlab/evaluation/vqa_score.py
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modified from https://github.com/linzhiqiu/t2v_metrics
|
| 2 |
+
# Copyright 2023 Zhiqiu Lin
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import torch
|
| 7 |
+
import torch.distributed as dist
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
import mmcv
|
| 11 |
+
|
| 12 |
+
from typing import List, Optional, Tuple, Union
|
| 13 |
+
from dataclasses import dataclass, field
|
| 14 |
+
from torch.distributed.fsdp import MixedPrecision, ShardingStrategy, FullyShardedDataParallel
|
| 15 |
+
from torch.distributed.fsdp.wrap import ModuleWrapPolicy
|
| 16 |
+
from transformers import (
|
| 17 |
+
AutoConfig, AutoTokenizer, AutoModelForSeq2SeqLM, T5Config, T5ForConditionalGeneration,
|
| 18 |
+
CLIPVisionModel, CLIPImageProcessor, CLIPVisionConfig)
|
| 19 |
+
from transformers.models.t5.modeling_t5 import T5Block
|
| 20 |
+
from transformers.modeling_outputs import Seq2SeqLMOutput
|
| 21 |
+
from mmcv.runner import get_dist_info
|
| 22 |
+
from mmgen.core.registry import METRICS
|
| 23 |
+
from mmgen.core.evaluation.metrics import Metric
|
| 24 |
+
|
| 25 |
+
IMAGE_TOKEN_INDEX = -200
|
| 26 |
+
CONTEXT_LEN = 2048
|
| 27 |
+
SYSTEM_MSG = "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions."
|
| 28 |
+
IGNORE_INDEX = -100
|
| 29 |
+
DEFAULT_IMAGE_TOKEN = "<image>"
|
| 30 |
+
|
| 31 |
+
default_question_template = 'Does this figure show "{}"? Please answer yes or no.'
|
| 32 |
+
default_answer_template = "Yes"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def t5_tokenizer_image_token(prompt, tokenizer, image_token_index=IMAGE_TOKEN_INDEX, return_tensors=None):
|
| 36 |
+
prompt_chunks = [tokenizer(chunk).input_ids for chunk in prompt.split('<image>')]
|
| 37 |
+
|
| 38 |
+
def insert_separator(X, sep):
|
| 39 |
+
return [ele for sublist in zip(X, [sep] * len(X)) for ele in sublist][:-1]
|
| 40 |
+
|
| 41 |
+
input_ids = []
|
| 42 |
+
# Since there's no bos_token_id, simply concatenate the tokenized prompt_chunks with the image_token_index
|
| 43 |
+
for x in insert_separator(prompt_chunks, [image_token_index]):
|
| 44 |
+
input_ids.extend(x)
|
| 45 |
+
|
| 46 |
+
if return_tensors is not None:
|
| 47 |
+
if return_tensors == 'pt':
|
| 48 |
+
return torch.tensor(input_ids, dtype=torch.long)
|
| 49 |
+
raise ValueError(f'Unsupported tensor type: {return_tensors}')
|
| 50 |
+
return input_ids
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def format_question(question, conversation_style='plain'):
|
| 54 |
+
if conversation_style == 't5_plain': # for 1st stage t5 model
|
| 55 |
+
question = DEFAULT_IMAGE_TOKEN + question
|
| 56 |
+
elif conversation_style == 't5_chat': # for 2nd stage t5 model
|
| 57 |
+
question = SYSTEM_MSG + " USER: " + DEFAULT_IMAGE_TOKEN + "\n" + question + " ASSISTANT: "
|
| 58 |
+
elif conversation_style == 't5_chat_no_system': # for 2nd stage t5 model
|
| 59 |
+
question = "USER: " + DEFAULT_IMAGE_TOKEN + "\n" + question + " ASSISTANT: "
|
| 60 |
+
elif conversation_style == 't5_chat_no_system_no_user': # for 2nd stage t5 model
|
| 61 |
+
question = "" + DEFAULT_IMAGE_TOKEN + "\n" + question + " : "
|
| 62 |
+
# elif conversation_style == 't5_chat_ood_system': # for 2nd stage t5 model
|
| 63 |
+
# question = SYSTEM_MSG + " HUMAN: " + DEFAULT_IMAGE_TOKEN + "\n" + question + " GPT: "
|
| 64 |
+
else:
|
| 65 |
+
raise NotImplementedError()
|
| 66 |
+
return question
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def format_answer(answer, conversation_style='plain'):
|
| 70 |
+
return answer
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class CLIPVisionTower(nn.Module):
|
| 74 |
+
def __init__(self, vision_tower, args, delay_load=False):
|
| 75 |
+
super().__init__()
|
| 76 |
+
|
| 77 |
+
self.is_loaded = False
|
| 78 |
+
|
| 79 |
+
self.vision_tower_name = vision_tower
|
| 80 |
+
self.select_layer = args.mm_vision_select_layer
|
| 81 |
+
self.select_feature = getattr(args, 'mm_vision_select_feature', 'patch')
|
| 82 |
+
|
| 83 |
+
if not delay_load:
|
| 84 |
+
self.load_model()
|
| 85 |
+
else:
|
| 86 |
+
self.cfg_only = CLIPVisionConfig.from_pretrained(self.vision_tower_name)
|
| 87 |
+
|
| 88 |
+
def load_model(self):
|
| 89 |
+
self.image_processor = CLIPImageProcessor.from_pretrained(self.vision_tower_name)
|
| 90 |
+
self.vision_tower = CLIPVisionModel.from_pretrained(self.vision_tower_name)
|
| 91 |
+
self.vision_tower.requires_grad_(False)
|
| 92 |
+
|
| 93 |
+
self.is_loaded = True
|
| 94 |
+
|
| 95 |
+
def feature_select(self, image_forward_outs):
|
| 96 |
+
image_features = image_forward_outs.hidden_states[self.select_layer]
|
| 97 |
+
if self.select_feature == 'patch':
|
| 98 |
+
image_features = image_features[:, 1:]
|
| 99 |
+
elif self.select_feature == 'cls_patch':
|
| 100 |
+
image_features = image_features
|
| 101 |
+
else:
|
| 102 |
+
raise ValueError(f'Unexpected select feature: {self.select_feature}')
|
| 103 |
+
return image_features
|
| 104 |
+
|
| 105 |
+
@torch.no_grad()
|
| 106 |
+
def forward(self, images):
|
| 107 |
+
if type(images) is list:
|
| 108 |
+
image_features = []
|
| 109 |
+
for image in images:
|
| 110 |
+
image_forward_out = self.vision_tower(image.to(device=self.device, dtype=self.dtype).unsqueeze(0),
|
| 111 |
+
output_hidden_states=True)
|
| 112 |
+
image_feature = self.feature_select(image_forward_out).to(image.dtype)
|
| 113 |
+
image_features.append(image_feature)
|
| 114 |
+
else:
|
| 115 |
+
image_forward_outs = self.vision_tower(images.to(device=self.device, dtype=self.dtype),
|
| 116 |
+
output_hidden_states=True)
|
| 117 |
+
image_features = self.feature_select(image_forward_outs).to(images.dtype)
|
| 118 |
+
|
| 119 |
+
return image_features
|
| 120 |
+
|
| 121 |
+
@property
|
| 122 |
+
def dummy_feature(self):
|
| 123 |
+
return torch.zeros(1, self.hidden_size, device=self.device, dtype=self.dtype)
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def dtype(self):
|
| 127 |
+
return self.vision_tower.dtype
|
| 128 |
+
|
| 129 |
+
@property
|
| 130 |
+
def device(self):
|
| 131 |
+
return self.vision_tower.device
|
| 132 |
+
|
| 133 |
+
@property
|
| 134 |
+
def config(self):
|
| 135 |
+
if self.is_loaded:
|
| 136 |
+
return self.vision_tower.config
|
| 137 |
+
else:
|
| 138 |
+
return self.cfg_only
|
| 139 |
+
|
| 140 |
+
@property
|
| 141 |
+
def hidden_size(self):
|
| 142 |
+
return self.config.hidden_size
|
| 143 |
+
|
| 144 |
+
@property
|
| 145 |
+
def num_patches(self):
|
| 146 |
+
return (self.config.image_size // self.config.patch_size) ** 2
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class IdentityMap(nn.Module):
|
| 150 |
+
def __init__(self):
|
| 151 |
+
super().__init__()
|
| 152 |
+
|
| 153 |
+
def forward(self, x, *args, **kwargs):
|
| 154 |
+
return x
|
| 155 |
+
|
| 156 |
+
@property
|
| 157 |
+
def config(self):
|
| 158 |
+
return {"mm_projector_type": 'identity'}
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def build_vision_tower(vision_tower_cfg, **kwargs):
|
| 162 |
+
vision_tower = getattr(vision_tower_cfg, 'mm_vision_tower', getattr(vision_tower_cfg, 'vision_tower', None))
|
| 163 |
+
is_absolute_path_exists = os.path.exists(vision_tower)
|
| 164 |
+
if is_absolute_path_exists or vision_tower.startswith("openai") or vision_tower.startswith("laion"):
|
| 165 |
+
return CLIPVisionTower(vision_tower, args=vision_tower_cfg, **kwargs)
|
| 166 |
+
|
| 167 |
+
raise ValueError(f'Unknown vision tower: {vision_tower}')
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def build_vision_projector(config, delay_load=False, **kwargs):
|
| 171 |
+
projector_type = getattr(config, 'mm_projector_type', 'linear')
|
| 172 |
+
|
| 173 |
+
if projector_type == 'linear':
|
| 174 |
+
return nn.Linear(config.mm_hidden_size, config.hidden_size)
|
| 175 |
+
|
| 176 |
+
mlp_gelu_match = re.match(r'^mlp(\d+)x_gelu$', projector_type)
|
| 177 |
+
if mlp_gelu_match:
|
| 178 |
+
mlp_depth = int(mlp_gelu_match.group(1))
|
| 179 |
+
modules = [nn.Linear(config.mm_hidden_size, config.hidden_size)]
|
| 180 |
+
for _ in range(1, mlp_depth):
|
| 181 |
+
modules.append(nn.GELU())
|
| 182 |
+
modules.append(nn.Linear(config.hidden_size, config.hidden_size))
|
| 183 |
+
return nn.Sequential(*modules)
|
| 184 |
+
|
| 185 |
+
if projector_type == 'identity':
|
| 186 |
+
return IdentityMap()
|
| 187 |
+
|
| 188 |
+
raise ValueError(f'Unknown projector type: {projector_type}')
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
@dataclass
|
| 192 |
+
class ModelArguments:
|
| 193 |
+
tune_mm_mlp_adapter: bool = field(default=False)
|
| 194 |
+
vision_tower: Optional[str] = field(default='openai/clip-vit-large-patch14-336')
|
| 195 |
+
mm_vision_select_layer: Optional[int] = field(default=-2) # default to the second last layer in llava1.5
|
| 196 |
+
pretrain_mm_mlp_adapter: Optional[str] = field(default=None)
|
| 197 |
+
mm_projector_type: Optional[str] = field(default='mlp2x_gelu')
|
| 198 |
+
mm_vision_select_feature: Optional[str] = field(default="patch")
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class CLIPT5Config(T5Config):
|
| 202 |
+
model_type = "clip_t5"
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
class CLIPT5ForConditionalGeneration(T5ForConditionalGeneration):
|
| 206 |
+
# This class supports both T5 and FlanT5
|
| 207 |
+
config_class = CLIPT5Config
|
| 208 |
+
|
| 209 |
+
def __init__(self, config):
|
| 210 |
+
super(CLIPT5ForConditionalGeneration, self).__init__(config)
|
| 211 |
+
self.embed_tokens = self.encoder.embed_tokens
|
| 212 |
+
if hasattr(config, "mm_vision_tower"):
|
| 213 |
+
self.vision_tower = build_vision_tower(config, delay_load=False)
|
| 214 |
+
self.mm_projector = build_vision_projector(config)
|
| 215 |
+
|
| 216 |
+
def get_vision_tower(self):
|
| 217 |
+
vision_tower = getattr(self, 'vision_tower', None)
|
| 218 |
+
if type(vision_tower) is list:
|
| 219 |
+
vision_tower = vision_tower[0]
|
| 220 |
+
return vision_tower
|
| 221 |
+
|
| 222 |
+
def get_model(self):
|
| 223 |
+
return self # for compatibility with LlavaMetaForCausalLM
|
| 224 |
+
|
| 225 |
+
def prepare_inputs_labels_for_multimodal(
|
| 226 |
+
self, input_ids, attention_mask, decoder_attention_mask, past_key_values, labels, images
|
| 227 |
+
):
|
| 228 |
+
# The labels are now separated from the input_ids.
|
| 229 |
+
vision_tower = self.get_vision_tower()
|
| 230 |
+
if vision_tower is None or images is None or input_ids.shape[1] == 1:
|
| 231 |
+
raise NotImplementedError()
|
| 232 |
+
|
| 233 |
+
if type(images) is list or images.ndim == 5:
|
| 234 |
+
concat_images = torch.cat([image for image in images], dim=0)
|
| 235 |
+
image_features = self.encode_images(concat_images)
|
| 236 |
+
split_sizes = [image.shape[0] for image in images]
|
| 237 |
+
image_features = torch.split(image_features, split_sizes, dim=0)
|
| 238 |
+
image_features = [x.flatten(0, 1) for x in image_features]
|
| 239 |
+
else:
|
| 240 |
+
image_features = self.encode_images(images)
|
| 241 |
+
|
| 242 |
+
new_input_embeds = []
|
| 243 |
+
cur_image_idx = 0
|
| 244 |
+
for _, cur_input_ids in enumerate(input_ids):
|
| 245 |
+
if (cur_input_ids == IMAGE_TOKEN_INDEX).sum() == 0:
|
| 246 |
+
# multimodal LLM, but the current sample is not multimodal
|
| 247 |
+
raise NotImplementedError()
|
| 248 |
+
image_token_indices = torch.where(cur_input_ids == IMAGE_TOKEN_INDEX)[0]
|
| 249 |
+
cur_new_input_embeds = []
|
| 250 |
+
while image_token_indices.numel() > 0:
|
| 251 |
+
cur_image_features = image_features[cur_image_idx]
|
| 252 |
+
image_token_start = image_token_indices[0]
|
| 253 |
+
cur_new_input_embeds.append(self.embed_tokens(cur_input_ids[:image_token_start]))
|
| 254 |
+
cur_new_input_embeds.append(cur_image_features)
|
| 255 |
+
cur_image_idx += 1
|
| 256 |
+
cur_input_ids = cur_input_ids[image_token_start + 1:]
|
| 257 |
+
image_token_indices = torch.where(cur_input_ids == IMAGE_TOKEN_INDEX)[0]
|
| 258 |
+
if cur_input_ids.numel() > 0:
|
| 259 |
+
cur_new_input_embeds.append(self.embed_tokens(cur_input_ids))
|
| 260 |
+
cur_new_input_embeds = [x.to(device=self.device) for x in cur_new_input_embeds]
|
| 261 |
+
cur_new_input_embeds = torch.cat(cur_new_input_embeds, dim=0)
|
| 262 |
+
new_input_embeds.append(cur_new_input_embeds)
|
| 263 |
+
|
| 264 |
+
if any(x.shape != new_input_embeds[0].shape for x in new_input_embeds):
|
| 265 |
+
max_len = max(x.shape[0] for x in new_input_embeds)
|
| 266 |
+
|
| 267 |
+
new_input_embeds_align = []
|
| 268 |
+
_input_embeds_lengths = []
|
| 269 |
+
for cur_new_embed in new_input_embeds:
|
| 270 |
+
_input_embeds_lengths.append(cur_new_embed.shape[0])
|
| 271 |
+
cur_new_embed = torch.cat((cur_new_embed,
|
| 272 |
+
torch.zeros((max_len - cur_new_embed.shape[0], cur_new_embed.shape[1]),
|
| 273 |
+
dtype=cur_new_embed.dtype, device=cur_new_embed.device)), dim=0)
|
| 274 |
+
new_input_embeds_align.append(cur_new_embed)
|
| 275 |
+
new_input_embeds = torch.stack(new_input_embeds_align, dim=0)
|
| 276 |
+
|
| 277 |
+
if attention_mask is not None:
|
| 278 |
+
new_attention_mask = []
|
| 279 |
+
for cur_attention_mask, _input_embeds_length in zip(attention_mask, _input_embeds_lengths):
|
| 280 |
+
new_attn_mask_pad_left = torch.full((_input_embeds_length - input_ids.shape[1],), True,
|
| 281 |
+
dtype=attention_mask.dtype, device=attention_mask.device)
|
| 282 |
+
new_attn_mask_pad_right = torch.full((new_input_embeds.shape[1] - _input_embeds_length,), False,
|
| 283 |
+
dtype=attention_mask.dtype, device=attention_mask.device)
|
| 284 |
+
cur_new_attention_mask = torch.cat(
|
| 285 |
+
(new_attn_mask_pad_left, cur_attention_mask, new_attn_mask_pad_right), dim=0)
|
| 286 |
+
new_attention_mask.append(cur_new_attention_mask)
|
| 287 |
+
attention_mask = torch.stack(new_attention_mask, dim=0)
|
| 288 |
+
assert attention_mask.shape == new_input_embeds.shape[:2]
|
| 289 |
+
else:
|
| 290 |
+
new_input_embeds = torch.stack(new_input_embeds, dim=0)
|
| 291 |
+
|
| 292 |
+
if attention_mask is not None:
|
| 293 |
+
new_attn_mask_pad_left = torch.full(
|
| 294 |
+
(attention_mask.shape[0], new_input_embeds.shape[1] - input_ids.shape[1]), True,
|
| 295 |
+
dtype=attention_mask.dtype, device=attention_mask.device)
|
| 296 |
+
attention_mask = torch.cat((new_attn_mask_pad_left, attention_mask), dim=1)
|
| 297 |
+
assert attention_mask.shape == new_input_embeds.shape[:2]
|
| 298 |
+
|
| 299 |
+
return None, attention_mask, decoder_attention_mask, past_key_values, new_input_embeds, labels
|
| 300 |
+
|
| 301 |
+
def encode_images(self, images):
|
| 302 |
+
image_features = self.get_vision_tower()(images)
|
| 303 |
+
image_features = self.mm_projector(image_features)
|
| 304 |
+
return image_features
|
| 305 |
+
|
| 306 |
+
def initialize_vision_modules(self, model_args, fsdp=None):
|
| 307 |
+
vision_tower = model_args.vision_tower
|
| 308 |
+
mm_vision_select_layer = model_args.mm_vision_select_layer
|
| 309 |
+
mm_vision_select_feature = model_args.mm_vision_select_feature
|
| 310 |
+
pretrain_mm_mlp_adapter = model_args.pretrain_mm_mlp_adapter
|
| 311 |
+
|
| 312 |
+
self.config.mm_vision_tower = vision_tower
|
| 313 |
+
self.config.pretrain_mm_mlp_adapter = pretrain_mm_mlp_adapter
|
| 314 |
+
|
| 315 |
+
if self.get_vision_tower() is None:
|
| 316 |
+
vision_tower = build_vision_tower(model_args)
|
| 317 |
+
|
| 318 |
+
if fsdp is not None and len(fsdp) > 0:
|
| 319 |
+
self.vision_tower = [vision_tower]
|
| 320 |
+
else:
|
| 321 |
+
self.vision_tower = vision_tower
|
| 322 |
+
else:
|
| 323 |
+
if fsdp is not None and len(fsdp) > 0:
|
| 324 |
+
vision_tower = self.vision_tower[0]
|
| 325 |
+
else:
|
| 326 |
+
vision_tower = self.vision_tower
|
| 327 |
+
if not vision_tower.is_loaded:
|
| 328 |
+
vision_tower.load_model()
|
| 329 |
+
|
| 330 |
+
self.config.use_mm_proj = True
|
| 331 |
+
self.config.mm_projector_type = getattr(model_args, 'mm_projector_type', 'mlp2x_gelu')
|
| 332 |
+
self.config.mm_hidden_size = vision_tower.hidden_size
|
| 333 |
+
self.config.mm_vision_select_layer = mm_vision_select_layer
|
| 334 |
+
self.config.mm_vision_select_feature = mm_vision_select_feature
|
| 335 |
+
|
| 336 |
+
if getattr(self, 'mm_projector', None) is None:
|
| 337 |
+
self.mm_projector = build_vision_projector(self.config)
|
| 338 |
+
|
| 339 |
+
if pretrain_mm_mlp_adapter is not None:
|
| 340 |
+
mm_projector_weights = torch.load(pretrain_mm_mlp_adapter, map_location='cpu')
|
| 341 |
+
|
| 342 |
+
def get_w(weights, keyword):
|
| 343 |
+
return {k.split(keyword + '.')[1]: v for k, v in weights.items() if keyword in k}
|
| 344 |
+
|
| 345 |
+
self.mm_projector.load_state_dict(get_w(mm_projector_weights, 'mm_projector'))
|
| 346 |
+
|
| 347 |
+
def forward(
|
| 348 |
+
self,
|
| 349 |
+
input_ids: torch.LongTensor = None,
|
| 350 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 351 |
+
decoder_attention_mask: Optional[torch.Tensor] = None,
|
| 352 |
+
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
| 353 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 354 |
+
labels: Optional[torch.LongTensor] = None,
|
| 355 |
+
use_cache: Optional[bool] = None,
|
| 356 |
+
output_attentions: Optional[bool] = None,
|
| 357 |
+
output_hidden_states: Optional[bool] = None,
|
| 358 |
+
images: Optional[torch.FloatTensor] = None,
|
| 359 |
+
return_dict: Optional[bool] = None,
|
| 360 |
+
**kwargs,
|
| 361 |
+
) -> Union[Tuple[torch.FloatTensor], Seq2SeqLMOutput]:
|
| 362 |
+
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
| 363 |
+
output_hidden_states = (
|
| 364 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 365 |
+
)
|
| 366 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 367 |
+
|
| 368 |
+
if inputs_embeds is None:
|
| 369 |
+
_, attention_mask, decoder_attention_mask, past_key_values, inputs_embeds, labels = \
|
| 370 |
+
self.prepare_inputs_labels_for_multimodal(input_ids, attention_mask, decoder_attention_mask,
|
| 371 |
+
past_key_values, labels, images)
|
| 372 |
+
|
| 373 |
+
# decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
|
| 374 |
+
outputs = super(CLIPT5ForConditionalGeneration, self).forward(
|
| 375 |
+
input_ids=None, # will be None if inputs_embeds is not None
|
| 376 |
+
attention_mask=attention_mask,
|
| 377 |
+
decoder_attention_mask=decoder_attention_mask,
|
| 378 |
+
labels=labels,
|
| 379 |
+
past_key_values=past_key_values,
|
| 380 |
+
inputs_embeds=inputs_embeds,
|
| 381 |
+
use_cache=use_cache,
|
| 382 |
+
output_attentions=output_attentions,
|
| 383 |
+
output_hidden_states=output_hidden_states,
|
| 384 |
+
return_dict=return_dict,
|
| 385 |
+
**kwargs,
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
return outputs
|
| 389 |
+
|
| 390 |
+
@torch.no_grad()
|
| 391 |
+
def generate(
|
| 392 |
+
self,
|
| 393 |
+
inputs: Optional[torch.Tensor] = None,
|
| 394 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 395 |
+
images: Optional[torch.Tensor] = None,
|
| 396 |
+
**kwargs,
|
| 397 |
+
):
|
| 398 |
+
assert images is not None, "images must be provided"
|
| 399 |
+
assert inputs is not None, "inputs must be provided"
|
| 400 |
+
assert attention_mask is not None, "attention_mask must be provided"
|
| 401 |
+
_, attention_mask, _, _, inputs_embeds, _ = \
|
| 402 |
+
self.prepare_inputs_labels_for_multimodal(inputs, attention_mask, None, None, None, images)
|
| 403 |
+
# decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
|
| 404 |
+
outputs = super(CLIPT5ForConditionalGeneration, self).generate(
|
| 405 |
+
input_ids=None, # will be None if inputs_embeds is not None
|
| 406 |
+
attention_mask=attention_mask,
|
| 407 |
+
inputs_embeds=inputs_embeds,
|
| 408 |
+
)
|
| 409 |
+
return outputs
|
| 410 |
+
|
| 411 |
+
def prepare_inputs_for_generation(
|
| 412 |
+
self,
|
| 413 |
+
input_ids,
|
| 414 |
+
past_key_values=None,
|
| 415 |
+
attention_mask=None,
|
| 416 |
+
head_mask=None,
|
| 417 |
+
decoder_head_mask=None,
|
| 418 |
+
decoder_attention_mask=None,
|
| 419 |
+
cross_attn_head_mask=None,
|
| 420 |
+
use_cache=None,
|
| 421 |
+
encoder_outputs=None,
|
| 422 |
+
inputs_embeds=None,
|
| 423 |
+
**kwargs,
|
| 424 |
+
):
|
| 425 |
+
# cut decoder_input_ids if past_key_values is used
|
| 426 |
+
if past_key_values is not None:
|
| 427 |
+
past_length = past_key_values[0][0].shape[2]
|
| 428 |
+
|
| 429 |
+
# Some generation methods already pass only the last input ID
|
| 430 |
+
if input_ids.shape[1] > past_length:
|
| 431 |
+
remove_prefix_length = past_length
|
| 432 |
+
else:
|
| 433 |
+
# Default to old behavior: keep only final ID
|
| 434 |
+
remove_prefix_length = input_ids.shape[1] - 1
|
| 435 |
+
|
| 436 |
+
input_ids = input_ids[:, remove_prefix_length:]
|
| 437 |
+
|
| 438 |
+
# if `inputs_embeds` are passed, we only want to use them in the 1st generation step
|
| 439 |
+
if inputs_embeds is not None and past_key_values is None:
|
| 440 |
+
model_inputs = {"inputs_embeds": inputs_embeds}
|
| 441 |
+
else:
|
| 442 |
+
model_inputs = {"input_ids": input_ids}
|
| 443 |
+
|
| 444 |
+
model_inputs.update({
|
| 445 |
+
"decoder_input_ids": input_ids,
|
| 446 |
+
"past_key_values": past_key_values,
|
| 447 |
+
"encoder_outputs": encoder_outputs,
|
| 448 |
+
"attention_mask": attention_mask,
|
| 449 |
+
"head_mask": head_mask,
|
| 450 |
+
"decoder_head_mask": decoder_head_mask,
|
| 451 |
+
"decoder_attention_mask": decoder_attention_mask,
|
| 452 |
+
"cross_attn_head_mask": cross_attn_head_mask,
|
| 453 |
+
"use_cache": use_cache,
|
| 454 |
+
})
|
| 455 |
+
return model_inputs
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
AutoConfig.register("clip_t5", CLIPT5Config)
|
| 459 |
+
AutoModelForSeq2SeqLM.register(CLIPT5Config, CLIPT5ForConditionalGeneration)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
_vqascore_cache = {}
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def load_vqascore(device, dtype, use_fsdp=True):
|
| 466 |
+
# Create cache key from arguments
|
| 467 |
+
cache_key = f"{device}_{dtype}_{use_fsdp}"
|
| 468 |
+
|
| 469 |
+
# Check if model is already cached
|
| 470 |
+
if cache_key in _vqascore_cache:
|
| 471 |
+
return _vqascore_cache[cache_key]
|
| 472 |
+
|
| 473 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 474 |
+
'google/flan-t5-xxl', use_fast=False, model_max_length=2048)
|
| 475 |
+
model = CLIPT5ForConditionalGeneration.from_pretrained(
|
| 476 |
+
'zhiqiulin/clip-flant5-xxl',
|
| 477 |
+
torch_dtype=dtype,
|
| 478 |
+
use_cache=False,
|
| 479 |
+
freeze_mm_mlp_adapter=True)
|
| 480 |
+
model.requires_grad_(False)
|
| 481 |
+
model.resize_token_embeddings(len(tokenizer))
|
| 482 |
+
|
| 483 |
+
if use_fsdp:
|
| 484 |
+
mmcv.print_log('Wrapping VQAScore model with FSDP.')
|
| 485 |
+
model = FullyShardedDataParallel(
|
| 486 |
+
model,
|
| 487 |
+
device_id=torch.cuda.current_device(),
|
| 488 |
+
use_orig_params=False,
|
| 489 |
+
mixed_precision=MixedPrecision(
|
| 490 |
+
param_dtype=dtype,
|
| 491 |
+
reduce_dtype=dtype,
|
| 492 |
+
buffer_dtype=dtype,
|
| 493 |
+
cast_root_forward_inputs=False),
|
| 494 |
+
sharding_strategy=ShardingStrategy.HYBRID_SHARD,
|
| 495 |
+
auto_wrap_policy=ModuleWrapPolicy([T5Block]))
|
| 496 |
+
else:
|
| 497 |
+
model.to(device)
|
| 498 |
+
|
| 499 |
+
result = model, tokenizer
|
| 500 |
+
_vqascore_cache[cache_key] = result
|
| 501 |
+
return result
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
@METRICS.register_module()
|
| 505 |
+
class VQAScore(Metric):
|
| 506 |
+
name = 'VQAScore'
|
| 507 |
+
requires_prompt = True
|
| 508 |
+
|
| 509 |
+
def __init__(self,
|
| 510 |
+
num_images=None,
|
| 511 |
+
use_fsdp=True):
|
| 512 |
+
super().__init__(num_images)
|
| 513 |
+
use_fsdp = use_fsdp and torch.cuda.is_available() and dist.is_initialized() and dist.get_world_size() > 0
|
| 514 |
+
|
| 515 |
+
self.use_fsdp = use_fsdp
|
| 516 |
+
self.dtype = torch.bfloat16
|
| 517 |
+
self.device = 'cuda' if use_fsdp else 'cpu'
|
| 518 |
+
|
| 519 |
+
self.model, self.tokenizer = load_vqascore(device=self.device, dtype=self.dtype, use_fsdp=use_fsdp)
|
| 520 |
+
self.model.eval()
|
| 521 |
+
image_processor = self.model.get_vision_tower().image_processor
|
| 522 |
+
image_size = tuple(image_processor.crop_size.values())
|
| 523 |
+
assert len(image_size) == 2 and image_size[0] == image_size[1]
|
| 524 |
+
self.image_size = image_size[0]
|
| 525 |
+
self.image_mean = torch.tensor(image_processor.image_mean, device=self.device).view(3, 1, 1)
|
| 526 |
+
self.image_std = torch.tensor(image_processor.image_std, device=self.device).view(3, 1, 1)
|
| 527 |
+
self.clamp_high = (1 - self.image_mean) / self.image_std
|
| 528 |
+
self.clamp_low = -self.image_mean / self.image_std
|
| 529 |
+
|
| 530 |
+
def prepare(self):
|
| 531 |
+
self.scores = []
|
| 532 |
+
|
| 533 |
+
def resize(self, imgs):
|
| 534 |
+
h, w = imgs.shape[2:]
|
| 535 |
+
if h != w:
|
| 536 |
+
pad_size = max(h, w)
|
| 537 |
+
pad_h = pad_size - h
|
| 538 |
+
pad_w = pad_size - w
|
| 539 |
+
imgs = F.pad(
|
| 540 |
+
imgs, (pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2), mode='constant', value=0)
|
| 541 |
+
h = w = pad_size
|
| 542 |
+
if h != self.image_size:
|
| 543 |
+
imgs = F.interpolate(imgs, size=self.image_size, mode='bicubic', align_corners=False, antialias=True)
|
| 544 |
+
imgs = torch.maximum(torch.minimum(imgs, self.clamp_high), self.clamp_low)
|
| 545 |
+
return imgs
|
| 546 |
+
|
| 547 |
+
@torch.no_grad()
|
| 548 |
+
def feed_op(self, batch, mode):
|
| 549 |
+
imgs = batch['imgs']
|
| 550 |
+
prompts = batch['prompts']
|
| 551 |
+
|
| 552 |
+
imgs = (imgs.to(device=self.device, dtype=torch.float32) / 2 + 0.5).clamp(0, 1)
|
| 553 |
+
imgs = self.resize((imgs - self.image_mean) / self.image_std).to(dtype=self.dtype)
|
| 554 |
+
|
| 555 |
+
# ========= preprocess prompts =========
|
| 556 |
+
questions = [default_question_template.format(prompt) for prompt in prompts]
|
| 557 |
+
answers = [default_answer_template.format(prompt) for prompt in prompts]
|
| 558 |
+
|
| 559 |
+
questions = [format_question(question, conversation_style='t5_chat') for question in questions]
|
| 560 |
+
answers = [format_answer(answer, conversation_style='t5_chat') for answer in answers]
|
| 561 |
+
|
| 562 |
+
input_ids = [t5_tokenizer_image_token(question, self.tokenizer, return_tensors='pt') for question in questions]
|
| 563 |
+
labels = [t5_tokenizer_image_token(answer, self.tokenizer, return_tensors='pt') for answer in answers]
|
| 564 |
+
|
| 565 |
+
input_ids = torch.nn.utils.rnn.pad_sequence(
|
| 566 |
+
input_ids, batch_first=True, padding_value=0)[:, :self.tokenizer.model_max_length]
|
| 567 |
+
labels = torch.nn.utils.rnn.pad_sequence(
|
| 568 |
+
labels, batch_first=True, padding_value=IGNORE_INDEX)[:, :self.tokenizer.model_max_length]
|
| 569 |
+
|
| 570 |
+
input_ids = input_ids.to(device=self.device)
|
| 571 |
+
labels = labels.to(device=self.device)
|
| 572 |
+
|
| 573 |
+
attention_mask = input_ids.ne(self.tokenizer.pad_token_id).to(device=self.device)
|
| 574 |
+
decoder_attention_mask = labels.ne(IGNORE_INDEX).to(device=self.device)
|
| 575 |
+
|
| 576 |
+
outputs = self.model(
|
| 577 |
+
input_ids=input_ids,
|
| 578 |
+
attention_mask=attention_mask,
|
| 579 |
+
decoder_attention_mask=decoder_attention_mask,
|
| 580 |
+
labels=labels,
|
| 581 |
+
images=imgs,
|
| 582 |
+
past_key_values=None,
|
| 583 |
+
inputs_embeds=None,
|
| 584 |
+
use_cache=None,
|
| 585 |
+
output_attentions=None,
|
| 586 |
+
output_hidden_states=None,
|
| 587 |
+
return_dict=True,
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
logits = outputs.logits
|
| 591 |
+
bs, seq_len, vocab_size = logits.size()
|
| 592 |
+
vqa_score = (-F.cross_entropy(
|
| 593 |
+
logits.reshape(bs * seq_len, vocab_size), labels.reshape(bs * seq_len), reduction='none'
|
| 594 |
+
).reshape(bs, 2).mean(dim=1)).exp() # (bs, )
|
| 595 |
+
|
| 596 |
+
if dist.is_initialized():
|
| 597 |
+
ws = dist.get_world_size()
|
| 598 |
+
placeholder = [torch.empty_like(vqa_score) for _ in range(ws)]
|
| 599 |
+
dist.all_gather(placeholder, vqa_score)
|
| 600 |
+
vqa_score = torch.stack(placeholder, dim=1).reshape(vqa_score.size(0) * ws)
|
| 601 |
+
|
| 602 |
+
if (dist.is_initialized() and dist.get_rank() == 0) or not dist.is_initialized():
|
| 603 |
+
self.scores.append(vqa_score.float().cpu())
|
| 604 |
+
|
| 605 |
+
def feed(self, batch, mode):
|
| 606 |
+
if mode == 'reals':
|
| 607 |
+
return 0
|
| 608 |
+
|
| 609 |
+
if self.num_images is None:
|
| 610 |
+
self.feed_op(batch, mode)
|
| 611 |
+
|
| 612 |
+
else:
|
| 613 |
+
_, ws = get_dist_info()
|
| 614 |
+
|
| 615 |
+
if self.num_fake_feeded == self.num_fake_need:
|
| 616 |
+
return 0
|
| 617 |
+
|
| 618 |
+
if isinstance(batch, dict):
|
| 619 |
+
batch_size = len(list(batch.values())[0])
|
| 620 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 621 |
+
batch_to_feed = {k: v[:end] for k, v in batch.items()}
|
| 622 |
+
else:
|
| 623 |
+
batch_size = batch.shape[0]
|
| 624 |
+
end = min(batch_size, self.num_fake_need - self.num_fake_feeded)
|
| 625 |
+
batch_to_feed = batch[:end]
|
| 626 |
+
|
| 627 |
+
global_end = min(batch_size * ws,
|
| 628 |
+
self.num_fake_need - self.num_fake_feeded)
|
| 629 |
+
self.feed_op(batch_to_feed, mode)
|
| 630 |
+
self.num_fake_feeded += global_end
|
| 631 |
+
return end
|
| 632 |
+
|
| 633 |
+
@torch.no_grad()
|
| 634 |
+
def summary(self):
|
| 635 |
+
scores = torch.cat(self.scores, dim=0)
|
| 636 |
+
if self.num_images is not None:
|
| 637 |
+
assert scores.shape[0] >= self.num_images
|
| 638 |
+
scores = scores[:self.num_images]
|
| 639 |
+
mean_score = scores.mean().item()
|
| 640 |
+
self._result_dict = dict(vqascore=mean_score)
|
| 641 |
+
self._result_str = f'VQAScore: {mean_score:.4f}'
|
| 642 |
+
return mean_score
|
| 643 |
+
|
| 644 |
+
def clear_fake_data(self):
|
| 645 |
+
self.scores = []
|
| 646 |
+
self.num_fake_feeded = 0
|
| 647 |
+
|
| 648 |
+
def clear(self, clear_reals=False):
|
| 649 |
+
self.clear_fake_data()
|
| 650 |
+
|
| 651 |
+
def load_to_gpu(self):
|
| 652 |
+
if torch.cuda.is_available() and not isinstance(self.model, FullyShardedDataParallel):
|
| 653 |
+
self.model.cuda()
|
| 654 |
+
self.image_mean = self.image_mean.cuda()
|
| 655 |
+
self.image_std = self.image_std.cuda()
|
| 656 |
+
self.clamp_high = self.clamp_high.cuda()
|
| 657 |
+
self.clamp_low = self.clamp_low.cuda()
|
| 658 |
+
self.device = 'cuda'
|
| 659 |
+
|
| 660 |
+
def offload_to_cpu(self):
|
| 661 |
+
if not isinstance(self.model, FullyShardedDataParallel):
|
| 662 |
+
self.model.cpu()
|
| 663 |
+
self.image_mean = self.image_mean.cpu()
|
| 664 |
+
self.image_std = self.image_std.cpu()
|
| 665 |
+
self.clamp_high = self.clamp_high.cpu()
|
| 666 |
+
self.clamp_low = self.clamp_low.cpu()
|
| 667 |
+
self.device = 'cpu'
|
lakonlab/models/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .losses import *
|
| 2 |
+
from .architecture import *
|
| 3 |
+
from .diffusions import *
|
| 4 |
+
from .diffusion_2d import Diffusion2D
|
| 5 |
+
from .latent_diffusion_class_image import LatentDiffusionClassImage
|
| 6 |
+
from .latent_diffusion_text_image import LatentDiffusionTextImage
|
lakonlab/models/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (484 Bytes). View file
|
|
|
lakonlab/models/__pycache__/base.cpython-310.pyc
ADDED
|
Binary file (5.01 kB). View file
|
|
|
lakonlab/models/__pycache__/base_diffusion.cpython-310.pyc
ADDED
|
Binary file (5.23 kB). View file
|
|
|
lakonlab/models/__pycache__/diffusion_2d.cpython-310.pyc
ADDED
|
Binary file (2.31 kB). View file
|
|
|
lakonlab/models/__pycache__/latent_diffusion_class_image.cpython-310.pyc
ADDED
|
Binary file (3.29 kB). View file
|
|
|
lakonlab/models/__pycache__/latent_diffusion_text_image.cpython-310.pyc
ADDED
|
Binary file (5.31 kB). View file
|
|
|
lakonlab/models/architecture/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .ddpm import *
|
| 2 |
+
from .diffusers import *
|
| 3 |
+
from .gmflow import *
|
| 4 |
+
from .dxflow import *
|
lakonlab/models/architecture/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (304 Bytes). View file
|
|
|
lakonlab/models/architecture/__pycache__/utils.cpython-310.pyc
ADDED
|
Binary file (2.23 kB). View file
|
|
|
lakonlab/models/architecture/ddpm/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .denoising import DenoisingUnetMod
|
| 2 |
+
from .modules import (
|
| 3 |
+
MultiHeadAttentionMod, DenoisingResBlockMod, DenoisingDownsampleMod, DenoisingUpsampleMod)
|
| 4 |
+
|
| 5 |
+
__all__ = ['DenoisingUnetMod', 'MultiHeadAttentionMod', 'DenoisingResBlockMod',
|
| 6 |
+
'DenoisingDownsampleMod', 'DenoisingUpsampleMod']
|
lakonlab/models/architecture/ddpm/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (475 Bytes). View file
|
|
|
lakonlab/models/architecture/ddpm/__pycache__/denoising.cpython-310.pyc
ADDED
|
Binary file (4.8 kB). View file
|
|
|