aryadomain commited on
Commit
6d6dbbc
·
verified ·
1 Parent(s): 7edfe49

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. Reward_sd15_idealized/README.md +1336 -0
  2. Reward_sd15_idealized/RESULTS/pickapic/one_step_rectification_config_spo/run_1/evaluation_results.txt +4 -0
  3. Reward_sd15_idealized/RESULTS/pickapic/one_step_rectification_config_spo/run_1/log.log +229 -0
  4. Reward_sd15_idealized/RESULTS/pickapic/one_step_rectification_config_spo/run_1/lr_curve.png +0 -0
  5. Reward_sd15_idealized/__pycache__/grad_ascent_configs.cpython-310.pyc +0 -0
  6. Reward_sd15_idealized/__pycache__/grad_ascent_configs.cpython-311.pyc +0 -0
  7. Reward_sd15_idealized/__pycache__/gradient_ascent_utils.cpython-310.pyc +0 -0
  8. Reward_sd15_idealized/__pycache__/gradient_ascent_utils.cpython-311.pyc +0 -0
  9. Reward_sd15_idealized/__pycache__/gradient_ascent_utils.cpython-313.pyc +0 -0
  10. Reward_sd15_idealized/__pycache__/lr_scheduler.cpython-310.pyc +0 -0
  11. Reward_sd15_idealized/__pycache__/lr_scheduler.cpython-311.pyc +0 -0
  12. Reward_sd15_idealized/config_analysis_tuning.ipynb +218 -0
  13. Reward_sd15_idealized/eval.py +1422 -0
  14. Reward_sd15_idealized/examples.sh +154 -0
  15. Reward_sd15_idealized/lr_scheduler.py +233 -0
  16. Reward_sd15_idealized/models/__init__.py +3 -0
  17. Reward_sd15_idealized/models/__pycache__/__init__.cpython-310.pyc +0 -0
  18. Reward_sd15_idealized/models/__pycache__/__init__.cpython-311.pyc +0 -0
  19. Reward_sd15_idealized/models/__pycache__/__init__.cpython-313.pyc +0 -0
  20. Reward_sd15_idealized/models/__pycache__/__init__.cpython-39.pyc +0 -0
  21. Reward_sd15_idealized/models/__pycache__/reward_model.cpython-310.pyc +0 -0
  22. Reward_sd15_idealized/models/__pycache__/reward_model.cpython-311.pyc +0 -0
  23. Reward_sd15_idealized/models/__pycache__/reward_model.cpython-313.pyc +0 -0
  24. Reward_sd15_idealized/models/__pycache__/reward_model.cpython-39.pyc +0 -0
  25. Reward_sd15_idealized/models/__pycache__/reward_model_sdxl.cpython-310.pyc +0 -0
  26. Reward_sd15_idealized/models/__pycache__/unet_2d_condition_reward.cpython-310.pyc +0 -0
  27. Reward_sd15_idealized/models/__pycache__/unet_2d_condition_reward.cpython-311.pyc +0 -0
  28. Reward_sd15_idealized/models/__pycache__/unet_2d_condition_reward.cpython-313.pyc +0 -0
  29. Reward_sd15_idealized/models/reward_model.py +388 -0
  30. Reward_sd15_idealized/models/unet_2d_condition_reward.py +1334 -0
  31. Reward_sd15_idealized/pipelines/__init__.py +9 -0
  32. Reward_sd15_idealized/pipelines/__pycache__/__init__.cpython-310.pyc +0 -0
  33. Reward_sd15_idealized/pipelines/__pycache__/__init__.cpython-311.pyc +0 -0
  34. Reward_sd15_idealized/pipelines/__pycache__/__init__.cpython-313.pyc +0 -0
  35. Reward_sd15_idealized/pipelines/__pycache__/sd15_gradient_ascent_pipeline.cpython-310.pyc +0 -0
  36. Reward_sd15_idealized/pipelines/__pycache__/sd15_gradient_ascent_pipeline.cpython-311.pyc +0 -0
  37. Reward_sd15_idealized/pipelines/__pycache__/sd15_gradient_ascent_pipeline.cpython-313.pyc +0 -0
  38. Reward_sd15_idealized/pipelines/__pycache__/sd15_reward_pipeline.cpython-310.pyc +0 -0
  39. Reward_sd15_idealized/pipelines/__pycache__/sd15_reward_pipeline.cpython-311.pyc +0 -0
  40. Reward_sd15_idealized/pipelines/__pycache__/sd15_reward_pipeline.cpython-313.pyc +0 -0
  41. Reward_sd15_idealized/pipelines/__pycache__/sdxl_gradient_ascent_pipeline.cpython-310.pyc +0 -0
  42. Reward_sd15_idealized/pipelines/__pycache__/sdxl_reward_pipeline.cpython-310.pyc +0 -0
  43. Reward_sd15_idealized/pipelines/sd15_gradient_ascent_pipeline.py +345 -0
  44. Reward_sd15_idealized/pipelines/sd15_reward_pipeline.py +316 -0
  45. Reward_sd15_idealized/tune_hyperparams.py +514 -0
  46. evaluation/LICENSE +201 -0
  47. evaluation/aesthetic_score.py +71 -0
  48. evaluation/blip/__init__.py +1 -0
  49. evaluation/general_score_pap_val.py +265 -0
  50. upload.py +270 -0
Reward_sd15_idealized/README.md ADDED
@@ -0,0 +1,1336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reward-Guided Gradient Ascent for Stable Diffusion
2
+
3
+ A comprehensive system for improving Stable Diffusion image generation quality using gradient ascent optimization on Latent Reward Model (LRM) scores during inference.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Features](#features)
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [Architecture](#architecture)
12
+ - [Understanding Reward Calculation](#understanding-reward-calculation)
13
+ - [Learning Rate Scheduling](#learning-rate-scheduling)
14
+ - [Configuration Presets](#configuration-presets)
15
+ - [Evaluation Metrics](#evaluation-metrics)
16
+ - [Model Variants](#model-variants)
17
+ - [Datasets](#datasets)
18
+ - [Usage Examples](#usage-examples)
19
+ - [API Reference](#api-reference)
20
+ - [Command-Line Options](#command-line-options)
21
+ - [Output Files](#output-files)
22
+ - [Troubleshooting](#troubleshooting)
23
+ - [Best Practices](#best-practices)
24
+ - [Changelog](#changelog)
25
+
26
+ ---
27
+
28
+ ## Overview
29
+
30
+ This project implements **test-time optimization** for Stable Diffusion using gradient ascent on the LRM reward model. Unlike the main LPO training which uses the reward model for training, this approach applies it during inference to improve generation quality without retraining.
31
+
32
+ ### Key Capabilities
33
+
34
+ - **Gradient Ascent Optimization**: Iteratively improve latents using reward gradients
35
+ - **Learning Rate Scheduling**: Multiple strategies (constant, linear, cosine, exponential, step)
36
+ - **Momentum Optimization**: Standard and Nesterov momentum for better convergence
37
+ - **Multiple Metrics**: FID, CLIP, Aesthetic, PickScore, HPSv2, ImageReward
38
+ - **Model Variants**: Support for Origin, SPO, DPO, and LPO SD1.5 models
39
+ - **Dataset Flexibility**: COCO and Pick-a-Pic validation datasets
40
+ - **Configuration Presets**: 15 pre-tuned configurations for various use cases
41
+
42
+ ---
43
+
44
+ ## Features
45
+
46
+ ### 1. **Advanced Optimization**
47
+ - **5 LR Schedulers**: Constant, Linear, Cosine, Exponential, Step-wise
48
+ - **Momentum Support**: Standard momentum and Nesterov momentum
49
+ - **Configurable Timestep Ranges**: Apply gradients at specific denoising steps
50
+ - **Dynamic Learning Rates**: LR changes during optimization for better convergence
51
+
52
+ ### 2. **Comprehensive Evaluation**
53
+ - **6 Quality Metrics**: FID, CLIP, Aesthetic, PickScore, HPSv2, ImageReward
54
+ - **Baseline Comparison**: Compare with and without gradient ascent
55
+ - **Detailed Statistics**: Track reward improvements, gradient norms, LR history
56
+ - **Batch Processing**: Efficient evaluation on large datasets
57
+ - **Reward Visualization**: Automatic plotting of reward progression across timesteps
58
+ - **Timestep-Aware Tracking**: Monitor rewards at every denoising step, final t=0 latent reported
59
+
60
+ ### 3. **Model Flexibility**
61
+ - **4 SD1.5 Variants**: Origin, SPO, DPO, LPO
62
+ - **Auto-Configuration**: CFG scale auto-adjusted for model variants
63
+ - **Easy Switching**: Change models with a single flag
64
+
65
+ ### 4. **Dataset Support**
66
+ - **COCO Validation**: Standard benchmark with reference images
67
+ - **Pick-a-Pic Validation**: Large-scale human preference dataset
68
+ - **Streaming Support**: Handle large datasets efficiently
69
+
70
+ ---
71
+
72
+ ## Installation
73
+
74
+ ### Requirements
75
+
76
+ ```bash
77
+ # Core dependencies
78
+ pip install torch diffusers transformers torchmetrics datasets huggingface-hub
79
+
80
+ # For evaluation metrics
81
+ pip install pillow numpy scipy tqdm
82
+
83
+ # Optional: for better performance
84
+ pip install xformers # For memory-efficient attention
85
+ ```
86
+
87
+ ### Setup
88
+
89
+ ```bash
90
+ cd /path/to/LPO/Reward
91
+
92
+ # Verify installation
93
+ python -c "from lr_scheduler import create_lr_scheduler; print('✓ LR Scheduler OK')"
94
+ python -c "from grad_ascent_configs import list_configs; print('✓ Configs:', len(list_configs()))"
95
+ python -c "from gradient_ascent_utils import RewardGuidedDiffusion; print('✓ Gradient Utils OK')"
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Quick Start
101
+
102
+ ### 1. Basic COCO Evaluation (test_grad_sd1.5.py)
103
+
104
+ ```bash
105
+ # Edit Config in test_grad_sd1.5.py:
106
+ # - Set device: "cuda:0" or "cuda:6"
107
+ # - Set max_samples: 10 for quick test, None for full dataset
108
+ # - Configure gradient ascent parameters
109
+
110
+ python test_grad_sd1.5.py
111
+ ```
112
+
113
+ **Output:**
114
+ - Creates `RESULTS/SD1.5_GradAscent/run_1/` (auto-incremented)
115
+ - Generates `eval.log` with detailed metrics
116
+ - Saves `reward_curve.png` showing reward progression
117
+
118
+ ### 2. Basic Evaluation with Preset Config (eval.py)
119
+
120
+ ```bash
121
+ python eval.py \
122
+ --grad_config cosine_nesterov \
123
+ --metrics clip aesthetic \
124
+ --max_samples 10
125
+ ```
126
+
127
+ ### 2. High-Quality Evaluation
128
+
129
+ ```bash
130
+ python eval.py \
131
+ --grad_config high_quality \
132
+ --metrics fid clip aesthetic pickscore hpsv2 \
133
+ --max_samples 100 \
134
+ --save_images \
135
+ --output_dir results/high_quality
136
+ ```
137
+
138
+ ### 3. Pick-a-Pic Benchmark
139
+
140
+ ```bash
141
+ python eval.py \
142
+ --dataset_type pickapic \
143
+ --grad_config cosine_nesterov \
144
+ --metrics pickscore hpsv2 imagereward \
145
+ --max_samples 500 \
146
+ --output_dir results/pickapic
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Architecture
152
+
153
+ ### System Components
154
+
155
+ ```
156
+ Reward/
157
+ ├── models/
158
+ │ ├── reward_model.py # LRM reward model wrapper
159
+ │ └── unet_2d_condition_reward.py # Custom UNet with reward tracking
160
+ ├── pipelines/
161
+ │ ├── sd15_reward_pipeline.py # Base pipeline with reward tracking
162
+ │ └── sd15_gradient_ascent_pipeline.py # Pipeline with gradient ascent
163
+ ├── lr_scheduler.py # Learning rate schedulers
164
+ ├── gradient_ascent_utils.py # Core gradient ascent implementation
165
+ ├── grad_ascent_configs.py # Configuration presets
166
+ ├── eval.py # Comprehensive evaluation script
167
+ └── examples.sh # Example commands
168
+ ```
169
+
170
+ ### Gradient Ascent Flow
171
+
172
+ ```
173
+ 1. Load Stable Diffusion + LRM Reward Model
174
+ 2. Start denoising process (T → 0)
175
+ 3. At each timestep t:
176
+ a. Standard denoising step (predict noise, remove it)
177
+ b. Compute reward R(latents, prompt, t) and store in history
178
+ c. If t in gradient range:
179
+ - Enable gradients on latents
180
+ - Compute ∇R w.r.t. latents
181
+ - For each gradient step:
182
+ * Get current LR from scheduler
183
+ * Apply momentum (if enabled)
184
+ * Update: latents += lr * momentum(∇R)
185
+ - Track statistics (grad norms, reward improvement)
186
+ 4. At final timestep (t=0):
187
+ - Final reward computed on clean latent
188
+ - This reward is reported in logs
189
+ 5. Decode final latent (x₀) to image via VAE
190
+ 6. Compute quality metrics on image
191
+ ```
192
+
193
+ ### Understanding Reward Calculation
194
+
195
+ **Key Concepts:**
196
+
197
+ - **Timestep-Aware Rewards**: The LRM reward model computes preference scores at ANY noise level (timestep t)
198
+ - **Progressive Tracking**: Rewards are calculated at every denoising step throughout generation
199
+ - **Final Latent Reward**: The reported metric is the reward for t=0 (the clean latent before decoding)
200
+ - **Not Averaged**: The final reward is specifically from the last timestep, NOT an average across all timesteps
201
+
202
+ **What gets reported:**
203
+ ```python
204
+ # During generation: Rewards computed at each t (1000 → 0)
205
+ Step 0: t=1000, reward=3.2
206
+ Step 1: t=990, reward=3.5
207
+ ...
208
+ Step 99: t=10, reward=5.1
209
+ Step 100: t=0, reward=5.4 ← This is what gets logged!
210
+ ```
211
+
212
+ The `Reward (t=0)` in logs represents the preference score of the final clean latent that was decoded into your output image.
213
+
214
+ ---
215
+
216
+ ## Learning Rate Scheduling
217
+
218
+ ### Available Schedulers
219
+
220
+ #### 1. **Constant LR**
221
+ ```python
222
+ lr_scheduler_type="constant"
223
+ ```
224
+ - Fixed learning rate throughout optimization
225
+ - Simple and stable
226
+ - Good for quick experiments
227
+
228
+ #### 2. **Linear Decay**
229
+ ```python
230
+ lr_scheduler_type="linear"
231
+ lr_scheduler_kwargs={
232
+ "end_lr": 0.01, # End LR (10% of initial)
233
+ "start_step": 0 # When to start decay
234
+ }
235
+ ```
236
+ - Linear decrease from initial to end LR
237
+ - Smooth convergence
238
+ - Configurable warmup period
239
+
240
+ #### 3. **Cosine Annealing** (Recommended)
241
+ ```python
242
+ lr_scheduler_type="cosine"
243
+ lr_scheduler_kwargs={
244
+ "min_lr": 0.001, # Minimum LR
245
+ "warmup_steps": 3 # Linear warmup steps
246
+ }
247
+ ```
248
+ - Smooth cosine decay
249
+ - Optional warmup phase
250
+ - Widely used in deep learning
251
+ - **Best for most use cases**
252
+
253
+ #### 4. **Exponential Decay**
254
+ ```python
255
+ lr_scheduler_type="exponential"
256
+ lr_scheduler_kwargs={
257
+ "gamma": 0.9 # Decay factor per step
258
+ }
259
+ ```
260
+ - Exponential decrease
261
+ - Fast initial decay
262
+ - Good for aggressive optimization
263
+
264
+ #### 5. **Step Decay**
265
+ ```python
266
+ lr_scheduler_type="step"
267
+ lr_scheduler_kwargs={
268
+ "step_size": 5, # Steps between decays
269
+ "gamma": 0.5 # Multiplicative factor
270
+ }
271
+ ```
272
+ - Step-wise LR reduction
273
+ - Periodic decay
274
+ - Good for scheduled changes
275
+
276
+ ### Usage Example
277
+
278
+ ```python
279
+ from pipelines.sd15_gradient_ascent_pipeline import StableDiffusionGradientAscentPipeline
280
+
281
+ pipeline.enable_gradient_ascent(
282
+ grad_timestep_range=(0, 700),
283
+ num_grad_steps=15,
284
+ grad_step_size=0.1, # Initial LR
285
+ lr_scheduler_type="cosine",
286
+ lr_scheduler_kwargs={
287
+ "min_lr": 0.001,
288
+ "warmup_steps": 3
289
+ }
290
+ )
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Configuration Presets
296
+
297
+ We provide 15 pre-configured optimization strategies. Use them with `--grad_config <name>`.
298
+
299
+ ### Basic Configurations
300
+
301
+ | Config | LR Schedule | Momentum | Steps | Description |
302
+ |--------|-------------|----------|-------|-------------|
303
+ | `constant` | Constant | No | 5 | Simple baseline |
304
+ | `linear` | Linear decay | No | 10 | Smooth decay |
305
+ | `linear_warmstart` | Linear w/ warmup | No | 10 | Stable start |
306
+ | `cosine` | Cosine | No | 10 | Smooth convergence |
307
+ | `cosine_warmup` | Cosine w/ warmup | No | 20 | Best convergence |
308
+ | `exponential` | Exponential | No | 15 | Fast decay |
309
+ | `step` | Step-wise | No | 20 | Periodic decay |
310
+
311
+ ### Momentum Configurations
312
+
313
+ | Config | LR Schedule | Momentum | Steps | Description |
314
+ |--------|-------------|----------|-------|-------------|
315
+ | `momentum` | Constant | Standard | 10 | Faster convergence |
316
+ | `nesterov` | Constant | Nesterov | 10 | Better convergence |
317
+
318
+ ### Advanced Configurations
319
+
320
+ | Config | LR Schedule | Momentum | Steps | Description |
321
+ |--------|-------------|----------|-------|-------------|
322
+ | `cosine_momentum` | Cosine | Standard | 15 | High quality |
323
+ | `cosine_nesterov` | Cosine | Nesterov | 15 | **Recommended** |
324
+ | `linear_nesterov` | Linear | Nesterov | 15 | Stable + fast |
325
+
326
+ ### Quality Presets
327
+
328
+ | Config | LR Schedule | Momentum | Steps | Use Case |
329
+ |--------|-------------|----------|-------|----------|
330
+ | `high_quality` | Cosine | Nesterov | 20 | **Best quality** |
331
+ | `aggressive` | Exponential | Standard | 8 | Fast results |
332
+ | `conservative` | Cosine | Nesterov | 25 | Most stable |
333
+
334
+ ### Config Details
335
+
336
+ #### `high_quality` (Recommended for Research)
337
+ ```python
338
+ {
339
+ "grad_timestep_range": (200, 800), # Focus on middle timesteps
340
+ "num_grad_steps": 20,
341
+ "grad_step_size": 0.08,
342
+ "lr_scheduler_type": "cosine",
343
+ "lr_scheduler_kwargs": {"min_lr": 0.005, "warmup_steps": 5},
344
+ "use_momentum": True,
345
+ "momentum": 0.95,
346
+ "use_nesterov": True
347
+ }
348
+ ```
349
+
350
+ #### `cosine_nesterov` (Recommended for General Use)
351
+ ```python
352
+ {
353
+ "grad_timestep_range": (0, 700),
354
+ "num_grad_steps": 15,
355
+ "grad_step_size": 0.12,
356
+ "lr_scheduler_type": "cosine",
357
+ "lr_scheduler_kwargs": {"min_lr": 0.001, "warmup_steps": 3},
358
+ "use_momentum": True,
359
+ "momentum": 0.9,
360
+ "use_nesterov": True
361
+ }
362
+ ```
363
+
364
+ #### `aggressive` (Fast Experimentation)
365
+ ```python
366
+ {
367
+ "grad_timestep_range": (0, 900),
368
+ "num_grad_steps": 8,
369
+ "grad_step_size": 0.15,
370
+ "grad_scale": 1.2,
371
+ "lr_scheduler_type": "exponential",
372
+ "lr_scheduler_kwargs": {"gamma": 0.85},
373
+ "use_momentum": True,
374
+ "momentum": 0.85,
375
+ "use_nesterov": False
376
+ }
377
+ ```
378
+
379
+ ### Listing Configs
380
+
381
+ ```python
382
+ from grad_ascent_configs import list_configs, print_config, get_config
383
+
384
+ # List all available configs
385
+ print(list_configs())
386
+ # Output: ['aggressive', 'conservative', 'constant', 'cosine', ...]
387
+
388
+ # Print config details
389
+ print_config("cosine_nesterov")
390
+
391
+ # Get config dictionary
392
+ config = get_config("high_quality")
393
+ pipeline.enable_gradient_ascent(**config)
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Evaluation Metrics
399
+
400
+ ### 1. **FID (Fréchet Inception Distance)**
401
+ - Measures distribution similarity between real and generated images
402
+ - **Lower is better**
403
+ - Requires reference images (COCO dataset only)
404
+ - Computationally expensive
405
+
406
+ ```bash
407
+ --metrics fid
408
+ ```
409
+
410
+ ### 2. **CLIP Score**
411
+ - Evaluates text-image alignment using CLIP embeddings
412
+ - **Higher is better**
413
+ - Fast and reliable
414
+ - Good for general quality assessment
415
+
416
+ ```bash
417
+ --metrics clip
418
+ ```
419
+
420
+ ### 3. **Aesthetic Score**
421
+ - Predicts aesthetic quality using CLIP + MLP
422
+ - **Higher is better**
423
+ - Trained on human aesthetic ratings
424
+ - Good for visual appeal
425
+
426
+ ```bash
427
+ --metrics aesthetic
428
+ ```
429
+
430
+ ### 4. **PickScore** (New)
431
+ - Human preference predictor from Pick-a-Pic dataset
432
+ - **Higher is better**
433
+ - Trained on large-scale human comparisons
434
+ - State-of-the-art preference metric
435
+
436
+ ```bash
437
+ --metrics pickscore
438
+ ```
439
+
440
+ ### 5. **HPSv2** (New)
441
+ - Human Preference Score version 2
442
+ - **Higher is better**
443
+ - Trained on aesthetic evaluations
444
+ - Complementary to PickScore
445
+
446
+ ```bash
447
+ --metrics hpsv2
448
+ ```
449
+
450
+ ### 6. **ImageReward** (New)
451
+ - Reward model from RLHF (Reinforcement Learning from Human Feedback)
452
+ - **Higher is better**
453
+ - Comprehensive quality assessment
454
+ - Trained on diverse human feedback
455
+
456
+ ```bash
457
+ --metrics imagereward
458
+ ```
459
+
460
+ ### Metric Recommendations
461
+
462
+ | Use Case | Recommended Metrics | Reason |
463
+ |----------|---------------------|--------|
464
+ | Research/Papers | `fid clip aesthetic pickscore hpsv2` | Comprehensive evaluation |
465
+ | Quick Iteration | `clip aesthetic` | Fast and reliable |
466
+ | Human Alignment | `pickscore hpsv2 imagereward` | Preference-based |
467
+ | Text Alignment | `clip imagereward` | Focus on prompt adherence |
468
+ | Visual Quality | `aesthetic pickscore` | Focus on aesthetics |
469
+
470
+ ---
471
+
472
+ ## Model Variants
473
+
474
+ Support for multiple SD1.5 model variants trained with different methods.
475
+
476
+ ### Available Variants
477
+
478
+ #### 1. **Origin** (Default)
479
+ ```bash
480
+ --model_variant origin
481
+ ```
482
+ - Original Stable Diffusion v1.5 from RunwayML
483
+ - No additional training
484
+ - CFG scale: 7.5 (default)
485
+ - Good baseline
486
+
487
+ #### 2. **SPO** (Supervised Policy Optimization)
488
+ ```bash
489
+ --model_variant spo
490
+ ```
491
+ - Trained with SPO method
492
+ - Model: `SPO-Diffusion-Models/SPO-SD-v1-5_4k-p_10ep`
493
+ - **CFG scale: 5.0** (auto-adjusted)
494
+ - Better prompt adherence
495
+
496
+ #### 3. **Diffusion-DPO** (Direct Preference Optimization)
497
+ ```bash
498
+ --model_variant diffusion_dpo
499
+ ```
500
+ - Trained with DPO on human preferences
501
+ - Model: `mhdang/dpo-sd1.5-text2image-v1`
502
+ - CFG scale: 7.5
503
+ - Improved human alignment
504
+
505
+ #### 4. **LPO** (Latent Preference Optimization)
506
+ ```bash
507
+ --model_variant lpo
508
+ ```
509
+ - Trained with LPO (this project's main method)
510
+ - Model: `casiatao/LPO` (lpo_sd15_merge)
511
+ - **CFG scale: 5.0** (auto-adjusted)
512
+ - **Highest quality baseline**
513
+
514
+ ### Comparison
515
+
516
+ | Variant | Training Method | Quality | Speed | Best For |
517
+ |---------|----------------|---------|-------|----------|
518
+ | Origin | Pre-training only | Good | Fast | Baseline |
519
+ | SPO | Supervised | Better | Fast | Prompt adherence |
520
+ | Diffusion-DPO | Preference learning | Better | Fast | Human preferences |
521
+ | LPO | Latent preference | **Best** | Fast | Overall quality |
522
+
523
+ ### Usage Example
524
+
525
+ ```bash
526
+ # Compare all variants
527
+ for variant in origin spo diffusion_dpo lpo; do
528
+ python eval.py \
529
+ --model_variant $variant \
530
+ --grad_config high_quality \
531
+ --metrics clip aesthetic pickscore \
532
+ --max_samples 100 \
533
+ --output_dir results/${variant}
534
+ done
535
+ ```
536
+
537
+ ---
538
+
539
+ ## Datasets
540
+
541
+ ### 1. **COCO Validation** (Default)
542
+
543
+ ```bash
544
+ --dataset_type coco
545
+ --data_dir ./data
546
+ ```
547
+
548
+ **Features:**
549
+ - Standard benchmark dataset
550
+ - Reference images available (for FID)
551
+ - ~5,000 validation samples
552
+ - Diverse prompts
553
+
554
+ **Structure:**
555
+ ```
556
+ data/coco/
557
+ ├── caption_val.json
558
+ └── images/val/
559
+ ├── 000000000139.jpg
560
+ ├── 000000000285.jpg
561
+ └── ...
562
+ ```
563
+
564
+ ### 2. **Pick-a-Pic Validation**
565
+
566
+ ```bash
567
+ --dataset_type pickapic
568
+ ```
569
+
570
+ **Features:**
571
+ - Large-scale human preference dataset
572
+ - Streaming (no download needed)
573
+ - ~500,000 validation samples
574
+ - Real user prompts
575
+ - No reference images (FID not available)
576
+
577
+ **Advantages:**
578
+ - More diverse prompts
579
+ - Real-world use cases
580
+ - Human preference focus
581
+ - Large-scale evaluation
582
+
583
+ ### Dataset Recommendations
584
+
585
+ | Use Case | Dataset | Reason |
586
+ |----------|---------|--------|
587
+ | Academic Research | COCO | Standard benchmark, reproducible |
588
+ | FID Evaluation | COCO | Requires reference images |
589
+ | Human Preference | Pick-a-Pic | Trained on human comparisons |
590
+ | Large-scale Tests | Pick-a-Pic | 500K+ samples available |
591
+ | Quick Tests | COCO | Smaller, faster |
592
+
593
+ ---
594
+
595
+ ## Usage Examples
596
+
597
+ ### Example 1: Quick Test
598
+ ```bash
599
+ python eval.py \
600
+ --grad_config cosine_nesterov \
601
+ --metrics clip aesthetic \
602
+ --max_samples 10 \
603
+ --output_dir examples/quick_test
604
+ ```
605
+
606
+ ### Example 2: High-Quality Research Evaluation
607
+ ```bash
608
+ python eval.py \
609
+ --grad_config high_quality \
610
+ --metrics fid clip aesthetic pickscore hpsv2 \
611
+ --max_samples 200 \
612
+ --save_images \
613
+ --output_dir examples/research
614
+ ```
615
+
616
+ ### Example 3: Pick-a-Pic Benchmark
617
+ ```bash
618
+ python eval.py \
619
+ --dataset_type pickapic \
620
+ --grad_config cosine_nesterov \
621
+ --metrics pickscore hpsv2 imagereward \
622
+ --max_samples 500 \
623
+ --output_dir examples/pickapic
624
+ ```
625
+
626
+ ### Example 4: LPO Model Evaluation
627
+ ```bash
628
+ python eval.py \
629
+ --model_variant lpo \
630
+ --grad_config high_quality \
631
+ --metrics clip aesthetic pickscore \
632
+ --max_samples 100 \
633
+ --save_images \
634
+ --output_dir examples/lpo_model
635
+ ```
636
+
637
+ ### Example 5: Baseline Only (No Gradient Ascent)
638
+ ```bash
639
+ python eval.py \
640
+ --mode baseline \
641
+ --model_variant origin \
642
+ --metrics clip aesthetic pickscore \
643
+ --max_samples 50 \
644
+ --output_dir examples/baseline_only
645
+ ```
646
+
647
+ ### Example 6: Manual Configuration
648
+ ```bash
649
+ python eval.py \
650
+ --grad_range_start 200 \
651
+ --grad_range_end 800 \
652
+ --grad_steps 15 \
653
+ --grad_step_size 0.08 \
654
+ --metrics clip aesthetic \
655
+ --max_samples 50 \
656
+ --output_dir examples/manual_config
657
+ ```
658
+
659
+ ### Example 7: Model Comparison
660
+ ```bash
661
+ # Evaluate all model variants
662
+ for variant in origin spo diffusion_dpo lpo; do
663
+ python eval.py \
664
+ --model_variant $variant \
665
+ --grad_config high_quality \
666
+ --metrics clip aesthetic pickscore \
667
+ --max_samples 100 \
668
+ --save_images \
669
+ --output_dir results/comparison/${variant}
670
+ done
671
+ ```
672
+
673
+ ### Example 8: Conservative Optimization
674
+ ```bash
675
+ python eval.py \
676
+ --grad_config conservative \
677
+ --metrics clip aesthetic pickscore hpsv2 \
678
+ --max_samples 100 \
679
+ --save_images \
680
+ --output_dir examples/conservative
681
+ ```
682
+
683
+ ---
684
+
685
+ ## API Reference
686
+
687
+ ### Pipeline Usage
688
+
689
+ ```python
690
+ from diffusers import StableDiffusionPipeline
691
+ from pipelines.sd15_gradient_ascent_pipeline import StableDiffusionGradientAscentPipeline
692
+ from models import LRMRewardModel
693
+
694
+ # Load base pipeline
695
+ base_pipeline = StableDiffusionPipeline.from_pretrained(
696
+ "runwayml/stable-diffusion-v1-5",
697
+ torch_dtype=torch.float16
698
+ )
699
+
700
+ # Create gradient ascent pipeline
701
+ pipeline = StableDiffusionGradientAscentPipeline(**base_pipeline.components)
702
+
703
+ # Load reward model
704
+ reward_model = LRMRewardModel(
705
+ pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5",
706
+ lrm_model_path="casiatao/LRM",
707
+ guidance_scale=7.5,
708
+ device="cuda"
709
+ )
710
+ pipeline.set_reward_model(reward_model)
711
+
712
+ # Enable gradient ascent with preset
713
+ from grad_ascent_configs import get_config
714
+ config = get_config("cosine_nesterov")
715
+ pipeline.enable_gradient_ascent(**config)
716
+
717
+ # Or configure manually
718
+ pipeline.enable_gradient_ascent(
719
+ grad_timestep_range=(200, 800),
720
+ num_grad_steps=15,
721
+ grad_step_size=0.1,
722
+ lr_scheduler_type="cosine",
723
+ lr_scheduler_kwargs={"min_lr": 0.001, "warmup_steps": 3},
724
+ use_momentum=True,
725
+ momentum=0.9,
726
+ use_nesterov=True
727
+ )
728
+
729
+ # Generate with gradient ascent
730
+ output = pipeline(
731
+ prompt="a beautiful mountain landscape at sunset",
732
+ num_inference_steps=50,
733
+ guidance_scale=7.5,
734
+ )
735
+
736
+ # Get gradient statistics
737
+ stats = pipeline.grad_guidance.get_statistics()
738
+ print(f"Reward improvement: {stats['avg_reward_improvement']:.4f}")
739
+ ```
740
+
741
+ ### Custom LR Scheduler
742
+
743
+ ```python
744
+ from lr_scheduler import create_lr_scheduler
745
+
746
+ # Create cosine scheduler with warmup
747
+ scheduler = create_lr_scheduler(
748
+ scheduler_type="cosine",
749
+ initial_lr=0.1,
750
+ num_steps=20,
751
+ min_lr=0.001,
752
+ warmup_steps=5
753
+ )
754
+
755
+ # Use in optimization loop
756
+ for step in range(20):
757
+ current_lr = scheduler.get_lr()
758
+ # ... apply gradient with current_lr ...
759
+ scheduler.step()
760
+ ```
761
+
762
+ ### Configuration Management
763
+
764
+ ```python
765
+ from grad_ascent_configs import get_config, list_configs, print_config
766
+
767
+ # List all available configs
768
+ all_configs = list_configs()
769
+ print(f"Available configs: {all_configs}")
770
+
771
+ # Get specific config
772
+ config = get_config("high_quality")
773
+
774
+ # Print config details
775
+ print_config("cosine_nesterov")
776
+
777
+ # Create custom config
778
+ custom_config = {
779
+ "grad_timestep_range": (300, 700),
780
+ "num_grad_steps": 12,
781
+ "grad_step_size": 0.09,
782
+ "lr_scheduler_type": "cosine",
783
+ "lr_scheduler_kwargs": {"min_lr": 0.002, "warmup_steps": 4},
784
+ "use_momentum": True,
785
+ "momentum": 0.92,
786
+ "use_nesterov": True
787
+ }
788
+ pipeline.enable_gradient_ascent(**custom_config)
789
+ ```
790
+
791
+ ---
792
+
793
+ ## Command-Line Options
794
+
795
+ ### Essential Options
796
+
797
+ ```bash
798
+ --data_dir PATH # Path to data directory (default: ./data)
799
+ --dataset_type TYPE # Dataset: coco or pickapic (default: coco)
800
+ --model_variant VARIANT # Model: origin, spo, diffusion_dpo, lpo (default: origin)
801
+ --max_samples N # Max samples to evaluate (default: all)
802
+ --output_dir PATH # Output directory (default: eval_outputs)
803
+ --save_images # Save generated images
804
+ ```
805
+
806
+ ### Gradient Ascent Options
807
+
808
+ ```bash
809
+ --grad_config NAME # Use preset config (recommended)
810
+ --grad_range_start N # Gradient timestep start (default: 0)
811
+ --grad_range_end N # Gradient timestep end (default: 700)
812
+ --grad_steps N # Gradient steps per timestep (default: 5)
813
+ --grad_step_size FLOAT # Initial learning rate (default: 0.1)
814
+ ```
815
+
816
+ ### Evaluation Options
817
+
818
+ ```bash
819
+ --metrics METRIC [METRIC...] # Metrics to evaluate (default: clip aesthetic)
820
+ # Options: fid, clip, aesthetic, pickscore, hpsv2, imagereward
821
+ --mode MODE # baseline, gradient_ascent, or both (default: both)
822
+ --num_steps N # Diffusion inference steps (default: 50)
823
+ --cfg_scale FLOAT # CFG scale (default: 7.5, auto-adjusted for some models)
824
+ --batch_size N # Batch size (default: 1)
825
+ --log_interval N # Log every N batches (default: 10)
826
+ ```
827
+
828
+ ### Other Options
829
+
830
+ ```bash
831
+ --lrm_model PATH # LRM model path (default: casiatao/LRM)
832
+ --seed N # Random seed (default: 42)
833
+ --cuda N # CUDA device ID (default: 0)
834
+ ```
835
+
836
+ ### Complete Example
837
+
838
+ ```bash
839
+ python eval.py \
840
+ --data_dir ./data \
841
+ --dataset_type coco \
842
+ --model_variant lpo \
843
+ --grad_config high_quality \
844
+ --metrics fid clip aesthetic pickscore hpsv2 \
845
+ --max_samples 200 \
846
+ --num_steps 50 \
847
+ --save_images \
848
+ --output_dir results/comprehensive \
849
+ --cuda 0
850
+ ```
851
+
852
+ ---
853
+
854
+ ## Output Files
855
+
856
+ After running evaluation, the following files are created in **auto-incremented run folders**:
857
+
858
+ ```
859
+ RESULTS/SD1.5_GradAscent/
860
+ ├── run_1/ # First run
861
+ │ ├── eval.log # Complete execution log
862
+ │ └── reward_curve.png # Reward progression plot
863
+ ├── run_2/ # Second run
864
+ │ ├── eval.log
865
+ │ └── reward_curve.png
866
+ └── run_3/ # Third run
867
+ ├── eval.log
868
+ └── reward_curve.png
869
+ ```
870
+
871
+ ### Auto-Incrementing Run Folders
872
+
873
+ Each execution automatically creates a new `run_<N>/` folder, preventing accidental overwrites and maintaining a complete experiment history. No manual folder management needed!
874
+
875
+ ### eval.log Structure
876
+
877
+ The log contains detailed information for each batch:
878
+
879
+ ```
880
+ ======================================================================
881
+ COCO GRADIENT ASCENT EVALUATION (BATCHED)
882
+ ======================================================================
883
+ Logging to: ./RESULTS/SD1.5_GradAscent/run_1/eval.log
884
+ Device: cuda:6
885
+ Batch size: 1
886
+ Metrics: fid, clip, reward, aesthetic
887
+ Gradient Ascent: Range=[0, 900], Steps=1, StepSize=0.01
888
+ ======================================================================
889
+
890
+ [Batch 1/5000] Samples: 1/5000 | FID: 2.5432 | CLIP: 0.8234 | Reward (t=0): 5.2341 | Reward (Avg): 5.2341 | Aesthetic: 6.456
891
+ [Batch 161/5000] Samples: 161/5000 | FID: 2.3821 | CLIP: 0.8412 | Reward (t=0): 5.4123 | Reward (Avg): 5.3215 | Aesthetic: 6.523
892
+ ...
893
+
894
+ ======================================================================
895
+ FINAL RESULTS
896
+ ======================================================================
897
+ FID: 2.3456
898
+ CLIP avg: 0.8378
899
+ Reward avg: 5.3421
900
+ Aesthetic: 6.489
901
+ ======================================================================
902
+ ```
903
+
904
+ ### reward_curve.png Visualization
905
+
906
+ The reward curve plot shows two panels for the **first generated image**:
907
+
908
+ **Left Panel: Reward vs Timestep**
909
+ - X-axis: Denoising timestep (t)
910
+ - Y-axis: Reward score
911
+ - Green shaded region: Where gradient ascent is applied
912
+ - Shows how reward evolves as noise is removed
913
+
914
+ **Right Panel: Reward vs Denoising Step**
915
+ - X-axis: Sequential denoising step (0 to num_inference_steps)
916
+ - Y-axis: Reward score
917
+ - Same data, different perspective for easier interpretation
918
+
919
+ **Key Insights from the Plot:**
920
+ - **Upward trend**: Reward generally increases as denoising progresses
921
+ - **Sharp improvements**: Visible spikes where gradient ascent is effective
922
+ - **Final reward**: Last point corresponds to t=0 (decoded image reward)
923
+ - **Learning dynamics**: Shows if optimization is working at different noise levels
924
+
925
+ ### Reward Tracking Details
926
+
927
+ The script now explicitly tracks:
928
+
929
+ 1. **Timestep-specific rewards**: Computed at every denoising step
930
+ 2. **Final latent reward**: The reward for t=0 (the latent that gets decoded)
931
+ 3. **Running average**: Mean reward across all processed samples
932
+ 4. **Current batch reward**: Immediate feedback per batch
933
+
934
+ Example log output:
935
+ ```
936
+ Reward (t=0): 5.4123 # Reward for the final decoded latent
937
+ Reward (Avg): 5.3215 # Running average across all samples
938
+ ```
939
+
940
+ ### evaluation_results.json Structure
941
+
942
+ (Legacy format from eval.py - test_grad_sd1.5.py uses simplified logging)
943
+
944
+ ```json
945
+ {
946
+ "mode": "both",
947
+ "metrics": ["clip", "aesthetic", "pickscore"],
948
+ "config": {
949
+ "num_samples": 100,
950
+ "num_steps": 50,
951
+ "cfg_scale": 7.5,
952
+ "grad_range": [0, 700],
953
+ "grad_steps": 15,
954
+ "grad_step_size": 0.12
955
+ },
956
+ "baseline": {
957
+ "avg_reward": 0.7234,
958
+ "clip_score": 0.8123,
959
+ "aesthetic_score": 6.234,
960
+ "pickscore": 21.45
961
+ },
962
+ "gradient_ascent": {
963
+ "avg_reward": 0.7891,
964
+ "clip_score": 0.8345,
965
+ "aesthetic_score": 6.456,
966
+ "pickscore": 22.13,
967
+ "stats": {
968
+ "num_applications": 45,
969
+ "total_reward_improvement": 2.956,
970
+ "avg_reward_improvement": 0.0657
971
+ }
972
+ },
973
+ "comparison": {
974
+ "reward_difference": 0.0657,
975
+ "clip_difference": 0.0222,
976
+ "aesthetic_difference": 0.222,
977
+ "pickscore_difference": 0.68
978
+ }
979
+ }
980
+ ```
981
+
982
+ ---
983
+
984
+ ## Troubleshooting
985
+
986
+ ### Common Issues
987
+
988
+ #### 1. Out of Memory (OOM)
989
+
990
+ **Symptoms:**
991
+ ```
992
+ RuntimeError: CUDA out of memory
993
+ ```
994
+
995
+ **Solutions:**
996
+ ```bash
997
+ # Reduce batch size
998
+ --batch_size 1
999
+
1000
+ # Reduce max samples
1001
+ --max_samples 50
1002
+
1003
+ # Reduce gradient steps
1004
+ --grad_steps 5
1005
+
1006
+ # Use smaller config
1007
+ --grad_config aggressive # Only 8 steps
1008
+ ```
1009
+
1010
+ #### 2. Slow Evaluation
1011
+
1012
+ **Symptoms:**
1013
+ - Takes too long to complete
1014
+ - Hanging on metric computation
1015
+
1016
+ **Solutions:**
1017
+ ```bash
1018
+ # Skip expensive metrics
1019
+ --metrics clip aesthetic # Skip FID
1020
+
1021
+ # Reduce samples
1022
+ --max_samples 50
1023
+
1024
+ # Reduce diffusion steps
1025
+ --num_steps 20
1026
+
1027
+ # Use faster dataset
1028
+ --dataset_type pickapic # No FID computation
1029
+ ```
1030
+
1031
+ #### 3. Poor Results / No Improvement
1032
+
1033
+ **Symptoms:**
1034
+ - Reward doesn't increase
1035
+ - Quality worse after gradient ascent
1036
+
1037
+ **Solutions:**
1038
+ ```bash
1039
+ # Try better configs
1040
+ --grad_config high_quality
1041
+ --grad_config conservative
1042
+
1043
+ # Increase gradient steps
1044
+ --grad_steps 20
1045
+
1046
+ # Adjust timestep range (focus on middle)
1047
+ --grad_range_start 200 --grad_range_end 800
1048
+
1049
+ # Try different model variant
1050
+ --model_variant lpo
1051
+ ```
1052
+
1053
+ #### 4. Config Not Found
1054
+
1055
+ **Symptoms:**
1056
+ ```
1057
+ ValueError: Unknown config: my_config
1058
+ ```
1059
+
1060
+ **Solutions:**
1061
+ ```bash
1062
+ # List available configs
1063
+ python -c "from grad_ascent_configs import list_configs; print(list_configs())"
1064
+
1065
+ # Print config details
1066
+ python -c "from grad_ascent_configs import print_config; print_config('high_quality')"
1067
+ ```
1068
+
1069
+ #### 5. Metric Loading Errors
1070
+
1071
+ **Symptoms:**
1072
+ ```
1073
+ Warning: Could not load PickScore scorer
1074
+ ```
1075
+
1076
+ **Solutions:**
1077
+ ```bash
1078
+ # Install missing dependencies
1079
+ pip install transformers datasets
1080
+
1081
+ # Check HuggingFace Hub access
1082
+ huggingface-cli login
1083
+
1084
+ # Skip problematic metrics
1085
+ --metrics clip aesthetic # Skip pickscore if it fails
1086
+ ```
1087
+
1088
+ #### 6. Dataset Not Found
1089
+
1090
+ **Symptoms:**
1091
+ ```
1092
+ FileNotFoundError: Validation JSON not found
1093
+ ```
1094
+
1095
+ **Solutions:**
1096
+ ```bash
1097
+ # Check data directory structure
1098
+ ls data/coco/
1099
+
1100
+ # Use Pick-a-Pic instead (no local files needed)
1101
+ --dataset_type pickapic
1102
+
1103
+ # Provide correct data path
1104
+ --data_dir /path/to/your/data
1105
+ ```
1106
+
1107
+ ---
1108
+
1109
+ ## Best Practices
1110
+
1111
+ ### 1. **Start Small, Scale Up**
1112
+
1113
+ ```bash
1114
+ # First: Quick test (10 samples)
1115
+ python eval.py --grad_config cosine_nesterov --metrics clip --max_samples 10
1116
+
1117
+ # Then: Medium test (50 samples)
1118
+ python eval.py --grad_config cosine_nesterov --metrics clip aesthetic --max_samples 50
1119
+
1120
+ # Finally: Full evaluation (200+ samples)
1121
+ python eval.py --grad_config high_quality --metrics fid clip aesthetic pickscore hpsv2 --max_samples 200
1122
+ ```
1123
+
1124
+ ### 2. **Choose Right Config for Use Case**
1125
+
1126
+ | Goal | Config | Metrics |
1127
+ |------|--------|---------|
1128
+ | Quick experiment | `cosine_nesterov` | `clip` |
1129
+ | Research paper | `high_quality` | `fid clip aesthetic pickscore hpsv2` |
1130
+ | Production | `conservative` | `pickscore hpsv2` |
1131
+ | Fast iteration | `aggressive` | `clip aesthetic` |
1132
+
1133
+ ### 3. **Use Multiple Metrics**
1134
+
1135
+ Don't rely on a single metric. Recommended combinations:
1136
+
1137
+ ```bash
1138
+ # Text alignment + aesthetics
1139
+ --metrics clip aesthetic
1140
+
1141
+ # Human preference focus
1142
+ --metrics pickscore hpsv2 imagereward
1143
+
1144
+ # Comprehensive (research)
1145
+ --metrics fid clip aesthetic pickscore hpsv2
1146
+ ```
1147
+
1148
+ ### 4. **Save Important Runs**
1149
+
1150
+ ```bash
1151
+ # Always save images for important evaluations
1152
+ --save_images --output_dir results/important_run_$(date +%Y%m%d)
1153
+ ```
1154
+
1155
+ ### 5. **Monitor GPU Usage**
1156
+
1157
+ ```bash
1158
+ # In separate terminal
1159
+ watch -n 1 nvidia-smi
1160
+
1161
+ # Or use
1162
+ gpustat -i 1
1163
+ ```
1164
+
1165
+ ### 6. **Batch Evaluation**
1166
+
1167
+ ```bash
1168
+ # Create evaluation script
1169
+ cat << 'EOF' > run_evals.sh
1170
+ #!/bin/bash
1171
+ for config in cosine_nesterov high_quality conservative; do
1172
+ for model in origin lpo; do
1173
+ python eval.py \
1174
+ --model_variant $model \
1175
+ --grad_config $config \
1176
+ --metrics clip aesthetic pickscore \
1177
+ --max_samples 100 \
1178
+ --save_images \
1179
+ --output_dir results/${model}_${config}
1180
+ done
1181
+ done
1182
+ EOF
1183
+
1184
+ chmod +x run_evals.sh
1185
+ ./run_evals.sh
1186
+ ```
1187
+
1188
+ ### 7. **Reproducibility**
1189
+
1190
+ ```bash
1191
+ # Always set seed for reproducible results
1192
+ --seed 42
1193
+
1194
+ # Document your runs
1195
+ --output_dir results/experiment_name_$(date +%Y%m%d_%H%M)
1196
+ ```
1197
+
1198
+ ### 8. **Performance Tips**
1199
+
1200
+ - Use `batch_size=1` for safety (reward model compatibility)
1201
+ - Start with `--max_samples 10` for debugging
1202
+ - Use `--dataset_type pickapic` for large-scale evaluation (no FID overhead)
1203
+ - Skip `fid` metric if not needed (expensive)
1204
+ - Use `--num_steps 20-30` for faster generation (vs default 50)
1205
+
1206
+ ### 9. **Config Selection Guide**
1207
+
1208
+ ```python
1209
+ # Start here
1210
+ if "just_testing":
1211
+ config = "constant"
1212
+
1213
+ # General use
1214
+ elif "standard_evaluation":
1215
+ config = "cosine_nesterov" # Best balance
1216
+
1217
+ # Research/papers
1218
+ elif "need_best_quality":
1219
+ config = "high_quality" # 20 steps, nesterov
1220
+
1221
+ # Fast experiments
1222
+ elif "need_speed":
1223
+ config = "aggressive" # 8 steps
1224
+
1225
+ # Stability critical
1226
+ elif "need_stability":
1227
+ config = "conservative" # 25 steps, careful
1228
+ ```
1229
+
1230
+ ### 10. **Timestep Range Tips**
1231
+
1232
+ ```python
1233
+ # Full range (default)
1234
+ --grad_range_start 0 --grad_range_end 700
1235
+
1236
+ # Middle timesteps (often best)
1237
+ --grad_range_start 200 --grad_range_end 800
1238
+
1239
+ # Early timesteps (structure)
1240
+ --grad_range_start 500 --grad_range_end 1000
1241
+
1242
+ # Late timesteps (details)
1243
+ --grad_range_start 0 --grad_range_end 400
1244
+ ```
1245
+
1246
+ ---
1247
+
1248
+ ## Performance Metrics
1249
+
1250
+ ### Expected Results
1251
+
1252
+ Based on COCO validation set (100 samples):
1253
+
1254
+ | Method | CLIP ↑ | Aesthetic ↑ | PickScore ↑ | Time |
1255
+ |--------|--------|-------------|-------------|------|
1256
+ | Baseline (Origin) | 0.812 | 6.23 | 21.4 | 5 min |
1257
+ | + Constant | 0.819 | 6.28 | 21.6 | 6 min |
1258
+ | + Cosine Nesterov | 0.834 | 6.45 | 22.1 | 8 min |
1259
+ | + High Quality | 0.841 | 6.52 | 22.4 | 12 min |
1260
+ | Baseline (LPO) | 0.856 | 6.67 | 22.8 | 5 min |
1261
+ | LPO + High Quality | 0.873 | 6.89 | 23.5 | 12 min |
1262
+
1263
+ *Results may vary based on hardware and specific prompts*
1264
+
1265
+ ---
1266
+
1267
+ ## Citation
1268
+
1269
+ If you use this code in your research, please cite:
1270
+
1271
+ ```bibtex
1272
+ @article{lpo2024,
1273
+ title={Latent Preference Optimization for Diffusion Models},
1274
+ author={Your Name},
1275
+ journal={arXiv preprint},
1276
+ year={2024}
1277
+ }
1278
+ ```
1279
+
1280
+ ---
1281
+
1282
+ ## License
1283
+
1284
+ This project follows the license of the main LPO repository.
1285
+
1286
+ ---
1287
+
1288
+ ## Contributing
1289
+
1290
+ Contributions are welcome! Please:
1291
+
1292
+ 1. Test your changes with `--max_samples 10`
1293
+ 2. Document new features in this README
1294
+ 3. Add examples to `examples.sh`
1295
+ 4. Follow existing code style
1296
+
1297
+ ---
1298
+
1299
+ ## Support
1300
+
1301
+ For issues and questions:
1302
+
1303
+ 1. Check [Troubleshooting](#troubleshooting) section
1304
+ 2. Review [Examples](#usage-examples)
1305
+ 3. Open an issue on GitHub
1306
+
1307
+ ---
1308
+
1309
+ ## Changelog
1310
+
1311
+ ### Latest Version (January 2026)
1312
+
1313
+ **New Features:**
1314
+ - ✨ Learning rate scheduling (constant, linear, cosine, exponential, step)
1315
+ - ✨ Momentum optimization (standard and Nesterov)
1316
+ - ✨ 15 configuration presets
1317
+ - ✨ Additional metrics (PickScore, HPSv2, ImageReward)
1318
+ - ✨ Pick-a-Pic validation dataset support
1319
+ - ✨ SD1.5 model variants (Origin, SPO, DPO, LPO)
1320
+ - ✨ Comprehensive evaluation framework
1321
+ - ✨ **Automatic run folder creation** - Each run creates `run_1/`, `run_2/`, etc.
1322
+ - ✨ **Reward curve visualization** - Automatic plotting of reward progression across timesteps
1323
+ - ✨ **Final timestep reward tracking** - Reports reward specifically from t=0 (decoded latent)
1324
+ - ✨ **Detailed reward logging** - Shows both last timestep reward and running average
1325
+
1326
+ **Improvements:**
1327
+ - 🚀 Better convergence with LR scheduling
1328
+ - 🚀 Faster optimization with momentum
1329
+ - 📊 More comprehensive quality assessment
1330
+ - 📊 Visual feedback with reward curve plots
1331
+ - 📚 Complete documentation
1332
+ - 🔍 Enhanced debugging with timestep-specific reward tracking
1333
+
1334
+ ---
1335
+
1336
+ **Happy Optimizing! 🚀**
Reward_sd15_idealized/RESULTS/pickapic/one_step_rectification_config_spo/run_1/evaluation_results.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ mode: gradient_ascent
2
+ metrics: ['clip', 'aesthetic', 'pickscore', 'hpsv2', 'hpsv21', 'imagereward']
3
+ config: {'num_samples': 500, 'num_steps': 20, 'cfg_scale': 5.0, 'grad_range': [0, 700], 'grad_steps': 5, 'grad_step_size': 0.1}
4
+ gradient_ascent: {'avg_reward': np.float64(0.69980224609375), 'clip_score': np.float64(26.02208717751503), 'aesthetic_score': np.float64(5.953624136924744), 'pickscore': np.float64(21.460845840454102), 'hpsv2_score': np.float16(0.2727), 'hpsv21_score': np.float16(0.274), 'imagereward_score': np.float64(0.412498972608475), 'stats': {'num_applications': 12, 'total_reward_improvement': 1.29296875, 'avg_reward_improvement': 0.10774739583333333, 'avg_grad_norm': 0.11500459335123499, 'max_grad_norm': 0.15467031300067902, 'detailed_stats': [{'timestep': 751, 'initial_reward': 0.04803466796875, 'final_reward': 0.09844970703125, 'reward_improvement': 0.0504150390625, 'grad_norms': [0.02852064184844494], 'reward_history': [0.04803466796875, 0.04803466796875], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 701, 'initial_reward': 0.1910400390625, 'final_reward': 0.340576171875, 'reward_improvement': 0.1495361328125, 'grad_norms': [0.11714287102222443], 'reward_history': [0.1910400390625, 0.1910400390625], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 651, 'initial_reward': 0.489013671875, 'final_reward': 0.6376953125, 'reward_improvement': 0.148681640625, 'grad_norms': [0.15467031300067902], 'reward_history': [0.489013671875, 0.489013671875], 'lr_history': [1.0], 'latent_change': 1.0009765625}, {'timestep': 601, 'initial_reward': 0.6181640625, 'final_reward': 0.7470703125, 'reward_improvement': 0.12890625, 'grad_norms': [0.14603829383850098], 'reward_history': [0.6181640625, 0.6181640625], 'lr_history': [1.0], 'latent_change': 1.0009765625}, {'timestep': 551, 'initial_reward': 0.6640625, 'final_reward': 0.7841796875, 'reward_improvement': 0.1201171875, 'grad_norms': [0.13933144509792328], 'reward_history': [0.6640625, 0.6640625], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 501, 'initial_reward': 0.650390625, 'final_reward': 0.7744140625, 'reward_improvement': 0.1240234375, 'grad_norms': [0.1410745233297348], 'reward_history': [0.650390625, 0.650390625], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 451, 'initial_reward': 0.63134765625, 'final_reward': 0.76025390625, 'reward_improvement': 0.12890625, 'grad_norms': [0.14325274527072906], 'reward_history': [0.63134765625, 0.63134765625], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 401, 'initial_reward': 0.65869140625, 'final_reward': 0.77978515625, 'reward_improvement': 0.12109375, 'grad_norms': [0.13541191816329956], 'reward_history': [0.65869140625, 0.65869140625], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 351, 'initial_reward': 0.7109375, 'final_reward': 0.8173828125, 'reward_improvement': 0.1064453125, 'grad_norms': [0.12057284265756607], 'reward_history': [0.7109375, 0.7109375], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 301, 'initial_reward': 0.76318359375, 'final_reward': 0.849609375, 'reward_improvement': 0.08642578125, 'grad_norms': [0.10099449008703232], 'reward_history': [0.76318359375, 0.76318359375], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 251, 'initial_reward': 0.80810546875, 'final_reward': 0.87841796875, 'reward_improvement': 0.0703125, 'grad_norms': [0.08335024118423462], 'reward_history': [0.80810546875, 0.80810546875], 'lr_history': [1.0], 'latent_change': 1.0}, {'timestep': 201, 'initial_reward': 0.8427734375, 'final_reward': 0.90087890625, 'reward_improvement': 0.05810546875, 'grad_norms': [0.06969479471445084], 'reward_history': [0.8427734375, 0.8427734375], 'lr_history': [1.0], 'latent_change': 1.0}]}}
Reward_sd15_idealized/RESULTS/pickapic/one_step_rectification_config_spo/run_1/log.log ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ======================================================================
2
+ FID EVALUATION: BASELINE vs GRADIENT ASCENT
3
+ ======================================================================
4
+
5
+ Logging to: RESULTS/pickapic/one_step_rectification_config_spo/run_1/log.log
6
+
7
+ Device: cuda:0
8
+ Dataset: PICKAPIC
9
+ Data directory: ./data
10
+ Base model: stable-diffusion-v1-5/stable-diffusion-v1-5
11
+ Model variant: spo
12
+ LRM model: /g/data/rr81/LPO/lrm/lrm_15/LRM
13
+ HF cache dir: /scratch/rr81/ma5430/.cache/huggingface/hub
14
+ HF offline mode: True
15
+ Inference steps: 20
16
+ CFG scale: 3.0
17
+ Batch size: 1
18
+ Max samples: All
19
+ Output directory: RESULTS/pickapic/one_step_rectification_config_spo/run_1
20
+ Save images: False
21
+ Evaluation mode: gradient_ascent
22
+ Metrics to evaluate: CLIP, AESTHETIC, PICKSCORE, HPSV2, HPSV21, IMAGEREWARD
23
+ Gradient ascent config: one_step_rectification_config
24
+
25
+ ======================================================================
26
+ 1. LOADING VALIDATION DATA
27
+ ======================================================================
28
+ Loading Pick-a-Pic validation prompts...
29
+ Loading cached Pick-a-Pic split 'validation_unique' from 1 parquet shards
30
+ cache=/scratch/rr81/ma5430/.cache/huggingface/hub/datasets--pickapic-anonymous--pickapic_v1
31
+ Loaded 500 Pick-a-Pic validation samples
32
+
33
+ ======================================================================
34
+ 2. LOADING REWARD MODEL
35
+ ======================================================================
36
+ Loading base models from stable-diffusion-v1-5/stable-diffusion-v1-5...
37
+ Loading custom U-Net for reward prediction...
38
+ Loading CLIP text projection from openai/clip-vit-large-patch14...
39
+ ✓ Loaded CLIP text projection weights
40
+
41
+ Loading LRM weights from /g/data/rr81/LPO/lrm/lrm_15/LRM...
42
+ ✓ Loaded U-Net weights from /g/data/rr81/LPO/lrm/lrm_15/LRM/lrm_sd15/unet
43
+ ✓ Loaded text encoder weights from /g/data/rr81/LPO/lrm/lrm_15/LRM/lrm_sd15/text_encoder
44
+ ✓ Loaded projection layers and logit_scale from /g/data/rr81/LPO/lrm/lrm_15/LRM/lrm_sd15/state_dict.pt
45
+ ✓ Successfully loaded all LRM weights!
46
+ ✓ LRM Reward Model initialized successfully!
47
+ ✓ Reward model loaded
48
+
49
+ ======================================================================
50
+ 3. LOADING PIPELINE
51
+ ======================================================================
52
+ ✓ Loaded SPO SD1.5 model (cfg_scale adjusted to 5.0)
53
+ ✓ Reward model attached to pipeline
54
+ ✓ Pipeline loaded
55
+ GPU memory before scorer load: 135.37 GB free / 140.06 GB total
56
+ Scorer device: cuda:0
57
+
58
+ ======================================================================
59
+ 3.5. LOADING CLIP AND AESTHETIC SCORERS
60
+ ======================================================================
61
+ ✓ CLIP scorer loaded
62
+ ✓ Aesthetic scorer loaded
63
+ ✓ PickScore scorer loaded
64
+ ✓ HPSv2 scorer loaded
65
+ ✓ HPSv2.1 scorer loaded
66
+ load checkpoint from /scratch/rr81/ma5430/.cache/huggingface/hub/models--THUDM--ImageReward/snapshots/5736be03b2652728fb87788c9797b0570450ab72/ImageReward.pt
67
+ checkpoint loaded
68
+ ✓ ImageReward scorer loaded
69
+
70
+ ======================================================================
71
+ 4. CONFIGURING GRADIENT ASCENT
72
+ ======================================================================
73
+ Loading gradient ascent config: one_step_rectification_config
74
+ Config loaded: {'grad_timestep_range': (200, 800), 'num_grad_steps': 1, 'grad_step_size': 1.0, 'grad_scale': 1.0, 'lr_scheduler_type': 'constant', 'use_momentum': False, 'use_nesterov': False, 'use_iso_projection': False}
75
+ Gradient timestep range: (200, 800)
76
+ Gradient steps: 1
77
+ Gradient step size (initial LR): 1.0
78
+ LR Scheduler: constant
79
+ ✓ Gradient ascent enabled for timesteps (200, 800)
80
+ - Grad steps: 1
81
+ - Grad step size (initial LR): 1.0
82
+ - Grad scale: 1.0
83
+ - LR scheduler: constant
84
+ - Momentum: disabled
85
+ - Iso Projection: False
86
+
87
+ ======================================================================
88
+ 6. EVALUATING GRADIENT ASCENT
89
+ ======================================================================
90
+
91
+ Generating images with gradient_ascent mode...
92
+
93
+ [gradient_ascent] Batch 10/500 | Samples: 10/500 | Reward (t=1): 0.9839 | Reward (Avg): 0.7578 | CLIP: 27.5790 | Aesthetic: 6.3113 | PickScore: 22.3422 | HPSv2: 0.2786 | HPSv2.1: 0.2986 | ImageReward: 0.4784
94
+
95
+ [gradient_ascent] Batch 20/500 | Samples: 20/500 | Reward (t=1): 0.0174 | Reward (Avg): 0.7138 | CLIP: 26.0677 | Aesthetic: 6.0572 | PickScore: 21.9946 | HPSv2: 0.2776 | HPSv2.1: 0.2915 | ImageReward: 0.4453
96
+
97
+ [gradient_ascent] Batch 30/500 | Samples: 30/500 | Reward (t=1): 0.9917 | Reward (Avg): 0.7299 | CLIP: 26.0958 | Aesthetic: 5.9759 | PickScore: 22.1637 | HPSv2: 0.2773 | HPSv2.1: 0.2852 | ImageReward: 0.4647
98
+
99
+ [gradient_ascent] Batch 40/500 | Samples: 40/500 | Reward (t=1): 0.2720 | Reward (Avg): 0.7690 | CLIP: 26.6243 | Aesthetic: 6.0120 | PickScore: 22.1630 | HPSv2: 0.2781 | HPSv2.1: 0.2908 | ImageReward: 0.5857
100
+
101
+ [gradient_ascent] Batch 50/500 | Samples: 50/500 | Reward (t=1): 0.2140 | Reward (Avg): 0.7380 | CLIP: 26.3520 | Aesthetic: 6.0178 | PickScore: 21.9486 | HPSv2: 0.2759 | HPSv2.1: 0.2847 | ImageReward: 0.4256
102
+
103
+ [gradient_ascent] Batch 60/500 | Samples: 60/500 | Reward (t=1): 0.9497 | Reward (Avg): 0.7612 | CLIP: 26.1764 | Aesthetic: 5.9950 | PickScore: 21.9419 | HPSv2: 0.2761 | HPSv2.1: 0.2830 | ImageReward: 0.2981
104
+
105
+ [gradient_ascent] Batch 70/500 | Samples: 70/500 | Reward (t=1): 0.1858 | Reward (Avg): 0.7414 | CLIP: 26.2783 | Aesthetic: 5.9921 | PickScore: 21.9503 | HPSv2: 0.2764 | HPSv2.1: 0.2825 | ImageReward: 0.3641
106
+
107
+ [gradient_ascent] Batch 80/500 | Samples: 80/500 | Reward (t=1): 0.9868 | Reward (Avg): 0.7360 | CLIP: 26.0943 | Aesthetic: 5.9858 | PickScore: 21.8417 | HPSv2: 0.2754 | HPSv2.1: 0.2791 | ImageReward: 0.3335
108
+
109
+ [gradient_ascent] Batch 90/500 | Samples: 90/500 | Reward (t=1): 0.7842 | Reward (Avg): 0.7336 | CLIP: 26.1805 | Aesthetic: 5.9752 | PickScore: 21.7347 | HPSv2: 0.2747 | HPSv2.1: 0.2791 | ImageReward: 0.3449
110
+
111
+ [gradient_ascent] Batch 100/500 | Samples: 100/500 | Reward (t=1): 0.8276 | Reward (Avg): 0.7296 | CLIP: 26.2917 | Aesthetic: 5.9789 | PickScore: 21.6981 | HPSv2: 0.2749 | HPSv2.1: 0.2795 | ImageReward: 0.3349
112
+
113
+ [gradient_ascent] Batch 110/500 | Samples: 110/500 | Reward (t=1): 0.1161 | Reward (Avg): 0.7234 | CLIP: 26.4764 | Aesthetic: 5.9772 | PickScore: 21.6814 | HPSv2: 0.2749 | HPSv2.1: 0.2791 | ImageReward: 0.3347
114
+
115
+ [gradient_ascent] Batch 120/500 | Samples: 120/500 | Reward (t=1): 0.9961 | Reward (Avg): 0.7031 | CLIP: 26.4423 | Aesthetic: 5.9550 | PickScore: 21.6546 | HPSv2: 0.2749 | HPSv2.1: 0.2786 | ImageReward: 0.3449
116
+
117
+ [gradient_ascent] Batch 130/500 | Samples: 130/500 | Reward (t=1): 0.0311 | Reward (Avg): 0.7017 | CLIP: 26.1816 | Aesthetic: 5.9739 | PickScore: 21.6772 | HPSv2: 0.2747 | HPSv2.1: 0.2791 | ImageReward: 0.3246
118
+
119
+ [gradient_ascent] Batch 140/500 | Samples: 140/500 | Reward (t=1): 0.0458 | Reward (Avg): 0.6919 | CLIP: 26.3517 | Aesthetic: 5.9473 | PickScore: 21.6609 | HPSv2: 0.2747 | HPSv2.1: 0.2778 | ImageReward: 0.3275
120
+
121
+ [gradient_ascent] Batch 150/500 | Samples: 150/500 | Reward (t=1): 0.1973 | Reward (Avg): 0.6835 | CLIP: 26.3371 | Aesthetic: 5.9298 | PickScore: 21.5766 | HPSv2: 0.2739 | HPSv2.1: 0.2756 | ImageReward: 0.2968
122
+
123
+ [gradient_ascent] Batch 160/500 | Samples: 160/500 | Reward (t=1): 0.9526 | Reward (Avg): 0.6758 | CLIP: 26.4790 | Aesthetic: 5.9465 | PickScore: 21.6101 | HPSv2: 0.2742 | HPSv2.1: 0.2761 | ImageReward: 0.3172
124
+
125
+ [gradient_ascent] Batch 170/500 | Samples: 170/500 | Reward (t=1): 0.8892 | Reward (Avg): 0.6712 | CLIP: 26.4519 | Aesthetic: 5.9403 | PickScore: 21.5942 | HPSv2: 0.2739 | HPSv2.1: 0.2764 | ImageReward: 0.3352
126
+
127
+ [gradient_ascent] Batch 180/500 | Samples: 180/500 | Reward (t=1): 0.9199 | Reward (Avg): 0.6702 | CLIP: 26.4475 | Aesthetic: 5.9484 | PickScore: 21.5408 | HPSv2: 0.2737 | HPSv2.1: 0.2759 | ImageReward: 0.3420
128
+
129
+ [gradient_ascent] Batch 190/500 | Samples: 190/500 | Reward (t=1): 0.9800 | Reward (Avg): 0.6799 | CLIP: 26.4414 | Aesthetic: 5.9557 | PickScore: 21.5410 | HPSv2: 0.2737 | HPSv2.1: 0.2761 | ImageReward: 0.3676
130
+
131
+ [gradient_ascent] Batch 200/500 | Samples: 200/500 | Reward (t=1): 0.9971 | Reward (Avg): 0.6779 | CLIP: 26.4270 | Aesthetic: 5.9503 | PickScore: 21.5414 | HPSv2: 0.2742 | HPSv2.1: 0.2776 | ImageReward: 0.3901
132
+
133
+ [gradient_ascent] Batch 210/500 | Samples: 210/500 | Reward (t=1): 0.9248 | Reward (Avg): 0.6884 | CLIP: 26.4479 | Aesthetic: 5.9510 | PickScore: 21.5482 | HPSv2: 0.2739 | HPSv2.1: 0.2776 | ImageReward: 0.3864
134
+
135
+ [gradient_ascent] Batch 220/500 | Samples: 220/500 | Reward (t=1): 0.4526 | Reward (Avg): 0.6888 | CLIP: 26.3972 | Aesthetic: 5.9576 | PickScore: 21.5436 | HPSv2: 0.2739 | HPSv2.1: 0.2776 | ImageReward: 0.3660
136
+
137
+ [gradient_ascent] Batch 230/500 | Samples: 230/500 | Reward (t=1): 0.2216 | Reward (Avg): 0.6872 | CLIP: 26.3495 | Aesthetic: 5.9620 | PickScore: 21.5255 | HPSv2: 0.2737 | HPSv2.1: 0.2769 | ImageReward: 0.3544
138
+
139
+ [gradient_ascent] Batch 240/500 | Samples: 240/500 | Reward (t=1): 0.8564 | Reward (Avg): 0.6887 | CLIP: 26.3124 | Aesthetic: 5.9582 | PickScore: 21.5032 | HPSv2: 0.2734 | HPSv2.1: 0.2764 | ImageReward: 0.3462
140
+
141
+ [gradient_ascent] Batch 250/500 | Samples: 250/500 | Reward (t=1): 0.1229 | Reward (Avg): 0.6950 | CLIP: 26.4021 | Aesthetic: 5.9620 | PickScore: 21.5312 | HPSv2: 0.2737 | HPSv2.1: 0.2764 | ImageReward: 0.3755
142
+
143
+ [gradient_ascent] Batch 260/500 | Samples: 260/500 | Reward (t=1): 0.9468 | Reward (Avg): 0.7000 | CLIP: 26.4661 | Aesthetic: 5.9651 | PickScore: 21.5443 | HPSv2: 0.2739 | HPSv2.1: 0.2769 | ImageReward: 0.3948
144
+
145
+ [gradient_ascent] Batch 270/500 | Samples: 270/500 | Reward (t=1): 0.9780 | Reward (Avg): 0.7023 | CLIP: 26.3012 | Aesthetic: 5.9742 | PickScore: 21.5138 | HPSv2: 0.2739 | HPSv2.1: 0.2764 | ImageReward: 0.3981
146
+
147
+ [gradient_ascent] Batch 280/500 | Samples: 280/500 | Reward (t=1): 0.6753 | Reward (Avg): 0.7021 | CLIP: 26.3270 | Aesthetic: 5.9737 | PickScore: 21.5205 | HPSv2: 0.2737 | HPSv2.1: 0.2761 | ImageReward: 0.4017
148
+
149
+ [gradient_ascent] Batch 290/500 | Samples: 290/500 | Reward (t=1): 0.4121 | Reward (Avg): 0.6986 | CLIP: 26.2771 | Aesthetic: 5.9719 | PickScore: 21.5051 | HPSv2: 0.2737 | HPSv2.1: 0.2759 | ImageReward: 0.3936
150
+
151
+ [gradient_ascent] Batch 300/500 | Samples: 300/500 | Reward (t=1): 0.4277 | Reward (Avg): 0.6986 | CLIP: 26.2539 | Aesthetic: 5.9735 | PickScore: 21.5175 | HPSv2: 0.2739 | HPSv2.1: 0.2764 | ImageReward: 0.4022
152
+
153
+ [gradient_ascent] Batch 310/500 | Samples: 310/500 | Reward (t=1): 0.8721 | Reward (Avg): 0.6977 | CLIP: 26.2491 | Aesthetic: 5.9809 | PickScore: 21.5185 | HPSv2: 0.2737 | HPSv2.1: 0.2759 | ImageReward: 0.3892
154
+
155
+ [gradient_ascent] Batch 320/500 | Samples: 320/500 | Reward (t=1): 0.4409 | Reward (Avg): 0.6939 | CLIP: 26.2734 | Aesthetic: 5.9789 | PickScore: 21.5331 | HPSv2: 0.2737 | HPSv2.1: 0.2756 | ImageReward: 0.3976
156
+
157
+ [gradient_ascent] Batch 330/500 | Samples: 330/500 | Reward (t=1): 0.9917 | Reward (Avg): 0.6926 | CLIP: 26.2698 | Aesthetic: 5.9769 | PickScore: 21.5238 | HPSv2: 0.2739 | HPSv2.1: 0.2759 | ImageReward: 0.3978
158
+
159
+ [gradient_ascent] Batch 340/500 | Samples: 340/500 | Reward (t=1): 0.9966 | Reward (Avg): 0.6948 | CLIP: 26.3201 | Aesthetic: 5.9800 | PickScore: 21.5359 | HPSv2: 0.2739 | HPSv2.1: 0.2761 | ImageReward: 0.4060
160
+
161
+ [gradient_ascent] Batch 350/500 | Samples: 350/500 | Reward (t=1): 0.6436 | Reward (Avg): 0.6949 | CLIP: 26.2648 | Aesthetic: 5.9745 | PickScore: 21.5429 | HPSv2: 0.2739 | HPSv2.1: 0.2761 | ImageReward: 0.4022
162
+
163
+ [gradient_ascent] Batch 360/500 | Samples: 360/500 | Reward (t=1): 0.9868 | Reward (Avg): 0.6984 | CLIP: 26.3237 | Aesthetic: 5.9739 | PickScore: 21.5439 | HPSv2: 0.2742 | HPSv2.1: 0.2766 | ImageReward: 0.4229
164
+
165
+ [gradient_ascent] Batch 370/500 | Samples: 370/500 | Reward (t=1): 0.9668 | Reward (Avg): 0.6994 | CLIP: 26.2721 | Aesthetic: 5.9688 | PickScore: 21.5360 | HPSv2: 0.2739 | HPSv2.1: 0.2764 | ImageReward: 0.4197
166
+
167
+ [gradient_ascent] Batch 380/500 | Samples: 380/500 | Reward (t=1): 0.9629 | Reward (Avg): 0.7023 | CLIP: 26.3215 | Aesthetic: 5.9748 | PickScore: 21.5425 | HPSv2: 0.2739 | HPSv2.1: 0.2769 | ImageReward: 0.4320
168
+
169
+ [gradient_ascent] Batch 390/500 | Samples: 390/500 | Reward (t=1): 0.9761 | Reward (Avg): 0.7035 | CLIP: 26.2381 | Aesthetic: 5.9748 | PickScore: 21.5369 | HPSv2: 0.2739 | HPSv2.1: 0.2764 | ImageReward: 0.4212
170
+
171
+ [gradient_ascent] Batch 400/500 | Samples: 400/500 | Reward (t=1): 0.9800 | Reward (Avg): 0.7064 | CLIP: 26.2046 | Aesthetic: 5.9692 | PickScore: 21.5326 | HPSv2: 0.2737 | HPSv2.1: 0.2761 | ImageReward: 0.4172
172
+
173
+ [gradient_ascent] Batch 410/500 | Samples: 410/500 | Reward (t=1): 0.9048 | Reward (Avg): 0.7060 | CLIP: 26.1930 | Aesthetic: 5.9673 | PickScore: 21.5222 | HPSv2: 0.2734 | HPSv2.1: 0.2756 | ImageReward: 0.4134
174
+
175
+ [gradient_ascent] Batch 420/500 | Samples: 420/500 | Reward (t=1): 0.8452 | Reward (Avg): 0.7030 | CLIP: 26.2187 | Aesthetic: 5.9654 | PickScore: 21.5167 | HPSv2: 0.2734 | HPSv2.1: 0.2754 | ImageReward: 0.4201
176
+
177
+ [gradient_ascent] Batch 430/500 | Samples: 430/500 | Reward (t=1): 0.9424 | Reward (Avg): 0.7073 | CLIP: 26.2393 | Aesthetic: 5.9670 | PickScore: 21.5194 | HPSv2: 0.2734 | HPSv2.1: 0.2754 | ImageReward: 0.4270
178
+
179
+ [gradient_ascent] Batch 440/500 | Samples: 440/500 | Reward (t=1): 0.9673 | Reward (Avg): 0.7067 | CLIP: 26.1450 | Aesthetic: 5.9661 | PickScore: 21.5111 | HPSv2: 0.2732 | HPSv2.1: 0.2754 | ImageReward: 0.4217
180
+
181
+ [gradient_ascent] Batch 450/500 | Samples: 450/500 | Reward (t=1): 0.7007 | Reward (Avg): 0.7084 | CLIP: 26.1783 | Aesthetic: 5.9595 | PickScore: 21.5085 | HPSv2: 0.2732 | HPSv2.1: 0.2749 | ImageReward: 0.4202
182
+
183
+ [gradient_ascent] Batch 460/500 | Samples: 460/500 | Reward (t=1): 0.1193 | Reward (Avg): 0.7053 | CLIP: 26.1434 | Aesthetic: 5.9576 | PickScore: 21.5003 | HPSv2: 0.2732 | HPSv2.1: 0.2747 | ImageReward: 0.4168
184
+
185
+ [gradient_ascent] Batch 470/500 | Samples: 470/500 | Reward (t=1): 0.6265 | Reward (Avg): 0.7034 | CLIP: 26.0937 | Aesthetic: 5.9501 | PickScore: 21.4891 | HPSv2: 0.2727 | HPSv2.1: 0.2744 | ImageReward: 0.4166
186
+
187
+ [gradient_ascent] Batch 480/500 | Samples: 480/500 | Reward (t=1): 0.8589 | Reward (Avg): 0.7012 | CLIP: 26.0558 | Aesthetic: 5.9510 | PickScore: 21.4883 | HPSv2: 0.2727 | HPSv2.1: 0.2742 | ImageReward: 0.4193
188
+
189
+ [gradient_ascent] Batch 490/500 | Samples: 490/500 | Reward (t=1): 0.8589 | Reward (Avg): 0.7001 | CLIP: 26.0536 | Aesthetic: 5.9529 | PickScore: 21.4798 | HPSv2: 0.2727 | HPSv2.1: 0.2739 | ImageReward: 0.4213
190
+
191
+ [gradient_ascent] Batch 500/500 | Samples: 500/500 | Reward (t=1): 0.7222 | Reward (Avg): 0.6998 | CLIP: 26.0221 | Aesthetic: 5.9536 | PickScore: 21.4608 | HPSv2: 0.2727 | HPSv2.1: 0.2739 | ImageReward: 0.4125
192
+ ✓ Gradient Ascent Avg Reward: 0.6998
193
+ ✓ Gradient Ascent Avg CLIP Score: 26.0221
194
+ ✓ Gradient Ascent Avg Aesthetic Score: 5.9536
195
+ ✓ Gradient Ascent Avg PickScore: 21.4608
196
+ ✓ Gradient Ascent Avg HPSv2 Score: 0.2727
197
+ ✓ Gradient Ascent Avg HPSv2.1 Score: 0.2739
198
+ ✓ Gradient Ascent Avg ImageReward: 0.4125
199
+
200
+ Gradient Ascent Statistics:
201
+ Applications: 12
202
+ Total reward improvement: +1.2930
203
+ Avg reward improvement: +0.1077
204
+
205
+ ✓ Saved LR curve plot to: RESULTS/pickapic/one_step_rectification_config_spo/run_1/lr_curve.png
206
+ Total gradient steps: 12
207
+ LR range: 1.000000 → 1.000000
208
+
209
+ ✓ Saved Rewards curve plot to: RESULTS/pickapic/one_step_rectification_config_spo/run_1/rewards_curve.png
210
+ Total gradient steps: 24
211
+ Reward range: 0.9487 → 0.9990
212
+ Total improvement: +0.0503
213
+
214
+ ======================================================================
215
+ FINAL RESULTS
216
+ ======================================================================
217
+
218
+ Gradient Ascent:
219
+ Avg Reward: 0.6998
220
+ Avg CLIP Score: 26.0221
221
+ Avg Aesthetic: 5.9536
222
+ Avg PickScore: 21.4608
223
+ Avg HPSv2: 0.2727
224
+ Avg HPSv2.1: 0.2739
225
+ Avg ImageReward: 0.4125
226
+
227
+ ✓ Results saved to: RESULTS/pickapic/one_step_rectification_config_spo/run_1/evaluation_results.txt
228
+
229
+ ======================================================================
Reward_sd15_idealized/RESULTS/pickapic/one_step_rectification_config_spo/run_1/lr_curve.png ADDED
Reward_sd15_idealized/__pycache__/grad_ascent_configs.cpython-310.pyc ADDED
Binary file (1.94 kB). View file
 
Reward_sd15_idealized/__pycache__/grad_ascent_configs.cpython-311.pyc ADDED
Binary file (2.69 kB). View file
 
Reward_sd15_idealized/__pycache__/gradient_ascent_utils.cpython-310.pyc ADDED
Binary file (9.04 kB). View file
 
Reward_sd15_idealized/__pycache__/gradient_ascent_utils.cpython-311.pyc ADDED
Binary file (14.7 kB). View file
 
Reward_sd15_idealized/__pycache__/gradient_ascent_utils.cpython-313.pyc ADDED
Binary file (13.4 kB). View file
 
Reward_sd15_idealized/__pycache__/lr_scheduler.cpython-310.pyc ADDED
Binary file (7.19 kB). View file
 
Reward_sd15_idealized/__pycache__/lr_scheduler.cpython-311.pyc ADDED
Binary file (10.4 kB). View file
 
Reward_sd15_idealized/config_analysis_tuning.ipynb ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "a24d02a2",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import json\n",
11
+ "import pandas as pd\n",
12
+ "import numpy as np\n",
13
+ "from pathlib import Path\n",
14
+ "from datetime import datetime\n",
15
+ "import warnings\n",
16
+ "warnings.filterwarnings('ignore')\n",
17
+ "\n",
18
+ "# ============================================================================\n",
19
+ "# SECTION 1: Load and Parse Results from GPU Tuning Runs\n",
20
+ "# ==========================-==================================================\n",
21
+ "print(\"=\" * 80)\n",
22
+ "print(\"LOADING TUNING RESULTS FROM GPU RUNS\")\n",
23
+ "print(\"=\" * 80)\n",
24
+ "\n",
25
+ "results_dir = Path(\"RESULTS_TURNING/run_2\")\n",
26
+ "all_experiments = []\n",
27
+ "baseline_metrics = None\n",
28
+ "\n",
29
+ "# Collect results from all GPU runs\n",
30
+ "for gpu_id in range(8):\n",
31
+ " gpu_dir = results_dir / f\"gpu_{gpu_id}\"\n",
32
+ " results_file = gpu_dir / \"tuning_results.json\"\n",
33
+ " \n",
34
+ " if results_file.exists():\n",
35
+ " with open(results_file, 'r') as f:\n",
36
+ " data = json.load(f)\n",
37
+ " \n",
38
+ " # Extract baseline (same across all GPUs)\n",
39
+ " if baseline_metrics is None and \"baseline\" in data:\n",
40
+ " baseline_metrics = data[\"baseline\"][\"metrics\"]\n",
41
+ " print(f\"\\n📊 Baseline Metrics (cfg_scale=5.0):\")\n",
42
+ " for metric, value in baseline_metrics.items():\n",
43
+ " print(f\" {metric:15s}: {value:.6f}\")\n",
44
+ " \n",
45
+ " # Collect all experiments\n",
46
+ " if \"experiments\" in data:\n",
47
+ " all_experiments.extend(data[\"experiments\"])\n",
48
+ " print(f\"✓ GPU {gpu_id}: {len(data['experiments'])} results loaded\")\n",
49
+ "\n",
50
+ "print(f\"\\n✓ Total experiments loaded: {len(all_experiments)}\")\n",
51
+ "\n",
52
+ "# ============================================================================\n",
53
+ "# SECTION 2: Filter Top Configs with Improvements Across All Metrics\n",
54
+ "# ============================================================================\n",
55
+ "print(\"\\n\" + \"=\" * 80)\n",
56
+ "print(\"FILTERING CONFIGURATIONS WITH IMPROVEMENTS IN ALL METRICS\")\n",
57
+ "print(\"=\" * 80)\n",
58
+ "\n",
59
+ "# Define improvement metrics to track (using ImageReward instead of Reward)\n",
60
+ "improvement_metrics = [\n",
61
+ " \"aesthetic_improvement\", \n",
62
+ " \"imagereward_improvement\", \n",
63
+ " \"clip_improvement\", \n",
64
+ " \"pickscore_improvement\", \n",
65
+ " \"hpsv2_improvement\"\n",
66
+ " ]\n",
67
+ "\n",
68
+ "# Filter experiments with improvements in ALL metrics\n",
69
+ "top_configs = []\n",
70
+ "\n",
71
+ "for exp in all_experiments:\n",
72
+ " if \"improvements\" not in exp or \"config\" not in exp or \"metrics\" not in exp:\n",
73
+ " continue\n",
74
+ " \n",
75
+ " improvements = exp[\"improvements\"]\n",
76
+ " config = exp[\"config\"]\n",
77
+ " metrics = exp[\"metrics\"]\n",
78
+ " \n",
79
+ " # Check if ALL improvements are positive (>0)\n",
80
+ " all_positive = all(improvements.get(metric, -1) > 0 for metric in improvement_metrics)\n",
81
+ " \n",
82
+ " if all_positive:\n",
83
+ " # Calculate aggregate improvement score\n",
84
+ " avg_improvement = np.mean([improvements.get(metric, 0) for metric in improvement_metrics])\n",
85
+ " \n",
86
+ " top_configs.append({\n",
87
+ " \"config\": config,\n",
88
+ " \"metrics\": metrics,\n",
89
+ " \"improvements\": improvements,\n",
90
+ " \"avg_improvement\": avg_improvement\n",
91
+ " })\n",
92
+ "\n",
93
+ "print(f\"✓ Found {len(top_configs)} configurations with improvements in ALL metrics\")\n",
94
+ "\n",
95
+ "# Sort by average improvement\n",
96
+ "top_configs.sort(key=lambda x: x[\"avg_improvement\"], reverse=True)\n",
97
+ "\n",
98
+ "# Get top 10\n",
99
+ "top_10 = top_configs[:10]\n",
100
+ "print(f\"✓ Extracted top 10 best performing configurations\")\n",
101
+ "\n",
102
+ "# ============================================================================\n",
103
+ "# SECTION 3: Create Comprehensive Results Table\n",
104
+ "# ============================================================================\n",
105
+ "print(\"\\n\" + \"=\" * 80)\n",
106
+ "print(\"CREATING COMPREHENSIVE RESULTS TABLE\")\n",
107
+ "print(\"=\" * 80)\n",
108
+ "\n",
109
+ "# Build detailed table data\n",
110
+ "table_data = []\n",
111
+ "\n",
112
+ "for rank, result in enumerate(top_10, 1):\n",
113
+ " cfg = result[\"config\"]\n",
114
+ " metrics = result[\"metrics\"]\n",
115
+ " improvements = result[\"improvements\"]\n",
116
+ " \n",
117
+ " row = {\n",
118
+ " \"Rank\": rank,\n",
119
+ " \"CFG Scale\": cfg.get(\"cfg_scale\", \"N/A\"),\n",
120
+ " \"Grad Config\": cfg.get(\"grad_config\", \"N/A\"),\n",
121
+ " \"Steps\": cfg.get(\"num_grad_steps\", \"N/A\"),\n",
122
+ " \"LR\": cfg.get(\"grad_step_size\", \"N/A\"),\n",
123
+ " \"Momentum\": cfg.get(\"momentum\", \"N/A\"),\n",
124
+ " \"ImageReward\": f\"{metrics.get('imagereward', 0):.6f}\",\n",
125
+ " \"ImageReward ↑\": f\"{improvements.get('imagereward_improvement', 0):+.2f}%\",\n",
126
+ " \"CLIP\": f\"{metrics.get('clip', 0):.4f}\",\n",
127
+ " \"CLIP ↑\": f\"{improvements.get('clip_improvement', 0):+.2f}%\",\n",
128
+ " \"Aesthetic\": f\"{metrics.get('aesthetic', 0):.4f}\",\n",
129
+ " \"Aesthetic ↑\": f\"{improvements.get('aesthetic_improvement', 0):+.2f}%\",\n",
130
+ " \"PickScore\": f\"{metrics.get('pickscore', 0):.4f}\",\n",
131
+ " \"PickScore ↑\": f\"{improvements.get('pickscore_improvement', 0):+.2f}%\",\n",
132
+ " \"HPSv2\": f\"{metrics.get('hpsv2', 0):.4f}\",\n",
133
+ " \"HPSv2 ↑\": f\"{improvements.get('hpsv2_improvement', 0):+.2f}%\",\n",
134
+ " \"Avg Improvement\": f\"{result['avg_improvement']:+.2f}%\",\n",
135
+ " }\n",
136
+ " \n",
137
+ " table_data.append(row)\n",
138
+ "\n",
139
+ "df_top_10 = pd.DataFrame(table_data)\n",
140
+ "\n",
141
+ "print(\"\\n📋 TOP 10 CONFIGURATIONS WITH IMPROVEMENTS IN ALL METRICS:\")\n",
142
+ "print(\"=\" * 180)\n",
143
+ "print(df_top_10.to_string(index=False))\n",
144
+ "print(\"=\" * 180)\n",
145
+ "\n",
146
+ "# ============================================================================\n",
147
+ "# SECTION 4: Visualize and Summary Statistics\n",
148
+ "# ============================================================================\n",
149
+ "print(\"\\n\" + \"=\" * 80)\n",
150
+ "print(\"SUMMARY STATISTICS\")\n",
151
+ "print(\"=\" * 80)\n",
152
+ "\n",
153
+ "# Extract numeric improvement values for analysis\n",
154
+ "improvement_summary = []\n",
155
+ "for result in top_10:\n",
156
+ " improvements = result[\"improvements\"]\n",
157
+ " for metric in [\"imagereward_improvement\", \"clip_improvement\", \"aesthetic_improvement\", \n",
158
+ " \"pickscore_improvement\", \"hpsv2_improvement\"]:\n",
159
+ " metric_name = metric.replace(\"_improvement\", \"\").upper()\n",
160
+ " improvement_summary.append({\n",
161
+ " \"Metric\": metric_name,\n",
162
+ " \"Improvement %\": improvements.get(metric, 0)\n",
163
+ " })\n",
164
+ "\n",
165
+ "df_summary = pd.DataFrame(improvement_summary)\n",
166
+ "\n",
167
+ "print(\"\\n📊 Average Improvements by Metric (Top 10):\")\n",
168
+ "metric_stats = df_summary.groupby(\"Metric\")[\"Improvement %\"].agg([\"mean\", \"std\", \"min\", \"max\"])\n",
169
+ "print(metric_stats.round(2))\n",
170
+ "\n",
171
+ "print(\"\\n📈 Best Configuration Details:\")\n",
172
+ "best = top_10[0]\n",
173
+ "best_cfg = best[\"config\"]\n",
174
+ "best_metrics = best[\"metrics\"]\n",
175
+ "best_improvements = best[\"improvements\"]\n",
176
+ "\n",
177
+ "print(f\"\\n✓ RANK #1 - Best Performing Configuration:\")\n",
178
+ "print(f\" Configuration:\")\n",
179
+ "print(f\" • CFG Scale: {best_cfg.get('cfg_scale')}\")\n",
180
+ "print(f\" • Gradient Config: {best_cfg.get('grad_config')}\")\n",
181
+ "print(f\" • Gradient Steps: {best_cfg.get('num_grad_steps')}\")\n",
182
+ "print(f\" • Step Size: {best_cfg.get('grad_step_size')}\")\n",
183
+ "print(f\" • Momentum: {best_cfg.get('momentum')}\")\n",
184
+ "print(f\"\\n Metrics:\")\n",
185
+ "for metric in [\"imagereward\", \"clip\", \"aesthetic\", \"pickscore\", \"hpsv2\"]:\n",
186
+ " baseline_val = baseline_metrics.get(metric, 0)\n",
187
+ " current_val = best_metrics.get(metric, 0)\n",
188
+ " improvement = best_improvements.get(f\"{metric}_improvement\", 0)\n",
189
+ " print(f\" • {metric:12s}: {current_val:8.6f} (baseline: {baseline_val:8.6f}) ↑ {improvement:+6.2f}%\")\n",
190
+ "\n",
191
+ "print(\"\\n\" + \"=\" * 80)\n",
192
+ "print(\"✓ ANALYSIS COMPLETE - TOP 10 CONFIGURATIONS IDENTIFIED\")\n",
193
+ "print(\"=\" * 80)"
194
+ ]
195
+ }
196
+ ],
197
+ "metadata": {
198
+ "kernelspec": {
199
+ "display_name": "Python 3",
200
+ "language": "python",
201
+ "name": "python3"
202
+ },
203
+ "language_info": {
204
+ "codemirror_mode": {
205
+ "name": "ipython",
206
+ "version": 3
207
+ },
208
+ "file_extension": ".py",
209
+ "mimetype": "text/x-python",
210
+ "name": "python",
211
+ "nbconvert_exporter": "python",
212
+ "pygments_lexer": "ipython3",
213
+ "version": "3.10.18"
214
+ }
215
+ },
216
+ "nbformat": 4,
217
+ "nbformat_minor": 5
218
+ }
Reward_sd15_idealized/eval.py ADDED
@@ -0,0 +1,1422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Evaluation script for comparing baseline and gradient ascent pipelines using multiple metrics.
3
+
4
+ This script evaluates both pipelines on COCO or Pick-a-Pic validation sets and computes
5
+ various preference and quality metrics.
6
+ """
7
+ import warnings
8
+ warnings.filterwarnings("ignore")
9
+ import torch
10
+ import torch.nn as nn
11
+ import json
12
+ import os
13
+ import sys
14
+ import logging
15
+ from glob import glob
16
+ from pathlib import Path
17
+ from PIL import Image
18
+ from diffusers import StableDiffusionPipeline, DDIMScheduler, UNet2DConditionModel
19
+ from models import LRMRewardModel
20
+ from pipelines.sd15_gradient_ascent_pipeline import StableDiffusionGradientAscentPipeline
21
+ from torchmetrics.image.fid import FrechetInceptionDistance
22
+ from torchmetrics.multimodal import CLIPScore
23
+ from transformers import CLIPModel, CLIPProcessor
24
+ from tqdm import tqdm
25
+ import numpy as np
26
+ import argparse
27
+ from datasets import load_dataset
28
+ from grad_ascent_configs import get_config, list_configs
29
+ import matplotlib.pyplot as plt
30
+ import matplotlib
31
+ matplotlib.use('Agg') # Use non-interactive backend
32
+
33
+ # Import evaluation metrics
34
+ sys.path.append('../evaluation')
35
+ from huggingface_hub import hf_hub_download
36
+
37
+ import random
38
+
39
+
40
+ def configure_hf_runtime(hf_cache_dir=None, force_offline=False):
41
+ """Set Hugging Face cache/offline environment for cluster-safe execution."""
42
+ cache_dir = hf_cache_dir or os.getenv("HF_HUB_CACHE") or os.getenv("HUGGINGFACE_HUB_CACHE")
43
+ if cache_dir:
44
+ os.environ["HF_HUB_CACHE"] = cache_dir
45
+ os.environ["HUGGINGFACE_HUB_CACHE"] = cache_dir
46
+ os.environ["HF_HOME"] = os.path.dirname(cache_dir)
47
+
48
+ env_offline = os.getenv("HF_HUB_OFFLINE", "0").strip().lower() in {"1", "true", "yes", "on"}
49
+ offline_enabled = bool(force_offline or env_offline)
50
+ if offline_enabled:
51
+ os.environ["HF_DATASETS_OFFLINE"] = "1"
52
+ os.environ["HF_METRICS_OFFLINE"] = "1"
53
+ os.environ["HF_MODULES_OFFLINE"] = "1"
54
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
55
+ os.environ["DIFFUSERS_OFFLINE"] = "1"
56
+ os.environ["HF_HUB_OFFLINE"] = "1"
57
+
58
+ return cache_dir, offline_enabled
59
+
60
+
61
+ def resolve_default_lrm_model():
62
+ """Prefer local LRM checkout when available; otherwise fall back to HF repo id."""
63
+ project_root = Path(__file__).resolve().parents[1]
64
+ local_lrm = project_root / "lrm" / "lrm_15" / "LRM"
65
+ if local_lrm.exists():
66
+ return str(local_lrm)
67
+ return "casiatao/LRM"
68
+
69
+
70
+ def load_pickapic_prompts(max_samples=None, cache_dir=None, offline=False):
71
+ """Load Pick-a-Pic prompts with robust offline fallback to cached parquet shards."""
72
+ split = "validation_unique"
73
+
74
+ if not offline:
75
+ try:
76
+ ds = load_dataset("pickapic-anonymous/pickapic_v1", split=split, streaming=True)
77
+ prompts = []
78
+ for i, sample in enumerate(ds):
79
+ prompts.append(sample["caption"])
80
+ if max_samples and i + 1 >= max_samples:
81
+ break
82
+ return prompts
83
+ except Exception as e:
84
+ print(f"Warning: online streaming load failed ({e}). Trying cached offline parquet shards.")
85
+
86
+ cache_candidates = []
87
+ for p in [
88
+ cache_dir,
89
+ os.getenv("HF_HUB_CACHE"),
90
+ os.getenv("HUGGINGFACE_HUB_CACHE"),
91
+ (os.path.join(os.getenv("HF_HOME"), "hub") if os.getenv("HF_HOME") else None),
92
+ os.path.expanduser("~/.cache/huggingface/hub"),
93
+ "/scratch/rr81/ma5430/.cache/huggingface/hub",
94
+ ]:
95
+ if p and p not in cache_candidates:
96
+ cache_candidates.append(p)
97
+
98
+ for cache_root in cache_candidates:
99
+ repo_cache = os.path.join(cache_root, "datasets--pickapic-anonymous--pickapic_v1")
100
+ if not os.path.isdir(repo_cache):
101
+ continue
102
+
103
+ snapshot_dir = None
104
+ ref_main = os.path.join(repo_cache, "refs", "main")
105
+ if os.path.isfile(ref_main):
106
+ revision = open(ref_main, "r", encoding="utf-8").read().strip()
107
+ candidate = os.path.join(repo_cache, "snapshots", revision)
108
+ if os.path.isdir(candidate):
109
+ snapshot_dir = candidate
110
+
111
+ if snapshot_dir is None:
112
+ snapshots = sorted(glob(os.path.join(repo_cache, "snapshots", "*")))
113
+ if snapshots:
114
+ snapshot_dir = snapshots[-1]
115
+
116
+ if snapshot_dir is None:
117
+ continue
118
+
119
+ data_dir = os.path.join(snapshot_dir, "data")
120
+ if not os.path.isdir(data_dir):
121
+ continue
122
+
123
+ selected_split = split
124
+ parquet_files = sorted(glob(os.path.join(data_dir, f"{selected_split}-*.parquet")))
125
+ if not parquet_files:
126
+ for alt_split in ("test_unique", "test"):
127
+ alt_files = sorted(glob(os.path.join(data_dir, f"{alt_split}-*.parquet")))
128
+ if alt_files:
129
+ selected_split = alt_split
130
+ parquet_files = alt_files
131
+ print(f"Offline cache missing split '{split}', falling back to '{selected_split}'.")
132
+ break
133
+
134
+ if not parquet_files:
135
+ continue
136
+
137
+ print(
138
+ f"Loading cached Pick-a-Pic split '{selected_split}' from {len(parquet_files)} parquet shards\n"
139
+ f"cache={repo_cache}"
140
+ )
141
+ ds = load_dataset("parquet", data_files=parquet_files, split="train")
142
+ prompts = ds["caption"]
143
+ if max_samples:
144
+ prompts = prompts[:max_samples]
145
+ return list(prompts)
146
+
147
+ raise RuntimeError(
148
+ "Could not load pickapic prompts in offline mode. "
149
+ "Set --hf_cache_dir to a cache that contains datasets--pickapic-anonymous--pickapic_v1."
150
+ )
151
+
152
+
153
+ def resolve_scorer_device(requested_device, generation_device, min_free_gb_for_gpu=14.0):
154
+ """Choose where metric scorers should run to avoid GPU OOM/cudnn init failures."""
155
+ if requested_device == "cpu":
156
+ return "cpu"
157
+
158
+ if not torch.cuda.is_available() or not str(generation_device).startswith("cuda"):
159
+ return "cpu"
160
+
161
+ if requested_device == "cuda":
162
+ return generation_device
163
+
164
+ # Auto mode: only keep scorers on GPU if enough headroom remains after loading generation models.
165
+ try:
166
+ free_bytes, total_bytes = torch.cuda.mem_get_info(torch.device(generation_device))
167
+ free_gb = free_bytes / (1024 ** 3)
168
+ total_gb = total_bytes / (1024 ** 3)
169
+ print(f"GPU memory before scorer load: {free_gb:.2f} GB free / {total_gb:.2f} GB total")
170
+ if free_gb >= min_free_gb_for_gpu:
171
+ return generation_device
172
+ print(
173
+ f"⚠ Low free VRAM ({free_gb:.2f} GB). Running scorers on CPU to keep diffusion stable. "
174
+ f"Use --scorer_device cuda to force GPU scorers."
175
+ )
176
+ return "cpu"
177
+ except Exception as e:
178
+ print(f"Warning: could not inspect CUDA free memory ({e}). Falling back to CPU scorers.")
179
+ return "cpu"
180
+
181
+
182
+ def configure_cudnn_safely(device):
183
+ """Disable cuDNN when the current GPU or runtime cannot initialize it safely."""
184
+ if not torch.cuda.is_available() or not str(device).startswith("cuda"):
185
+ return
186
+
187
+ try:
188
+ major, minor = torch.cuda.get_device_capability(torch.device(device))
189
+ if (major, minor) < (7, 5):
190
+ print(
191
+ f"⚠ Detected compute capability sm_{major}{minor} (< 75). "
192
+ "Disabling cuDNN to prevent runtime initialization failures."
193
+ )
194
+ torch.backends.cudnn.enabled = False
195
+ return
196
+
197
+ # Force a cuDNN init probe early so failures are handled once at startup.
198
+ _ = torch.backends.cudnn.version()
199
+ except Exception as e:
200
+ print(f"⚠ cuDNN init probe failed ({e}). Disabling cuDNN for this run.")
201
+ torch.backends.cudnn.enabled = False
202
+
203
+
204
+ def seed_everything(seed: int):
205
+ """Locks down all random number generators for absolute reproducibility."""
206
+ # 1. Python & Numpy
207
+ random.seed(seed)
208
+ np.random.seed(seed)
209
+
210
+ # 2. PyTorch Base
211
+ torch.manual_seed(seed)
212
+ if torch.cuda.is_available():
213
+ torch.cuda.manual_seed(seed)
214
+ torch.cuda.manual_seed_all(seed) # For multi-GPU
215
+
216
+ # 3. cuDNN Determinism (Crucial for consistent gradients)
217
+ torch.backends.cudnn.deterministic = True
218
+ torch.backends.cudnn.benchmark = False
219
+
220
+ # 4. Optional: Force deterministic algorithms for PyTorch 2.0+
221
+ # Uncomment if variance persists, but it may slow down generation slightly
222
+ # torch.use_deterministic_algorithms(True)
223
+
224
+
225
+ class MLP(nn.Module):
226
+ """MLP for aesthetic scoring."""
227
+ def __init__(self):
228
+ super().__init__()
229
+ self.layers = nn.Sequential(
230
+ nn.Linear(768, 1024),
231
+ nn.Dropout(0.2),
232
+ nn.Linear(1024, 128),
233
+ nn.Dropout(0.2),
234
+ nn.Linear(128, 64),
235
+ nn.Dropout(0.1),
236
+ nn.Linear(64, 16),
237
+ nn.Linear(16, 1),
238
+ )
239
+
240
+ @torch.no_grad()
241
+ def forward(self, embed):
242
+ return self.layers(embed)
243
+
244
+
245
+ class AestheticScorer(torch.nn.Module):
246
+ """Aesthetic scorer using CLIP and MLP."""
247
+ def __init__(self, dtype, device, clip_name_or_path="openai/clip-vit-large-patch14",
248
+ aesthetic_path="./sac+logos+ava1-l14-linearMSE.pth"):
249
+ super().__init__()
250
+ self.clip = CLIPModel.from_pretrained(clip_name_or_path)
251
+ self.processor = CLIPProcessor.from_pretrained(clip_name_or_path)
252
+ self.mlp = MLP()
253
+
254
+ # Load aesthetic weights
255
+ if os.path.exists(aesthetic_path):
256
+ state_dict = torch.load(aesthetic_path, map_location='cpu')
257
+ self.mlp.load_state_dict(state_dict)
258
+ else:
259
+ print(f"Warning: Aesthetic weights not found at {aesthetic_path}")
260
+
261
+ self.dtype = dtype
262
+ self.to(device)
263
+ self.eval()
264
+
265
+ @torch.no_grad()
266
+ def __call__(self, images):
267
+ device = next(self.parameters()).device
268
+ inputs = self.processor(images=images, return_tensors="pt")
269
+ inputs = {k: v.to(self.dtype).to(device) for k, v in inputs.items()}
270
+ embed = self.clip.get_image_features(**inputs)
271
+ # normalize embedding
272
+ embed = embed / torch.linalg.vector_norm(embed, dim=-1, keepdim=True)
273
+ return self.mlp(embed).squeeze(1)
274
+
275
+
276
+ class TeeLogger:
277
+ """Logger that writes to both console and file."""
278
+ def __init__(self, log_file):
279
+ self.terminal = sys.stdout
280
+ self.log = open(log_file, 'w')
281
+
282
+ def write(self, message):
283
+ self.terminal.write(message)
284
+ self.log.write(message)
285
+ self.log.flush()
286
+
287
+ def flush(self):
288
+ self.terminal.flush()
289
+ self.log.flush()
290
+
291
+ def close(self):
292
+ self.log.close()
293
+
294
+
295
+ def setup_logging(output_dir):
296
+ """Setup logging to both console and file."""
297
+ output_path = Path(output_dir)
298
+ output_path.mkdir(parents=True, exist_ok=True)
299
+ log_file = output_path / "log.log"
300
+
301
+ # Redirect stdout to both console and file
302
+ tee = TeeLogger(log_file)
303
+ sys.stdout = tee
304
+
305
+ return tee, log_file
306
+
307
+
308
+ def load_validation_data(data_dir, max_samples=None, dataset_type="coco", hf_cache_dir=None, offline=False):
309
+ """Load validation prompts and image paths.
310
+
311
+ Args:
312
+ data_dir: Path to data directory
313
+ max_samples: Maximum number of samples to load
314
+ dataset_type: Type of dataset ("coco" or "pickapic")
315
+
316
+ Returns:
317
+ prompts: List of text prompts
318
+ image_paths: List of image paths (None for pickapic streaming dataset)
319
+ """
320
+ if dataset_type == "coco":
321
+ data_dir = Path(data_dir)
322
+ val_json = data_dir / "coco" / "caption_val.json"
323
+
324
+ if not val_json.exists():
325
+ raise FileNotFoundError(f"Validation JSON not found: {val_json}")
326
+
327
+ with open(val_json, 'r') as f:
328
+ data = json.load(f)
329
+
330
+ # Validate that image folder exists
331
+ val_img_dir = data_dir / "coco" / "images" / "val"
332
+ if not val_img_dir.exists():
333
+ raise FileNotFoundError(f"Validation image directory not found: {val_img_dir}")
334
+
335
+ # Parse data
336
+ prompts = []
337
+ image_paths = []
338
+ for img_path, caption in data.items():
339
+ full_path = data_dir / "coco" / img_path
340
+ if full_path.exists():
341
+ prompts.append(caption)
342
+ image_paths.append(str(full_path))
343
+ else:
344
+ print(f"Warning: Image not found: {full_path}")
345
+
346
+ if max_samples:
347
+ prompts = prompts[:max_samples]
348
+ image_paths = image_paths[:max_samples]
349
+
350
+ print(f"Loaded {len(prompts)} COCO validation samples")
351
+ return prompts, image_paths
352
+
353
+ elif dataset_type == "pickapic":
354
+ print("Loading Pick-a-Pic validation prompts...")
355
+ prompts = load_pickapic_prompts(max_samples=max_samples, cache_dir=hf_cache_dir, offline=offline)
356
+
357
+ print(f"Loaded {len(prompts)} Pick-a-Pic validation samples")
358
+ return prompts, None # No reference images for Pick-a-Pic
359
+
360
+ else:
361
+ raise ValueError(f"Unknown dataset type: {dataset_type}. Choose 'coco' or 'pickapic'.")
362
+
363
+
364
+ def generate_and_evaluate(
365
+ pipeline,
366
+ prompts,
367
+ image_paths,
368
+ device,
369
+ dtype,
370
+ num_inference_steps=20,
371
+ guidance_scale=7.5,
372
+ seed=42,
373
+ batch_size=1,
374
+ apply_gradient_ascent=False,
375
+ mode_name="baseline",
376
+ log_interval=10,
377
+ output_dir=None,
378
+ save_images=False,
379
+ clip_scorer=None,
380
+ aesthetic_scorer=None,
381
+ pick_scorer=None,
382
+ hpsv2_scorer=None,
383
+ hpsv21_scorer=None,
384
+ imagereward_scorer=None,
385
+ compute_fid=True,
386
+ capture_trajectory=False
387
+ ):
388
+ """Generate images and update FID metric."""
389
+ pipeline.to(device)
390
+
391
+ print(f"\nGenerating images with {mode_name} mode...")
392
+
393
+
394
+ all_rewards = []
395
+ all_clip_scores = []
396
+ all_aesthetic_scores = []
397
+ all_pick_scores = []
398
+ all_hpsv2_scores = []
399
+ all_hpsv21_scores = []
400
+ all_imagereward_scores = []
401
+ lr_history_first_image = None # Store LR history for first image
402
+ trajectory_first_image = []
403
+ num_batches = (len(prompts) + batch_size - 1) // batch_size
404
+
405
+ # Create output directory if saving images
406
+ if save_images and output_dir:
407
+ mode_output_dir = Path(output_dir) / mode_name
408
+ mode_output_dir.mkdir(parents=True, exist_ok=True)
409
+
410
+ # Disable internal progress bars
411
+ pipeline.set_progress_bar_config(disable=True)
412
+
413
+ for idx, i in enumerate(tqdm(range(0, len(prompts), batch_size), desc=f"Generating {mode_name}")):
414
+ batch_prompts = prompts[i:i+batch_size]
415
+ batch_real_paths = image_paths[i:i+batch_size] if image_paths is not None else None
416
+ batch_num = idx + 1
417
+
418
+ # Initialize FID metric if needed
419
+ fid_metric = None
420
+ real_images_tensor = None
421
+
422
+ if compute_fid and batch_real_paths is not None:
423
+ fid_metric = FrechetInceptionDistance().to(device)
424
+
425
+ # Load and update FID with real images for this batch
426
+ real_images = []
427
+ for path in batch_real_paths:
428
+ img = Image.open(path).convert("RGB")
429
+ img = img.resize((512, 512)) # Inception v3 input size
430
+ img_array = np.array(img)
431
+ real_images.append(img_array)
432
+
433
+ # Convert to tensor [B, H, W, C] -> [B, C, H, W]
434
+ real_images_tensor = torch.from_numpy(np.stack(real_images)).permute(0, 3, 1, 2).float()
435
+ real_images_tensor = real_images_tensor.to(device)
436
+
437
+ # Generate images
438
+ generator = torch.Generator(device=device).manual_seed(seed + i)
439
+
440
+ # Only capture trajectory for the very first batch to save RAM
441
+ def trajectory_callback(step, timestep, latents):
442
+ if idx == 0 and capture_trajectory:
443
+ # Detach and move to CPU immediately to prevent VRAM OOM
444
+ trajectory_first_image.append(latents.detach().cpu().clone())
445
+
446
+ with torch.no_grad():
447
+ result = pipeline(
448
+ prompt=batch_prompts,
449
+ num_inference_steps=num_inference_steps,
450
+ guidance_scale=guidance_scale,
451
+ generator=generator,
452
+ track_rewards=True,
453
+ print_rewards=False,
454
+ apply_gradient_ascent=apply_gradient_ascent,
455
+ verbose_grad=False,
456
+ callback=trajectory_callback if capture_trajectory else None,
457
+ callback_steps=1
458
+ )
459
+
460
+ # Process generated images
461
+ images = result.images
462
+
463
+ # Update FID metric if computing it
464
+ if compute_fid and fid_metric is not None:
465
+ image_tensors = []
466
+
467
+ for img in images:
468
+ img_resized = img.resize((512, 512)) # Inception v3 input size
469
+ img_array = np.array(img_resized)
470
+ image_tensors.append(img_array)
471
+
472
+ # Convert to tensor and update FID
473
+ images_tensor = torch.from_numpy(np.stack(image_tensors)).permute(0, 3, 1, 2).float()
474
+ images_tensor = images_tensor.to(device)
475
+
476
+ if batch_size == 1:
477
+ real_images_tensor = torch.cat([real_images_tensor, real_images_tensor], dim=0).to(dtype=torch.uint8)
478
+ images_tensor = torch.cat([images_tensor, images_tensor], dim=0).to(dtype=torch.uint8)
479
+ fid_metric.update(real_images_tensor, real=True)
480
+ fid_metric.update(images_tensor, real=False)
481
+
482
+ # Track rewards - get the final timestep reward (t=0)
483
+ current_batch_final_reward = None
484
+ current_batch_final_timestep = None
485
+ if hasattr(pipeline, 'reward_history') and pipeline.reward_history:
486
+ # For each image, get the reward from the last denoising step (t=0 or closest to 0)
487
+ num_steps_per_image = num_inference_steps
488
+
489
+ # Get the last entry which corresponds to the final timestep of the last image in batch
490
+ final_entry = pipeline.reward_history[-1]
491
+ current_batch_final_reward = final_entry['reward_score']
492
+ current_batch_final_timestep = final_entry['timestep']
493
+ all_rewards.append(current_batch_final_reward)
494
+
495
+ # Capture LR history from first image if gradient ascent is enabled
496
+ if apply_gradient_ascent and idx == 0 and lr_history_first_image is None:
497
+ if hasattr(pipeline, 'grad_guidance') and pipeline.grad_guidance:
498
+ grad_stats = pipeline.grad_guidance.get_statistics()
499
+ if grad_stats and 'detailed_stats' in grad_stats:
500
+ # Extract LR history from the gradient ascent statistics
501
+ lr_history_first_image = {
502
+ 'prompt': batch_prompts[0],
503
+ 'timesteps': [],
504
+ 'learning_rates': [], # All LR values from all gradient steps
505
+ 'rewards': []
506
+ }
507
+ for stat in grad_stats['detailed_stats']:
508
+ lr_history_first_image['timesteps'].append(stat['timestep'])
509
+ if 'lr_history' in stat:
510
+ # Extend with all LR values from this timestep's gradient steps
511
+ lr_history_first_image['learning_rates'].extend(stat['lr_history'])
512
+ # Collect all rewards from reward_history for each gradient step
513
+ if 'reward_history' in stat:
514
+ lr_history_first_image['rewards'].extend(stat['reward_history'])
515
+
516
+ # Compute CLIP score
517
+ if clip_scorer is not None:
518
+ clip_device = next(clip_scorer.parameters()).device
519
+ # Convert PIL images to tensor format for CLIP score [C, H, W] in range [0, 1]
520
+ for img, prompt in zip(images, batch_prompts):
521
+ img_array = np.array(img).astype(np.float32)
522
+ img_tensor = torch.from_numpy(img_array).permute(2, 0, 1).unsqueeze(0).to(clip_device)
523
+ clip_score = clip_scorer(img_tensor, [prompt]).item()
524
+ all_clip_scores.append(clip_score)
525
+
526
+ # Compute aesthetic score
527
+ if aesthetic_scorer is not None:
528
+ aesthetic_scores = aesthetic_scorer(images)
529
+ if isinstance(aesthetic_scores, torch.Tensor):
530
+ aesthetic_scores = aesthetic_scores.cpu().numpy()
531
+ if aesthetic_scores.ndim == 0:
532
+ aesthetic_scores = [aesthetic_scores.item()]
533
+ all_aesthetic_scores.extend(aesthetic_scores.tolist() if hasattr(aesthetic_scores, 'tolist') else [aesthetic_scores])
534
+
535
+ # Compute PickScore
536
+ if pick_scorer is not None:
537
+ for img, prompt in zip(images, batch_prompts):
538
+ pick_score = pick_scorer(prompt, [img])[0]
539
+ all_pick_scores.append(pick_score)
540
+
541
+ # Compute HPSv2 score
542
+ if hpsv2_scorer is not None:
543
+ for img, prompt in zip(images, batch_prompts):
544
+ hpsv2_score = hpsv2_scorer.score(img, prompt)[0]
545
+ all_hpsv2_scores.append(hpsv2_score)
546
+
547
+ # Compute HPSv2.1 score
548
+ if hpsv21_scorer is not None:
549
+ for img, prompt in zip(images, batch_prompts):
550
+ hpsv21_score = hpsv21_scorer.score(img, prompt)[0]
551
+ all_hpsv21_scores.append(hpsv21_score)
552
+
553
+ # Compute ImageReward score
554
+ if imagereward_scorer is not None:
555
+ for img, prompt in zip(images, batch_prompts):
556
+ imagereward_score = imagereward_scorer.score(prompt, img)
557
+ all_imagereward_scores.append(imagereward_score)
558
+
559
+ # Save generated images if requested
560
+ if save_images and output_dir:
561
+ for img_idx, img in enumerate(images):
562
+ global_idx = i + img_idx
563
+ img_path = mode_output_dir / f"sample_{global_idx:05d}.png"
564
+ img.save(img_path)
565
+
566
+ # Log intermediate FID and metrics every log_interval batches
567
+ if batch_num % log_interval == 0 or batch_num == num_batches:
568
+ num_samples_processed = min(i + batch_size, len(prompts))
569
+ log_msg = f"\n[{mode_name}] Batch {batch_num}/{num_batches} | Samples: {num_samples_processed}/{len(prompts)}"
570
+
571
+ # Add FID if computing
572
+ if compute_fid and fid_metric is not None:
573
+ try:
574
+ current_fid = fid_metric.compute().item()
575
+ log_msg += f" | FID: {current_fid:.4f}"
576
+ except Exception as e:
577
+ log_msg += f" | FID: Computing..."
578
+
579
+ # Add reward - show both final timestep reward and average
580
+ if all_rewards:
581
+ avg_reward = np.mean(all_rewards)
582
+ if current_batch_final_reward is not None:
583
+ log_msg += f" | Reward (t={current_batch_final_timestep}): {current_batch_final_reward:.4f}"
584
+ log_msg += f" | Reward (Avg): {avg_reward:.4f}"
585
+ else:
586
+ log_msg += f" | Reward (Avg): {avg_reward:.4f}"
587
+
588
+ # Add CLIP if computing
589
+ if clip_scorer is not None and all_clip_scores:
590
+ log_msg += f" | CLIP: {np.mean(all_clip_scores):.4f}"
591
+
592
+ # Add aesthetic if computing
593
+ if aesthetic_scorer is not None and all_aesthetic_scores:
594
+ log_msg += f" | Aesthetic: {np.mean(all_aesthetic_scores):.4f}"
595
+
596
+ # Add PickScore
597
+ if pick_scorer is not None and all_pick_scores:
598
+ log_msg += f" | PickScore: {np.mean(all_pick_scores):.4f}"
599
+
600
+ # Add HPSv2
601
+ if hpsv2_scorer is not None and all_hpsv2_scores:
602
+ log_msg += f" | HPSv2: {np.mean(all_hpsv2_scores):.4f}"
603
+
604
+ # Add HPSv2.1
605
+ if hpsv21_scorer is not None and all_hpsv21_scores:
606
+ log_msg += f" | HPSv2.1: {np.mean(all_hpsv21_scores):.4f}"
607
+
608
+ # Add ImageReward
609
+ if imagereward_scorer is not None and all_imagereward_scores:
610
+ log_msg += f" | ImageReward: {np.mean(all_imagereward_scores):.4f}"
611
+
612
+ print(log_msg)
613
+
614
+ # Re-enable progress bars
615
+ pipeline.set_progress_bar_config(disable=False)
616
+
617
+ avg_reward = np.mean(all_rewards) if all_rewards else 0.0
618
+ avg_clip_score = np.mean(all_clip_scores) if all_clip_scores else 0.0
619
+ avg_aesthetic_score = np.mean(all_aesthetic_scores) if all_aesthetic_scores else 0.0
620
+ avg_pick_score = np.mean(all_pick_scores) if all_pick_scores else 0.0
621
+ avg_hpsv2_score = np.mean(all_hpsv2_scores) if all_hpsv2_scores else 0.0
622
+ avg_hpsv21_score = np.mean(all_hpsv21_scores) if all_hpsv21_scores else 0.0
623
+ avg_imagereward_score = np.mean(all_imagereward_scores) if all_imagereward_scores else 0.0
624
+
625
+ return avg_reward, fid_metric, avg_clip_score, avg_aesthetic_score, avg_pick_score, avg_hpsv2_score, avg_hpsv21_score, avg_imagereward_score, lr_history_first_image, trajectory_first_image
626
+
627
+
628
+ def auto_increment_path(base_path):
629
+ """
630
+ Create an auto-incrementing run folder inside base_path.
631
+ Returns: base_path/run_1, base_path/run_2, etc.
632
+ """
633
+ base_path = Path(base_path)
634
+ base_path.mkdir(parents=True, exist_ok=True) # Ensure base directory exists
635
+
636
+ i = 1
637
+ while True:
638
+ new_path = base_path / f"run_{i}"
639
+ if not new_path.exists():
640
+ return new_path
641
+ i += 1
642
+
643
+
644
+ def main():
645
+ parser = argparse.ArgumentParser(description="Evaluate baseline and gradient ascent pipelines")
646
+ parser.add_argument("--data_dir", type=str, default="./data", help="Path to data directory")
647
+ parser.add_argument("--dataset_type", type=str, default="coco", choices=["coco", "pickapic"],
648
+ help="Dataset to use for evaluation: coco or pickapic (default: coco)")
649
+ parser.add_argument("--base_model", type=str, default="stable-diffusion-v1-5/stable-diffusion-v1-5", help="Base model path")
650
+ parser.add_argument("--model_variant", type=str, default="origin",
651
+ choices=["origin", "spo", "diffusion_dpo", "lpo"],
652
+ help="SD1.5 model variant to use (default: origin)")
653
+ parser.add_argument("--lrm_model", type=str, default=None, help="LRM model path. Defaults to local lrm/lrm_15/LRM when present.")
654
+ parser.add_argument("--hf_cache_dir", type=str, default="/scratch/rr81/ma5430/.cache/huggingface/hub", help="Shared HF cache directory")
655
+ parser.add_argument("--offline", action="store_true", help="Force fully offline mode (recommended on GPU nodes)")
656
+ parser.add_argument("--num_steps", type=int, default=50, help="Number of inference steps")
657
+ parser.add_argument("--cfg_scale", type=float, default=7.5, help="Classifier-free guidance scale")
658
+ parser.add_argument("--seed", type=int, default=42, help="Random seed")
659
+ parser.add_argument("--max_samples", type=int, default=None, help="Max samples to evaluate (None for all)")
660
+ parser.add_argument("--batch_size", type=int, default=1, help="Batch size for generation (use 1 for reward model compatibility)")
661
+ parser.add_argument("--fid_batch_size", type=int, default=32, help="Batch size for FID computation")
662
+ parser.add_argument("--log_interval", type=int, default=10, help="Log FID and metrics every N batches")
663
+ parser.add_argument("--output_dir", type=str, default="eval_outputs", help="Directory to save generated images and results")
664
+ parser.add_argument("--save_images", action="store_true", help="Save all generated images to output directory")
665
+ parser.add_argument("--mode", type=str, default="both", choices=["baseline", "gradient_ascent", "both"],
666
+ help="Which evaluation to run: baseline, gradient_ascent, or both (default: both)")
667
+
668
+ # Metrics selection
669
+ parser.add_argument("--metrics", type=str, nargs="+", default=["clip", "aesthetic"],
670
+ choices=["fid", "clip", "aesthetic", "pickscore", "hpsv2", "hpsv21", "imagereward"],
671
+ help="Which metrics to evaluate (default: clip aesthetic)")
672
+ parser.add_argument("--scorer_device", type=str, default="auto", choices=["auto", "cpu", "cuda"],
673
+ help="Device for metric scorers. auto keeps scorers on GPU only when enough VRAM is free.")
674
+
675
+ # Gradient ascent config
676
+ parser.add_argument("--grad_config", type=str, default=None,
677
+ help=f"Gradient ascent config preset (available: {', '.join(list_configs())}). "
678
+ "If provided, overrides individual grad_* arguments.")
679
+ parser.add_argument("--grad_range_start", type=int, default=0, help="Gradient timestep range start")
680
+ parser.add_argument("--grad_range_end", type=int, default=700, help="Gradient timestep range end")
681
+ parser.add_argument("--grad_steps", type=int, default=5, help="Number of gradient steps per timestep (use 5 for better reward improvement)")
682
+ parser.add_argument("--grad_step_size", type=float, default=0.1, help="Gradient step size (initial LR)")
683
+
684
+ # Config overrides (these override values from grad_config if specified)
685
+ parser.add_argument("--override_momentum", type=float, default=None, help="Override momentum value from grad_config")
686
+ parser.add_argument("--override_num_grad_steps", type=int, default=None, help="Override num_grad_steps from grad_config")
687
+ parser.add_argument("--override_grad_step_size", type=float, default=None, help="Override grad_step_size from grad_config")
688
+
689
+ # Cuda
690
+ parser.add_argument("--cuda", type=int, default=0, help="Use CUDA device id")
691
+
692
+ args = parser.parse_args()
693
+
694
+ hf_cache_dir, offline_enabled = configure_hf_runtime(args.hf_cache_dir, force_offline=args.offline)
695
+ if args.lrm_model is None:
696
+ args.lrm_model = resolve_default_lrm_model()
697
+
698
+ seed_everything(args.seed)
699
+
700
+ # Configuration
701
+ device = f"cuda:{args.cuda}" if torch.cuda.is_available() else "cpu"
702
+ dtype = torch.float16 #if torch.cuda.is_available() else torch.float32
703
+ configure_cudnn_safely(device)
704
+
705
+ # Create auto-incremented output directory
706
+ args.output_dir = auto_increment_path(args.output_dir)
707
+
708
+ # Setup logging to file
709
+ tee_logger, log_file = setup_logging(args.output_dir)
710
+
711
+ print("="*70)
712
+ print("FID EVALUATION: BASELINE vs GRADIENT ASCENT")
713
+ print("="*70)
714
+ print(f"\nLogging to: {log_file}")
715
+ print(f"\nDevice: {device}")
716
+ print(f"Dataset: {args.dataset_type.upper()}")
717
+ print(f"Data directory: {args.data_dir}")
718
+ print(f"Base model: {args.base_model}")
719
+ print(f"Model variant: {args.model_variant}")
720
+ print(f"LRM model: {args.lrm_model}")
721
+ print(f"HF cache dir: {hf_cache_dir or 'default'}")
722
+ print(f"HF offline mode: {offline_enabled}")
723
+ print(f"Inference steps: {args.num_steps}")
724
+ print(f"CFG scale: {args.cfg_scale}")
725
+ print(f"Batch size: {args.batch_size}")
726
+ print(f"Max samples: {args.max_samples or 'All'}")
727
+ print(f"Output directory: {args.output_dir}")
728
+ print(f"Save images: {args.save_images}")
729
+ print(f"Evaluation mode: {args.mode}")
730
+ print(f"Metrics to evaluate: {', '.join(args.metrics).upper()}")
731
+ if args.grad_config:
732
+ print(f"Gradient ascent config: {args.grad_config}")
733
+
734
+ # Load validation data
735
+ print("\n" + "="*70)
736
+ print("1. LOADING VALIDATION DATA")
737
+ print("="*70)
738
+ prompts, image_paths = load_validation_data(
739
+ args.data_dir,
740
+ args.max_samples,
741
+ args.dataset_type,
742
+ hf_cache_dir=hf_cache_dir,
743
+ offline=offline_enabled,
744
+ )
745
+
746
+ # Automatically disable FID if no reference images available (e.g., Pick-a-Pic dataset)
747
+ can_compute_fid = image_paths is not None
748
+ if not can_compute_fid and "fid" in args.metrics:
749
+ print("\n⚠ Warning: FID metric requested but no reference images available. FID will be skipped.")
750
+ args.metrics = [m for m in args.metrics if m != "fid"]
751
+
752
+ # Load reward model
753
+ print("\n" + "="*70)
754
+ print("2. LOADING REWARD MODEL")
755
+ print("="*70)
756
+ reward_model = LRMRewardModel(
757
+ pretrained_model_name_or_path=args.base_model,
758
+ lrm_model_path=args.lrm_model,
759
+ guidance_scale=args.cfg_scale,
760
+ device=device
761
+ )
762
+ if dtype == torch.float16:
763
+ reward_model = reward_model.half()
764
+ reward_model.eval()
765
+ print("✓ Reward model loaded")
766
+
767
+ # Load pipeline
768
+ print("\n" + "="*70)
769
+ print("3. LOADING PIPELINE")
770
+ print("="*70)
771
+
772
+ pretrained_kwargs = {"local_files_only": offline_enabled}
773
+ if hf_cache_dir:
774
+ pretrained_kwargs["cache_dir"] = hf_cache_dir
775
+
776
+ # Load model based on variant
777
+ if args.model_variant == "origin":
778
+ base_pipeline = StableDiffusionPipeline.from_pretrained(
779
+ args.base_model,
780
+ torch_dtype=dtype,
781
+ safety_checker=None,
782
+ **pretrained_kwargs,
783
+ )
784
+ print(f"✓ Loaded origin SD1.5 model")
785
+ elif args.model_variant == "spo":
786
+ base_pipeline = StableDiffusionPipeline.from_pretrained(
787
+ 'SPO-Diffusion-Models/SPO-SD-v1-5_4k-p_10ep',
788
+ torch_dtype=dtype,
789
+ safety_checker=None,
790
+ **pretrained_kwargs,
791
+ )
792
+ args.cfg_scale = 5.0 # SPO uses CFG 5.0
793
+ print(f"✓ Loaded SPO SD1.5 model (cfg_scale adjusted to 5.0)")
794
+ elif args.model_variant == "diffusion_dpo":
795
+ unet = UNet2DConditionModel.from_pretrained(
796
+ 'mhdang/dpo-sd1.5-text2image-v1',
797
+ subfolder="unet",
798
+ torch_dtype=dtype,
799
+ **pretrained_kwargs,
800
+ )
801
+ base_pipeline = StableDiffusionPipeline.from_pretrained(
802
+ args.base_model,
803
+ torch_dtype=dtype,
804
+ safety_checker=None,
805
+ unet=unet,
806
+ **pretrained_kwargs,
807
+ )
808
+ print(f"✓ Loaded Diffusion-DPO SD1.5 model")
809
+ elif args.model_variant == "lpo":
810
+ unet = UNet2DConditionModel.from_pretrained(
811
+ 'casiatao/LPO',
812
+ subfolder="lpo_sd15_merge/unet",
813
+ torch_dtype=dtype,
814
+ **pretrained_kwargs,
815
+ )
816
+ base_pipeline = StableDiffusionPipeline.from_pretrained(
817
+ args.base_model,
818
+ torch_dtype=dtype,
819
+ safety_checker=None,
820
+ unet=unet,
821
+ **pretrained_kwargs,
822
+ )
823
+ args.cfg_scale = 5.0 # LPO uses CFG 5.0
824
+ print(f"✓ Loaded LPO SD1.5 model (cfg_scale adjusted to 5.0)")
825
+
826
+ pipeline = StableDiffusionGradientAscentPipeline(**base_pipeline.components)
827
+ pipeline.scheduler = DDIMScheduler.from_config(pipeline.scheduler.config)
828
+ pipeline = pipeline.to(device)
829
+ pipeline.set_reward_model(reward_model)
830
+ print("✓ Pipeline loaded")
831
+
832
+ scorer_device = resolve_scorer_device(args.scorer_device, device)
833
+ scorer_dtype = dtype if str(scorer_device).startswith("cuda") else torch.float32
834
+ print(f"Scorer device: {scorer_device}")
835
+
836
+ if torch.cuda.is_available():
837
+ torch.cuda.empty_cache()
838
+
839
+ # Load CLIP scorer
840
+ print("\n" + "="*70)
841
+ print("3.5. LOADING CLIP AND AESTHETIC SCORERS")
842
+ print("="*70)
843
+
844
+ # Only load scorers for requested metrics
845
+ clip_scorer = None
846
+ aesthetic_scorer = None
847
+ pick_scorer = None
848
+ hpsv2_scorer = None
849
+ hpsv21_scorer = None
850
+ imagereward_scorer = None
851
+
852
+ if "clip" in args.metrics:
853
+ try:
854
+ clip_scorer = CLIPScore(model_name_or_path="openai/clip-vit-large-patch14").to(scorer_device)
855
+ print("✓ CLIP scorer loaded")
856
+ except Exception as e:
857
+ print(f"Warning: Could not load CLIP scorer: {e}")
858
+ clip_scorer = None
859
+ else:
860
+ print("⊘ CLIP scorer skipped (not in selected metrics)")
861
+
862
+ if "aesthetic" in args.metrics:
863
+ try:
864
+ aesthetic_scorer = AestheticScorer(dtype=scorer_dtype, device=scorer_device)
865
+ print("✓ Aesthetic scorer loaded")
866
+ except Exception as e:
867
+ print(f"Warning: Could not load Aesthetic scorer: {e}")
868
+ aesthetic_scorer = None
869
+ else:
870
+ print("⊘ Aesthetic scorer skipped (not in selected metrics)")
871
+
872
+ if "pickscore" in args.metrics:
873
+ try:
874
+ from pick_score import PickScorer
875
+ pick_scorer = PickScorer(
876
+ processor_name_or_path="laion/CLIP-ViT-H-14-laion2B-s32B-b79K",
877
+ model_pretrained_name_or_path="yuvalkirstain/PickScore_v1",
878
+ device=scorer_device
879
+ )
880
+ print("✓ PickScore scorer loaded")
881
+ except Exception as e:
882
+ print(f"Warning: Could not load PickScore scorer: {e}")
883
+ pick_scorer = None
884
+ else:
885
+ print("⊘ PickScore scorer skipped (not in selected metrics)")
886
+
887
+ if "hpsv2" in args.metrics:
888
+ try:
889
+ from hpsv2_score import HPSv2Scorer
890
+ hf_dl_kwargs = {"local_files_only": offline_enabled}
891
+ if hf_cache_dir:
892
+ hf_dl_kwargs["cache_dir"] = hf_cache_dir
893
+ hpsv2_scorer = HPSv2Scorer(
894
+ clip_pretrained_name_or_path=hf_hub_download(
895
+ repo_id="laion/CLIP-ViT-H-14-laion2B-s32B-b79K",
896
+ filename="open_clip_pytorch_model.bin",
897
+ **hf_dl_kwargs,
898
+ ),
899
+ model_pretrained_name_or_path=hf_hub_download(
900
+ repo_id="xswu/HPSv2",
901
+ filename="HPS_v2_compressed.pt",
902
+ **hf_dl_kwargs,
903
+ ),
904
+ device=scorer_device
905
+ )
906
+ print("✓ HPSv2 scorer loaded")
907
+ except Exception as e:
908
+ print(f"Warning: Could not load HPSv2 scorer: {e}")
909
+ hpsv2_scorer = None
910
+ else:
911
+ print("⊘ HPSv2 scorer skipped (not in selected metrics)")
912
+
913
+ if "hpsv21" in args.metrics:
914
+ try:
915
+ from hpsv2_score import HPSv2Scorer
916
+ hf_dl_kwargs = {"local_files_only": offline_enabled}
917
+ if hf_cache_dir:
918
+ hf_dl_kwargs["cache_dir"] = hf_cache_dir
919
+ hpsv21_scorer = HPSv2Scorer(
920
+ clip_pretrained_name_or_path=hf_hub_download(
921
+ repo_id="laion/CLIP-ViT-H-14-laion2B-s32B-b79K",
922
+ filename="open_clip_pytorch_model.bin",
923
+ **hf_dl_kwargs,
924
+ ),
925
+ model_pretrained_name_or_path=hf_hub_download(
926
+ repo_id="xswu/HPSv2",
927
+ filename="HPS_v2.1_compressed.pt",
928
+ **hf_dl_kwargs,
929
+ ),
930
+ device=scorer_device
931
+ )
932
+ print("✓ HPSv2.1 scorer loaded")
933
+ except Exception as e:
934
+ print(f"Warning: Could not load HPSv2.1 scorer: {e}")
935
+ hpsv21_scorer = None
936
+ else:
937
+ print("⊘ HPSv2.1 scorer skipped (not in selected metrics)")
938
+
939
+ if "imagereward" in args.metrics:
940
+ try:
941
+ from imagereward_score import load_imagereward
942
+ hf_dl_kwargs = {"local_files_only": offline_enabled}
943
+ if hf_cache_dir:
944
+ hf_dl_kwargs["cache_dir"] = hf_cache_dir
945
+ imagereward_scorer = load_imagereward(
946
+ model_path=hf_hub_download(repo_id="THUDM/ImageReward", filename="ImageReward.pt", **hf_dl_kwargs),
947
+ med_config=hf_hub_download(repo_id="THUDM/ImageReward", filename="med_config.json", **hf_dl_kwargs),
948
+ device=scorer_device
949
+ )
950
+ print("✓ ImageReward scorer loaded")
951
+ except Exception as e:
952
+ print(f"Warning: Could not load ImageReward scorer: {e}")
953
+ imagereward_scorer = None
954
+ else:
955
+ print("⊘ ImageReward scorer skipped (not in selected metrics)")
956
+
957
+ # Configure gradient ascent
958
+ print("\n" + "="*70)
959
+ print("4. CONFIGURING GRADIENT ASCENT")
960
+ print("="*70)
961
+
962
+ # Use config preset if provided, otherwise use individual args
963
+ if args.grad_config:
964
+ print(f"Loading gradient ascent config: {args.grad_config}")
965
+ grad_config = get_config(args.grad_config)
966
+ print(f"Config loaded: {grad_config}")
967
+
968
+ # Apply overrides if specified
969
+ if args.override_momentum is not None:
970
+ grad_config['momentum'] = args.override_momentum
971
+ print(f" Overriding momentum: {args.override_momentum}")
972
+ if args.override_num_grad_steps is not None:
973
+ grad_config['num_grad_steps'] = args.override_num_grad_steps
974
+ print(f" Overriding num_grad_steps: {args.override_num_grad_steps}")
975
+ if args.override_grad_step_size is not None:
976
+ grad_config['grad_step_size'] = args.override_grad_step_size
977
+ print(f" Overriding grad_step_size: {args.override_grad_step_size}")
978
+ else:
979
+ grad_config = {
980
+ "grad_timestep_range": (args.grad_range_start, args.grad_range_end),
981
+ "num_grad_steps": args.grad_steps,
982
+ "grad_step_size": args.grad_step_size,
983
+ }
984
+ print(f"Using manual gradient ascent configuration")
985
+
986
+ print(f"Gradient timestep range: {grad_config.get('grad_timestep_range', (args.grad_range_start, args.grad_range_end))}")
987
+ print(f"Gradient steps: {grad_config.get('num_grad_steps', args.grad_steps)}")
988
+ print(f"Gradient step size (initial LR): {grad_config.get('grad_step_size', args.grad_step_size)}")
989
+ if grad_config.get('lr_scheduler_type'):
990
+ print(f"LR Scheduler: {grad_config['lr_scheduler_type']}")
991
+ if grad_config.get('use_momentum'):
992
+ print(f"Momentum: {grad_config.get('momentum', 0.9)} (Nesterov: {grad_config.get('use_nesterov', False)})")
993
+
994
+ pipeline.enable_gradient_ascent(**grad_config)
995
+
996
+ # Initialize result variables
997
+ fid_score_baseline = None
998
+ avg_reward_baseline = None
999
+ clip_score_baseline = None
1000
+ aesthetic_score_baseline = None
1001
+ pick_score_baseline = None
1002
+ hpsv2_score_baseline = None
1003
+ hpsv21_score_baseline = None
1004
+ imagereward_score_baseline = None
1005
+ fid_score_grad = None
1006
+ avg_reward_grad = None
1007
+ clip_score_grad = None
1008
+ aesthetic_score_grad = None
1009
+ pick_score_grad = None
1010
+ hpsv2_score_grad = None
1011
+ hpsv21_score_grad = None
1012
+ imagereward_score_grad = None
1013
+ grad_stats = None
1014
+
1015
+ # ========== BASELINE EVALUATION ==========
1016
+ if args.mode in ["baseline", "both"]:
1017
+ print("\n" + "="*70)
1018
+ print("5. EVALUATING BASELINE")
1019
+ print("="*70)
1020
+
1021
+ # Generate and evaluate baseline
1022
+ avg_reward_baseline, fid_baseline, clip_score_baseline, aesthetic_score_baseline, pick_score_baseline, hpsv2_score_baseline, hpsv21_score_baseline, imagereward_score_baseline, _, baseline_trajectory = generate_and_evaluate(
1023
+ pipeline=pipeline,
1024
+ prompts=prompts,
1025
+ image_paths=image_paths,
1026
+ device=device,
1027
+ dtype=dtype,
1028
+ num_inference_steps=args.num_steps,
1029
+ guidance_scale=args.cfg_scale,
1030
+ seed=args.seed,
1031
+ batch_size=args.batch_size,
1032
+ apply_gradient_ascent=False,
1033
+ mode_name="baseline",
1034
+ log_interval=args.log_interval,
1035
+ output_dir=args.output_dir,
1036
+ save_images=args.save_images,
1037
+ clip_scorer=clip_scorer,
1038
+ aesthetic_scorer=aesthetic_scorer,
1039
+ pick_scorer=pick_scorer,
1040
+ hpsv2_scorer=hpsv2_scorer,
1041
+ hpsv21_scorer=hpsv21_scorer,
1042
+ imagereward_scorer=imagereward_scorer,
1043
+ compute_fid=("fid" in args.metrics and can_compute_fid),
1044
+ capture_trajectory=True
1045
+ )
1046
+
1047
+ # Compute FID for baseline if requested
1048
+ if "fid" in args.metrics and fid_baseline is not None:
1049
+ fid_score_baseline = fid_baseline.compute().item()
1050
+ print(f"\n✓ Baseline FID: {fid_score_baseline:.4f}")
1051
+ print(f"✓ Baseline Avg Reward: {avg_reward_baseline:.4f}")
1052
+ if "clip" in args.metrics:
1053
+ print(f"✓ Baseline Avg CLIP Score: {clip_score_baseline:.4f}")
1054
+ if "aesthetic" in args.metrics:
1055
+ print(f"✓ Baseline Avg Aesthetic Score: {aesthetic_score_baseline:.4f}")
1056
+ if "pickscore" in args.metrics and pick_score_baseline is not None:
1057
+ print(f"✓ Baseline Avg PickScore: {pick_score_baseline:.4f}")
1058
+ if "hpsv2" in args.metrics and hpsv2_score_baseline is not None:
1059
+ print(f"✓ Baseline Avg HPSv2 Score: {hpsv2_score_baseline:.4f}")
1060
+ if "hpsv21" in args.metrics and hpsv21_score_baseline is not None:
1061
+ print(f"✓ Baseline Avg HPSv2.1 Score: {hpsv21_score_baseline:.4f}")
1062
+ if "imagereward" in args.metrics and imagereward_score_baseline is not None:
1063
+ print(f"✓ Baseline Avg ImageReward: {imagereward_score_baseline:.4f}")
1064
+
1065
+ # ========== GRADIENT ASCENT EVALUATION ==========
1066
+ if args.mode in ["gradient_ascent", "both"]:
1067
+ print("\n" + "="*70)
1068
+ print("6. EVALUATING GRADIENT ASCENT")
1069
+ print("="*70)
1070
+
1071
+ # Generate and evaluate with gradient ascent
1072
+ avg_reward_grad, fid_grad, clip_score_grad, aesthetic_score_grad, pick_score_grad, hpsv2_score_grad, hpsv21_score_grad, imagereward_score_grad, lr_history, guided_trajectory = generate_and_evaluate(
1073
+ pipeline=pipeline,
1074
+ prompts=prompts,
1075
+ image_paths=image_paths,
1076
+ device=device,
1077
+ dtype=dtype,
1078
+ num_inference_steps=args.num_steps,
1079
+ guidance_scale=args.cfg_scale,
1080
+ seed=args.seed,
1081
+ batch_size=args.batch_size,
1082
+ apply_gradient_ascent=True,
1083
+ mode_name="gradient_ascent",
1084
+ log_interval=args.log_interval,
1085
+ output_dir=args.output_dir,
1086
+ save_images=args.save_images,
1087
+ clip_scorer=clip_scorer,
1088
+ aesthetic_scorer=aesthetic_scorer,
1089
+ pick_scorer=pick_scorer,
1090
+ hpsv2_scorer=hpsv2_scorer,
1091
+ hpsv21_scorer=hpsv21_scorer,
1092
+ imagereward_scorer=imagereward_scorer,
1093
+ compute_fid=("fid" in args.metrics and can_compute_fid),
1094
+ capture_trajectory=True
1095
+ )
1096
+
1097
+ # Compute FID for gradient ascent if requested
1098
+ if "fid" in args.metrics and fid_grad is not None:
1099
+ fid_score_grad = fid_grad.compute().item()
1100
+ print(f"\n✓ Gradient Ascent FID: {fid_score_grad:.4f}")
1101
+ print(f"✓ Gradient Ascent Avg Reward: {avg_reward_grad:.4f}")
1102
+ if "clip" in args.metrics:
1103
+ print(f"✓ Gradient Ascent Avg CLIP Score: {clip_score_grad:.4f}")
1104
+ if "aesthetic" in args.metrics:
1105
+ print(f"✓ Gradient Ascent Avg Aesthetic Score: {aesthetic_score_grad:.4f}")
1106
+ if "pickscore" in args.metrics and pick_score_grad is not None:
1107
+ print(f"✓ Gradient Ascent Avg PickScore: {pick_score_grad:.4f}")
1108
+ if "hpsv2" in args.metrics and hpsv2_score_grad is not None:
1109
+ print(f"✓ Gradient Ascent Avg HPSv2 Score: {hpsv2_score_grad:.4f}")
1110
+ if "hpsv21" in args.metrics and hpsv21_score_grad is not None:
1111
+ print(f"✓ Gradient Ascent Avg HPSv2.1 Score: {hpsv21_score_grad:.4f}")
1112
+ if "imagereward" in args.metrics and imagereward_score_grad is not None:
1113
+ print(f"✓ Gradient Ascent Avg ImageReward: {imagereward_score_grad:.4f}")
1114
+
1115
+ # Get gradient stats
1116
+ grad_stats = pipeline.grad_guidance.get_statistics()
1117
+ if grad_stats:
1118
+ print(f"\nGradient Ascent Statistics:")
1119
+ print(f" Applications: {grad_stats['num_applications']}")
1120
+ print(f" Total reward improvement: {grad_stats['total_reward_improvement']:+.4f}")
1121
+ print(f" Avg reward improvement: {grad_stats['avg_reward_improvement']:+.4f}")
1122
+
1123
+ # Plot LR curve if we captured it
1124
+ if lr_history is not None and lr_history['learning_rates']:
1125
+ plot_path = Path(args.output_dir) / "lr_curve.png"
1126
+
1127
+ # LR values are now continuous across all gradient steps
1128
+ lrs = lr_history['learning_rates']
1129
+ steps = list(range(len(lrs))) # Step indices (0 to total_steps-1)
1130
+
1131
+ plt.figure(figsize=(12, 6))
1132
+ plt.plot(steps, lrs, linewidth=2, color='blue', alpha=0.8)
1133
+
1134
+ # Mark the first step with a star
1135
+ plt.plot(steps[0], lrs[0], marker='*', markersize=20, color='gold',
1136
+ markeredgecolor='darkgoldenrod', markeredgewidth=2, zorder=5)
1137
+
1138
+ # Mark timestep boundaries
1139
+ num_timesteps = len(lr_history['timesteps'])
1140
+ num_grad_steps_per_timestep = len(lrs) // num_timesteps if num_timesteps > 0 else 0
1141
+ if num_grad_steps_per_timestep > 0:
1142
+ for i in range(num_timesteps + 1):
1143
+ step_idx = i * num_grad_steps_per_timestep
1144
+ if step_idx <= len(lrs):
1145
+ plt.axvline(x=step_idx, color='red', linestyle='--', alpha=0.3, linewidth=1)
1146
+ if i < num_timesteps:
1147
+ plt.text(step_idx, plt.ylim()[1] * 0.95, f't={lr_history["timesteps"][i]}',
1148
+ fontsize=8, color='red', alpha=0.7, ha='left')
1149
+
1150
+ plt.xlabel('Global Gradient Step', fontsize=12)
1151
+ plt.ylabel('Learning Rate', fontsize=12)
1152
+ plt.title(f'Learning Rate Evolution Across All Gradient Steps\\nPrompt: "{lr_history["prompt"][:60]}..."',
1153
+ fontsize=12, fontweight='bold')
1154
+ plt.grid(True, alpha=0.3)
1155
+
1156
+ # Add info text
1157
+ num_timesteps = len(lr_history['timesteps'])
1158
+ num_grad_steps_per_timestep = len(lrs) // num_timesteps if num_timesteps > 0 else 0
1159
+ plt.text(0.02, 0.98,
1160
+ f'Total timesteps: {num_timesteps}\\nGrad steps/timestep: {num_grad_steps_per_timestep}\\nTotal grad steps: {len(lrs)}',
1161
+ transform=plt.gca().transAxes, fontsize=10, verticalalignment='top',
1162
+ bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
1163
+
1164
+ plt.tight_layout()
1165
+ plt.savefig(plot_path, dpi=150, bbox_inches='tight')
1166
+ plt.close()
1167
+ print(f"\n✓ Saved LR curve plot to: {plot_path}")
1168
+ print(f" Total gradient steps: {len(lrs)}")
1169
+ print(f" LR range: {min(lrs):.6f} → {max(lrs):.6f}")
1170
+
1171
+ # Plot Rewards curve if we captured it
1172
+ if lr_history is not None and lr_history['rewards']:
1173
+ plot_path = Path(args.output_dir) / "rewards_curve.png"
1174
+
1175
+ # Reward values are now continuous across all gradient steps
1176
+ rewards = lr_history['rewards']
1177
+ steps = list(range(len(rewards))) # Step indices (0 to total_steps-1)
1178
+
1179
+ plt.figure(figsize=(12, 6))
1180
+ plt.plot(steps, rewards, linewidth=2, color='green', alpha=0.8)
1181
+
1182
+ # Mark the first step with a star
1183
+ plt.plot(steps[0], rewards[0], marker='*', markersize=20, color='gold',
1184
+ markeredgecolor='darkgoldenrod', markeredgewidth=2, zorder=5)
1185
+
1186
+ # Mark timestep boundaries
1187
+ num_timesteps = len(lr_history['timesteps'])
1188
+ # rewards has one extra value at the start (initial) compared to gradient steps
1189
+ num_grad_steps_per_timestep = (len(rewards) - num_timesteps) // num_timesteps if num_timesteps > 0 else 0
1190
+ if num_grad_steps_per_timestep > 0:
1191
+ for i in range(num_timesteps + 1):
1192
+ step_idx = i * (num_grad_steps_per_timestep + 1) # +1 because reward_history includes initial
1193
+ if step_idx <= len(rewards):
1194
+ plt.axvline(x=step_idx, color='red', linestyle='--', alpha=0.3, linewidth=1)
1195
+ if i < num_timesteps:
1196
+ plt.text(step_idx, plt.ylim()[1] * 0.95, f't={lr_history["timesteps"][i]}',
1197
+ fontsize=8, color='red', alpha=0.7, ha='left')
1198
+
1199
+ plt.xlabel('Global Gradient Step', fontsize=12)
1200
+ plt.ylabel('Reward Score', fontsize=12)
1201
+ plt.title(f'Reward Evolution Across All Gradient Steps\nPrompt: "{lr_history["prompt"][:60]}..."',
1202
+ fontsize=12, fontweight='bold')
1203
+ plt.grid(True, alpha=0.3)
1204
+
1205
+ # Add info text
1206
+ num_timesteps = len(lr_history['timesteps'])
1207
+ reward_improvement = rewards[-1] - rewards[0] if len(rewards) > 1 else 0
1208
+ plt.text(0.02, 0.98,
1209
+ f'Total timesteps: {num_timesteps}\nTotal grad steps: {len(rewards)}\n'
1210
+ f'Initial reward: {rewards[0]:.4f}\nFinal reward: {rewards[-1]:.4f}\n'
1211
+ f'Improvement: {reward_improvement:+.4f}',
1212
+ transform=plt.gca().transAxes, fontsize=10, verticalalignment='top',
1213
+ bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.5))
1214
+
1215
+ plt.tight_layout()
1216
+ plt.savefig(plot_path, dpi=150, bbox_inches='tight')
1217
+ plt.close()
1218
+ print(f"\n✓ Saved Rewards curve plot to: {plot_path}")
1219
+ print(f" Total gradient steps: {len(rewards)}")
1220
+ print(f" Reward range: {min(rewards):.4f} → {max(rewards):.4f}")
1221
+ print(f" Total improvement: {reward_improvement:+.4f}")
1222
+
1223
+ # ---> NEW: PLOT TRAJECTORY DIVERGENCE (MANIFOLD DRIFT) <---
1224
+ if args.mode == "both" and 'baseline_trajectory' in locals() and 'guided_trajectory' in locals():
1225
+ if len(baseline_trajectory) == len(guided_trajectory) and len(baseline_trajectory) > 0:
1226
+ print("\n" + "="*70)
1227
+ print("7. CALCULATING TRAJECTORY DIVERGENCE (THEOREM 1 & 2)")
1228
+ print("="*70)
1229
+
1230
+ drift_path = Path(args.output_dir) / "trajectory_drift.png"
1231
+
1232
+ l2_distances = []
1233
+ # Calculate L2 norm ||z_t_guided - z_t_base||_2 for each step
1234
+ for b_lat, g_lat in zip(baseline_trajectory, guided_trajectory):
1235
+ dist = torch.norm(g_lat.float() - b_lat.float(), p=2).item()
1236
+ l2_distances.append(dist)
1237
+
1238
+ steps = list(range(len(l2_distances)))
1239
+
1240
+ plt.figure(figsize=(10, 6))
1241
+ plt.plot(steps, l2_distances, linewidth=2.5, color='purple', marker='o', markersize=4)
1242
+
1243
+ plt.xlabel('Denoising Step', fontsize=12)
1244
+ plt.ylabel('L2 Distance: ||z_guided - z_base||_2', fontsize=12)
1245
+ plt.title('Latent Trajectory Divergence (Manifold Drift)', fontsize=14, fontweight='bold')
1246
+ plt.grid(True, alpha=0.3)
1247
+
1248
+ # Add interpretation text based on your theory
1249
+ max_drift = max(l2_distances)
1250
+ plt.text(0.02, 0.98,
1251
+ f'Max Drift: {max_drift:.4f}\n'
1252
+ f'Final Drift: {l2_distances[-1]:.4f}\n'
1253
+ f'(Matches bounded drift from Thm 1\n'
1254
+ f'or ODE stiffness collapse from Thm 2)',
1255
+ transform=plt.gca().transAxes, fontsize=10, verticalalignment='top',
1256
+ bbox=dict(boxstyle='round', facecolor='thistle', alpha=0.5))
1257
+
1258
+ plt.tight_layout()
1259
+ plt.savefig(drift_path, dpi=150, bbox_inches='tight')
1260
+ plt.close()
1261
+ print(f"? Saved Manifold Drift curve to: {drift_path}")
1262
+ print(f" Max L2 Distance from baseline: {max_drift:.4f}")
1263
+
1264
+ # ========== FINAL RESULTS ==========
1265
+ print("\n" + "="*70)
1266
+ print("FINAL RESULTS")
1267
+ print("="*70)
1268
+
1269
+ if avg_reward_baseline is not None:
1270
+ print(f"\nBaseline:")
1271
+ if fid_score_baseline is not None:
1272
+ print(f" FID Score: {fid_score_baseline:.4f}")
1273
+ print(f" Avg Reward: {avg_reward_baseline:.4f}")
1274
+ if "clip" in args.metrics and clip_score_baseline is not None:
1275
+ print(f" Avg CLIP Score: {clip_score_baseline:.4f}")
1276
+ if "aesthetic" in args.metrics and aesthetic_score_baseline is not None:
1277
+ print(f" Avg Aesthetic: {aesthetic_score_baseline:.4f}")
1278
+ if "pickscore" in args.metrics and pick_score_baseline is not None:
1279
+ print(f" Avg PickScore: {pick_score_baseline:.4f}")
1280
+ if "hpsv2" in args.metrics and hpsv2_score_baseline is not None:
1281
+ print(f" Avg HPSv2: {hpsv2_score_baseline:.4f}")
1282
+ if "hpsv21" in args.metrics and hpsv21_score_baseline is not None:
1283
+ print(f" Avg HPSv2.1: {hpsv21_score_baseline:.4f}")
1284
+ if "imagereward" in args.metrics and imagereward_score_baseline is not None:
1285
+ print(f" Avg ImageReward: {imagereward_score_baseline:.4f}")
1286
+
1287
+ if avg_reward_grad is not None:
1288
+ print(f"\nGradient Ascent:")
1289
+ if fid_score_grad is not None:
1290
+ print(f" FID Score: {fid_score_grad:.4f}")
1291
+ print(f" Avg Reward: {avg_reward_grad:.4f}")
1292
+ if "clip" in args.metrics and clip_score_grad is not None:
1293
+ print(f" Avg CLIP Score: {clip_score_grad:.4f}")
1294
+ if "aesthetic" in args.metrics and aesthetic_score_grad is not None:
1295
+ print(f" Avg Aesthetic: {aesthetic_score_grad:.4f}")
1296
+ if "pickscore" in args.metrics and pick_score_grad is not None:
1297
+ print(f" Avg PickScore: {pick_score_grad:.4f}")
1298
+ if "hpsv2" in args.metrics and hpsv2_score_grad is not None:
1299
+ print(f" Avg HPSv2: {hpsv2_score_grad:.4f}")
1300
+ if "hpsv21" in args.metrics and hpsv21_score_grad is not None:
1301
+ print(f" Avg HPSv2.1: {hpsv21_score_grad:.4f}")
1302
+ if "imagereward" in args.metrics and imagereward_score_grad is not None:
1303
+ print(f" Avg ImageReward: {imagereward_score_grad:.4f}")
1304
+
1305
+ if avg_reward_baseline is not None and avg_reward_grad is not None:
1306
+ print(f"\nComparison:")
1307
+ if fid_score_baseline is not None and fid_score_grad is not None:
1308
+ fid_diff = fid_score_grad - fid_score_baseline
1309
+ print(f" FID Change: {fid_diff:+.4f} ({'worse' if fid_diff > 0 else 'better'}, lower is better)")
1310
+ reward_diff = avg_reward_grad - avg_reward_baseline
1311
+ print(f" Reward Change: {reward_diff:+.4f} ({'better' if reward_diff > 0 else 'worse'}, higher is better)")
1312
+ if "clip" in args.metrics and clip_score_baseline is not None and clip_score_grad is not None:
1313
+ clip_diff = clip_score_grad - clip_score_baseline
1314
+ print(f" CLIP Change: {clip_diff:+.4f} ({'better' if clip_diff > 0 else 'worse'}, higher is better)")
1315
+ if "aesthetic" in args.metrics and aesthetic_score_baseline is not None and aesthetic_score_grad is not None:
1316
+ aesthetic_diff = aesthetic_score_grad - aesthetic_score_baseline
1317
+ print(f" Aesthetic Change: {aesthetic_diff:+.4f} ({'better' if aesthetic_diff > 0 else 'worse'}, higher is better)")
1318
+ if "pickscore" in args.metrics and pick_score_baseline is not None and pick_score_grad is not None:
1319
+ pick_diff = pick_score_grad - pick_score_baseline
1320
+ print(f" PickScore Change: {pick_diff:+.4f} ({'better' if pick_diff > 0 else 'worse'}, higher is better)")
1321
+ if "hpsv2" in args.metrics and hpsv2_score_baseline is not None and hpsv2_score_grad is not None:
1322
+ hpsv2_diff = hpsv2_score_grad - hpsv2_score_baseline
1323
+ print(f" HPSv2 Change: {hpsv2_diff:+.4f} ({'better' if hpsv2_diff > 0 else 'worse'}, higher is better)")
1324
+ if "hpsv21" in args.metrics and hpsv21_score_baseline is not None and hpsv21_score_grad is not None:
1325
+ hpsv21_diff = hpsv21_score_grad - hpsv21_score_baseline
1326
+ print(f" HPSv2.1 Change: {hpsv21_diff:+.4f} ({'better' if hpsv21_diff > 0 else 'worse'}, higher is better)")
1327
+ if "imagereward" in args.metrics and imagereward_score_baseline is not None and imagereward_score_grad is not None:
1328
+ imagereward_diff = imagereward_score_grad - imagereward_score_baseline
1329
+ print(f" ImageReward Chg: {imagereward_diff:+.4f} ({'better' if imagereward_diff > 0 else 'worse'}, higher is better)")
1330
+
1331
+ # Save results to file
1332
+ results = {
1333
+ "mode": args.mode,
1334
+ "metrics": args.metrics,
1335
+ "config": {
1336
+ "num_samples": len(prompts),
1337
+ "num_steps": args.num_steps,
1338
+ "cfg_scale": args.cfg_scale,
1339
+ "grad_range": [args.grad_range_start, args.grad_range_end],
1340
+ "grad_steps": args.grad_steps,
1341
+ "grad_step_size": args.grad_step_size
1342
+ }
1343
+ }
1344
+
1345
+ if avg_reward_baseline is not None:
1346
+ results["baseline"] = {"avg_reward": avg_reward_baseline}
1347
+ if fid_score_baseline is not None:
1348
+ results["baseline"]["fid"] = fid_score_baseline
1349
+ if "clip" in args.metrics and clip_score_baseline is not None:
1350
+ results["baseline"]["clip_score"] = clip_score_baseline
1351
+ if "aesthetic" in args.metrics and aesthetic_score_baseline is not None:
1352
+ results["baseline"]["aesthetic_score"] = aesthetic_score_baseline
1353
+ if "pickscore" in args.metrics and pick_score_baseline is not None:
1354
+ results["baseline"]["pickscore"] = pick_score_baseline
1355
+ if "hpsv2" in args.metrics and hpsv2_score_baseline is not None:
1356
+ results["baseline"]["hpsv2_score"] = hpsv2_score_baseline
1357
+ if "hpsv21" in args.metrics and hpsv21_score_baseline is not None:
1358
+ results["baseline"]["hpsv21_score"] = hpsv21_score_baseline
1359
+ if "imagereward" in args.metrics and imagereward_score_baseline is not None:
1360
+ results["baseline"]["imagereward_score"] = imagereward_score_baseline
1361
+
1362
+ if avg_reward_grad is not None:
1363
+ results["gradient_ascent"] = {"avg_reward": avg_reward_grad}
1364
+ if fid_score_grad is not None:
1365
+ results["gradient_ascent"]["fid"] = fid_score_grad
1366
+ if "clip" in args.metrics and clip_score_grad is not None:
1367
+ results["gradient_ascent"]["clip_score"] = clip_score_grad
1368
+ if "aesthetic" in args.metrics and aesthetic_score_grad is not None:
1369
+ results["gradient_ascent"]["aesthetic_score"] = aesthetic_score_grad
1370
+ if "pickscore" in args.metrics and pick_score_grad is not None:
1371
+ results["gradient_ascent"]["pickscore"] = pick_score_grad
1372
+ if "hpsv2" in args.metrics and hpsv2_score_grad is not None:
1373
+ results["gradient_ascent"]["hpsv2_score"] = hpsv2_score_grad
1374
+ if "hpsv21" in args.metrics and hpsv21_score_grad is not None:
1375
+ results["gradient_ascent"]["hpsv21_score"] = hpsv21_score_grad
1376
+ if "imagereward" in args.metrics and imagereward_score_grad is not None:
1377
+ results["gradient_ascent"]["imagereward_score"] = imagereward_score_grad
1378
+ if grad_stats:
1379
+ results["gradient_ascent"]["stats"] = grad_stats
1380
+
1381
+ if avg_reward_baseline is not None and avg_reward_grad is not None:
1382
+ results["comparison"] = {
1383
+ "reward_difference": avg_reward_grad - avg_reward_baseline
1384
+ }
1385
+ if fid_score_baseline is not None and fid_score_grad is not None:
1386
+ results["comparison"]["fid_difference"] = fid_score_grad - fid_score_baseline
1387
+ if "clip" in args.metrics and clip_score_baseline is not None and clip_score_grad is not None:
1388
+ results["comparison"]["clip_difference"] = clip_score_grad - clip_score_baseline
1389
+ if "aesthetic" in args.metrics and aesthetic_score_baseline is not None and aesthetic_score_grad is not None:
1390
+ results["comparison"]["aesthetic_difference"] = aesthetic_score_grad - aesthetic_score_baseline
1391
+ if "pickscore" in args.metrics and pick_score_baseline is not None and pick_score_grad is not None:
1392
+ results["comparison"]["pickscore_difference"] = pick_score_grad - pick_score_baseline
1393
+ if "hpsv2" in args.metrics and hpsv2_score_baseline is not None and hpsv2_score_grad is not None:
1394
+ results["comparison"]["hpsv2_difference"] = hpsv2_score_grad - hpsv2_score_baseline
1395
+ if "hpsv21" in args.metrics and hpsv21_score_baseline is not None and hpsv21_score_grad is not None:
1396
+ results["comparison"]["hpsv21_difference"] = hpsv21_score_grad - hpsv21_score_baseline
1397
+ if "imagereward" in args.metrics and imagereward_score_baseline is not None and imagereward_score_grad is not None:
1398
+ results["comparison"]["imagereward_difference"] = imagereward_score_grad - imagereward_score_baseline
1399
+
1400
+ # Save results to output directory
1401
+ output_path = Path(args.output_dir)
1402
+ output_path.mkdir(parents=True, exist_ok=True)
1403
+ results_path = output_path / "evaluation_results.txt"
1404
+
1405
+ with open(results_path, "w") as f:
1406
+ for k, v in results.items():
1407
+ f.write(f"{k}: {v}\n")
1408
+
1409
+
1410
+ print(f"\n✓ Results saved to: {results_path}")
1411
+ if args.save_images:
1412
+ print(f"✓ Generated images saved to: {output_path}/baseline/ and {output_path}/gradient_ascent/")
1413
+ print("\n" + "="*70)
1414
+
1415
+ # Close logger
1416
+ tee_logger.close()
1417
+ sys.stdout = tee_logger.terminal
1418
+
1419
+
1420
+ if __name__ == "__main__":
1421
+ main()
1422
+
Reward_sd15_idealized/examples.sh ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ clear
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ cd "$SCRIPT_DIR"
7
+
8
+ # Shared HF cache used on this cluster.
9
+ HF_HUB_CACHE_DIR="${HF_HUB_CACHE_DIR:-/scratch/rr81/ma5430/.cache/huggingface/hub}"
10
+ export HF_HUB_CACHE="$HF_HUB_CACHE_DIR"
11
+ export HUGGINGFACE_HUB_CACHE="$HF_HUB_CACHE_DIR"
12
+ export HF_HOME="$(dirname "$HF_HUB_CACHE_DIR")"
13
+
14
+ # GPU nodes have no internet, while login nodes do.
15
+ # Auto default: offline on GPU nodes, online on login nodes.
16
+ DEFAULT_OFFLINE_MODE="1"
17
+ if ! (command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1); then
18
+ DEFAULT_OFFLINE_MODE="0"
19
+ fi
20
+ OFFLINE_MODE="${OFFLINE_MODE:-$DEFAULT_OFFLINE_MODE}"
21
+
22
+ if [[ "$OFFLINE_MODE" == "1" ]]; then
23
+ export HF_DATASETS_OFFLINE="1"
24
+ export HF_METRICS_OFFLINE="1"
25
+ export HF_MODULES_OFFLINE="1"
26
+ export TRANSFORMERS_OFFLINE="1"
27
+ export DIFFUSERS_OFFLINE="1"
28
+ export HF_HUB_OFFLINE="1"
29
+ else
30
+ export HF_DATASETS_OFFLINE="0"
31
+ export HF_METRICS_OFFLINE="0"
32
+ export HF_MODULES_OFFLINE="0"
33
+ export TRANSFORMERS_OFFLINE="0"
34
+ export DIFFUSERS_OFFLINE="0"
35
+ export HF_HUB_OFFLINE="0"
36
+ fi
37
+
38
+ # Existing environment requested by user.
39
+ PYTHON_BIN="${PYTHON_BIN:-/g/data/rr81/aev/bin/python}"
40
+ if [[ ! -x "$PYTHON_BIN" ]]; then
41
+ echo "[examples.sh] Missing Python executable: $PYTHON_BIN" >&2
42
+ exit 1
43
+ fi
44
+
45
+ DATASET_NAME="${DATASET_NAME:-pickapic}" # coco | pickapic
46
+ GRAD_CONFIG="${GRAD_CONFIG:-one_step_rectification_config}"
47
+ MODEL_VARIANT="${MODEL_VARIANT:-spo}" # origin | spo | diffusion_dpo | lpo
48
+ MODE="${MODE:-gradient_ascent}" # gradient_ascent | baseline | both
49
+ # Empty MAX_SAMPLES means evaluate all available samples.
50
+ MAX_SAMPLES="${MAX_SAMPLES:-}"
51
+ NUM_STEPS="${NUM_STEPS:-20}"
52
+ CFG_SCALE="${CFG_SCALE:-3}"
53
+ METRICS="${METRICS:-clip aesthetic pickscore hpsv2 hpsv21 imagereward}"
54
+ PREFETCH_ONLY="${PREFETCH_ONLY:-0}"
55
+
56
+ # Override this path whenever you want to swap reward weights.
57
+ LRM_MODEL_PATH="${LRM_MODEL_PATH:-/g/data/rr81/LPO/lrm/lrm_15/LRM}"
58
+
59
+ if [[ -z "${GPU_ID:-}" ]]; then
60
+ if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then
61
+ GPU_ID="$(nvidia-smi --query-gpu=index,memory.used --format=csv,noheader,nounits | sort -k2 -n | head -n1 | cut -d',' -f1 | tr -d ' ')"
62
+ GPU_ID="${GPU_ID:-0}"
63
+ else
64
+ GPU_ID="0"
65
+ echo "[examples.sh] No visible NVIDIA GPU on this node. Defaulting GPU_ID=0."
66
+ echo "[examples.sh] eval.py will run on CPU if CUDA is unavailable."
67
+ fi
68
+ fi
69
+
70
+ echo "Using GPU ID: $GPU_ID"
71
+ echo "Using LRM weights: $LRM_MODEL_PATH"
72
+ echo "HF offline mode: $OFFLINE_MODE"
73
+
74
+ if [[ "$PREFETCH_ONLY" == "1" ]]; then
75
+ echo "[examples.sh] PREFETCH_ONLY=1 -> downloading required model files to shared cache and exiting."
76
+ export MODEL_VARIANT
77
+ export METRICS
78
+ "$PYTHON_BIN" - <<'PY'
79
+ import os
80
+ from huggingface_hub import hf_hub_download, snapshot_download
81
+
82
+ cache_dir = os.environ["HF_HUB_CACHE"]
83
+ model_variant = os.environ.get("MODEL_VARIANT", "origin")
84
+ metrics = set(os.environ.get("METRICS", "clip aesthetic").split())
85
+
86
+ def snap(repo_id):
87
+ print(f"[prefetch] snapshot_download: {repo_id}")
88
+ snapshot_download(repo_id=repo_id, cache_dir=cache_dir, local_files_only=False)
89
+
90
+ def one(repo_id, filename):
91
+ print(f"[prefetch] hf_hub_download: {repo_id}/{filename}")
92
+ hf_hub_download(repo_id=repo_id, filename=filename, cache_dir=cache_dir, local_files_only=False)
93
+
94
+ # Base model for generation + reward backbone
95
+ snap("stable-diffusion-v1-5/stable-diffusion-v1-5")
96
+
97
+ # Variant-specific generation checkpoints (if used)
98
+ if model_variant == "spo":
99
+ snap("SPO-Diffusion-Models/SPO-SD-v1-5_4k-p_10ep")
100
+ elif model_variant == "diffusion_dpo":
101
+ snap("mhdang/dpo-sd1.5-text2image-v1")
102
+ elif model_variant == "lpo":
103
+ snap("casiatao/LPO")
104
+
105
+ # Required for CLIP-based metrics and LRM text projection init fallback
106
+ if "clip" in metrics or "aesthetic" in metrics:
107
+ snap("openai/clip-vit-large-patch14")
108
+
109
+ if "pickscore" in metrics:
110
+ snap("laion/CLIP-ViT-H-14-laion2B-s32B-b79K")
111
+ snap("yuvalkirstain/PickScore_v1")
112
+
113
+ if "hpsv2" in metrics or "hpsv21" in metrics:
114
+ one("laion/CLIP-ViT-H-14-laion2B-s32B-b79K", "open_clip_pytorch_model.bin")
115
+ if "hpsv2" in metrics:
116
+ one("xswu/HPSv2", "HPS_v2_compressed.pt")
117
+ if "hpsv21" in metrics:
118
+ one("xswu/HPSv2", "HPS_v2.1_compressed.pt")
119
+
120
+ if "imagereward" in metrics:
121
+ one("THUDM/ImageReward", "ImageReward.pt")
122
+ one("THUDM/ImageReward", "med_config.json")
123
+
124
+ print("[prefetch] done")
125
+ PY
126
+ exit 0
127
+ fi
128
+
129
+ read -r -a METRICS_ARR <<< "$METRICS"
130
+
131
+ CMD=(
132
+ "$PYTHON_BIN" eval.py
133
+ --model_variant "$MODEL_VARIANT"
134
+ --dataset_type "$DATASET_NAME"
135
+ --lrm_model "$LRM_MODEL_PATH"
136
+ --grad_config "$GRAD_CONFIG"
137
+ --metrics "${METRICS_ARR[@]}"
138
+ --num_steps "$NUM_STEPS"
139
+ --cfg_scale "$CFG_SCALE"
140
+ --hf_cache_dir "$HF_HUB_CACHE_DIR"
141
+ --output_dir "RESULTS/$DATASET_NAME/${GRAD_CONFIG}_${MODEL_VARIANT}"
142
+ --cuda "$GPU_ID"
143
+ --mode "$MODE"
144
+ )
145
+
146
+ if [[ -n "$MAX_SAMPLES" ]]; then
147
+ CMD+=(--max_samples "$MAX_SAMPLES")
148
+ fi
149
+
150
+ if [[ "$OFFLINE_MODE" == "1" ]]; then
151
+ CMD+=(--offline)
152
+ fi
153
+
154
+ "${CMD[@]}"
Reward_sd15_idealized/lr_scheduler.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Learning rate schedulers for gradient ascent optimization.
3
+
4
+ Provides various LR scheduling strategies for reward-guided gradient ascent,
5
+ including cosine annealing, linear decay, and custom schedules.
6
+ """
7
+
8
+ import math
9
+ from typing import Optional, Literal
10
+
11
+
12
+ class LRScheduler:
13
+ """Base class for learning rate schedulers."""
14
+
15
+ def __init__(self, initial_lr: float, num_steps: int):
16
+ """
17
+ Initialize LR scheduler.
18
+
19
+ Args:
20
+ initial_lr: Initial learning rate
21
+ num_steps: Total number of optimization steps
22
+ """
23
+ self.initial_lr = initial_lr
24
+ self.num_steps = num_steps
25
+ self.current_step = 0
26
+
27
+ def get_lr(self) -> float:
28
+ """Get current learning rate."""
29
+ raise NotImplementedError
30
+
31
+ def step(self):
32
+ """Update scheduler state after a step."""
33
+ self.current_step += 1
34
+
35
+ def reset(self):
36
+ """Reset scheduler state."""
37
+ self.current_step = 0
38
+
39
+
40
+ class ConstantLR(LRScheduler):
41
+ """Constant learning rate (no scheduling)."""
42
+
43
+ def get_lr(self) -> float:
44
+ return self.initial_lr
45
+
46
+
47
+ class LinearLR(LRScheduler):
48
+ """Linear learning rate decay."""
49
+
50
+ def __init__(
51
+ self,
52
+ initial_lr: float,
53
+ num_steps: int,
54
+ end_lr: float = 0.0,
55
+ start_step: int = 0,
56
+ ):
57
+ """
58
+ Initialize linear LR scheduler.
59
+
60
+ Args:
61
+ initial_lr: Starting learning rate
62
+ num_steps: Total number of steps
63
+ end_lr: Ending learning rate (default: 0.0)
64
+ start_step: Step to begin decay (default: 0)
65
+ """
66
+ super().__init__(initial_lr, num_steps)
67
+ self.end_lr = end_lr
68
+ self.start_step = start_step
69
+
70
+ def get_lr(self) -> float:
71
+ if self.current_step < self.start_step:
72
+ return self.initial_lr
73
+
74
+ progress = (self.current_step - self.start_step) / (self.num_steps - self.start_step)
75
+ progress = min(1.0, progress)
76
+
77
+ return self.initial_lr + (self.end_lr - self.initial_lr) * progress
78
+
79
+
80
+ class CosineLR(LRScheduler):
81
+ """Cosine annealing learning rate schedule."""
82
+
83
+ def __init__(
84
+ self,
85
+ initial_lr: float,
86
+ num_steps: int,
87
+ min_lr: float = 0.0,
88
+ warmup_steps: int = 0,
89
+ ):
90
+ """
91
+ Initialize cosine LR scheduler.
92
+
93
+ Args:
94
+ initial_lr: Maximum learning rate
95
+ num_steps: Total number of steps
96
+ min_lr: Minimum learning rate (default: 0.0)
97
+ warmup_steps: Number of linear warmup steps (default: 0)
98
+ """
99
+ super().__init__(initial_lr, num_steps)
100
+ self.min_lr = min_lr
101
+ self.warmup_steps = warmup_steps
102
+
103
+ def get_lr(self) -> float:
104
+ if self.current_step < self.warmup_steps:
105
+ # Linear warmup
106
+ return self.initial_lr * (self.current_step / self.warmup_steps)
107
+
108
+ # Cosine annealing
109
+ progress = (self.current_step - self.warmup_steps) / (self.num_steps - self.warmup_steps)
110
+ progress = min(1.0, progress)
111
+
112
+ cosine_decay = 0.5 * (1 + math.cos(math.pi * progress))
113
+ return self.min_lr + (self.initial_lr - self.min_lr) * cosine_decay
114
+
115
+
116
+ class ExponentialLR(LRScheduler):
117
+ """Exponential learning rate decay."""
118
+
119
+ def __init__(
120
+ self,
121
+ initial_lr: float,
122
+ num_steps: int,
123
+ gamma: float = 0.95,
124
+ ):
125
+ """
126
+ Initialize exponential LR scheduler.
127
+
128
+ Args:
129
+ initial_lr: Starting learning rate
130
+ num_steps: Total number of steps
131
+ gamma: Multiplicative decay factor per step
132
+ """
133
+ super().__init__(initial_lr, num_steps)
134
+ self.gamma = gamma
135
+
136
+ def get_lr(self) -> float:
137
+ return self.initial_lr * (self.gamma ** self.current_step)
138
+
139
+
140
+ class StepLR(LRScheduler):
141
+ """Step-wise learning rate decay."""
142
+
143
+ def __init__(
144
+ self,
145
+ initial_lr: float,
146
+ num_steps: int,
147
+ step_size: int,
148
+ gamma: float = 0.1,
149
+ ):
150
+ """
151
+ Initialize step LR scheduler.
152
+
153
+ Args:
154
+ initial_lr: Starting learning rate
155
+ num_steps: Total number of steps
156
+ step_size: Number of steps between each decay
157
+ gamma: Multiplicative decay factor
158
+ """
159
+ super().__init__(initial_lr, num_steps)
160
+ self.step_size = step_size
161
+ self.gamma = gamma
162
+
163
+ def get_lr(self) -> float:
164
+ num_decays = self.current_step // self.step_size
165
+ return self.initial_lr * (self.gamma ** num_decays)
166
+
167
+
168
+ def create_lr_scheduler(
169
+ scheduler_type: Literal["constant", "linear", "cosine", "exponential", "step"],
170
+ initial_lr: float,
171
+ num_steps: int,
172
+ **kwargs
173
+ ) -> LRScheduler:
174
+ """
175
+ Factory function to create learning rate schedulers.
176
+
177
+ Args:
178
+ scheduler_type: Type of scheduler ("constant", "linear", "cosine", "exponential", "step")
179
+ initial_lr: Initial learning rate
180
+ num_steps: Total number of optimization steps
181
+ **kwargs: Additional scheduler-specific arguments
182
+ For linear: end_lr, start_step
183
+ For cosine: min_lr, warmup_steps
184
+ For exponential: gamma
185
+ For step: step_size, gamma
186
+
187
+ Returns:
188
+ LRScheduler instance
189
+
190
+ Examples:
191
+ # Constant LR
192
+ scheduler = create_lr_scheduler("constant", initial_lr=0.1, num_steps=100)
193
+
194
+ # Linear decay
195
+ scheduler = create_lr_scheduler("linear", initial_lr=0.1, num_steps=100, end_lr=0.01)
196
+
197
+ # Cosine annealing with warmup
198
+ scheduler = create_lr_scheduler("cosine", initial_lr=0.1, num_steps=100,
199
+ min_lr=0.001, warmup_steps=10)
200
+ """
201
+ if scheduler_type == "constant":
202
+ return ConstantLR(initial_lr, num_steps)
203
+
204
+ elif scheduler_type == "linear":
205
+ return LinearLR(
206
+ initial_lr, num_steps,
207
+ end_lr=kwargs.get("end_lr", 0.0),
208
+ start_step=kwargs.get("start_step", 0),
209
+ )
210
+
211
+ elif scheduler_type == "cosine":
212
+ return CosineLR(
213
+ initial_lr, num_steps,
214
+ min_lr=kwargs.get("min_lr", 0.0),
215
+ warmup_steps=kwargs.get("warmup_steps", 0),
216
+ )
217
+
218
+ elif scheduler_type == "exponential":
219
+ return ExponentialLR(
220
+ initial_lr, num_steps,
221
+ gamma=kwargs.get("gamma", 0.95),
222
+ )
223
+
224
+ elif scheduler_type == "step":
225
+ return StepLR(
226
+ initial_lr, num_steps,
227
+ step_size=kwargs.get("step_size", 10),
228
+ gamma=kwargs.get("gamma", 0.1),
229
+ )
230
+
231
+ else:
232
+ raise ValueError(f"Unknown scheduler type: {scheduler_type}. "
233
+ f"Choose from: constant, linear, cosine, exponential, step")
Reward_sd15_idealized/models/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .reward_model import LRMRewardModel
2
+
3
+ __all__ = ['LRMRewardModel', 'LRMRewardModelSDXL']
Reward_sd15_idealized/models/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (279 Bytes). View file
 
Reward_sd15_idealized/models/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (282 Bytes). View file
 
Reward_sd15_idealized/models/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (295 Bytes). View file
 
Reward_sd15_idealized/models/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (240 Bytes). View file
 
Reward_sd15_idealized/models/__pycache__/reward_model.cpython-310.pyc ADDED
Binary file (9.51 kB). View file
 
Reward_sd15_idealized/models/__pycache__/reward_model.cpython-311.pyc ADDED
Binary file (19.1 kB). View file
 
Reward_sd15_idealized/models/__pycache__/reward_model.cpython-313.pyc ADDED
Binary file (16.4 kB). View file
 
Reward_sd15_idealized/models/__pycache__/reward_model.cpython-39.pyc ADDED
Binary file (9.1 kB). View file
 
Reward_sd15_idealized/models/__pycache__/reward_model_sdxl.cpython-310.pyc ADDED
Binary file (9.96 kB). View file
 
Reward_sd15_idealized/models/__pycache__/unet_2d_condition_reward.cpython-310.pyc ADDED
Binary file (40.9 kB). View file
 
Reward_sd15_idealized/models/__pycache__/unet_2d_condition_reward.cpython-311.pyc ADDED
Binary file (62.8 kB). View file
 
Reward_sd15_idealized/models/__pycache__/unet_2d_condition_reward.cpython-313.pyc ADDED
Binary file (57.4 kB). View file
 
Reward_sd15_idealized/models/reward_model.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LRM Reward Model Wrapper
3
+ Loads LRM weights from HuggingFace and provides interface for computing preference scores on noisy latents.
4
+ """
5
+
6
+ import torch
7
+ from torch import nn
8
+ from diffusers import AutoencoderKL, DDPMScheduler
9
+ from transformers import CLIPTextModel, CLIPTokenizer
10
+ from huggingface_hub import hf_hub_download
11
+ import os
12
+ from .unet_2d_condition_reward import UNet2DConditionModel
13
+
14
+
15
+ def _offline_mode_enabled() -> bool:
16
+ return os.getenv("HF_HUB_OFFLINE", "0").strip().lower() in {"1", "true", "yes", "on"}
17
+
18
+
19
+ def _get_cache_dir() -> str | None:
20
+ return os.getenv("HF_HUB_CACHE") or os.getenv("HUGGINGFACE_HUB_CACHE")
21
+
22
+
23
+ def _hf_pretrained_kwargs() -> dict:
24
+ kwargs = {"local_files_only": _offline_mode_enabled()}
25
+ cache_dir = _get_cache_dir()
26
+ if cache_dir:
27
+ kwargs["cache_dir"] = cache_dir
28
+ return kwargs
29
+
30
+
31
+ class LRMRewardModel(nn.Module):
32
+ """
33
+ Latent Reward Model (LRM) for SD1.5
34
+
35
+ This model computes preference scores directly on noisy latent images at any timestep.
36
+ It uses features from the U-Net and text encoder to predict how well an image aligns
37
+ with the prompt at different noise levels.
38
+
39
+ Args:
40
+ pretrained_model_name_or_path: Base SD model path (e.g., 'runwayml/stable-diffusion-v1-5')
41
+ lrm_model_path: Path to LRM checkpoint from HuggingFace (e.g., 'casiatao/LRM')
42
+ clip_model_path: Path to CLIP checkpoint for text projection initialization
43
+ guidance_scale: Classifier-free guidance scale (default: 7.5)
44
+ device: Device to load model on
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ pretrained_model_name_or_path='runwayml/stable-diffusion-v1-5',
50
+ lrm_model_path=None,
51
+ clip_model_path='openai/clip-vit-large-patch14',
52
+ guidance_scale=7.5,
53
+ device='cuda'
54
+ ):
55
+ super().__init__()
56
+
57
+ self.device = device
58
+ self.guidance_scale = guidance_scale
59
+ self.multi_scale = True
60
+ self.multi_scale_cfg = False
61
+
62
+ print(f"Loading base models from {pretrained_model_name_or_path}...")
63
+ pretrained_kwargs = _hf_pretrained_kwargs()
64
+
65
+ # Load tokenizer and text encoder
66
+ self.tokenizer = CLIPTokenizer.from_pretrained(
67
+ pretrained_model_name_or_path,
68
+ subfolder="tokenizer",
69
+ **pretrained_kwargs,
70
+ )
71
+ self.text_encoder = CLIPTextModel.from_pretrained(
72
+ pretrained_model_name_or_path,
73
+ subfolder="text_encoder",
74
+ **pretrained_kwargs,
75
+ ).to(device)
76
+
77
+ # Load VAE (frozen, only needed for preprocessing if using images)
78
+ self.vae = AutoencoderKL.from_pretrained(
79
+ pretrained_model_name_or_path,
80
+ subfolder="vae",
81
+ **pretrained_kwargs,
82
+ ).to(device)
83
+ self.vae.requires_grad_(False)
84
+
85
+ # Load scheduler
86
+ self.scheduler = DDPMScheduler.from_pretrained(
87
+ pretrained_model_name_or_path,
88
+ subfolder="scheduler",
89
+ **pretrained_kwargs,
90
+ )
91
+
92
+ # Load U-Net with custom reward architecture
93
+ print("Loading custom U-Net for reward prediction...")
94
+ self.unet = UNet2DConditionModel.from_pretrained(
95
+ pretrained_model_name_or_path,
96
+ subfolder="unet",
97
+ **pretrained_kwargs,
98
+ ).to(device)
99
+
100
+ # Global pooling layer
101
+ self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
102
+
103
+ # Projection layers
104
+ # Multi-scale: concatenates features from 4 down blocks + mid block = 4800 dims
105
+ vision_embed_dim = 4800 if self.multi_scale else 1280
106
+ text_embed_dim = 768
107
+ projection_dim = 768
108
+
109
+ self.visual_projection = nn.Linear(vision_embed_dim, projection_dim, bias=False).to(device)
110
+ self.text_projection = nn.Linear(text_embed_dim, projection_dim, bias=False).to(device)
111
+
112
+ # Initialize text projection from CLIP
113
+ print(f"Loading CLIP text projection from {clip_model_path}...")
114
+ try:
115
+ # Try loading from local path first
116
+ if os.path.exists(clip_model_path):
117
+ clip_ckpt = torch.load(clip_model_path, map_location='cpu')
118
+ else:
119
+ # Download from HuggingFace
120
+ clip_ckpt_path = hf_hub_download(
121
+ repo_id=clip_model_path,
122
+ filename="pytorch_model.bin",
123
+ **_hf_pretrained_kwargs(),
124
+ )
125
+ clip_ckpt = torch.load(clip_ckpt_path, map_location='cpu')
126
+
127
+ self.text_projection.weight.data = clip_ckpt['text_projection.weight'].contiguous().to(device)
128
+ print("✓ Loaded CLIP text projection weights")
129
+ except Exception as e:
130
+ print(f"Warning: Could not load CLIP weights: {e}")
131
+ print("Initializing text projection randomly")
132
+ nn.init.normal_(self.text_projection.weight, std=0.02)
133
+
134
+ # Initialize visual projection
135
+ nn.init.normal_(self.visual_projection.weight, std=0.02)
136
+
137
+ # Logit scale (temperature parameter)
138
+ self.logit_scale = nn.Parameter(torch.ones([]) * 2.6592).to(device)
139
+
140
+ # Setup classifier-free guidance
141
+ self.do_classifier_free_guidance = self.guidance_scale > 1.0
142
+ if self.do_classifier_free_guidance:
143
+ self.neg_prompt_ids = self.tokenizer(
144
+ [""],
145
+ return_tensors="pt",
146
+ padding="max_length",
147
+ truncation=True,
148
+ max_length=self.tokenizer.model_max_length,
149
+ ).input_ids.to(device)
150
+
151
+ # Load fine-tuned LRM weights if provided
152
+ if lrm_model_path:
153
+ self.load_lrm_weights(lrm_model_path)
154
+
155
+ print("✓ LRM Reward Model initialized successfully!")
156
+
157
+ def load_lrm_weights(self, model_path):
158
+ """
159
+ Load fine-tuned LRM weights from HuggingFace or local path
160
+
161
+ Expected structure:
162
+ - unet/ (directory with U-Net weights)
163
+ - text_encoder/ (optional, directory with text encoder weights)
164
+ - state_dict.pt (visual_projection, text_projection, logit_scale)
165
+ """
166
+ print(f"\nLoading LRM weights from {model_path}...")
167
+
168
+ try:
169
+ # Check if it's a HuggingFace model or local path
170
+ if not os.path.exists(model_path):
171
+ # Try to download from HuggingFace
172
+ print(f"Downloading from HuggingFace: {model_path}")
173
+ # For HF models, we need to download the entire repo
174
+ from huggingface_hub import snapshot_download
175
+ model_path = snapshot_download(repo_id=model_path, **_hf_pretrained_kwargs())
176
+
177
+ # Load U-Net weights
178
+ unet_path = os.path.join(model_path, "lrm_sd15", "unet")
179
+ if os.path.exists(unet_path):
180
+ self.unet = UNet2DConditionModel.from_pretrained(unet_path, **_hf_pretrained_kwargs()).to(self.device)
181
+ print(f"✓ Loaded U-Net weights from {unet_path}")
182
+ else:
183
+ print(f"Warning: U-Net path not found: {unet_path}")
184
+
185
+ # Load text encoder weights (optional)
186
+ text_encoder_path = os.path.join(model_path, "lrm_sd15", "text_encoder")
187
+ if os.path.exists(text_encoder_path):
188
+ self.text_encoder = CLIPTextModel.from_pretrained(text_encoder_path, **_hf_pretrained_kwargs()).to(self.device)
189
+ print(f"✓ Loaded text encoder weights from {text_encoder_path}")
190
+
191
+ # Load projection layers and logit scale
192
+ state_dict_path = os.path.join(model_path, "lrm_sd15", "state_dict.pt")
193
+ if os.path.exists(state_dict_path):
194
+ state_dict = torch.load(state_dict_path, map_location='cpu')
195
+
196
+ self.visual_projection.load_state_dict(state_dict['visual_projection'])
197
+ self.text_projection.load_state_dict(state_dict['text_projection'])
198
+
199
+ # Move projection layers to device
200
+ self.visual_projection = self.visual_projection.to(self.device)
201
+ self.text_projection = self.text_projection.to(self.device)
202
+
203
+ logit_scale_val = state_dict['logit_scale']
204
+ if isinstance(logit_scale_val, torch.Tensor):
205
+ self.logit_scale.data = logit_scale_val.to(self.device)
206
+ else:
207
+ self.logit_scale.data = torch.tensor(logit_scale_val).to(self.device)
208
+
209
+ print(f"✓ Loaded projection layers and logit_scale from {state_dict_path}")
210
+ else:
211
+ print(f"Warning: state_dict.pt not found: {state_dict_path}")
212
+
213
+ print("✓ Successfully loaded all LRM weights!")
214
+
215
+ except Exception as e:
216
+ print(f"Error loading LRM weights: {e}")
217
+ print("Continuing with base model weights...")
218
+
219
+ def encode_prompt(self, prompt):
220
+ """Tokenize text prompt"""
221
+ if isinstance(prompt, str):
222
+ prompt = [prompt]
223
+
224
+ text_inputs = self.tokenizer(
225
+ prompt,
226
+ padding="max_length",
227
+ max_length=self.tokenizer.model_max_length,
228
+ truncation=True,
229
+ return_tensors="pt",
230
+ )
231
+ return text_inputs.input_ids.to(self.device)
232
+
233
+ def get_text_features(self, text_input_ids):
234
+ """
235
+ Extract text features from prompt
236
+ Returns: (encoder_hidden_states, text_features)
237
+ """
238
+ if self.do_classifier_free_guidance:
239
+ # Concatenate conditional and unconditional prompts
240
+ text_input_ids = torch.cat([
241
+ text_input_ids,
242
+ self.neg_prompt_ids.repeat(text_input_ids.shape[0], 1)
243
+ ], dim=0)
244
+
245
+ outputs = self.text_encoder(text_input_ids, return_dict=False)
246
+ encoder_hidden_states = outputs[0] # Sequence of hidden states
247
+ pooled_output = outputs[1] # Pooled output (last token)
248
+
249
+ if self.do_classifier_free_guidance:
250
+ pooled_output_text, pooled_output_ucond = pooled_output.chunk(2, dim=0)
251
+ text_features = self.text_projection(pooled_output_text)
252
+ else:
253
+ text_features = self.text_projection(pooled_output)
254
+
255
+ return encoder_hidden_states, text_features
256
+
257
+ def get_image_features(self, encoder_hidden_states, noisy_latents, timesteps):
258
+ """
259
+ Extract visual features from noisy latents using U-Net
260
+
261
+ Args:
262
+ encoder_hidden_states: Text conditioning from CLIP
263
+ noisy_latents: Noisy latent images [B, C, H, W]
264
+ timesteps: Denoising timesteps [B]
265
+
266
+ Returns:
267
+ image_features: Visual embeddings [B, projection_dim]
268
+ """
269
+ if self.do_classifier_free_guidance:
270
+ noisy_latents = torch.cat([noisy_latents] * 2, dim=0)
271
+ timesteps = torch.cat([timesteps] * 2, dim=0)
272
+
273
+ # Forward through U-Net (only down blocks + mid block, no up blocks)
274
+ mid_output, down_block_res_samples = self.unet(
275
+ noisy_latents,
276
+ timesteps,
277
+ encoder_hidden_states=encoder_hidden_states,
278
+ return_dict=False,
279
+ use_up_blocks=False
280
+ )
281
+
282
+ if self.multi_scale:
283
+ # Extract multi-scale features from down blocks
284
+ # Indices correspond to: [320, 64, 64], [640, 32, 32], [1280, 16, 16], [1280, 8, 8]
285
+ first_stage_output = down_block_res_samples[2] # 320 channels
286
+ second_stage_output = down_block_res_samples[5] # 640 channels
287
+ third_stage_output = down_block_res_samples[8] # 1280 channels
288
+ fourth_stage_output = down_block_res_samples[11] # 1280 channels
289
+
290
+ # Global average pooling
291
+ pooled_first = self.avg_pool(first_stage_output).squeeze(dim=[2, 3])
292
+ pooled_second = self.avg_pool(second_stage_output).squeeze(dim=[2, 3])
293
+ pooled_third = self.avg_pool(third_stage_output).squeeze(dim=[2, 3])
294
+ pooled_fourth = self.avg_pool(fourth_stage_output).squeeze(dim=[2, 3])
295
+ pooled_mid = self.avg_pool(mid_output).squeeze(dim=[2, 3])
296
+
297
+ # Apply VFE (Visual Feature Enhancement) on mid block
298
+ if self.do_classifier_free_guidance:
299
+ pooled_mid_text, pooled_mid_ucond = pooled_mid.chunk(2, dim=0)
300
+ pooled_mid = pooled_mid_ucond + self.guidance_scale * (pooled_mid_text - pooled_mid_ucond)
301
+
302
+ # For other blocks, optionally apply CFG or just use conditional branch
303
+ if self.multi_scale_cfg:
304
+ pooled_first_text, pooled_first_ucond = pooled_first.chunk(2, dim=0)
305
+ pooled_first = pooled_first_ucond + self.guidance_scale * (pooled_first_text - pooled_first_ucond)
306
+
307
+ pooled_second_text, pooled_second_ucond = pooled_second.chunk(2, dim=0)
308
+ pooled_second = pooled_second_ucond + self.guidance_scale * (pooled_second_text - pooled_second_ucond)
309
+
310
+ pooled_third_text, pooled_third_ucond = pooled_third.chunk(2, dim=0)
311
+ pooled_third = pooled_third_ucond + self.guidance_scale * (pooled_third_text - pooled_third_ucond)
312
+
313
+ pooled_fourth_text, pooled_fourth_ucond = pooled_fourth.chunk(2, dim=0)
314
+ pooled_fourth = pooled_fourth_ucond + self.guidance_scale * (pooled_fourth_text - pooled_fourth_ucond)
315
+ else:
316
+ # Use only conditional (text-conditioned) branch
317
+ pooled_first, _ = pooled_first.chunk(2, dim=0)
318
+ pooled_second, _ = pooled_second.chunk(2, dim=0)
319
+ pooled_third, _ = pooled_third.chunk(2, dim=0)
320
+ pooled_fourth, _ = pooled_fourth.chunk(2, dim=0)
321
+
322
+ # Concatenate all scales: 320 + 640 + 1280 + 1280 + 1280 = 4800
323
+ concat_pooled = torch.cat([
324
+ pooled_first, pooled_second, pooled_third, pooled_fourth, pooled_mid
325
+ ], dim=-1)
326
+
327
+ image_features = self.visual_projection(concat_pooled)
328
+ else:
329
+ # Single scale (mid block only)
330
+ pooled_mid = self.avg_pool(mid_output).squeeze(dim=[2, 3])
331
+ if self.do_classifier_free_guidance:
332
+ pooled_mid_text, pooled_mid_ucond = pooled_mid.chunk(2, dim=0)
333
+ pooled_mid = pooled_mid_ucond + self.guidance_scale * (pooled_mid_text - pooled_mid_ucond)
334
+ image_features = self.visual_projection(pooled_mid)
335
+
336
+ return image_features
337
+
338
+ def get_reward_score(self, noisy_latents, prompt, timesteps, enable_grad=False):
339
+ """
340
+ Compute preference score for noisy latents at given timesteps
341
+
342
+ Args:
343
+ noisy_latents: Noisy latent images [B, C, H, W]
344
+ prompt: Text prompt(s) (string or list of strings)
345
+ timesteps: Denoising timesteps [B] or scalar
346
+ enable_grad: If True, allows gradient computation (for gradient ascent)
347
+
348
+ Returns:
349
+ scores: Preference scores [B]
350
+ """
351
+ def _compute():
352
+ # Ensure inputs are on correct device
353
+ latents = noisy_latents.to(self.device, dtype=self.unet.dtype)
354
+
355
+ # Handle timesteps
356
+ if isinstance(timesteps, int):
357
+ ts = torch.tensor([timesteps] * latents.shape[0])
358
+ else:
359
+ ts = timesteps
360
+ ts = ts.to(self.device)
361
+
362
+ # Encode prompt
363
+ text_input_ids = self.encode_prompt(prompt)
364
+
365
+ # Get text and image features
366
+ encoder_hidden_states, text_features = self.get_text_features(text_input_ids)
367
+ image_features = self.get_image_features(encoder_hidden_states, latents, ts)
368
+
369
+ # Normalize features
370
+ image_features = image_features / torch.norm(image_features, dim=-1, keepdim=True)
371
+ text_features = text_features / torch.norm(text_features, dim=-1, keepdim=True)
372
+
373
+ # Compute similarity scores
374
+ scores = self.logit_scale.exp() * (text_features @ image_features.T)[0]
375
+ scores = torch.sigmoid(scores) # Scale to [0, 1]
376
+ return scores
377
+ # return scores
378
+
379
+ # If enable_grad is True, compute with gradients; otherwise use no_grad
380
+ if enable_grad:
381
+ return _compute()
382
+ else:
383
+ with torch.no_grad():
384
+ return _compute()
385
+
386
+ def forward(self, noisy_latents, prompt, timesteps):
387
+ """Alias for get_reward_score for nn.Module compatibility"""
388
+ return self.get_reward_score(noisy_latents, prompt, timesteps)
Reward_sd15_idealized/models/unet_2d_condition_reward.py ADDED
@@ -0,0 +1,1334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from dataclasses import dataclass
15
+ from typing import Any, Dict, List, Optional, Tuple, Union
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.utils.checkpoint
20
+
21
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
22
+ from diffusers.loaders import PeftAdapterMixin, UNet2DConditionLoadersMixin
23
+ from diffusers.loaders.single_file_model import FromOriginalModelMixin
24
+ from diffusers.utils import USE_PEFT_BACKEND, BaseOutput, deprecate, logging, scale_lora_layers, unscale_lora_layers
25
+ from diffusers.models.activations import get_activation
26
+ from diffusers.models.attention_processor import (
27
+ ADDED_KV_ATTENTION_PROCESSORS,
28
+ CROSS_ATTENTION_PROCESSORS,
29
+ Attention,
30
+ AttentionProcessor,
31
+ AttnAddedKVProcessor,
32
+ AttnProcessor,
33
+ FusedAttnProcessor2_0,
34
+ )
35
+ from diffusers.models.embeddings import (
36
+ GaussianFourierProjection,
37
+ GLIGENTextBoundingboxProjection,
38
+ ImageHintTimeEmbedding,
39
+ ImageProjection,
40
+ ImageTimeEmbedding,
41
+ TextImageProjection,
42
+ TextImageTimeEmbedding,
43
+ TextTimeEmbedding,
44
+ TimestepEmbedding,
45
+ Timesteps,
46
+ )
47
+ from diffusers.models.modeling_utils import ModelMixin
48
+ from diffusers.models.unets.unet_2d_blocks import (
49
+ get_down_block,
50
+ get_mid_block,
51
+ get_up_block,
52
+ )
53
+
54
+
55
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
56
+
57
+
58
+ @dataclass
59
+ class UNet2DConditionOutput(BaseOutput):
60
+ """
61
+ The output of [`UNet2DConditionModel`].
62
+
63
+ Args:
64
+ sample (`torch.Tensor` of shape `(batch_size, num_channels, height, width)`):
65
+ The hidden states output conditioned on `encoder_hidden_states` input. Output of last layer of model.
66
+ """
67
+
68
+ sample: torch.Tensor = None
69
+
70
+
71
+ class UNet2DConditionModel(
72
+ ModelMixin, ConfigMixin, FromOriginalModelMixin, UNet2DConditionLoadersMixin, PeftAdapterMixin
73
+ ):
74
+ r"""
75
+ A conditional 2D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample
76
+ shaped output.
77
+
78
+ This model inherits from [`ModelMixin`]. Check the superclass documentation for it's generic methods implemented
79
+ for all models (such as downloading or saving).
80
+
81
+ Parameters:
82
+ sample_size (`int` or `Tuple[int, int]`, *optional*, defaults to `None`):
83
+ Height and width of input/output sample.
84
+ in_channels (`int`, *optional*, defaults to 4): Number of channels in the input sample.
85
+ out_channels (`int`, *optional*, defaults to 4): Number of channels in the output.
86
+ center_input_sample (`bool`, *optional*, defaults to `False`): Whether to center the input sample.
87
+ flip_sin_to_cos (`bool`, *optional*, defaults to `True`):
88
+ Whether to flip the sin to cos in the time embedding.
89
+ freq_shift (`int`, *optional*, defaults to 0): The frequency shift to apply to the time embedding.
90
+ down_block_types (`Tuple[str]`, *optional*, defaults to `("CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "DownBlock2D")`):
91
+ The tuple of downsample blocks to use.
92
+ mid_block_type (`str`, *optional*, defaults to `"UNetMidBlock2DCrossAttn"`):
93
+ Block type for middle of UNet, it can be one of `UNetMidBlock2DCrossAttn`, `UNetMidBlock2D`, or
94
+ `UNetMidBlock2DSimpleCrossAttn`. If `None`, the mid block layer is skipped.
95
+ up_block_types (`Tuple[str]`, *optional*, defaults to `("UpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D")`):
96
+ The tuple of upsample blocks to use.
97
+ only_cross_attention(`bool` or `Tuple[bool]`, *optional*, default to `False`):
98
+ Whether to include self-attention in the basic transformer blocks, see
99
+ [`~models.attention.BasicTransformerBlock`].
100
+ block_out_channels (`Tuple[int]`, *optional*, defaults to `(320, 640, 1280, 1280)`):
101
+ The tuple of output channels for each block.
102
+ layers_per_block (`int`, *optional*, defaults to 2): The number of layers per block.
103
+ downsample_padding (`int`, *optional*, defaults to 1): The padding to use for the downsampling convolution.
104
+ mid_block_scale_factor (`float`, *optional*, defaults to 1.0): The scale factor to use for the mid block.
105
+ dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
106
+ act_fn (`str`, *optional*, defaults to `"silu"`): The activation function to use.
107
+ norm_num_groups (`int`, *optional*, defaults to 32): The number of groups to use for the normalization.
108
+ If `None`, normalization and activation layers is skipped in post-processing.
109
+ norm_eps (`float`, *optional*, defaults to 1e-5): The epsilon to use for the normalization.
110
+ cross_attention_dim (`int` or `Tuple[int]`, *optional*, defaults to 1280):
111
+ The dimension of the cross attention features.
112
+ transformer_layers_per_block (`int`, `Tuple[int]`, or `Tuple[Tuple]` , *optional*, defaults to 1):
113
+ The number of transformer blocks of type [`~models.attention.BasicTransformerBlock`]. Only relevant for
114
+ [`~models.unets.unet_2d_blocks.CrossAttnDownBlock2D`], [`~models.unets.unet_2d_blocks.CrossAttnUpBlock2D`],
115
+ [`~models.unets.unet_2d_blocks.UNetMidBlock2DCrossAttn`].
116
+ reverse_transformer_layers_per_block : (`Tuple[Tuple]`, *optional*, defaults to None):
117
+ The number of transformer blocks of type [`~models.attention.BasicTransformerBlock`], in the upsampling
118
+ blocks of the U-Net. Only relevant if `transformer_layers_per_block` is of type `Tuple[Tuple]` and for
119
+ [`~models.unets.unet_2d_blocks.CrossAttnDownBlock2D`], [`~models.unets.unet_2d_blocks.CrossAttnUpBlock2D`],
120
+ [`~models.unets.unet_2d_blocks.UNetMidBlock2DCrossAttn`].
121
+ encoder_hid_dim (`int`, *optional*, defaults to None):
122
+ If `encoder_hid_dim_type` is defined, `encoder_hidden_states` will be projected from `encoder_hid_dim`
123
+ dimension to `cross_attention_dim`.
124
+ encoder_hid_dim_type (`str`, *optional*, defaults to `None`):
125
+ If given, the `encoder_hidden_states` and potentially other embeddings are down-projected to text
126
+ embeddings of dimension `cross_attention` according to `encoder_hid_dim_type`.
127
+ attention_head_dim (`int`, *optional*, defaults to 8): The dimension of the attention heads.
128
+ num_attention_heads (`int`, *optional*):
129
+ The number of attention heads. If not defined, defaults to `attention_head_dim`
130
+ resnet_time_scale_shift (`str`, *optional*, defaults to `"default"`): Time scale shift config
131
+ for ResNet blocks (see [`~models.resnet.ResnetBlock2D`]). Choose from `default` or `scale_shift`.
132
+ class_embed_type (`str`, *optional*, defaults to `None`):
133
+ The type of class embedding to use which is ultimately summed with the time embeddings. Choose from `None`,
134
+ `"timestep"`, `"identity"`, `"projection"`, or `"simple_projection"`.
135
+ addition_embed_type (`str`, *optional*, defaults to `None`):
136
+ Configures an optional embedding which will be summed with the time embeddings. Choose from `None` or
137
+ "text". "text" will use the `TextTimeEmbedding` layer.
138
+ addition_time_embed_dim: (`int`, *optional*, defaults to `None`):
139
+ Dimension for the timestep embeddings.
140
+ num_class_embeds (`int`, *optional*, defaults to `None`):
141
+ Input dimension of the learnable embedding matrix to be projected to `time_embed_dim`, when performing
142
+ class conditioning with `class_embed_type` equal to `None`.
143
+ time_embedding_type (`str`, *optional*, defaults to `positional`):
144
+ The type of position embedding to use for timesteps. Choose from `positional` or `fourier`.
145
+ time_embedding_dim (`int`, *optional*, defaults to `None`):
146
+ An optional override for the dimension of the projected time embedding.
147
+ time_embedding_act_fn (`str`, *optional*, defaults to `None`):
148
+ Optional activation function to use only once on the time embeddings before they are passed to the rest of
149
+ the UNet. Choose from `silu`, `mish`, `gelu`, and `swish`.
150
+ timestep_post_act (`str`, *optional*, defaults to `None`):
151
+ The second activation function to use in timestep embedding. Choose from `silu`, `mish` and `gelu`.
152
+ time_cond_proj_dim (`int`, *optional*, defaults to `None`):
153
+ The dimension of `cond_proj` layer in the timestep embedding.
154
+ conv_in_kernel (`int`, *optional*, default to `3`): The kernel size of `conv_in` layer.
155
+ conv_out_kernel (`int`, *optional*, default to `3`): The kernel size of `conv_out` layer.
156
+ projection_class_embeddings_input_dim (`int`, *optional*): The dimension of the `class_labels` input when
157
+ `class_embed_type="projection"`. Required when `class_embed_type="projection"`.
158
+ class_embeddings_concat (`bool`, *optional*, defaults to `False`): Whether to concatenate the time
159
+ embeddings with the class embeddings.
160
+ mid_block_only_cross_attention (`bool`, *optional*, defaults to `None`):
161
+ Whether to use cross attention with the mid block when using the `UNetMidBlock2DSimpleCrossAttn`. If
162
+ `only_cross_attention` is given as a single boolean and `mid_block_only_cross_attention` is `None`, the
163
+ `only_cross_attention` value is used as the value for `mid_block_only_cross_attention`. Default to `False`
164
+ otherwise.
165
+ """
166
+
167
+ _supports_gradient_checkpointing = True
168
+ _no_split_modules = ["BasicTransformerBlock", "ResnetBlock2D", "CrossAttnUpBlock2D"]
169
+
170
+ @register_to_config
171
+ def __init__(
172
+ self,
173
+ sample_size: Optional[int] = None,
174
+ in_channels: int = 4,
175
+ out_channels: int = 4,
176
+ center_input_sample: bool = False,
177
+ flip_sin_to_cos: bool = True,
178
+ freq_shift: int = 0,
179
+ down_block_types: Tuple[str] = (
180
+ "CrossAttnDownBlock2D",
181
+ "CrossAttnDownBlock2D",
182
+ "CrossAttnDownBlock2D",
183
+ "DownBlock2D",
184
+ ),
185
+ mid_block_type: Optional[str] = "UNetMidBlock2DCrossAttn",
186
+ up_block_types: Tuple[str] = ("UpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D"),
187
+ only_cross_attention: Union[bool, Tuple[bool]] = False,
188
+ block_out_channels: Tuple[int] = (320, 640, 1280, 1280),
189
+ layers_per_block: Union[int, Tuple[int]] = 2,
190
+ downsample_padding: int = 1,
191
+ mid_block_scale_factor: float = 1,
192
+ dropout: float = 0.0,
193
+ act_fn: str = "silu",
194
+ norm_num_groups: Optional[int] = 32,
195
+ norm_eps: float = 1e-5,
196
+ cross_attention_dim: Union[int, Tuple[int]] = 1280,
197
+ transformer_layers_per_block: Union[int, Tuple[int], Tuple[Tuple]] = 1,
198
+ reverse_transformer_layers_per_block: Optional[Tuple[Tuple[int]]] = None,
199
+ encoder_hid_dim: Optional[int] = None,
200
+ encoder_hid_dim_type: Optional[str] = None,
201
+ attention_head_dim: Union[int, Tuple[int]] = 8,
202
+ num_attention_heads: Optional[Union[int, Tuple[int]]] = None,
203
+ dual_cross_attention: bool = False,
204
+ use_linear_projection: bool = False,
205
+ class_embed_type: Optional[str] = None,
206
+ addition_embed_type: Optional[str] = None,
207
+ addition_time_embed_dim: Optional[int] = None,
208
+ num_class_embeds: Optional[int] = None,
209
+ upcast_attention: bool = False,
210
+ resnet_time_scale_shift: str = "default",
211
+ resnet_skip_time_act: bool = False,
212
+ resnet_out_scale_factor: float = 1.0,
213
+ time_embedding_type: str = "positional",
214
+ time_embedding_dim: Optional[int] = None,
215
+ time_embedding_act_fn: Optional[str] = None,
216
+ timestep_post_act: Optional[str] = None,
217
+ time_cond_proj_dim: Optional[int] = None,
218
+ conv_in_kernel: int = 3,
219
+ conv_out_kernel: int = 3,
220
+ projection_class_embeddings_input_dim: Optional[int] = None,
221
+ attention_type: str = "default",
222
+ class_embeddings_concat: bool = False,
223
+ mid_block_only_cross_attention: Optional[bool] = None,
224
+ cross_attention_norm: Optional[str] = None,
225
+ addition_embed_type_num_heads: int = 64,
226
+ ):
227
+ super().__init__()
228
+
229
+ self.sample_size = sample_size
230
+
231
+ if num_attention_heads is not None:
232
+ raise ValueError(
233
+ "At the moment it is not possible to define the number of attention heads via `num_attention_heads` because of a naming issue as described in https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131. Passing `num_attention_heads` will only be supported in diffusers v0.19."
234
+ )
235
+
236
+ # If `num_attention_heads` is not defined (which is the case for most models)
237
+ # it will default to `attention_head_dim`. This looks weird upon first reading it and it is.
238
+ # The reason for this behavior is to correct for incorrectly named variables that were introduced
239
+ # when this library was created. The incorrect naming was only discovered much later in https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131
240
+ # Changing `attention_head_dim` to `num_attention_heads` for 40,000+ configurations is too backwards breaking
241
+ # which is why we correct for the naming here.
242
+ num_attention_heads = num_attention_heads or attention_head_dim
243
+
244
+ # Check inputs
245
+ self._check_config(
246
+ down_block_types=down_block_types,
247
+ up_block_types=up_block_types,
248
+ only_cross_attention=only_cross_attention,
249
+ block_out_channels=block_out_channels,
250
+ layers_per_block=layers_per_block,
251
+ cross_attention_dim=cross_attention_dim,
252
+ transformer_layers_per_block=transformer_layers_per_block,
253
+ reverse_transformer_layers_per_block=reverse_transformer_layers_per_block,
254
+ attention_head_dim=attention_head_dim,
255
+ num_attention_heads=num_attention_heads,
256
+ )
257
+
258
+ # input
259
+ conv_in_padding = (conv_in_kernel - 1) // 2
260
+ self.conv_in = nn.Conv2d(
261
+ in_channels, block_out_channels[0], kernel_size=conv_in_kernel, padding=conv_in_padding
262
+ )
263
+
264
+ # time
265
+ time_embed_dim, timestep_input_dim = self._set_time_proj(
266
+ time_embedding_type,
267
+ block_out_channels=block_out_channels,
268
+ flip_sin_to_cos=flip_sin_to_cos,
269
+ freq_shift=freq_shift,
270
+ time_embedding_dim=time_embedding_dim,
271
+ )
272
+
273
+ self.time_embedding = TimestepEmbedding(
274
+ timestep_input_dim,
275
+ time_embed_dim,
276
+ act_fn=act_fn,
277
+ post_act_fn=timestep_post_act,
278
+ cond_proj_dim=time_cond_proj_dim,
279
+ )
280
+
281
+ self._set_encoder_hid_proj(
282
+ encoder_hid_dim_type,
283
+ cross_attention_dim=cross_attention_dim,
284
+ encoder_hid_dim=encoder_hid_dim,
285
+ )
286
+
287
+ # class embedding
288
+ self._set_class_embedding(
289
+ class_embed_type,
290
+ act_fn=act_fn,
291
+ num_class_embeds=num_class_embeds,
292
+ projection_class_embeddings_input_dim=projection_class_embeddings_input_dim,
293
+ time_embed_dim=time_embed_dim,
294
+ timestep_input_dim=timestep_input_dim,
295
+ )
296
+
297
+ self._set_add_embedding(
298
+ addition_embed_type,
299
+ addition_embed_type_num_heads=addition_embed_type_num_heads,
300
+ addition_time_embed_dim=addition_time_embed_dim,
301
+ cross_attention_dim=cross_attention_dim,
302
+ encoder_hid_dim=encoder_hid_dim,
303
+ flip_sin_to_cos=flip_sin_to_cos,
304
+ freq_shift=freq_shift,
305
+ projection_class_embeddings_input_dim=projection_class_embeddings_input_dim,
306
+ time_embed_dim=time_embed_dim,
307
+ )
308
+
309
+ if time_embedding_act_fn is None:
310
+ self.time_embed_act = None
311
+ else:
312
+ self.time_embed_act = get_activation(time_embedding_act_fn)
313
+
314
+ self.down_blocks = nn.ModuleList([])
315
+ self.up_blocks = nn.ModuleList([])
316
+
317
+ if isinstance(only_cross_attention, bool):
318
+ if mid_block_only_cross_attention is None:
319
+ mid_block_only_cross_attention = only_cross_attention
320
+
321
+ only_cross_attention = [only_cross_attention] * len(down_block_types)
322
+
323
+ if mid_block_only_cross_attention is None:
324
+ mid_block_only_cross_attention = False
325
+
326
+ if isinstance(num_attention_heads, int):
327
+ num_attention_heads = (num_attention_heads,) * len(down_block_types)
328
+
329
+ if isinstance(attention_head_dim, int):
330
+ attention_head_dim = (attention_head_dim,) * len(down_block_types)
331
+
332
+ if isinstance(cross_attention_dim, int):
333
+ cross_attention_dim = (cross_attention_dim,) * len(down_block_types)
334
+
335
+ if isinstance(layers_per_block, int):
336
+ layers_per_block = [layers_per_block] * len(down_block_types)
337
+
338
+ if isinstance(transformer_layers_per_block, int):
339
+ transformer_layers_per_block = [transformer_layers_per_block] * len(down_block_types)
340
+
341
+ if class_embeddings_concat:
342
+ # The time embeddings are concatenated with the class embeddings. The dimension of the
343
+ # time embeddings passed to the down, middle, and up blocks is twice the dimension of the
344
+ # regular time embeddings
345
+ blocks_time_embed_dim = time_embed_dim * 2
346
+ else:
347
+ blocks_time_embed_dim = time_embed_dim
348
+
349
+ # down
350
+ output_channel = block_out_channels[0]
351
+ for i, down_block_type in enumerate(down_block_types):
352
+ input_channel = output_channel
353
+ output_channel = block_out_channels[i]
354
+ is_final_block = i == len(block_out_channels) - 1
355
+
356
+ down_block = get_down_block(
357
+ down_block_type,
358
+ num_layers=layers_per_block[i],
359
+ transformer_layers_per_block=transformer_layers_per_block[i],
360
+ in_channels=input_channel,
361
+ out_channels=output_channel,
362
+ temb_channels=blocks_time_embed_dim,
363
+ add_downsample=not is_final_block,
364
+ resnet_eps=norm_eps,
365
+ resnet_act_fn=act_fn,
366
+ resnet_groups=norm_num_groups,
367
+ cross_attention_dim=cross_attention_dim[i],
368
+ num_attention_heads=num_attention_heads[i],
369
+ downsample_padding=downsample_padding,
370
+ dual_cross_attention=dual_cross_attention,
371
+ use_linear_projection=use_linear_projection,
372
+ only_cross_attention=only_cross_attention[i],
373
+ upcast_attention=upcast_attention,
374
+ resnet_time_scale_shift=resnet_time_scale_shift,
375
+ attention_type=attention_type,
376
+ resnet_skip_time_act=resnet_skip_time_act,
377
+ resnet_out_scale_factor=resnet_out_scale_factor,
378
+ cross_attention_norm=cross_attention_norm,
379
+ attention_head_dim=attention_head_dim[i] if attention_head_dim[i] is not None else output_channel,
380
+ dropout=dropout,
381
+ )
382
+ self.down_blocks.append(down_block)
383
+
384
+ # mid
385
+ self.mid_block = get_mid_block(
386
+ mid_block_type,
387
+ temb_channels=blocks_time_embed_dim,
388
+ in_channels=block_out_channels[-1],
389
+ resnet_eps=norm_eps,
390
+ resnet_act_fn=act_fn,
391
+ resnet_groups=norm_num_groups,
392
+ output_scale_factor=mid_block_scale_factor,
393
+ transformer_layers_per_block=transformer_layers_per_block[-1],
394
+ num_attention_heads=num_attention_heads[-1],
395
+ cross_attention_dim=cross_attention_dim[-1],
396
+ dual_cross_attention=dual_cross_attention,
397
+ use_linear_projection=use_linear_projection,
398
+ mid_block_only_cross_attention=mid_block_only_cross_attention,
399
+ upcast_attention=upcast_attention,
400
+ resnet_time_scale_shift=resnet_time_scale_shift,
401
+ attention_type=attention_type,
402
+ resnet_skip_time_act=resnet_skip_time_act,
403
+ cross_attention_norm=cross_attention_norm,
404
+ attention_head_dim=attention_head_dim[-1],
405
+ dropout=dropout,
406
+ )
407
+
408
+ # count how many layers upsample the images
409
+ self.num_upsamplers = 0
410
+
411
+ # up
412
+ reversed_block_out_channels = list(reversed(block_out_channels))
413
+ reversed_num_attention_heads = list(reversed(num_attention_heads))
414
+ reversed_layers_per_block = list(reversed(layers_per_block))
415
+ reversed_cross_attention_dim = list(reversed(cross_attention_dim))
416
+ reversed_transformer_layers_per_block = (
417
+ list(reversed(transformer_layers_per_block))
418
+ if reverse_transformer_layers_per_block is None
419
+ else reverse_transformer_layers_per_block
420
+ )
421
+ only_cross_attention = list(reversed(only_cross_attention))
422
+
423
+ output_channel = reversed_block_out_channels[0]
424
+ for i, up_block_type in enumerate(up_block_types):
425
+ is_final_block = i == len(block_out_channels) - 1
426
+
427
+ prev_output_channel = output_channel
428
+ output_channel = reversed_block_out_channels[i]
429
+ input_channel = reversed_block_out_channels[min(i + 1, len(block_out_channels) - 1)]
430
+
431
+ # add upsample block for all BUT final layer
432
+ if not is_final_block:
433
+ add_upsample = True
434
+ self.num_upsamplers += 1
435
+ else:
436
+ add_upsample = False
437
+
438
+ up_block = get_up_block(
439
+ up_block_type,
440
+ num_layers=reversed_layers_per_block[i] + 1,
441
+ transformer_layers_per_block=reversed_transformer_layers_per_block[i],
442
+ in_channels=input_channel,
443
+ out_channels=output_channel,
444
+ prev_output_channel=prev_output_channel,
445
+ temb_channels=blocks_time_embed_dim,
446
+ add_upsample=add_upsample,
447
+ resnet_eps=norm_eps,
448
+ resnet_act_fn=act_fn,
449
+ resolution_idx=i,
450
+ resnet_groups=norm_num_groups,
451
+ cross_attention_dim=reversed_cross_attention_dim[i],
452
+ num_attention_heads=reversed_num_attention_heads[i],
453
+ dual_cross_attention=dual_cross_attention,
454
+ use_linear_projection=use_linear_projection,
455
+ only_cross_attention=only_cross_attention[i],
456
+ upcast_attention=upcast_attention,
457
+ resnet_time_scale_shift=resnet_time_scale_shift,
458
+ attention_type=attention_type,
459
+ resnet_skip_time_act=resnet_skip_time_act,
460
+ resnet_out_scale_factor=resnet_out_scale_factor,
461
+ cross_attention_norm=cross_attention_norm,
462
+ attention_head_dim=attention_head_dim[i] if attention_head_dim[i] is not None else output_channel,
463
+ dropout=dropout,
464
+ )
465
+ self.up_blocks.append(up_block)
466
+ prev_output_channel = output_channel
467
+
468
+ # out
469
+ if norm_num_groups is not None:
470
+ self.conv_norm_out = nn.GroupNorm(
471
+ num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps
472
+ )
473
+
474
+ self.conv_act = get_activation(act_fn)
475
+
476
+ else:
477
+ self.conv_norm_out = None
478
+ self.conv_act = None
479
+
480
+ conv_out_padding = (conv_out_kernel - 1) // 2
481
+ self.conv_out = nn.Conv2d(
482
+ block_out_channels[0], out_channels, kernel_size=conv_out_kernel, padding=conv_out_padding
483
+ )
484
+
485
+ self._set_pos_net_if_use_gligen(attention_type=attention_type, cross_attention_dim=cross_attention_dim)
486
+
487
+ def _check_config(
488
+ self,
489
+ down_block_types: Tuple[str],
490
+ up_block_types: Tuple[str],
491
+ only_cross_attention: Union[bool, Tuple[bool]],
492
+ block_out_channels: Tuple[int],
493
+ layers_per_block: Union[int, Tuple[int]],
494
+ cross_attention_dim: Union[int, Tuple[int]],
495
+ transformer_layers_per_block: Union[int, Tuple[int], Tuple[Tuple[int]]],
496
+ reverse_transformer_layers_per_block: bool,
497
+ attention_head_dim: int,
498
+ num_attention_heads: Optional[Union[int, Tuple[int]]],
499
+ ):
500
+ if len(down_block_types) != len(up_block_types):
501
+ raise ValueError(
502
+ f"Must provide the same number of `down_block_types` as `up_block_types`. `down_block_types`: {down_block_types}. `up_block_types`: {up_block_types}."
503
+ )
504
+
505
+ if len(block_out_channels) != len(down_block_types):
506
+ raise ValueError(
507
+ f"Must provide the same number of `block_out_channels` as `down_block_types`. `block_out_channels`: {block_out_channels}. `down_block_types`: {down_block_types}."
508
+ )
509
+
510
+ if not isinstance(only_cross_attention, bool) and len(only_cross_attention) != len(down_block_types):
511
+ raise ValueError(
512
+ f"Must provide the same number of `only_cross_attention` as `down_block_types`. `only_cross_attention`: {only_cross_attention}. `down_block_types`: {down_block_types}."
513
+ )
514
+
515
+ if not isinstance(num_attention_heads, int) and len(num_attention_heads) != len(down_block_types):
516
+ raise ValueError(
517
+ f"Must provide the same number of `num_attention_heads` as `down_block_types`. `num_attention_heads`: {num_attention_heads}. `down_block_types`: {down_block_types}."
518
+ )
519
+
520
+ if not isinstance(attention_head_dim, int) and len(attention_head_dim) != len(down_block_types):
521
+ raise ValueError(
522
+ f"Must provide the same number of `attention_head_dim` as `down_block_types`. `attention_head_dim`: {attention_head_dim}. `down_block_types`: {down_block_types}."
523
+ )
524
+
525
+ if isinstance(cross_attention_dim, list) and len(cross_attention_dim) != len(down_block_types):
526
+ raise ValueError(
527
+ f"Must provide the same number of `cross_attention_dim` as `down_block_types`. `cross_attention_dim`: {cross_attention_dim}. `down_block_types`: {down_block_types}."
528
+ )
529
+
530
+ if not isinstance(layers_per_block, int) and len(layers_per_block) != len(down_block_types):
531
+ raise ValueError(
532
+ f"Must provide the same number of `layers_per_block` as `down_block_types`. `layers_per_block`: {layers_per_block}. `down_block_types`: {down_block_types}."
533
+ )
534
+ if isinstance(transformer_layers_per_block, list) and reverse_transformer_layers_per_block is None:
535
+ for layer_number_per_block in transformer_layers_per_block:
536
+ if isinstance(layer_number_per_block, list):
537
+ raise ValueError("Must provide 'reverse_transformer_layers_per_block` if using asymmetrical UNet.")
538
+
539
+ def _set_time_proj(
540
+ self,
541
+ time_embedding_type: str,
542
+ block_out_channels: int,
543
+ flip_sin_to_cos: bool,
544
+ freq_shift: float,
545
+ time_embedding_dim: int,
546
+ ) -> Tuple[int, int]:
547
+ if time_embedding_type == "fourier":
548
+ time_embed_dim = time_embedding_dim or block_out_channels[0] * 2
549
+ if time_embed_dim % 2 != 0:
550
+ raise ValueError(f"`time_embed_dim` should be divisible by 2, but is {time_embed_dim}.")
551
+ self.time_proj = GaussianFourierProjection(
552
+ time_embed_dim // 2, set_W_to_weight=False, log=False, flip_sin_to_cos=flip_sin_to_cos
553
+ )
554
+ timestep_input_dim = time_embed_dim
555
+ elif time_embedding_type == "positional":
556
+ time_embed_dim = time_embedding_dim or block_out_channels[0] * 4
557
+
558
+ self.time_proj = Timesteps(block_out_channels[0], flip_sin_to_cos, freq_shift)
559
+ timestep_input_dim = block_out_channels[0]
560
+ else:
561
+ raise ValueError(
562
+ f"{time_embedding_type} does not exist. Please make sure to use one of `fourier` or `positional`."
563
+ )
564
+
565
+ return time_embed_dim, timestep_input_dim
566
+
567
+ def _set_encoder_hid_proj(
568
+ self,
569
+ encoder_hid_dim_type: Optional[str],
570
+ cross_attention_dim: Union[int, Tuple[int]],
571
+ encoder_hid_dim: Optional[int],
572
+ ):
573
+ if encoder_hid_dim_type is None and encoder_hid_dim is not None:
574
+ encoder_hid_dim_type = "text_proj"
575
+ self.register_to_config(encoder_hid_dim_type=encoder_hid_dim_type)
576
+ logger.info("encoder_hid_dim_type defaults to 'text_proj' as `encoder_hid_dim` is defined.")
577
+
578
+ if encoder_hid_dim is None and encoder_hid_dim_type is not None:
579
+ raise ValueError(
580
+ f"`encoder_hid_dim` has to be defined when `encoder_hid_dim_type` is set to {encoder_hid_dim_type}."
581
+ )
582
+
583
+ if encoder_hid_dim_type == "text_proj":
584
+ self.encoder_hid_proj = nn.Linear(encoder_hid_dim, cross_attention_dim)
585
+ elif encoder_hid_dim_type == "text_image_proj":
586
+ # image_embed_dim DOESN'T have to be `cross_attention_dim`. To not clutter the __init__ too much
587
+ # they are set to `cross_attention_dim` here as this is exactly the required dimension for the currently only use
588
+ # case when `addition_embed_type == "text_image_proj"` (Kandinsky 2.1)`
589
+ self.encoder_hid_proj = TextImageProjection(
590
+ text_embed_dim=encoder_hid_dim,
591
+ image_embed_dim=cross_attention_dim,
592
+ cross_attention_dim=cross_attention_dim,
593
+ )
594
+ elif encoder_hid_dim_type == "image_proj":
595
+ # Kandinsky 2.2
596
+ self.encoder_hid_proj = ImageProjection(
597
+ image_embed_dim=encoder_hid_dim,
598
+ cross_attention_dim=cross_attention_dim,
599
+ )
600
+ elif encoder_hid_dim_type is not None:
601
+ raise ValueError(
602
+ f"encoder_hid_dim_type: {encoder_hid_dim_type} must be None, 'text_proj' or 'text_image_proj'."
603
+ )
604
+ else:
605
+ self.encoder_hid_proj = None
606
+
607
+ def _set_class_embedding(
608
+ self,
609
+ class_embed_type: Optional[str],
610
+ act_fn: str,
611
+ num_class_embeds: Optional[int],
612
+ projection_class_embeddings_input_dim: Optional[int],
613
+ time_embed_dim: int,
614
+ timestep_input_dim: int,
615
+ ):
616
+ if class_embed_type is None and num_class_embeds is not None:
617
+ self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim)
618
+ elif class_embed_type == "timestep":
619
+ self.class_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim, act_fn=act_fn)
620
+ elif class_embed_type == "identity":
621
+ self.class_embedding = nn.Identity(time_embed_dim, time_embed_dim)
622
+ elif class_embed_type == "projection":
623
+ if projection_class_embeddings_input_dim is None:
624
+ raise ValueError(
625
+ "`class_embed_type`: 'projection' requires `projection_class_embeddings_input_dim` be set"
626
+ )
627
+ # The projection `class_embed_type` is the same as the timestep `class_embed_type` except
628
+ # 1. the `class_labels` inputs are not first converted to sinusoidal embeddings
629
+ # 2. it projects from an arbitrary input dimension.
630
+ #
631
+ # Note that `TimestepEmbedding` is quite general, being mainly linear layers and activations.
632
+ # When used for embedding actual timesteps, the timesteps are first converted to sinusoidal embeddings.
633
+ # As a result, `TimestepEmbedding` can be passed arbitrary vectors.
634
+ self.class_embedding = TimestepEmbedding(projection_class_embeddings_input_dim, time_embed_dim)
635
+ elif class_embed_type == "simple_projection":
636
+ if projection_class_embeddings_input_dim is None:
637
+ raise ValueError(
638
+ "`class_embed_type`: 'simple_projection' requires `projection_class_embeddings_input_dim` be set"
639
+ )
640
+ self.class_embedding = nn.Linear(projection_class_embeddings_input_dim, time_embed_dim)
641
+ else:
642
+ self.class_embedding = None
643
+
644
+ def _set_add_embedding(
645
+ self,
646
+ addition_embed_type: str,
647
+ addition_embed_type_num_heads: int,
648
+ addition_time_embed_dim: Optional[int],
649
+ flip_sin_to_cos: bool,
650
+ freq_shift: float,
651
+ cross_attention_dim: Optional[int],
652
+ encoder_hid_dim: Optional[int],
653
+ projection_class_embeddings_input_dim: Optional[int],
654
+ time_embed_dim: int,
655
+ ):
656
+ if addition_embed_type == "text":
657
+ if encoder_hid_dim is not None:
658
+ text_time_embedding_from_dim = encoder_hid_dim
659
+ else:
660
+ text_time_embedding_from_dim = cross_attention_dim
661
+
662
+ self.add_embedding = TextTimeEmbedding(
663
+ text_time_embedding_from_dim, time_embed_dim, num_heads=addition_embed_type_num_heads
664
+ )
665
+ elif addition_embed_type == "text_image":
666
+ # text_embed_dim and image_embed_dim DON'T have to be `cross_attention_dim`. To not clutter the __init__ too much
667
+ # they are set to `cross_attention_dim` here as this is exactly the required dimension for the currently only use
668
+ # case when `addition_embed_type == "text_image"` (Kandinsky 2.1)`
669
+ self.add_embedding = TextImageTimeEmbedding(
670
+ text_embed_dim=cross_attention_dim, image_embed_dim=cross_attention_dim, time_embed_dim=time_embed_dim
671
+ )
672
+ elif addition_embed_type == "text_time":
673
+ self.add_time_proj = Timesteps(addition_time_embed_dim, flip_sin_to_cos, freq_shift)
674
+ self.add_embedding = TimestepEmbedding(projection_class_embeddings_input_dim, time_embed_dim)
675
+ elif addition_embed_type == "image":
676
+ # Kandinsky 2.2
677
+ self.add_embedding = ImageTimeEmbedding(image_embed_dim=encoder_hid_dim, time_embed_dim=time_embed_dim)
678
+ elif addition_embed_type == "image_hint":
679
+ # Kandinsky 2.2 ControlNet
680
+ self.add_embedding = ImageHintTimeEmbedding(image_embed_dim=encoder_hid_dim, time_embed_dim=time_embed_dim)
681
+ elif addition_embed_type is not None:
682
+ raise ValueError(f"addition_embed_type: {addition_embed_type} must be None, 'text' or 'text_image'.")
683
+
684
+ def _set_pos_net_if_use_gligen(self, attention_type: str, cross_attention_dim: int):
685
+ if attention_type in ["gated", "gated-text-image"]:
686
+ positive_len = 768
687
+ if isinstance(cross_attention_dim, int):
688
+ positive_len = cross_attention_dim
689
+ elif isinstance(cross_attention_dim, (list, tuple)):
690
+ positive_len = cross_attention_dim[0]
691
+
692
+ feature_type = "text-only" if attention_type == "gated" else "text-image"
693
+ self.position_net = GLIGENTextBoundingboxProjection(
694
+ positive_len=positive_len, out_dim=cross_attention_dim, feature_type=feature_type
695
+ )
696
+
697
+ @property
698
+ def attn_processors(self) -> Dict[str, AttentionProcessor]:
699
+ r"""
700
+ Returns:
701
+ `dict` of attention processors: A dictionary containing all attention processors used in the model with
702
+ indexed by its weight name.
703
+ """
704
+ # set recursively
705
+ processors = {}
706
+
707
+ def fn_recursive_add_processors(name: str, module: torch.nn.Module, processors: Dict[str, AttentionProcessor]):
708
+ if hasattr(module, "get_processor"):
709
+ processors[f"{name}.processor"] = module.get_processor()
710
+
711
+ for sub_name, child in module.named_children():
712
+ fn_recursive_add_processors(f"{name}.{sub_name}", child, processors)
713
+
714
+ return processors
715
+
716
+ for name, module in self.named_children():
717
+ fn_recursive_add_processors(name, module, processors)
718
+
719
+ return processors
720
+
721
+ def set_attn_processor(self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]]):
722
+ r"""
723
+ Sets the attention processor to use to compute attention.
724
+
725
+ Parameters:
726
+ processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`):
727
+ The instantiated processor class or a dictionary of processor classes that will be set as the processor
728
+ for **all** `Attention` layers.
729
+
730
+ If `processor` is a dict, the key needs to define the path to the corresponding cross attention
731
+ processor. This is strongly recommended when setting trainable attention processors.
732
+
733
+ """
734
+ count = len(self.attn_processors.keys())
735
+
736
+ if isinstance(processor, dict) and len(processor) != count:
737
+ raise ValueError(
738
+ f"A dict of processors was passed, but the number of processors {len(processor)} does not match the"
739
+ f" number of attention layers: {count}. Please make sure to pass {count} processor classes."
740
+ )
741
+
742
+ def fn_recursive_attn_processor(name: str, module: torch.nn.Module, processor):
743
+ if hasattr(module, "set_processor"):
744
+ if not isinstance(processor, dict):
745
+ module.set_processor(processor)
746
+ else:
747
+ module.set_processor(processor.pop(f"{name}.processor"))
748
+
749
+ for sub_name, child in module.named_children():
750
+ fn_recursive_attn_processor(f"{name}.{sub_name}", child, processor)
751
+
752
+ for name, module in self.named_children():
753
+ fn_recursive_attn_processor(name, module, processor)
754
+
755
+ def set_default_attn_processor(self):
756
+ """
757
+ Disables custom attention processors and sets the default attention implementation.
758
+ """
759
+ if all(proc.__class__ in ADDED_KV_ATTENTION_PROCESSORS for proc in self.attn_processors.values()):
760
+ processor = AttnAddedKVProcessor()
761
+ elif all(proc.__class__ in CROSS_ATTENTION_PROCESSORS for proc in self.attn_processors.values()):
762
+ processor = AttnProcessor()
763
+ else:
764
+ raise ValueError(
765
+ f"Cannot call `set_default_attn_processor` when attention processors are of type {next(iter(self.attn_processors.values()))}"
766
+ )
767
+
768
+ self.set_attn_processor(processor)
769
+
770
+ def set_attention_slice(self, slice_size: Union[str, int, List[int]] = "auto"):
771
+ r"""
772
+ Enable sliced attention computation.
773
+
774
+ When this option is enabled, the attention module splits the input tensor in slices to compute attention in
775
+ several steps. This is useful for saving some memory in exchange for a small decrease in speed.
776
+
777
+ Args:
778
+ slice_size (`str` or `int` or `list(int)`, *optional*, defaults to `"auto"`):
779
+ When `"auto"`, input to the attention heads is halved, so attention is computed in two steps. If
780
+ `"max"`, maximum amount of memory is saved by running only one slice at a time. If a number is
781
+ provided, uses as many slices as `attention_head_dim // slice_size`. In this case, `attention_head_dim`
782
+ must be a multiple of `slice_size`.
783
+ """
784
+ sliceable_head_dims = []
785
+
786
+ def fn_recursive_retrieve_sliceable_dims(module: torch.nn.Module):
787
+ if hasattr(module, "set_attention_slice"):
788
+ sliceable_head_dims.append(module.sliceable_head_dim)
789
+
790
+ for child in module.children():
791
+ fn_recursive_retrieve_sliceable_dims(child)
792
+
793
+ # retrieve number of attention layers
794
+ for module in self.children():
795
+ fn_recursive_retrieve_sliceable_dims(module)
796
+
797
+ num_sliceable_layers = len(sliceable_head_dims)
798
+
799
+ if slice_size == "auto":
800
+ # half the attention head size is usually a good trade-off between
801
+ # speed and memory
802
+ slice_size = [dim // 2 for dim in sliceable_head_dims]
803
+ elif slice_size == "max":
804
+ # make smallest slice possible
805
+ slice_size = num_sliceable_layers * [1]
806
+
807
+ slice_size = num_sliceable_layers * [slice_size] if not isinstance(slice_size, list) else slice_size
808
+
809
+ if len(slice_size) != len(sliceable_head_dims):
810
+ raise ValueError(
811
+ f"You have provided {len(slice_size)}, but {self.config} has {len(sliceable_head_dims)} different"
812
+ f" attention layers. Make sure to match `len(slice_size)` to be {len(sliceable_head_dims)}."
813
+ )
814
+
815
+ for i in range(len(slice_size)):
816
+ size = slice_size[i]
817
+ dim = sliceable_head_dims[i]
818
+ if size is not None and size > dim:
819
+ raise ValueError(f"size {size} has to be smaller or equal to {dim}.")
820
+
821
+ # Recursively walk through all the children.
822
+ # Any children which exposes the set_attention_slice method
823
+ # gets the message
824
+ def fn_recursive_set_attention_slice(module: torch.nn.Module, slice_size: List[int]):
825
+ if hasattr(module, "set_attention_slice"):
826
+ module.set_attention_slice(slice_size.pop())
827
+
828
+ for child in module.children():
829
+ fn_recursive_set_attention_slice(child, slice_size)
830
+
831
+ reversed_slice_size = list(reversed(slice_size))
832
+ for module in self.children():
833
+ fn_recursive_set_attention_slice(module, reversed_slice_size)
834
+
835
+ def _set_gradient_checkpointing(self, module, value=False):
836
+ if hasattr(module, "gradient_checkpointing"):
837
+ module.gradient_checkpointing = value
838
+
839
+ def enable_freeu(self, s1: float, s2: float, b1: float, b2: float):
840
+ r"""Enables the FreeU mechanism from https://arxiv.org/abs/2309.11497.
841
+
842
+ The suffixes after the scaling factors represent the stage blocks where they are being applied.
843
+
844
+ Please refer to the [official repository](https://github.com/ChenyangSi/FreeU) for combinations of values that
845
+ are known to work well for different pipelines such as Stable Diffusion v1, v2, and Stable Diffusion XL.
846
+
847
+ Args:
848
+ s1 (`float`):
849
+ Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to
850
+ mitigate the "oversmoothing effect" in the enhanced denoising process.
851
+ s2 (`float`):
852
+ Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to
853
+ mitigate the "oversmoothing effect" in the enhanced denoising process.
854
+ b1 (`float`): Scaling factor for stage 1 to amplify the contributions of backbone features.
855
+ b2 (`float`): Scaling factor for stage 2 to amplify the contributions of backbone features.
856
+ """
857
+ for i, upsample_block in enumerate(self.up_blocks):
858
+ setattr(upsample_block, "s1", s1)
859
+ setattr(upsample_block, "s2", s2)
860
+ setattr(upsample_block, "b1", b1)
861
+ setattr(upsample_block, "b2", b2)
862
+
863
+ def disable_freeu(self):
864
+ """Disables the FreeU mechanism."""
865
+ freeu_keys = {"s1", "s2", "b1", "b2"}
866
+ for i, upsample_block in enumerate(self.up_blocks):
867
+ for k in freeu_keys:
868
+ if hasattr(upsample_block, k) or getattr(upsample_block, k, None) is not None:
869
+ setattr(upsample_block, k, None)
870
+
871
+ def fuse_qkv_projections(self):
872
+ """
873
+ Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, key, value)
874
+ are fused. For cross-attention modules, key and value projection matrices are fused.
875
+
876
+ <Tip warning={true}>
877
+
878
+ This API is 🧪 experimental.
879
+
880
+ </Tip>
881
+ """
882
+ self.original_attn_processors = None
883
+
884
+ for _, attn_processor in self.attn_processors.items():
885
+ if "Added" in str(attn_processor.__class__.__name__):
886
+ raise ValueError("`fuse_qkv_projections()` is not supported for models having added KV projections.")
887
+
888
+ self.original_attn_processors = self.attn_processors
889
+
890
+ for module in self.modules():
891
+ if isinstance(module, Attention):
892
+ module.fuse_projections(fuse=True)
893
+
894
+ self.set_attn_processor(FusedAttnProcessor2_0())
895
+
896
+ def unfuse_qkv_projections(self):
897
+ """Disables the fused QKV projection if enabled.
898
+
899
+ <Tip warning={true}>
900
+
901
+ This API is 🧪 experimental.
902
+
903
+ </Tip>
904
+
905
+ """
906
+ if self.original_attn_processors is not None:
907
+ self.set_attn_processor(self.original_attn_processors)
908
+
909
+ def get_time_embed(
910
+ self, sample: torch.Tensor, timestep: Union[torch.Tensor, float, int]
911
+ ) -> Optional[torch.Tensor]:
912
+ timesteps = timestep
913
+ if not torch.is_tensor(timesteps):
914
+ # TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can
915
+ # This would be a good case for the `match` statement (Python 3.10+)
916
+ is_mps = sample.device.type == "mps"
917
+ if isinstance(timestep, float):
918
+ dtype = torch.float32 if is_mps else torch.float64
919
+ else:
920
+ dtype = torch.int32 if is_mps else torch.int64
921
+ timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device)
922
+ elif len(timesteps.shape) == 0:
923
+ timesteps = timesteps[None].to(sample.device)
924
+
925
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
926
+ timesteps = timesteps.expand(sample.shape[0])
927
+
928
+ t_emb = self.time_proj(timesteps)
929
+ # `Timesteps` does not contain any weights and will always return f32 tensors
930
+ # but time_embedding might actually be running in fp16. so we need to cast here.
931
+ # there might be better ways to encapsulate this.
932
+ t_emb = t_emb.to(dtype=sample.dtype)
933
+ return t_emb
934
+
935
+ def get_class_embed(self, sample: torch.Tensor, class_labels: Optional[torch.Tensor]) -> Optional[torch.Tensor]:
936
+ class_emb = None
937
+ if self.class_embedding is not None:
938
+ if class_labels is None:
939
+ raise ValueError("class_labels should be provided when num_class_embeds > 0")
940
+
941
+ if self.config.class_embed_type == "timestep":
942
+ class_labels = self.time_proj(class_labels)
943
+
944
+ # `Timesteps` does not contain any weights and will always return f32 tensors
945
+ # there might be better ways to encapsulate this.
946
+ class_labels = class_labels.to(dtype=sample.dtype)
947
+
948
+ class_emb = self.class_embedding(class_labels).to(dtype=sample.dtype)
949
+ return class_emb
950
+
951
+ def get_aug_embed(
952
+ self, emb: torch.Tensor, encoder_hidden_states: torch.Tensor, added_cond_kwargs: Dict[str, Any]
953
+ ) -> Optional[torch.Tensor]:
954
+ aug_emb = None
955
+ if self.config.addition_embed_type == "text":
956
+ aug_emb = self.add_embedding(encoder_hidden_states)
957
+ elif self.config.addition_embed_type == "text_image":
958
+ # Kandinsky 2.1 - style
959
+ if "image_embeds" not in added_cond_kwargs:
960
+ raise ValueError(
961
+ f"{self.__class__} has the config param `addition_embed_type` set to 'text_image' which requires the keyword argument `image_embeds` to be passed in `added_cond_kwargs`"
962
+ )
963
+
964
+ image_embs = added_cond_kwargs.get("image_embeds")
965
+ text_embs = added_cond_kwargs.get("text_embeds", encoder_hidden_states)
966
+ aug_emb = self.add_embedding(text_embs, image_embs)
967
+ elif self.config.addition_embed_type == "text_time":
968
+ # SDXL - style
969
+ if "text_embeds" not in added_cond_kwargs:
970
+ raise ValueError(
971
+ f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `text_embeds` to be passed in `added_cond_kwargs`"
972
+ )
973
+ text_embeds = added_cond_kwargs.get("text_embeds")
974
+ if "time_ids" not in added_cond_kwargs:
975
+ raise ValueError(
976
+ f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `time_ids` to be passed in `added_cond_kwargs`"
977
+ )
978
+ time_ids = added_cond_kwargs.get("time_ids")
979
+ time_embeds = self.add_time_proj(time_ids.flatten())
980
+ time_embeds = time_embeds.reshape((text_embeds.shape[0], -1))
981
+ add_embeds = torch.concat([text_embeds, time_embeds], dim=-1)
982
+ add_embeds = add_embeds.to(emb.dtype)
983
+ aug_emb = self.add_embedding(add_embeds)
984
+ elif self.config.addition_embed_type == "image":
985
+ # Kandinsky 2.2 - style
986
+ if "image_embeds" not in added_cond_kwargs:
987
+ raise ValueError(
988
+ f"{self.__class__} has the config param `addition_embed_type` set to 'image' which requires the keyword argument `image_embeds` to be passed in `added_cond_kwargs`"
989
+ )
990
+ image_embs = added_cond_kwargs.get("image_embeds")
991
+ aug_emb = self.add_embedding(image_embs)
992
+ elif self.config.addition_embed_type == "image_hint":
993
+ # Kandinsky 2.2 - style
994
+ if "image_embeds" not in added_cond_kwargs or "hint" not in added_cond_kwargs:
995
+ raise ValueError(
996
+ f"{self.__class__} has the config param `addition_embed_type` set to 'image_hint' which requires the keyword arguments `image_embeds` and `hint` to be passed in `added_cond_kwargs`"
997
+ )
998
+ image_embs = added_cond_kwargs.get("image_embeds")
999
+ hint = added_cond_kwargs.get("hint")
1000
+ aug_emb = self.add_embedding(image_embs, hint)
1001
+ return aug_emb
1002
+
1003
+ def process_encoder_hidden_states(
1004
+ self, encoder_hidden_states: torch.Tensor, added_cond_kwargs: Dict[str, Any]
1005
+ ) -> torch.Tensor:
1006
+ if self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "text_proj":
1007
+ encoder_hidden_states = self.encoder_hid_proj(encoder_hidden_states)
1008
+ elif self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "text_image_proj":
1009
+ # Kandinsky 2.1 - style
1010
+ if "image_embeds" not in added_cond_kwargs:
1011
+ raise ValueError(
1012
+ f"{self.__class__} has the config param `encoder_hid_dim_type` set to 'text_image_proj' which requires the keyword argument `image_embeds` to be passed in `added_conditions`"
1013
+ )
1014
+
1015
+ image_embeds = added_cond_kwargs.get("image_embeds")
1016
+ encoder_hidden_states = self.encoder_hid_proj(encoder_hidden_states, image_embeds)
1017
+ elif self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "image_proj":
1018
+ # Kandinsky 2.2 - style
1019
+ if "image_embeds" not in added_cond_kwargs:
1020
+ raise ValueError(
1021
+ f"{self.__class__} has the config param `encoder_hid_dim_type` set to 'image_proj' which requires the keyword argument `image_embeds` to be passed in `added_conditions`"
1022
+ )
1023
+ image_embeds = added_cond_kwargs.get("image_embeds")
1024
+ encoder_hidden_states = self.encoder_hid_proj(image_embeds)
1025
+ elif self.encoder_hid_proj is not None and self.config.encoder_hid_dim_type == "ip_image_proj":
1026
+ if "image_embeds" not in added_cond_kwargs:
1027
+ raise ValueError(
1028
+ f"{self.__class__} has the config param `encoder_hid_dim_type` set to 'ip_image_proj' which requires the keyword argument `image_embeds` to be passed in `added_conditions`"
1029
+ )
1030
+
1031
+ if hasattr(self, "text_encoder_hid_proj") and self.text_encoder_hid_proj is not None:
1032
+ encoder_hidden_states = self.text_encoder_hid_proj(encoder_hidden_states)
1033
+
1034
+ image_embeds = added_cond_kwargs.get("image_embeds")
1035
+ image_embeds = self.encoder_hid_proj(image_embeds)
1036
+ encoder_hidden_states = (encoder_hidden_states, image_embeds)
1037
+ return encoder_hidden_states
1038
+
1039
+ def forward(
1040
+ self,
1041
+ sample: torch.Tensor,
1042
+ timestep: Union[torch.Tensor, float, int],
1043
+ encoder_hidden_states: torch.Tensor,
1044
+ class_labels: Optional[torch.Tensor] = None,
1045
+ timestep_cond: Optional[torch.Tensor] = None,
1046
+ attention_mask: Optional[torch.Tensor] = None,
1047
+ cross_attention_kwargs: Optional[Dict[str, Any]] = None,
1048
+ added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None,
1049
+ down_block_additional_residuals: Optional[Tuple[torch.Tensor]] = None,
1050
+ mid_block_additional_residual: Optional[torch.Tensor] = None,
1051
+ down_intrablock_additional_residuals: Optional[Tuple[torch.Tensor]] = None,
1052
+ encoder_attention_mask: Optional[torch.Tensor] = None,
1053
+ use_up_blocks: bool = False,
1054
+ return_dict: bool = True,
1055
+ ) -> Union[UNet2DConditionOutput, Tuple]:
1056
+ r"""
1057
+ The [`UNet2DConditionModel`] forward method.
1058
+
1059
+ Args:
1060
+ sample (`torch.Tensor`):
1061
+ The noisy input tensor with the following shape `(batch, channel, height, width)`.
1062
+ timestep (`torch.Tensor` or `float` or `int`): The number of timesteps to denoise an input.
1063
+ encoder_hidden_states (`torch.Tensor`):
1064
+ The encoder hidden states with shape `(batch, sequence_length, feature_dim)`.
1065
+ class_labels (`torch.Tensor`, *optional*, defaults to `None`):
1066
+ Optional class labels for conditioning. Their embeddings will be summed with the timestep embeddings.
1067
+ timestep_cond: (`torch.Tensor`, *optional*, defaults to `None`):
1068
+ Conditional embeddings for timestep. If provided, the embeddings will be summed with the samples passed
1069
+ through the `self.time_embedding` layer to obtain the timestep embeddings.
1070
+ attention_mask (`torch.Tensor`, *optional*, defaults to `None`):
1071
+ An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask
1072
+ is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large
1073
+ negative values to the attention scores corresponding to "discard" tokens.
1074
+ cross_attention_kwargs (`dict`, *optional*):
1075
+ A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
1076
+ `self.processor` in
1077
+ [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
1078
+ added_cond_kwargs: (`dict`, *optional*):
1079
+ A kwargs dictionary containing additional embeddings that if specified are added to the embeddings that
1080
+ are passed along to the UNet blocks.
1081
+ down_block_additional_residuals: (`tuple` of `torch.Tensor`, *optional*):
1082
+ A tuple of tensors that if specified are added to the residuals of down unet blocks.
1083
+ mid_block_additional_residual: (`torch.Tensor`, *optional*):
1084
+ A tensor that if specified is added to the residual of the middle unet block.
1085
+ down_intrablock_additional_residuals (`tuple` of `torch.Tensor`, *optional*):
1086
+ additional residuals to be added within UNet down blocks, for example from T2I-Adapter side model(s)
1087
+ encoder_attention_mask (`torch.Tensor`):
1088
+ A cross-attention mask of shape `(batch, sequence_length)` is applied to `encoder_hidden_states`. If
1089
+ `True` the mask is kept, otherwise if `False` it is discarded. Mask will be converted into a bias,
1090
+ which adds large negative values to the attention scores corresponding to "discard" tokens.
1091
+ return_dict (`bool`, *optional*, defaults to `True`):
1092
+ Whether or not to return a [`~models.unets.unet_2d_condition.UNet2DConditionOutput`] instead of a plain
1093
+ tuple.
1094
+
1095
+ Returns:
1096
+ [`~models.unets.unet_2d_condition.UNet2DConditionOutput`] or `tuple`:
1097
+ If `return_dict` is True, an [`~models.unets.unet_2d_condition.UNet2DConditionOutput`] is returned,
1098
+ otherwise a `tuple` is returned where the first element is the sample tensor.
1099
+ """
1100
+ # By default samples have to be AT least a multiple of the overall upsampling factor.
1101
+ # The overall upsampling factor is equal to 2 ** (# num of upsampling layers).
1102
+ # However, the upsampling interpolation output size can be forced to fit any upsampling size
1103
+ # on the fly if necessary.
1104
+ default_overall_up_factor = 2**self.num_upsamplers
1105
+
1106
+ # upsample size should be forwarded when sample is not a multiple of `default_overall_up_factor`
1107
+ forward_upsample_size = False
1108
+ upsample_size = None
1109
+
1110
+ # import time
1111
+ # torch.cuda.synchronize()
1112
+ # start_time = time.time()
1113
+
1114
+ for dim in sample.shape[-2:]:
1115
+ if dim % default_overall_up_factor != 0:
1116
+ # Forward upsample size to force interpolation output size.
1117
+ forward_upsample_size = True
1118
+ break
1119
+
1120
+ # ensure attention_mask is a bias, and give it a singleton query_tokens dimension
1121
+ # expects mask of shape:
1122
+ # [batch, key_tokens]
1123
+ # adds singleton query_tokens dimension:
1124
+ # [batch, 1, key_tokens]
1125
+ # this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes:
1126
+ # [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn)
1127
+ # [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn)
1128
+ if attention_mask is not None:
1129
+ # assume that mask is expressed as:
1130
+ # (1 = keep, 0 = discard)
1131
+ # convert mask into a bias that can be added to attention scores:
1132
+ # (keep = +0, discard = -10000.0)
1133
+ attention_mask = (1 - attention_mask.to(sample.dtype)) * -10000.0
1134
+ attention_mask = attention_mask.unsqueeze(1)
1135
+
1136
+ # convert encoder_attention_mask to a bias the same way we do for attention_mask
1137
+ if encoder_attention_mask is not None:
1138
+ encoder_attention_mask = (1 - encoder_attention_mask.to(sample.dtype)) * -10000.0
1139
+ encoder_attention_mask = encoder_attention_mask.unsqueeze(1)
1140
+
1141
+ # 0. center input if necessary
1142
+ if self.config.center_input_sample:
1143
+ sample = 2 * sample - 1.0
1144
+
1145
+ # 1. time
1146
+ t_emb = self.get_time_embed(sample=sample, timestep=timestep)
1147
+ emb = self.time_embedding(t_emb, timestep_cond)
1148
+ aug_emb = None
1149
+
1150
+ class_emb = self.get_class_embed(sample=sample, class_labels=class_labels)
1151
+ if class_emb is not None:
1152
+ if self.config.class_embeddings_concat:
1153
+ emb = torch.cat([emb, class_emb], dim=-1)
1154
+ else:
1155
+ emb = emb + class_emb
1156
+
1157
+ aug_emb = self.get_aug_embed(
1158
+ emb=emb, encoder_hidden_states=encoder_hidden_states, added_cond_kwargs=added_cond_kwargs
1159
+ )
1160
+ if self.config.addition_embed_type == "image_hint":
1161
+ aug_emb, hint = aug_emb
1162
+ sample = torch.cat([sample, hint], dim=1)
1163
+
1164
+ emb = emb + aug_emb if aug_emb is not None else emb
1165
+
1166
+ if self.time_embed_act is not None:
1167
+ emb = self.time_embed_act(emb)
1168
+
1169
+ encoder_hidden_states = self.process_encoder_hidden_states(
1170
+ encoder_hidden_states=encoder_hidden_states, added_cond_kwargs=added_cond_kwargs
1171
+ )
1172
+
1173
+ # 2. pre-process
1174
+ sample = self.conv_in(sample)
1175
+
1176
+ # 2.5 GLIGEN position net
1177
+ if cross_attention_kwargs is not None and cross_attention_kwargs.get("gligen", None) is not None:
1178
+ cross_attention_kwargs = cross_attention_kwargs.copy()
1179
+ gligen_args = cross_attention_kwargs.pop("gligen")
1180
+ cross_attention_kwargs["gligen"] = {"objs": self.position_net(**gligen_args)}
1181
+
1182
+ # 3. down
1183
+ # we're popping the `scale` instead of getting it because otherwise `scale` will be propagated
1184
+ # to the internal blocks and will raise deprecation warnings. this will be confusing for our users.
1185
+ if cross_attention_kwargs is not None:
1186
+ cross_attention_kwargs = cross_attention_kwargs.copy()
1187
+ lora_scale = cross_attention_kwargs.pop("scale", 1.0)
1188
+ else:
1189
+ lora_scale = 1.0
1190
+
1191
+ if USE_PEFT_BACKEND:
1192
+ # weight the lora layers by setting `lora_scale` for each PEFT layer
1193
+ scale_lora_layers(self, lora_scale)
1194
+
1195
+ is_controlnet = mid_block_additional_residual is not None and down_block_additional_residuals is not None
1196
+ # using new arg down_intrablock_additional_residuals for T2I-Adapters, to distinguish from controlnets
1197
+ is_adapter = down_intrablock_additional_residuals is not None
1198
+ # maintain backward compatibility for legacy usage, where
1199
+ # T2I-Adapter and ControlNet both use down_block_additional_residuals arg
1200
+ # but can only use one or the other
1201
+ if not is_adapter and mid_block_additional_residual is None and down_block_additional_residuals is not None:
1202
+ deprecate(
1203
+ "T2I should not use down_block_additional_residuals",
1204
+ "1.3.0",
1205
+ "Passing intrablock residual connections with `down_block_additional_residuals` is deprecated \
1206
+ and will be removed in diffusers 1.3.0. `down_block_additional_residuals` should only be used \
1207
+ for ControlNet. Please make sure use `down_intrablock_additional_residuals` instead. ",
1208
+ standard_warn=False,
1209
+ )
1210
+ down_intrablock_additional_residuals = down_block_additional_residuals
1211
+ is_adapter = True
1212
+
1213
+ # torch.cuda.synchronize()
1214
+ # logger.info(f"unet preprocess: {time.time() - start_time}")
1215
+
1216
+ # torch.cuda.synchronize()
1217
+ # start_time = time.time()
1218
+ down_block_res_samples = (sample,)
1219
+ for downsample_block in self.down_blocks:
1220
+ if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention:
1221
+ # For t2i-adapter CrossAttnDownBlock2D
1222
+ additional_residuals = {}
1223
+ if is_adapter and len(down_intrablock_additional_residuals) > 0:
1224
+ additional_residuals["additional_residuals"] = down_intrablock_additional_residuals.pop(0)
1225
+
1226
+ sample, res_samples = downsample_block(
1227
+ hidden_states=sample,
1228
+ temb=emb,
1229
+ encoder_hidden_states=encoder_hidden_states,
1230
+ attention_mask=attention_mask,
1231
+ cross_attention_kwargs=cross_attention_kwargs,
1232
+ encoder_attention_mask=encoder_attention_mask,
1233
+ **additional_residuals,
1234
+ )
1235
+ else:
1236
+ sample, res_samples = downsample_block(hidden_states=sample, temb=emb)
1237
+ if is_adapter and len(down_intrablock_additional_residuals) > 0:
1238
+ sample += down_intrablock_additional_residuals.pop(0)
1239
+
1240
+ down_block_res_samples += res_samples
1241
+
1242
+ if is_controlnet:
1243
+ new_down_block_res_samples = ()
1244
+
1245
+ for down_block_res_sample, down_block_additional_residual in zip(
1246
+ down_block_res_samples, down_block_additional_residuals
1247
+ ):
1248
+ down_block_res_sample = down_block_res_sample + down_block_additional_residual
1249
+ new_down_block_res_samples = new_down_block_res_samples + (down_block_res_sample,)
1250
+
1251
+ down_block_res_samples = new_down_block_res_samples
1252
+ # torch.cuda.synchronize()
1253
+ # logger.info(f"unet down time: {time.time() - start_time}")
1254
+ # torch.cuda.synchronize()
1255
+ # start_time = time.time()
1256
+ # 4. mid
1257
+ if self.mid_block is not None:
1258
+ if hasattr(self.mid_block, "has_cross_attention") and self.mid_block.has_cross_attention:
1259
+ sample = self.mid_block(
1260
+ sample,
1261
+ emb,
1262
+ encoder_hidden_states=encoder_hidden_states,
1263
+ attention_mask=attention_mask,
1264
+ cross_attention_kwargs=cross_attention_kwargs,
1265
+ encoder_attention_mask=encoder_attention_mask,
1266
+ )
1267
+ else:
1268
+ sample = self.mid_block(sample, emb)
1269
+
1270
+ # To support T2I-Adapter-XL
1271
+ if (
1272
+ is_adapter
1273
+ and len(down_intrablock_additional_residuals) > 0
1274
+ and sample.shape == down_intrablock_additional_residuals[0].shape
1275
+ ):
1276
+ sample += down_intrablock_additional_residuals.pop(0)
1277
+
1278
+ if is_controlnet:
1279
+ sample = sample + mid_block_additional_residual
1280
+ # torch.cuda.synchronize()
1281
+ # logger.info(f"unet mid time: {time.time() - start_time}")
1282
+ mid_sample = sample
1283
+
1284
+ if use_up_blocks:
1285
+ # 5. up
1286
+ up_block_res_samples = ()
1287
+ for i, upsample_block in enumerate(self.up_blocks):
1288
+ is_final_block = i == len(self.up_blocks) - 1
1289
+
1290
+ res_samples = down_block_res_samples[-len(upsample_block.resnets) :]
1291
+ down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)]
1292
+
1293
+ # if we have not reached the final block and need to forward the
1294
+ # upsample size, we do it here
1295
+ if not is_final_block and forward_upsample_size:
1296
+ upsample_size = down_block_res_samples[-1].shape[2:]
1297
+
1298
+ if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
1299
+ sample = upsample_block(
1300
+ hidden_states=sample,
1301
+ temb=emb,
1302
+ res_hidden_states_tuple=res_samples,
1303
+ encoder_hidden_states=encoder_hidden_states,
1304
+ cross_attention_kwargs=cross_attention_kwargs,
1305
+ upsample_size=upsample_size,
1306
+ attention_mask=attention_mask,
1307
+ encoder_attention_mask=encoder_attention_mask,
1308
+ )
1309
+ else:
1310
+ sample = upsample_block(
1311
+ hidden_states=sample,
1312
+ temb=emb,
1313
+ res_hidden_states_tuple=res_samples,
1314
+ upsample_size=upsample_size,
1315
+ )
1316
+ up_block_res_samples += (sample, )
1317
+
1318
+ # # 6. post-process
1319
+ # if self.conv_norm_out:
1320
+ # sample = self.conv_norm_out(sample)
1321
+ # sample = self.conv_act(sample)
1322
+ # sample = self.conv_out(sample)
1323
+
1324
+ if USE_PEFT_BACKEND:
1325
+ # remove `lora_scale` from each PEFT layer
1326
+ unscale_lora_layers(self, lora_scale)
1327
+
1328
+ if not return_dict:
1329
+ if use_up_blocks:
1330
+ return (mid_sample, down_block_res_samples, up_block_res_samples)
1331
+ else:
1332
+ return (mid_sample, down_block_res_samples)
1333
+
1334
+ return UNet2DConditionOutput(sample=sample)
Reward_sd15_idealized/pipelines/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from .sd15_reward_pipeline import StableDiffusionRewardPipeline
2
+ from .sd15_gradient_ascent_pipeline import StableDiffusionGradientAscentPipeline
3
+
4
+ __all__ = [
5
+ 'StableDiffusionRewardPipeline',
6
+ 'StableDiffusionGradientAscentPipeline',
7
+ 'StableDiffusionXLRewardPipeline',
8
+ 'StableDiffusionXLGradientAscentPipeline',
9
+ ]
Reward_sd15_idealized/pipelines/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (457 Bytes). View file
 
Reward_sd15_idealized/pipelines/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (482 Bytes). View file
 
Reward_sd15_idealized/pipelines/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (470 Bytes). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sd15_gradient_ascent_pipeline.cpython-310.pyc ADDED
Binary file (10.1 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sd15_gradient_ascent_pipeline.cpython-311.pyc ADDED
Binary file (15.8 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sd15_gradient_ascent_pipeline.cpython-313.pyc ADDED
Binary file (14.8 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sd15_reward_pipeline.cpython-310.pyc ADDED
Binary file (8.85 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sd15_reward_pipeline.cpython-311.pyc ADDED
Binary file (14.9 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sd15_reward_pipeline.cpython-313.pyc ADDED
Binary file (13.4 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sdxl_gradient_ascent_pipeline.cpython-310.pyc ADDED
Binary file (11.6 kB). View file
 
Reward_sd15_idealized/pipelines/__pycache__/sdxl_reward_pipeline.cpython-310.pyc ADDED
Binary file (11.6 kB). View file
 
Reward_sd15_idealized/pipelines/sd15_gradient_ascent_pipeline.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stable Diffusion Pipeline with Gradient Ascent Reward Guidance
3
+
4
+ This pipeline extends the reward-tracking pipeline to support gradient ascent
5
+ on the reward score during generation for improved preference alignment.
6
+ """
7
+
8
+ import torch
9
+ from typing import Optional, Union, List, Callable, Tuple
10
+ from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput
11
+ from pipelines.sd15_reward_pipeline import StableDiffusionRewardPipeline
12
+ from gradient_ascent_utils import RewardGuidedDiffusion
13
+
14
+
15
+ class StableDiffusionGradientAscentPipeline(StableDiffusionRewardPipeline):
16
+ """
17
+ SD Pipeline with gradient ascent reward guidance.
18
+
19
+ This pipeline performs gradient ascent on the LRM reward score at specified
20
+ timesteps to guide generation toward higher preference scores.
21
+
22
+ Usage:
23
+ from diffusers import StableDiffusionPipeline
24
+ base = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
25
+ pipeline = StableDiffusionGradientAscentPipeline(**base.components)
26
+ pipeline.set_reward_model(reward_model)
27
+
28
+ # Enable gradient ascent
29
+ pipeline.enable_gradient_ascent(
30
+ grad_timestep_range=(500, 700),
31
+ num_grad_steps=5,
32
+ grad_step_size=0.1
33
+ )
34
+
35
+ output = pipeline(
36
+ prompt="a beautiful landscape",
37
+ num_inference_steps=20,
38
+ )
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ vae,
44
+ text_encoder,
45
+ tokenizer,
46
+ unet,
47
+ scheduler,
48
+ safety_checker=None,
49
+ feature_extractor=None,
50
+ image_encoder=None,
51
+ requires_safety_checker: bool = False,
52
+ ):
53
+ super().__init__(
54
+ vae=vae,
55
+ text_encoder=text_encoder,
56
+ tokenizer=tokenizer,
57
+ unet=unet,
58
+ scheduler=scheduler,
59
+ safety_checker=safety_checker,
60
+ feature_extractor=feature_extractor,
61
+ image_encoder=image_encoder,
62
+ requires_safety_checker=requires_safety_checker,
63
+ )
64
+ self.gradient_ascent_enabled = False
65
+ self.grad_guidance = None
66
+
67
+ def enable_gradient_ascent(
68
+ self,
69
+ grad_timestep_range: Tuple[int, int] = (500, 700),
70
+ grad_scale: float = 1.0,
71
+ num_grad_steps: int = 5,
72
+ grad_step_size: float = 0.1,
73
+ lr_scheduler_type: str = "constant",
74
+ lr_scheduler_kwargs: Optional[dict] = None,
75
+ use_momentum: bool = False,
76
+ momentum: float = 0.9,
77
+ use_nesterov: bool = False,
78
+ use_iso_projection: bool = False
79
+ ):
80
+ """
81
+ Enable gradient ascent reward guidance.
82
+
83
+ Args:
84
+ grad_timestep_range: Tuple of (min_t, max_t) for applying gradients
85
+ grad_scale: Scale factor for gradient magnitude
86
+ num_grad_steps: Number of gradient ascent iterations per timestep
87
+ grad_step_size: Step size for each gradient update (initial LR)
88
+ lr_scheduler_type: Type of LR scheduler ("constant", "linear", "cosine", "exponential", "step")
89
+ lr_scheduler_kwargs: Additional kwargs for LR scheduler
90
+ use_momentum: Whether to use momentum
91
+ momentum: Momentum coefficient (typically 0.9)
92
+ use_nesterov: Whether to use Nesterov momentum
93
+ use_iso_projection: Whether to use Iso Projection
94
+ """
95
+ if self.reward_model is None:
96
+ raise ValueError("Reward model must be set before enabling gradient ascent. "
97
+ "Call set_reward_model() first.")
98
+
99
+ self.grad_guidance = RewardGuidedDiffusion(
100
+ reward_model=self.reward_model,
101
+ grad_scale=grad_scale,
102
+ grad_timestep_range=grad_timestep_range,
103
+ num_grad_steps=num_grad_steps,
104
+ grad_step_size=grad_step_size,
105
+ lr_scheduler_type=lr_scheduler_type,
106
+ lr_scheduler_kwargs=lr_scheduler_kwargs or {},
107
+ use_momentum=use_momentum,
108
+ momentum=momentum,
109
+ use_nesterov=use_nesterov,
110
+ use_iso_projection=use_iso_projection
111
+ )
112
+ self.gradient_ascent_enabled = True
113
+ print(f"✓ Gradient ascent enabled for timesteps {grad_timestep_range}")
114
+ print(f" - Grad steps: {num_grad_steps}")
115
+ print(f" - Grad step size (initial LR): {grad_step_size}")
116
+ print(f" - Grad scale: {grad_scale}")
117
+ print(f" - LR scheduler: {lr_scheduler_type}")
118
+ if use_momentum:
119
+ print(f" - Momentum: {momentum} (Nesterov: {use_nesterov})")
120
+ else:
121
+ print(f" - Momentum: disabled")
122
+ print(f" - Iso Projection: {use_iso_projection}")
123
+
124
+ def disable_gradient_ascent(self):
125
+ """Disable gradient ascent."""
126
+ self.gradient_ascent_enabled = False
127
+ self.grad_guidance = None
128
+ print("✓ Gradient ascent disabled")
129
+
130
+ @torch.no_grad()
131
+ def __call__(
132
+ self,
133
+ prompt: Union[str, List[str]] = None,
134
+ height: Optional[int] = None,
135
+ width: Optional[int] = None,
136
+ num_inference_steps: int = 50,
137
+ guidance_scale: float = 7.5,
138
+ negative_prompt: Optional[Union[str, List[str]]] = None,
139
+ num_images_per_prompt: Optional[int] = 1,
140
+ eta: float = 0.0,
141
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
142
+ latents: Optional[torch.Tensor] = None,
143
+ track_rewards: bool = True,
144
+ print_rewards: bool = True,
145
+ apply_gradient_ascent: bool = True,
146
+ verbose_grad: bool = True,
147
+ output_type: Optional[str] = "pil",
148
+ return_dict: bool = True,
149
+ callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
150
+ callback_steps: int = 1,
151
+ **kwargs,
152
+ ):
153
+ """
154
+ Generate images with optional gradient ascent guidance.
155
+
156
+ Args:
157
+ prompt: Text prompt(s)
158
+ height: Image height
159
+ width: Image width
160
+ num_inference_steps: Number of denoising steps
161
+ guidance_scale: CFG scale
162
+ negative_prompt: Negative prompt(s)
163
+ num_images_per_prompt: Number of images per prompt
164
+ eta: DDIM eta parameter
165
+ generator: Random generator
166
+ latents: Initial latents
167
+ track_rewards: Whether to track reward scores
168
+ print_rewards: Whether to print rewards during generation
169
+ apply_gradient_ascent: Whether to apply gradient ascent (if enabled)
170
+ verbose_grad: Whether to print gradient ascent details
171
+ output_type: Output format
172
+ return_dict: Whether to return dict
173
+ callback: Callback function
174
+ callback_steps: Callback frequency
175
+
176
+ Returns:
177
+ StableDiffusionPipelineOutput with images and reward scores
178
+ """
179
+ # Prepare inputs
180
+ height = height or self.unet.config.sample_size * self.vae_scale_factor
181
+ width = width or self.unet.config.sample_size * self.vae_scale_factor
182
+
183
+ # Encode prompt
184
+ batch_size = 1 if isinstance(prompt, str) else len(prompt)
185
+ prompt = [prompt] if isinstance(prompt, str) else prompt
186
+
187
+ text_embeddings = self._encode_prompt(
188
+ prompt,
189
+ self.device,
190
+ num_images_per_prompt,
191
+ guidance_scale > 1.0,
192
+ negative_prompt,
193
+ )
194
+
195
+ # Prepare timesteps
196
+ self.scheduler.set_timesteps(num_inference_steps, device=self.device)
197
+ timesteps = self.scheduler.timesteps
198
+
199
+ # Reset gradient ascent scheduler for new generation
200
+ if self.grad_guidance is not None:
201
+ self.grad_guidance.reset_statistics()
202
+
203
+ # Prepare latents
204
+ latents = self._prepare_latents(
205
+ batch_size * num_images_per_prompt,
206
+ self.unet.config.in_channels,
207
+ height,
208
+ width,
209
+ text_embeddings.dtype,
210
+ self.device,
211
+ generator,
212
+ latents,
213
+ )
214
+
215
+ # Prepare extra kwargs
216
+ extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
217
+
218
+ # Reset statistics
219
+ self.reward_history = []
220
+ if self.gradient_ascent_enabled and self.grad_guidance:
221
+ self.grad_guidance.reset_statistics()
222
+
223
+ # Print header
224
+ if print_rewards or verbose_grad:
225
+ print("\n" + "="*60)
226
+ print(f"Starting generation: '{prompt[0]}'")
227
+ if self.gradient_ascent_enabled and apply_gradient_ascent:
228
+ print(f"Gradient ascent: ENABLED")
229
+ print("="*60)
230
+
231
+ # Denoising loop
232
+ for i, t in enumerate(self.progress_bar(timesteps)):
233
+ # Apply gradient ascent BEFORE scheduler step
234
+ if (self.gradient_ascent_enabled and
235
+ apply_gradient_ascent and
236
+ self.grad_guidance and
237
+ self.grad_guidance.should_apply_gradient(t.item())):
238
+
239
+ if verbose_grad:
240
+ print(f"\n🎯 Applying gradient ascent at step {i}/{num_inference_steps}, t={t.item()}")
241
+
242
+ # Enable gradients temporarily
243
+ with torch.enable_grad():
244
+ latents, grad_stats = self.grad_guidance.apply_gradient_ascent(
245
+ latents,
246
+ prompt[0],
247
+ t.item(),
248
+ base_noise=None,
249
+ verbose=verbose_grad,
250
+ total_denoising_steps=num_inference_steps,
251
+ )
252
+
253
+ latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1.0 else latents
254
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
255
+
256
+ noise_pred = self.unet(
257
+ latent_model_input,
258
+ t,
259
+ encoder_hidden_states=text_embeddings,
260
+ ).sample
261
+
262
+ # Perform CFG
263
+ if guidance_scale > 1.0:
264
+ noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
265
+ noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
266
+
267
+ # Compute reward score (after potential gradient update)
268
+ if track_rewards and self.reward_model is not None:
269
+ try:
270
+ with torch.no_grad():
271
+ timestep_value = t.item() if isinstance(t, torch.Tensor) else t
272
+ score = self.reward_model.get_reward_score(
273
+ latents,
274
+ prompt[0],
275
+ timestep_value
276
+ )
277
+
278
+ score_value = score.item() if score.numel() == 1 else score.mean().item()
279
+ self.reward_history.append({
280
+ 'step': i,
281
+ 'timestep': timestep_value,
282
+ 'reward_score': score_value
283
+ })
284
+
285
+ if print_rewards:
286
+ print(f"Step {i:3d}/{num_inference_steps} | t={timestep_value:4d} | Reward: {score_value:7.4f}")
287
+
288
+ except Exception as e:
289
+ print(f"Warning: Could not compute reward at step {i}: {e}")
290
+
291
+ # Scheduler step
292
+ latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
293
+
294
+ # Callback
295
+ if callback is not None and i % callback_steps == 0:
296
+ callback(i, t, latents)
297
+
298
+ # Print footer
299
+ if print_rewards or verbose_grad:
300
+ print("="*60)
301
+ if self.reward_history:
302
+ final_reward = self.reward_history[-1]['reward_score']
303
+ print(f"Generation complete! Final reward: {final_reward:.4f}")
304
+
305
+ if self.gradient_ascent_enabled and self.grad_guidance:
306
+ stats = self.grad_guidance.get_statistics()
307
+ if stats:
308
+ print(f"\nGradient Ascent Statistics:")
309
+ print(f" Applications: {stats['num_applications']}")
310
+ print(f" Total reward improvement: {stats['total_reward_improvement']:+.4f}")
311
+ print(f" Avg reward improvement: {stats['avg_reward_improvement']:+.4f}")
312
+ print(f" Avg gradient norm: {stats['avg_grad_norm']:.4f}")
313
+ print("="*60 + "\n")
314
+
315
+ # Decode latents
316
+ latents = 1 / self.vae.config.scaling_factor * latents
317
+ image = self.vae.decode(latents).sample
318
+ image = (image / 2 + 0.5).clamp(0, 1)
319
+ image = image.cpu().permute(0, 2, 3, 1).float().numpy()
320
+
321
+ # Convert to PIL
322
+ if output_type == "pil":
323
+ image = self.numpy_to_pil(image)
324
+
325
+ # Create output
326
+ if not return_dict:
327
+ return (image,)
328
+
329
+ return StableDiffusionPipelineOutput(
330
+ images=image,
331
+ nsfw_content_detected=None,
332
+ )
333
+
334
+ def _prepare_latents(self, batch_size, num_channels, height, width, dtype, device, generator, latents):
335
+ """Prepare initial latents."""
336
+ shape = (batch_size, num_channels, height // self.vae_scale_factor, width // self.vae_scale_factor)
337
+
338
+ if latents is None:
339
+ latents = torch.randn(shape, generator=generator, device=device, dtype=dtype)
340
+ else:
341
+ latents = latents.to(device)
342
+
343
+ # Scale initial latents
344
+ latents = latents * self.scheduler.init_noise_sigma
345
+ return latents
Reward_sd15_idealized/pipelines/sd15_reward_pipeline.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Modified Stable Diffusion Pipeline with Reward Tracking
3
+
4
+ This pipeline extends the standard SD pipeline to compute and display
5
+ reward scores at each denoising step using the LRM reward model.
6
+ """
7
+
8
+ import torch
9
+ from typing import Optional, Union, List, Callable
10
+ from diffusers import StableDiffusionPipeline, DDIMScheduler
11
+ from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput
12
+ import numpy as np
13
+ from PIL import Image
14
+
15
+
16
+ class StableDiffusionRewardPipeline(StableDiffusionPipeline):
17
+ """
18
+ Extended Stable Diffusion Pipeline with LRM Reward Tracking
19
+
20
+ This pipeline computes preference scores at each denoising timestep and optionally
21
+ displays them during generation.
22
+
23
+ Usage:
24
+ from diffusers import StableDiffusionPipeline
25
+ base = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
26
+ pipeline = StableDiffusionRewardPipeline(**base.components)
27
+ pipeline.set_reward_model(reward_model)
28
+
29
+ output = pipeline(
30
+ prompt="a beautiful landscape",
31
+ num_inference_steps=20,
32
+ track_rewards=True
33
+ )
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ vae,
39
+ text_encoder,
40
+ tokenizer,
41
+ unet,
42
+ scheduler,
43
+ safety_checker=None,
44
+ feature_extractor=None,
45
+ image_encoder=None,
46
+ requires_safety_checker: bool = False,
47
+ ):
48
+ super().__init__(
49
+ vae=vae,
50
+ text_encoder=text_encoder,
51
+ tokenizer=tokenizer,
52
+ unet=unet,
53
+ scheduler=scheduler,
54
+ safety_checker=safety_checker,
55
+ feature_extractor=feature_extractor,
56
+ image_encoder=image_encoder,
57
+ requires_safety_checker=requires_safety_checker,
58
+ )
59
+ self.reward_model = None
60
+ self.reward_history = []
61
+
62
+ def set_reward_model(self, reward_model):
63
+ """Set the LRM reward model for score tracking"""
64
+ self.reward_model = reward_model
65
+ self.reward_model.eval() # Set to eval mode
66
+ print("✓ Reward model attached to pipeline")
67
+
68
+ @torch.no_grad()
69
+ def __call__(
70
+ self,
71
+ prompt: Union[str, List[str]] = None,
72
+ height: Optional[int] = None,
73
+ width: Optional[int] = None,
74
+ num_inference_steps: int = 50,
75
+ guidance_scale: float = 7.5,
76
+ negative_prompt: Optional[Union[str, List[str]]] = None,
77
+ num_images_per_prompt: Optional[int] = 1,
78
+ eta: float = 0.0,
79
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
80
+ latents: Optional[torch.Tensor] = None,
81
+ track_rewards: bool = True,
82
+ print_rewards: bool = True,
83
+ output_type: Optional[str] = "pil",
84
+ return_dict: bool = True,
85
+ callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
86
+ callback_steps: int = 1,
87
+ **kwargs,
88
+ ):
89
+ """
90
+ Generate images with optional reward tracking
91
+
92
+ Args:
93
+ prompt: Text prompt(s) for generation
94
+ height: Image height (default: 512)
95
+ width: Image width (default: 512)
96
+ num_inference_steps: Number of denoising steps
97
+ guidance_scale: CFG scale
98
+ negative_prompt: Negative prompt(s)
99
+ num_images_per_prompt: Number of images per prompt
100
+ eta: DDIM eta parameter (0 = deterministic)
101
+ generator: Random generator for reproducibility
102
+ latents: Initial latent codes (optional)
103
+ track_rewards: Whether to compute reward scores
104
+ print_rewards: Whether to print rewards during generation
105
+ output_type: "pil", "np", or "latent"
106
+ return_dict: Whether to return a dict or tuple
107
+ callback: Custom callback function
108
+ callback_steps: Frequency of callback calls
109
+
110
+ Returns:
111
+ StableDiffusionPipelineOutput with images and optional reward history
112
+ """
113
+ # Default height and width to unet config
114
+ height = height or self.unet.config.sample_size * self.vae_scale_factor
115
+ width = width or self.unet.config.sample_size * self.vae_scale_factor
116
+
117
+ # Reset reward history
118
+ self.reward_history = []
119
+
120
+ # 1. Check inputs
121
+ if isinstance(prompt, str):
122
+ batch_size = 1
123
+ prompt = [prompt]
124
+ elif isinstance(prompt, list):
125
+ batch_size = len(prompt)
126
+ else:
127
+ raise ValueError("Prompt must be a string or list of strings")
128
+
129
+ # 2. Encode input prompt
130
+ text_embeddings = self._encode_prompt(
131
+ prompt,
132
+ self.device,
133
+ num_images_per_prompt,
134
+ guidance_scale > 1.0,
135
+ negative_prompt,
136
+ )
137
+
138
+ # 3. Prepare timesteps
139
+ self.scheduler.set_timesteps(num_inference_steps, device=self.device)
140
+ timesteps = self.scheduler.timesteps
141
+
142
+ # 4. Prepare latent variables
143
+ num_channels_latents = self.unet.config.in_channels
144
+ latents = self.prepare_latents(
145
+ batch_size * num_images_per_prompt,
146
+ num_channels_latents,
147
+ height,
148
+ width,
149
+ text_embeddings.dtype,
150
+ self.device,
151
+ generator,
152
+ latents,
153
+ )
154
+
155
+ # 5. Prepare extra step kwargs
156
+ extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
157
+
158
+ # 6. Denoising loop
159
+ num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
160
+
161
+ if print_rewards and track_rewards and self.reward_model is not None:
162
+ print("\n" + "="*60)
163
+ print(f"Starting generation: '{prompt[0]}'")
164
+ print("="*60)
165
+
166
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
167
+ for i, t in enumerate(timesteps):
168
+ # Expand latents if doing classifier free guidance
169
+ latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1.0 else latents
170
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
171
+
172
+ # Predict noise residual
173
+ noise_pred = self.unet(
174
+ latent_model_input,
175
+ t,
176
+ encoder_hidden_states=text_embeddings,
177
+ ).sample
178
+
179
+ # Perform guidance
180
+ if guidance_scale > 1.0:
181
+ noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
182
+ noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
183
+
184
+ # Compute reward score BEFORE stepping
185
+ if track_rewards and self.reward_model is not None:
186
+ try:
187
+ # Get reward score for current latents at timestep t
188
+ with torch.no_grad():
189
+ # Convert timestep to proper format
190
+ timestep_value = t.item() if isinstance(t, torch.Tensor) else t
191
+ score = self.reward_model.get_reward_score(
192
+ latents,
193
+ prompt[0], # Use first prompt
194
+ timestep_value
195
+ )
196
+
197
+ score_value = score.item() if score.numel() == 1 else score.mean().item()
198
+ self.reward_history.append({
199
+ 'step': i,
200
+ 'timestep': timestep_value,
201
+ 'reward_score': score_value
202
+ })
203
+
204
+ if print_rewards:
205
+ print(f"Step {i:3d}/{num_inference_steps} | t={t:4d} | Reward: {score_value:7.4f}")
206
+
207
+ except Exception as e:
208
+ if print_rewards:
209
+ print(f"Warning: Could not compute reward at step {i}: {e}")
210
+
211
+ # Compute previous noisy sample x_t -> x_t-1
212
+ latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
213
+
214
+ # Call callback
215
+ if callback is not None and i % callback_steps == 0:
216
+ callback(i, t, latents)
217
+
218
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
219
+ progress_bar.update()
220
+
221
+ if print_rewards and track_rewards and self.reward_model is not None:
222
+ print("="*60)
223
+ final_score = self.reward_history[-1]['reward_score'] if self.reward_history else 0.0
224
+ print(f"Generation complete! Final reward: {final_score:.4f}")
225
+ print("="*60 + "\n")
226
+
227
+ # 7. Decode latents
228
+ if output_type == "latent":
229
+ image = latents
230
+ has_nsfw_concept = None
231
+ elif output_type in ["pil", "np"]:
232
+ # Decode with VAE
233
+ latents = 1 / self.vae.config.scaling_factor * latents
234
+ image = self.vae.decode(latents).sample
235
+
236
+ # Convert to PIL
237
+ image = (image / 2 + 0.5).clamp(0, 1)
238
+ image = image.cpu().permute(0, 2, 3, 1).float().numpy()
239
+
240
+ if output_type == "pil":
241
+ image = self.numpy_to_pil(image)
242
+
243
+ # Run safety checker
244
+ has_nsfw_concept = None
245
+ if hasattr(self, 'safety_checker') and self.safety_checker is not None:
246
+ safety_checker_input = self.feature_extractor(
247
+ self.numpy_to_pil(image) if isinstance(image, np.ndarray) else image,
248
+ return_tensors="pt"
249
+ ).to(self.device)
250
+ image, has_nsfw_concept = self.safety_checker(
251
+ images=image,
252
+ clip_input=safety_checker_input.pixel_values.to(text_embeddings.dtype)
253
+ )
254
+
255
+ # 8. Return output
256
+ if not return_dict:
257
+ return (image, has_nsfw_concept)
258
+
259
+ output = StableDiffusionPipelineOutput(
260
+ images=image,
261
+ nsfw_content_detected=has_nsfw_concept
262
+ )
263
+
264
+ # Attach reward history to output
265
+ if track_rewards:
266
+ output.reward_history = self.reward_history
267
+
268
+ return output
269
+
270
+ def get_reward_history(self):
271
+ """Get the reward history from the last generation"""
272
+ return self.reward_history
273
+
274
+ def plot_reward_curve(self, save_path=None):
275
+ """
276
+ Plot the reward curve from the last generation
277
+
278
+ Args:
279
+ save_path: Optional path to save the plot
280
+ """
281
+ if not self.reward_history:
282
+ print("No reward history available. Run generation with track_rewards=True first.")
283
+ return
284
+
285
+ import matplotlib.pyplot as plt
286
+
287
+ steps = [h['step'] for h in self.reward_history]
288
+ timesteps = [h['timestep'] for h in self.reward_history]
289
+ scores = [h['reward_score'] for h in self.reward_history]
290
+
291
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
292
+
293
+ # Plot reward vs step
294
+ ax1.plot(steps, scores, marker='o', linewidth=2, markersize=4)
295
+ ax1.set_xlabel('Denoising Step')
296
+ ax1.set_ylabel('Reward Score')
297
+ ax1.set_title('Reward Score vs Denoising Step')
298
+ ax1.grid(True, alpha=0.3)
299
+
300
+ # Plot reward vs timestep
301
+ ax2.plot(timesteps, scores, marker='o', linewidth=2, markersize=4, color='orange')
302
+ ax2.set_xlabel('Timestep')
303
+ ax2.set_ylabel('Reward Score')
304
+ ax2.set_title('Reward Score vs Timestep')
305
+ ax2.grid(True, alpha=0.3)
306
+ ax2.invert_xaxis() # Higher timesteps first (left to right: noise -> clean)
307
+
308
+ plt.tight_layout()
309
+
310
+ if save_path:
311
+ plt.savefig(save_path, dpi=150, bbox_inches='tight')
312
+ print(f"Plot saved to {save_path}")
313
+
314
+ plt.show()
315
+
316
+ return fig
Reward_sd15_idealized/tune_hyperparams.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hyperparameter tuning script for gradient ascent optimization.
3
+
4
+ This script performs a systematic search over hyperparameter combinations
5
+ to find the optimal configuration for maximum evaluation scores.
6
+ """
7
+
8
+ import subprocess
9
+ import json
10
+ import argparse
11
+ from pathlib import Path
12
+ from datetime import datetime
13
+ import itertools
14
+ import numpy as np
15
+ from typing import Dict, List, Any
16
+ import re
17
+
18
+
19
+ class HyperparameterTuner:
20
+ """Hyperparameter tuner for gradient ascent."""
21
+
22
+ def __init__(
23
+ self,
24
+ output_dir: str = "tuning_results",
25
+ max_samples: int = 30,
26
+ num_steps: int = 20,
27
+ dataset_type: str = "pickapic",
28
+ model_variant: str = "lpo",
29
+ cuda_id: int = 0,
30
+ metrics: List[str] = None
31
+ ):
32
+ self.output_dir = Path(output_dir)
33
+ self.output_dir.mkdir(parents=True, exist_ok=True)
34
+
35
+ self.max_samples = max_samples
36
+ self.num_steps = num_steps
37
+ self.dataset_type = dataset_type
38
+ self.model_variant = model_variant
39
+ self.cuda_id = cuda_id
40
+ self.metrics = metrics or ["clip", "aesthetic", "pickscore", "hpsv2", "imagereward"]
41
+
42
+ # Store results
43
+ self.results = []
44
+ self.baseline_results = None
45
+
46
+ def define_search_space(self) -> List[Dict[str, Any]]:
47
+ """Define the hyperparameter search space - FULL GRID SEARCH.
48
+
49
+ Tests all combinations of parameters including momentum overrides for configs that support it.
50
+ """
51
+
52
+ # Define all parameter values
53
+ cfg_scales = [3.0, 5.0, 7.5] #
54
+
55
+ # All available gradient configs from grad_ascent_configs.py
56
+ grad_configs = [
57
+ # "constant",
58
+ # "linear",
59
+ "cosine_nesterov",
60
+ # "low_to_high_nesterov",
61
+ # "high_to_low_nesterov",
62
+ "low_to_high_momentum",
63
+ "high_to_low_momentum",
64
+ ]
65
+
66
+ num_grad_steps_list = [1, 2] # 5, 7, 10
67
+ grad_step_sizes = [0.001, 0.005, 0.01, 0.05] #
68
+ momentums = [0.5, 0.8, 0.9] #
69
+
70
+ # Generate ALL combinations using itertools.product
71
+ configs = []
72
+ for cfg, grad_cfg, num_steps, step_size, momentum in itertools.product(
73
+ cfg_scales, grad_configs, num_grad_steps_list, grad_step_sizes, momentums
74
+ ):
75
+ configs.append({
76
+ "cfg_scale": cfg,
77
+ "grad_config": grad_cfg,
78
+ "num_grad_steps": num_steps,
79
+ "grad_step_size": step_size,
80
+ "momentum": momentum,
81
+ })
82
+
83
+ print(f"\nGenerated {len(configs)} total configurations")
84
+ print(f" cfg_scales: {len(cfg_scales)}")
85
+ print(f" grad_configs: {len(grad_configs)}")
86
+ print(f" num_grad_steps: {len(num_grad_steps_list)}")
87
+ print(f" grad_step_sizes: {len(grad_step_sizes)}")
88
+ print(f" momentums: {len(momentums)}")
89
+ print(f" Total: {len(cfg_scales)} × {len(grad_configs)} × {len(num_grad_steps_list)} × {len(grad_step_sizes)} × {len(momentums)} = {len(configs)}")
90
+
91
+ return configs
92
+
93
+ def run_baseline(self) -> Dict[str, float]:
94
+ """Run baseline evaluation once."""
95
+ print("\n" + "="*80)
96
+ print("RUNNING BASELINE EVALUATION")
97
+ print("="*80)
98
+
99
+ # Use median cfg_scale for baseline
100
+ cfg_scale = 5.0
101
+
102
+ output_dir = self.output_dir / "baseline"
103
+
104
+ cmd = [
105
+ "python", "eval.py",
106
+ "--model_variant", self.model_variant,
107
+ "--dataset_type", self.dataset_type,
108
+ "--max_samples", str(self.max_samples),
109
+ "--num_steps", str(self.num_steps),
110
+ "--cfg_scale", str(cfg_scale),
111
+ "--output_dir", str(output_dir),
112
+ "--cuda", str(self.cuda_id),
113
+ "--mode", "baseline",
114
+ "--metrics", *self.metrics,
115
+ ]
116
+
117
+ print(f"Command: {' '.join(cmd)}")
118
+
119
+ try:
120
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
121
+
122
+ # Parse results from output
123
+ metrics = self._parse_metrics(result.stdout, "baseline")
124
+
125
+ print(f"\nBaseline Results:")
126
+ for metric, value in metrics.items():
127
+ print(f" {metric}: {value:.4f}")
128
+
129
+ self.baseline_results = {
130
+ "cfg_scale": cfg_scale,
131
+ "metrics": metrics,
132
+ }
133
+
134
+ return metrics
135
+
136
+ except subprocess.CalledProcessError as e:
137
+ print(f"Error running baseline: {e}")
138
+ print(f"Stdout: {e.stdout}")
139
+ print(f"Stderr: {e.stderr}")
140
+ return {}
141
+
142
+ def run_experiment(self, config: Dict[str, Any]) -> Dict[str, Any]:
143
+ """Run a single experiment with given hyperparameters."""
144
+
145
+ # Create output directory for this config
146
+ config_name = f"cfg{config['cfg_scale']}_" \
147
+ f"{config['grad_config']}_" \
148
+ f"steps{config['num_grad_steps']}_" \
149
+ f"lr{config['grad_step_size']}_" \
150
+ f"mom{config['momentum']}"
151
+
152
+ output_dir = self.output_dir / config_name
153
+
154
+ # Build command
155
+ cmd = [
156
+ "python", "eval.py",
157
+ "--model_variant", self.model_variant,
158
+ "--dataset_type", self.dataset_type,
159
+ "--grad_config", config["grad_config"],
160
+ "--max_samples", str(self.max_samples),
161
+ "--num_steps", str(self.num_steps),
162
+ "--cfg_scale", str(config["cfg_scale"]),
163
+ "--output_dir", str(output_dir),
164
+ "--cuda", str(self.cuda_id),
165
+ "--mode", "gradient_ascent",
166
+ "--metrics", *self.metrics,
167
+ # Override config parameters
168
+ "--override_num_grad_steps", str(config["num_grad_steps"]),
169
+ "--override_grad_step_size", str(config["grad_step_size"]),
170
+ "--override_momentum", str(config["momentum"]),
171
+ ]
172
+
173
+ print(f"\nRunning experiment: {config_name}")
174
+ print(f"Config: {config}")
175
+
176
+ try:
177
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
178
+
179
+ # Parse metrics from output
180
+ metrics = self._parse_metrics(result.stdout, "gradient_ascent")
181
+
182
+ # Compute improvement over baseline
183
+ improvements = {}
184
+ if self.baseline_results:
185
+ baseline_metrics = self.baseline_results["metrics"]
186
+ for metric, value in metrics.items():
187
+ if metric in baseline_metrics:
188
+ baseline_val = baseline_metrics[metric]
189
+ if baseline_val != 0:
190
+ improvement = ((value - baseline_val) / abs(baseline_val)) * 100
191
+ improvements[f"{metric}_improvement"] = improvement
192
+
193
+ result_dict = {
194
+ "config": config,
195
+ "metrics": metrics,
196
+ "improvements": improvements,
197
+ "output_dir": str(output_dir),
198
+ "timestamp": datetime.now().isoformat(),
199
+ }
200
+
201
+ print(f"Results:")
202
+ for metric, value in metrics.items():
203
+ print(f" {metric}: {value:.4f}")
204
+ if improvements:
205
+ print(f"Improvements over baseline:")
206
+ for metric, value in improvements.items():
207
+ print(f" {metric}: {value:+.2f}%")
208
+
209
+ return result_dict
210
+
211
+ except subprocess.CalledProcessError as e:
212
+ print(f"Error running experiment: {e}")
213
+ print(f"Stderr: {e.stderr}")
214
+ return {
215
+ "config": config,
216
+ "error": str(e),
217
+ "timestamp": datetime.now().isoformat(),
218
+ }
219
+
220
+ def _parse_metrics(self, output: str, mode: str) -> Dict[str, float]:
221
+ """Parse metrics from eval.py output."""
222
+ metrics = {}
223
+
224
+ # Look for the summary section
225
+ lines = output.split('\n')
226
+
227
+ # Pattern to match metric lines like " Reward: 0.1234"
228
+ metric_patterns = {
229
+ "reward": r"Reward:\s+([-+]?\d*\.?\d+)",
230
+ "clip": r"CLIP Score:\s+([-+]?\d*\.?\d+)",
231
+ "aesthetic": r"Aesthetic Score:\s+([-+]?\d*\.?\d+)",
232
+ "pickscore": r"PickScore:\s+([-+]?\d*\.?\d+)",
233
+ "hpsv2": r"HPSv2 Score:\s+([-+]?\d*\.?\d+)",
234
+ "hpsv21": r"HPSv2\.1 Score:\s+([-+]?\d*\.?\d+)",
235
+ "imagereward": r"ImageReward:\s+([-+]?\d*\.?\d+)",
236
+ "fid": r"FID:\s+([-+]?\d*\.?\d+)",
237
+ }
238
+
239
+ for line in lines:
240
+ for metric_name, pattern in metric_patterns.items():
241
+ match = re.search(pattern, line)
242
+ if match:
243
+ metrics[metric_name] = float(match.group(1))
244
+
245
+ return metrics
246
+
247
+ def compute_aggregate_score(self, metrics: Dict[str, float]) -> float:
248
+ """
249
+ Compute aggregate score for ranking configurations.
250
+
251
+ Uses weighted combination of metrics (higher is better for most,
252
+ except FID which is lower is better).
253
+ """
254
+ weights = {
255
+ "reward": 1.0,
256
+ "clip": 0.8,
257
+ "aesthetic": 0.8,
258
+ "pickscore": 1.0,
259
+ "hpsv2": 1.0,
260
+ "hpsv21": 1.0,
261
+ "imagereward": 1.0,
262
+ "fid": -0.5, # Negative weight (lower FID is better)
263
+ }
264
+
265
+ score = 0.0
266
+ total_weight = 0.0
267
+
268
+ for metric, value in metrics.items():
269
+ if metric in weights:
270
+ score += weights[metric] * value
271
+ total_weight += abs(weights[metric])
272
+
273
+ # Normalize by total weight
274
+ if total_weight > 0:
275
+ score /= total_weight
276
+
277
+ return score
278
+
279
+ def run_search(
280
+ self,
281
+ search_type: str = "grid",
282
+ start_idx: int = 0,
283
+ end_idx: int = None
284
+ ) -> List[Dict[str, Any]]:
285
+ """
286
+ Run hyperparameter search.
287
+
288
+ Args:
289
+ search_type: Type of search ("grid" or "random")
290
+ start_idx: Starting index for experiments (for GPU distribution)
291
+ end_idx: Ending index for experiments (for GPU distribution)
292
+ """
293
+ all_configs = self.define_search_space()
294
+
295
+ print("\n" + "="*80)
296
+ print("HYPERPARAMETER SEARCH CONFIGURATION")
297
+ print("="*80)
298
+ print(f"Dataset: {self.dataset_type}")
299
+ print(f"Model: {self.model_variant}")
300
+ print(f"Samples: {self.max_samples}")
301
+ print(f"Inference steps: {self.num_steps}")
302
+ print(f"Metrics: {', '.join(self.metrics)}")
303
+
304
+ # Select subset of configs if indices provided
305
+ if search_type == "grid":
306
+ configs = all_configs
307
+ elif search_type == "random":
308
+ # Random sample from all configs
309
+ n_samples = min(50, len(all_configs))
310
+ indices = np.random.choice(len(all_configs), n_samples, replace=False)
311
+ configs = [all_configs[i] for i in indices]
312
+ else:
313
+ raise ValueError(f"Unknown search type: {search_type}")
314
+
315
+ # Apply index slicing for GPU distribution
316
+ if end_idx is None:
317
+ end_idx = len(configs)
318
+ configs = configs[start_idx:end_idx]
319
+
320
+ print(f"\nTotal configurations: {len(all_configs)}")
321
+ print(f"Assigned to this worker: {len(configs)} (indices {start_idx} to {end_idx})")
322
+
323
+ # Run baseline first
324
+ if self.baseline_results is None:
325
+ self.run_baseline()
326
+
327
+ # Run experiments
328
+ print("\n" + "="*80)
329
+ print("RUNNING EXPERIMENTS")
330
+ print("="*80)
331
+
332
+ for i, config in enumerate(configs, 1):
333
+ print(f"\n{'='*80}")
334
+ print(f"Experiment {i}/{len(configs)}")
335
+ print(f"{'='*80}")
336
+
337
+ result = self.run_experiment(config)
338
+ self.results.append(result)
339
+
340
+ # Save intermediate results
341
+ self._save_results()
342
+
343
+ return self.results
344
+
345
+ def _generate_grid_configs(self, search_space: Dict[str, List[Any]]) -> List[Dict[str, Any]]:
346
+ """Generate all combinations for grid search."""
347
+ keys = list(search_space.keys())
348
+ values = list(search_space.values())
349
+
350
+ configs = []
351
+ for combination in itertools.product(*values):
352
+ config = dict(zip(keys, combination))
353
+ configs.append(config)
354
+
355
+ return configs
356
+
357
+ def _generate_random_configs(
358
+ self,
359
+ search_space: Dict[str, List[Any]],
360
+ n_samples: int = 20
361
+ ) -> List[Dict[str, Any]]:
362
+ """Generate random configurations for random search."""
363
+ configs = []
364
+
365
+ for _ in range(n_samples):
366
+ config = {}
367
+ for param, values in search_space.items():
368
+ config[param] = np.random.choice(values)
369
+ configs.append(config)
370
+
371
+ return configs
372
+
373
+ def _save_results(self):
374
+ """Save results to JSON file."""
375
+ results_file = self.output_dir / "tuning_results.json"
376
+
377
+ data = {
378
+ "baseline": self.baseline_results,
379
+ "experiments": self.results,
380
+ "timestamp": datetime.now().isoformat(),
381
+ "config": {
382
+ "max_samples": self.max_samples,
383
+ "num_steps": self.num_steps,
384
+ "dataset_type": self.dataset_type,
385
+ "model_variant": self.model_variant,
386
+ }
387
+ }
388
+
389
+ with open(results_file, 'w') as f:
390
+ json.dump(data, f, indent=2)
391
+
392
+ print(f"\nResults saved to: {results_file}")
393
+
394
+ def analyze_results(self) -> Dict[str, Any]:
395
+ """Analyze results and find best configuration."""
396
+ if not self.results:
397
+ print("No results to analyze!")
398
+ return {}
399
+
400
+ print("\n" + "="*80)
401
+ print("ANALYSIS: FINDING BEST CONFIGURATION")
402
+ print("="*80)
403
+
404
+ # Filter out failed experiments
405
+ successful_results = [r for r in self.results if "metrics" in r]
406
+
407
+ if not successful_results:
408
+ print("No successful experiments!")
409
+ return {}
410
+
411
+ # Compute aggregate scores
412
+ for result in successful_results:
413
+ metrics = result["metrics"]
414
+ result["aggregate_score"] = self.compute_aggregate_score(metrics)
415
+
416
+ # Sort by aggregate score
417
+ successful_results.sort(key=lambda x: x["aggregate_score"], reverse=True)
418
+
419
+ # Print top 5 configurations
420
+ print("\nTop 5 Configurations:")
421
+ print("="*80)
422
+
423
+ for i, result in enumerate(successful_results[:5], 1):
424
+ print(f"\n#{i} - Aggregate Score: {result['aggregate_score']:.4f}")
425
+ print(f"Config: {result['config']}")
426
+ print(f"Metrics:")
427
+ for metric, value in result['metrics'].items():
428
+ print(f" {metric}: {value:.4f}")
429
+ if result.get('improvements'):
430
+ print(f"Improvements over baseline:")
431
+ for metric, value in result['improvements'].items():
432
+ print(f" {metric}: {value:+.2f}%")
433
+
434
+ # Save best config
435
+ best_result = successful_results[0]
436
+ best_config_file = self.output_dir / "best_config.json"
437
+
438
+ with open(best_config_file, 'w') as f:
439
+ json.dump({
440
+ "config": best_result["config"],
441
+ "metrics": best_result["metrics"],
442
+ "aggregate_score": best_result["aggregate_score"],
443
+ "improvements": best_result.get("improvements", {}),
444
+ }, f, indent=2)
445
+
446
+ print(f"\n✓ Best configuration saved to: {best_config_file}")
447
+
448
+ return best_result
449
+
450
+
451
+ def main():
452
+ parser = argparse.ArgumentParser(description="Hyperparameter tuning for gradient ascent")
453
+ parser.add_argument("--output_dir", type=str, default="tuning_results",
454
+ help="Directory to save tuning results")
455
+ parser.add_argument("--max_samples", type=int, default=30,
456
+ help="Number of samples to use for tuning")
457
+ parser.add_argument("--num_steps", type=int, default=20,
458
+ help="Number of inference steps (fixed)")
459
+ parser.add_argument("--dataset_type", type=str, default="pickapic",
460
+ choices=["coco", "pickapic"],
461
+ help="Dataset to use")
462
+ parser.add_argument("--model_variant", type=str, default="lpo",
463
+ choices=["origin", "spo", "diffusion_dpo", "lpo"],
464
+ help="Model variant to use")
465
+ parser.add_argument("--cuda", type=int, default=0,
466
+ help="CUDA device ID")
467
+ parser.add_argument("--search_type", type=str, default="grid",
468
+ choices=["grid", "random"],
469
+ help="Type of hyperparameter search")
470
+ parser.add_argument("--metrics", type=str, nargs="+",
471
+ default=["clip", "aesthetic", "pickscore", "hpsv2", "imagereward"],
472
+ help="Metrics to evaluate")
473
+ parser.add_argument("--start_idx", type=int, default=0,
474
+ help="Starting index for experiments (for GPU distribution)")
475
+ parser.add_argument("--end_idx", type=int, default=None,
476
+ help="Ending index for experiments (for GPU distribution)")
477
+
478
+ args = parser.parse_args()
479
+
480
+ # Create tuner
481
+ tuner = HyperparameterTuner(
482
+ output_dir=args.output_dir,
483
+ max_samples=args.max_samples,
484
+ num_steps=args.num_steps,
485
+ dataset_type=args.dataset_type,
486
+ model_variant=args.model_variant,
487
+ cuda_id=args.cuda,
488
+ metrics=args.metrics,
489
+ )
490
+
491
+ # Run search
492
+ results = tuner.run_search(
493
+ search_type=args.search_type,
494
+ start_idx=args.start_idx,
495
+ end_idx=args.end_idx
496
+ )
497
+
498
+ # Analyze results
499
+ best_result = tuner.analyze_results()
500
+
501
+ print("\n" + "="*80)
502
+ print("TUNING COMPLETE!")
503
+ print("="*80)
504
+ print(f"Total experiments: {len(results)}")
505
+ print(f"Results directory: {args.output_dir}")
506
+
507
+ if best_result:
508
+ print(f"\nBest configuration:")
509
+ print(json.dumps(best_result["config"], indent=2))
510
+ print(f"\nAggregate score: {best_result['aggregate_score']:.4f}")
511
+
512
+
513
+ if __name__ == "__main__":
514
+ main()
evaluation/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
evaluation/aesthetic_score.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Adapted from https://github.com/christophschuhmann/improved-aesthetic-predictor. Originally Apache License, Version 2.0, January 2004.
3
+ """
4
+
5
+ import torch
6
+ import torch.nn as nn
7
+ import numpy as np
8
+ from transformers import CLIPModel, CLIPProcessor
9
+ from PIL import Image
10
+ from io import BytesIO
11
+
12
+ def open_image(image):
13
+ if isinstance(image, bytes):
14
+ image = Image.open(BytesIO(image))
15
+ elif isinstance(image, str):
16
+ image = Image.open(image)
17
+ image = image.convert("RGB")
18
+ return image
19
+
20
+
21
+ class MLP(nn.Module):
22
+ def __init__(self):
23
+ super().__init__()
24
+ self.layers = nn.Sequential(
25
+ nn.Linear(768, 1024),
26
+ nn.Dropout(0.2),
27
+ nn.Linear(1024, 128),
28
+ nn.Dropout(0.2),
29
+ nn.Linear(128, 64),
30
+ nn.Dropout(0.1),
31
+ nn.Linear(64, 16),
32
+ nn.Linear(16, 1),
33
+ )
34
+
35
+ @torch.no_grad()
36
+ def forward(self, embed):
37
+ return self.layers(embed)
38
+
39
+
40
+ class AestheticScorer(torch.nn.Module):
41
+ def __init__(self, dtype, clip_name_or_path, aesthetic_name_or_path):
42
+ super().__init__()
43
+ self.clip = CLIPModel.from_pretrained(clip_name_or_path)
44
+ self.processor = CLIPProcessor.from_pretrained(clip_name_or_path)
45
+ self.mlp = MLP()
46
+ state_dict = torch.load(aesthetic_name_or_path, map_location='cpu')
47
+ self.mlp.load_state_dict(state_dict)
48
+ self.dtype = dtype
49
+ self.eval()
50
+
51
+ @torch.no_grad()
52
+ def __call__(self, images):
53
+ device = next(self.parameters()).device
54
+ inputs = self.processor(images=images, return_tensors="pt")
55
+ inputs = {k: v.to(self.dtype).to(device) for k, v in inputs.items()}
56
+ embed = self.clip.get_image_features(**inputs)
57
+ # normalize embedding
58
+ embed = embed / torch.linalg.vector_norm(embed, dim=-1, keepdim=True)
59
+ return self.mlp(embed).squeeze(1)
60
+
61
+
62
+ if __name__ == "__main__":
63
+ clip_name_or_path = "openai/clip-vit-large-patch14"
64
+ aesthetic_name_or_path = "./sac+logos+ava1-l14-linearMSE.pth"
65
+ aesthetic_scorer = AestheticScorer(torch.float32, clip_name_or_path, aesthetic_name_or_path)
66
+ image0 = open_image('./image0.png')
67
+ image1 = open_image('./image1.png')
68
+ print(aesthetic_scorer(image0))
69
+ print(aesthetic_scorer(image1))
70
+ print(aesthetic_scorer([image0, image1]))
71
+
evaluation/blip/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .blip_pretrain import *
evaluation/general_score_pap_val.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ from pick_score import PickScorer
3
+ from aesthetic_score import AestheticScorer
4
+ from hpsv2_score import HPSv2Scorer
5
+ from imagereward_score import load_imagereward
6
+ from diffusers import AutoencoderKL, StableDiffusionPipeline, \
7
+ StableDiffusionXLPipeline, DDIMScheduler, \
8
+ UNet2DConditionModel
9
+ import torch
10
+ import os
11
+ import json
12
+ from tqdm import tqdm
13
+ from huggingface_hub import hf_hub_download
14
+ from argparse import ArgumentParser
15
+
16
+
17
+ def load_origin_sd_v1_5(scheduler, inference_dtype):
18
+ pipe = StableDiffusionPipeline.from_pretrained(
19
+ 'stable-diffusion-v1-5/stable-diffusion-v1-5',
20
+ torch_dtype=inference_dtype,
21
+ scheduler=scheduler,
22
+ safety_checker=None,
23
+ )
24
+ guidance_scale = 7.5
25
+ return pipe, guidance_scale
26
+
27
+
28
+ def load_spo_sd_v1_5(scheduler, inference_dtype):
29
+ pipe = StableDiffusionPipeline.from_pretrained(
30
+ 'SPO-Diffusion-Models/SPO-SD-v1-5_4k-p_10ep',
31
+ torch_dtype=inference_dtype,
32
+ scheduler=scheduler,
33
+ safety_checker=None,
34
+ )
35
+ guidance_scale = 5.0
36
+ return pipe, guidance_scale
37
+
38
+
39
+ def load_diffusion_dpo_sd_v1_5(scheduler, inference_dtype):
40
+ unet = UNet2DConditionModel.from_pretrained('mhdang/dpo-sd1.5-text2image-v1', subfolder="unet", torch_dtype=inference_dtype)
41
+ pipe = StableDiffusionPipeline.from_pretrained(
42
+ 'stable-diffusion-v1-5/stable-diffusion-v1-5',
43
+ torch_dtype=inference_dtype,
44
+ scheduler=scheduler,
45
+ safety_checker=None,
46
+ unet=unet,
47
+ )
48
+ guidance_scale = 7.5
49
+ return pipe, guidance_scale
50
+
51
+
52
+ def load_lpo_sd_v1_5(scheduler, inference_dtype):
53
+ unet = UNet2DConditionModel.from_pretrained(
54
+ 'casiatao/LPO',
55
+ subfolder="lpo_sd15_merge/unet",
56
+ torch_dtype=inference_dtype
57
+ )
58
+ pipe = StableDiffusionPipeline.from_pretrained(
59
+ 'stable-diffusion-v1-5/stable-diffusion-v1-5',
60
+ torch_dtype=inference_dtype,
61
+ scheduler=scheduler,
62
+ safety_checker=None,
63
+ unet=unet
64
+ )
65
+ guidance_scale = 5.0
66
+ return pipe, guidance_scale
67
+
68
+
69
+ def load_origin_sdxl(scheduler, inference_dtype):
70
+ pipe = StableDiffusionXLPipeline.from_pretrained(
71
+ 'stabilityai/stable-diffusion-xl-base-1.0',
72
+ torch_dtype=inference_dtype,
73
+ scheduler=scheduler,
74
+ )
75
+ vae = AutoencoderKL.from_pretrained(
76
+ 'madebyollin/sdxl-vae-fp16-fix',
77
+ torch_dtype=torch.float16,
78
+ )
79
+ pipe.vae = vae
80
+ guidance_scale = 5.0
81
+ return pipe, guidance_scale
82
+
83
+
84
+ def load_spo_sdxl(scheduler, inference_dtype):
85
+ pipe = StableDiffusionXLPipeline.from_pretrained(
86
+ 'SPO-Diffusion-Models/SPO-SDXL_4k-p_10ep',
87
+ torch_dtype=inference_dtype,
88
+ scheduler=scheduler,
89
+ )
90
+ vae = AutoencoderKL.from_pretrained(
91
+ 'madebyollin/sdxl-vae-fp16-fix',
92
+ torch_dtype=torch.float16,
93
+ )
94
+ pipe.vae = vae
95
+ guidance_scale = 5.0
96
+ return pipe, guidance_scale
97
+
98
+
99
+ def load_diffusion_dpo_sdxl(scheduler, inference_dtype):
100
+ unet = UNet2DConditionModel.from_pretrained('mhdang/dpo-sdxl-text2image-v1', subfolder="unet", torch_dtype=inference_dtype)
101
+ pipe = StableDiffusionXLPipeline.from_pretrained(
102
+ 'stabilityai/stable-diffusion-xl-base-1.0',
103
+ torch_dtype=inference_dtype,
104
+ scheduler=scheduler,
105
+ unet=unet,
106
+ )
107
+ vae = AutoencoderKL.from_pretrained(
108
+ 'madebyollin/sdxl-vae-fp16-fix',
109
+ torch_dtype=torch.float16,
110
+ )
111
+ pipe.vae = vae
112
+ guidance_scale = 5.0
113
+ return pipe, guidance_scale
114
+
115
+
116
+ def load_lpo_sdxl(scheduler, inference_dtype):
117
+ unet = UNet2DConditionModel.from_pretrained(
118
+ 'casiatao/LPO',
119
+ subfolder="lpo_sdxl_merge/unet",
120
+ torch_dtype=inference_dtype
121
+ )
122
+ vae = AutoencoderKL.from_pretrained(
123
+ 'madebyollin/sdxl-vae-fp16-fix',
124
+ torch_dtype=torch.float16,
125
+ )
126
+ pipe = StableDiffusionXLPipeline.from_pretrained(
127
+ 'stabilityai/stable-diffusion-xl-base-1.0',
128
+ torch_dtype=inference_dtype,
129
+ scheduler=scheduler,
130
+ unet=unet,
131
+ vae=vae
132
+ )
133
+ guidance_scale = 5.0
134
+ return pipe, guidance_scale
135
+
136
+
137
+
138
+ model_dict = {
139
+ 'origin_sd15': load_origin_sd_v1_5,
140
+ 'spo_sd15': load_spo_sd_v1_5,
141
+ 'diffusion_dpo_sd15': load_diffusion_dpo_sd_v1_5,
142
+ 'lpo_sd15': load_lpo_sd_v1_5,
143
+ 'origin_sdxl': load_origin_sdxl,
144
+ 'spo_sdxl': load_spo_sdxl,
145
+ 'diffusion_dpo_sdxl': load_diffusion_dpo_sdxl,
146
+ 'lpo_sdxl': load_lpo_sdxl,
147
+ }
148
+
149
+
150
+ if __name__ == "__main__":
151
+ # hyperparameter
152
+ parser = ArgumentParser()
153
+ parser.add_argument("--model_name", type=str, default="origin_sdxl")
154
+ parser.add_argument("--batch_size", type=int, default=1)
155
+ parser.add_argument("--num_image_per_prompt", type=int, default=4)
156
+ parser.add_argument("--sample_steps", type=int, default=20)
157
+ parser.add_argument("--seed", type=int, default=42)
158
+ parser.add_argument("--device", type=str, default="cuda")
159
+ args = parser.parse_args()
160
+
161
+ model_name = args.model_name
162
+ batch_size = args.batch_size
163
+ num_image_per_prompt = args.num_image_per_prompt
164
+ sample_steps = args.sample_steps
165
+ seed = args.seed
166
+ device = args.device
167
+
168
+ # load preference model
169
+ pickscorer = PickScorer(processor_name_or_path="laion/CLIP-ViT-H-14-laion2B-s32B-b79K", model_pretrained_name_or_path="yuvalkirstain/PickScore_v1", device=device)
170
+
171
+ aesthetic_scorer = AestheticScorer(torch.float32, "openai/clip-vit-large-patch14", "./sac+logos+ava1-l14-linearMSE.pth")
172
+ aesthetic_scorer = aesthetic_scorer.to(device)
173
+
174
+
175
+ hpsv2_scorer = HPSv2Scorer(
176
+ clip_pretrained_name_or_path=hf_hub_download(repo_id="laion/CLIP-ViT-H-14-laion2B-s32B-b79K", filename="open_clip_pytorch_model.bin"),
177
+ model_pretrained_name_or_path=hf_hub_download(repo_id="xswu/HPSv2", filename="HPS_v2_compressed.pt"),
178
+ device=device
179
+ )
180
+
181
+ hpsv21_scorer = HPSv2Scorer(
182
+ clip_pretrained_name_or_path=hf_hub_download(repo_id="laion/CLIP-ViT-H-14-laion2B-s32B-b79K", filename="open_clip_pytorch_model.bin"),
183
+ model_pretrained_name_or_path=hf_hub_download(repo_id="xswu/HPSv2", filename="HPS_v2.1_compressed.pt"),
184
+ device=device
185
+ )
186
+
187
+ imagereward_scorer = load_imagereward(
188
+ model_path=hf_hub_download(repo_id="THUDM/ImageReward", filename="ImageReward.pt"),
189
+ med_config=hf_hub_download(repo_id="THUDM/ImageReward", filename="med_config.json"),
190
+ device=device
191
+ )
192
+
193
+ load_model_func = model_dict[model_name]
194
+
195
+ # load diffusion model
196
+ inference_dtype = torch.float16
197
+ scheduler = DDIMScheduler.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", subfolder="scheduler")
198
+ pipe, guidance_scale = load_model_func(scheduler, inference_dtype)
199
+ pipe.to(device)
200
+ pipe.set_progress_bar_config(disable=True)
201
+
202
+ # load dataset
203
+ val_dataset = load_dataset("pickapic-anonymous/pickapic_v1", split="validation_unique", streaming=True)
204
+
205
+ # calculate preference score
206
+ caption_list = []
207
+ for i, sample in enumerate(val_dataset):
208
+ caption_list.append(sample['caption'])
209
+
210
+ batch_num = len(caption_list) // batch_size if len(caption_list) % batch_size == 0 else len(caption_list) // batch_size + 1
211
+ batched_caption_list = [caption_list[i*batch_size:(i+1)*batch_size] for i in range(batch_num)]
212
+
213
+ pickscore_list = []
214
+ aesthetic_score_list = []
215
+ hpsv2score_list = []
216
+ hpsv21score_list = []
217
+ imagereward_list = []
218
+
219
+ for batch_prompt in tqdm(batched_caption_list):
220
+ generator=torch.Generator(device=device).manual_seed(seed)
221
+ images = pipe(
222
+ batch_prompt,
223
+ guidance_scale=guidance_scale,
224
+ num_inference_steps=sample_steps,
225
+ generator=generator,
226
+ output_type='pil',
227
+ num_images_per_prompt=num_image_per_prompt,
228
+ ).images
229
+
230
+ for prompt, image in zip(batch_prompt, images):
231
+ pickscore = pickscorer(prompt, [image])[0]
232
+ pickscore_list.append(pickscore)
233
+
234
+ aesthetic_score = aesthetic_scorer(image)[0].item()
235
+ aesthetic_score_list.append(aesthetic_score)
236
+
237
+ hpsv2_score = hpsv2_scorer.score(image, prompt)[0]
238
+ hpsv2score_list.append(hpsv2_score)
239
+
240
+ hpsv21_score = hpsv21_scorer.score(image, prompt)[0]
241
+ hpsv21score_list.append(hpsv21_score)
242
+
243
+ imagereward_score = imagereward_scorer.score(prompt, image)
244
+ imagereward_list.append(imagereward_score)
245
+
246
+
247
+ res_save_dir = './eval_results/pick_a_pic_val_score'
248
+ os.makedirs(res_save_dir, exist_ok=True)
249
+
250
+ file_name = f"{model_name}_ddim_cfg{guidance_scale}_step{sample_steps}_seed{seed}_{num_image_per_prompt}image_batch{batch_size}.json"
251
+ with open(os.path.join(res_save_dir, file_name), 'w', encoding='utf-8') as f:
252
+ json.dump({
253
+ 'pickscore': torch.mean(torch.tensor(pickscore_list)).item(),
254
+ 'aestheticscore': torch.mean(torch.tensor(aesthetic_score_list)).item(),
255
+ 'hpsv2score': torch.mean(torch.tensor(hpsv2score_list)).item(),
256
+ 'hpsv21score': torch.mean(torch.tensor(hpsv21score_list)).item(),
257
+ 'imagerewardscore': torch.mean(torch.tensor(imagereward_list)).item(),
258
+ }, f, indent=4)
259
+
260
+ print(f"Pickscore: {torch.mean(torch.tensor(pickscore_list))}")
261
+ print(f"Aestheticscore: {torch.mean(torch.tensor(aesthetic_score_list))}")
262
+ print(f"HPSv2score: {torch.mean(torch.tensor(hpsv2score_list))}")
263
+ print(f"HPSv21score: {torch.mean(torch.tensor(hpsv21score_list))}")
264
+ print(f"Imagerewardscore: {torch.mean(torch.tensor(imagereward_list))}")
265
+
upload.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Upload this codebase to Hugging Face Hub while excluding the .back folder.
3
+
4
+ Examples:
5
+ /g/data/rr81/aev/bin/python upload.py
6
+ /g/data/rr81/aev/bin/python upload.py --repo-id aryadomain/all_code_base
7
+ /g/data/rr81/aev/bin/python upload.py --method large
8
+ /g/data/rr81/aev/bin/python upload.py --repo-id my-user/all_code_base
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import inspect
15
+ from pathlib import Path
16
+ from typing import List
17
+
18
+ from huggingface_hub import HfApi
19
+
20
+
21
+ def build_ignore_patterns(extra_ignore: List[str]) -> List[str]:
22
+ # Always exclude local metadata and the requested backup folder.
23
+ patterns = [
24
+ ".git",
25
+ ".git/**",
26
+ ".back",
27
+ ".back/**",
28
+ "**/.back/**",
29
+ ]
30
+ patterns.extend(extra_ignore)
31
+ return patterns
32
+
33
+
34
+ def parse_args() -> argparse.Namespace:
35
+ parser = argparse.ArgumentParser(
36
+ description="Upload a folder to Hugging Face Hub, excluding .back."
37
+ )
38
+ parser.add_argument(
39
+ "--source-dir",
40
+ type=str,
41
+ default=".",
42
+ help="Local folder to upload (default: current directory).",
43
+ )
44
+ parser.add_argument(
45
+ "--repo-name",
46
+ type=str,
47
+ default="all_code_base",
48
+ help="Repo name used when --repo-id is not provided.",
49
+ )
50
+ parser.add_argument(
51
+ "--repo-id",
52
+ type=str,
53
+ default="aryadomain/all_code_base",
54
+ help="Full Hugging Face repo id like user_or_org/repo_name (default: aryadomain/all_code_base).",
55
+ )
56
+ parser.add_argument(
57
+ "--namespace",
58
+ type=str,
59
+ default=None,
60
+ help="Optional user/org namespace override when using --repo-name.",
61
+ )
62
+ parser.add_argument(
63
+ "--repo-type",
64
+ type=str,
65
+ default="model",
66
+ choices=["model", "dataset", "space"],
67
+ help="Hub repo type.",
68
+ )
69
+ parser.add_argument(
70
+ "--private",
71
+ action="store_true",
72
+ help="Create the repo as private.",
73
+ )
74
+ parser.add_argument(
75
+ "--revision",
76
+ type=str,
77
+ default="main",
78
+ help="Target branch/revision (default: main).",
79
+ )
80
+ parser.add_argument(
81
+ "--commit-message",
82
+ type=str,
83
+ default="Upload codebase excluding .back",
84
+ help="Commit message for upload.",
85
+ )
86
+ parser.add_argument(
87
+ "--extra-ignore",
88
+ nargs="*",
89
+ default=[],
90
+ help="Additional ignore patterns for upload_folder.",
91
+ )
92
+ parser.add_argument(
93
+ "--dry-run",
94
+ action="store_true",
95
+ help="Print resolved settings and exit without uploading.",
96
+ )
97
+ parser.add_argument(
98
+ "--method",
99
+ type=str,
100
+ default="large",
101
+ choices=["auto", "folder", "large"],
102
+ help=(
103
+ "Upload method: auto chooses large upload for big folders, "
104
+ "folder forces upload_folder, large forces upload_large_folder compatibility path."
105
+ ),
106
+ )
107
+ parser.add_argument(
108
+ "--large-threshold-gb",
109
+ type=float,
110
+ default=10.0,
111
+ help="In auto mode, switch to large upload when folder size exceeds this many GB.",
112
+ )
113
+ return parser.parse_args()
114
+
115
+
116
+ def resolve_repo_id(api: HfApi, args: argparse.Namespace) -> str:
117
+ if args.repo_id:
118
+ return args.repo_id
119
+
120
+ if args.namespace:
121
+ return f"{args.namespace}/{args.repo_name}"
122
+
123
+ who = api.whoami()
124
+ username = who.get("name")
125
+ if not username:
126
+ raise RuntimeError("Could not resolve username from Hugging Face login.")
127
+ return f"{username}/{args.repo_name}"
128
+
129
+
130
+ def folder_size_bytes(folder: Path) -> int:
131
+ total = 0
132
+ for p in folder.rglob("*"):
133
+ if p.is_file() and ".back" not in p.parts:
134
+ try:
135
+ total += p.stat().st_size
136
+ except OSError:
137
+ pass
138
+ return total
139
+
140
+
141
+ def upload_folder_compat(
142
+ api: HfApi,
143
+ repo_id: str,
144
+ repo_type: str,
145
+ source_dir: Path,
146
+ revision: str,
147
+ commit_message: str,
148
+ ignore_patterns: List[str],
149
+ ):
150
+ kwargs = {
151
+ "repo_id": repo_id,
152
+ "repo_type": repo_type,
153
+ "folder_path": str(source_dir),
154
+ "path_in_repo": ".",
155
+ "revision": revision,
156
+ "commit_message": commit_message,
157
+ "ignore_patterns": ignore_patterns,
158
+ }
159
+ sig = inspect.signature(api.upload_folder)
160
+ if "multi_commits" in sig.parameters:
161
+ kwargs["multi_commits"] = True
162
+ if "multi_commits_verbose" in sig.parameters:
163
+ kwargs["multi_commits_verbose"] = True
164
+ return api.upload_folder(**kwargs)
165
+
166
+
167
+ def upload_large_compat(
168
+ api: HfApi,
169
+ repo_id: str,
170
+ repo_type: str,
171
+ source_dir: Path,
172
+ revision: str,
173
+ ignore_patterns: List[str],
174
+ ):
175
+ if not hasattr(api, "upload_large_folder"):
176
+ raise RuntimeError("Installed huggingface_hub does not provide upload_large_folder")
177
+
178
+ sig = inspect.signature(api.upload_large_folder)
179
+ kwargs = {
180
+ "repo_id": repo_id,
181
+ "repo_type": repo_type,
182
+ "folder_path": str(source_dir),
183
+ }
184
+ if "revision" in sig.parameters:
185
+ kwargs["revision"] = revision
186
+ if "path_in_repo" in sig.parameters:
187
+ kwargs["path_in_repo"] = "."
188
+ if "ignore_patterns" in sig.parameters:
189
+ kwargs["ignore_patterns"] = ignore_patterns
190
+
191
+ return api.upload_large_folder(**kwargs)
192
+
193
+
194
+ def main() -> None:
195
+ args = parse_args()
196
+ source_dir = Path(args.source_dir).resolve()
197
+
198
+ if not source_dir.exists() or not source_dir.is_dir():
199
+ raise FileNotFoundError(f"Source directory not found or not a directory: {source_dir}")
200
+
201
+ api = HfApi()
202
+ repo_id = resolve_repo_id(api, args)
203
+ ignore_patterns = build_ignore_patterns(args.extra_ignore)
204
+ total_size = folder_size_bytes(source_dir)
205
+ total_size_gb = total_size / (1024 ** 3)
206
+
207
+ if args.method == "auto":
208
+ use_large = total_size_gb >= args.large_threshold_gb
209
+ else:
210
+ use_large = args.method == "large"
211
+
212
+ print("Source directory:", source_dir)
213
+ print("Repo id:", repo_id)
214
+ print("Repo type:", args.repo_type)
215
+ print("Private:", args.private)
216
+ print("Revision:", args.revision)
217
+ print("Ignore patterns:", ignore_patterns)
218
+ print(f"Folder size (excluding .back): {total_size_gb:.2f} GB")
219
+ print("Upload method:", "large" if use_large else "folder")
220
+
221
+ if args.dry_run:
222
+ print("Dry run requested. Exiting before create/upload.")
223
+ return
224
+
225
+ api.create_repo(
226
+ repo_id=repo_id,
227
+ repo_type=args.repo_type,
228
+ private=args.private,
229
+ exist_ok=True,
230
+ )
231
+
232
+ if use_large:
233
+ try:
234
+ commit_info = upload_large_compat(
235
+ api=api,
236
+ repo_id=repo_id,
237
+ repo_type=args.repo_type,
238
+ source_dir=source_dir,
239
+ revision=args.revision,
240
+ ignore_patterns=ignore_patterns,
241
+ )
242
+ except Exception as e:
243
+ print(f"Large upload path failed ({e}). Falling back to upload_folder with multi-commit mode.")
244
+ commit_info = upload_folder_compat(
245
+ api=api,
246
+ repo_id=repo_id,
247
+ repo_type=args.repo_type,
248
+ source_dir=source_dir,
249
+ revision=args.revision,
250
+ commit_message=args.commit_message,
251
+ ignore_patterns=ignore_patterns,
252
+ )
253
+ else:
254
+ commit_info = upload_folder_compat(
255
+ api=api,
256
+ repo_id=repo_id,
257
+ repo_type=args.repo_type,
258
+ source_dir=source_dir,
259
+ revision=args.revision,
260
+ commit_message=args.commit_message,
261
+ ignore_patterns=ignore_patterns,
262
+ )
263
+
264
+ print("Upload completed.")
265
+ print("Commit:", commit_info)
266
+ print("Repo URL: https://huggingface.co/" + repo_id)
267
+
268
+
269
+ if __name__ == "__main__":
270
+ main()