liuhuijie03 commited on
Commit
f0395ef
·
1 Parent(s): ac6f904
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +3 -21
  2. lakonlab/__init__.py +20 -0
  3. lakonlab/__pycache__/__init__.cpython-310.pyc +0 -0
  4. lakonlab/__pycache__/__init__.cpython-313.pyc +0 -0
  5. lakonlab/__pycache__/version.cpython-310.pyc +0 -0
  6. lakonlab/apis/__init__.py +4 -0
  7. lakonlab/apis/__pycache__/__init__.cpython-310.pyc +0 -0
  8. lakonlab/apis/__pycache__/__init__.cpython-313.pyc +0 -0
  9. lakonlab/apis/__pycache__/inference.cpython-310.pyc +0 -0
  10. lakonlab/apis/__pycache__/train.cpython-310.pyc +0 -0
  11. lakonlab/apis/__pycache__/train.cpython-313.pyc +0 -0
  12. lakonlab/apis/inference.py +56 -0
  13. lakonlab/apis/train.py +166 -0
  14. lakonlab/datasets/__init__.py +8 -0
  15. lakonlab/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
  16. lakonlab/datasets/__pycache__/builder.cpython-310.pyc +0 -0
  17. lakonlab/datasets/__pycache__/checkerboard.cpython-310.pyc +0 -0
  18. lakonlab/datasets/__pycache__/image_prompts.cpython-310.pyc +0 -0
  19. lakonlab/datasets/__pycache__/imagenet.cpython-310.pyc +0 -0
  20. lakonlab/datasets/builder.py +61 -0
  21. lakonlab/datasets/checkerboard.py +59 -0
  22. lakonlab/datasets/image_prompts.py +432 -0
  23. lakonlab/datasets/imagenet.py +155 -0
  24. lakonlab/datasets/samplers/__init__.py +1 -0
  25. lakonlab/datasets/samplers/__pycache__/__init__.cpython-310.pyc +0 -0
  26. lakonlab/datasets/samplers/__pycache__/distributed_sampler.cpython-310.pyc +0 -0
  27. lakonlab/datasets/samplers/distributed_sampler.py +158 -0
  28. lakonlab/evaluation/__init__.py +8 -0
  29. lakonlab/evaluation/__pycache__/__init__.cpython-310.pyc +0 -0
  30. lakonlab/evaluation/__pycache__/eval_hooks.cpython-310.pyc +0 -0
  31. lakonlab/evaluation/__pycache__/hpsv3.cpython-310.pyc +0 -0
  32. lakonlab/evaluation/__pycache__/metrics.cpython-310.pyc +0 -0
  33. lakonlab/evaluation/__pycache__/vqa_score.cpython-310.pyc +0 -0
  34. lakonlab/evaluation/eval_hooks.py +318 -0
  35. lakonlab/evaluation/hpsv3.py +587 -0
  36. lakonlab/evaluation/metrics.py +1329 -0
  37. lakonlab/evaluation/vqa_score.py +667 -0
  38. lakonlab/models/__init__.py +6 -0
  39. lakonlab/models/__pycache__/__init__.cpython-310.pyc +0 -0
  40. lakonlab/models/__pycache__/base.cpython-310.pyc +0 -0
  41. lakonlab/models/__pycache__/base_diffusion.cpython-310.pyc +0 -0
  42. lakonlab/models/__pycache__/diffusion_2d.cpython-310.pyc +0 -0
  43. lakonlab/models/__pycache__/latent_diffusion_class_image.cpython-310.pyc +0 -0
  44. lakonlab/models/__pycache__/latent_diffusion_text_image.cpython-310.pyc +0 -0
  45. lakonlab/models/architecture/__init__.py +4 -0
  46. lakonlab/models/architecture/__pycache__/__init__.cpython-310.pyc +0 -0
  47. lakonlab/models/architecture/__pycache__/utils.cpython-310.pyc +0 -0
  48. lakonlab/models/architecture/ddpm/__init__.py +6 -0
  49. lakonlab/models/architecture/ddpm/__pycache__/__init__.cpython-310.pyc +0 -0
  50. 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="xxx"><img alt="Build" src="https://img.shields.io/badge/arXiv-Paper-da282a.svg"></a>
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