datnguyentien204 commited on
Commit
40440d6
Β·
verified Β·
1 Parent(s): 6576670

Upload RotEx inference code, demos, documentation, and pretrained checkpoints

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ demo/illumination_variation_space.jpg filter=lfs diff=lfs merge=lfs -text
37
+ demo/RotEx_Proposed.png filter=lfs diff=lfs merge=lfs -text
38
+ demo/VLA_Traj.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,3 +1,306 @@
1
- ---
2
- license: apache-2.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # [Official] RotEx: A Rotational Expert Network for Realistic Scene Synthesis Under Illumination Variation
2
+
3
+ <p align="center">
4
+ πŸ“„ <b>Paper: coming soon</b>&nbsp;&nbsp; | &nbsp;&nbsp;
5
+ πŸ’» <a href="https://github.com/val-utehy/RotEx"><b>Source Code</b></a>&nbsp;&nbsp; | &nbsp;&nbsp;
6
+ πŸ€— <a href="https://huggingface.co/val-utehy/RotEx"><b>Hugging Face</b></a>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <img src="demo/RotEx_Proposed.png" alt="Overview of RotEx and its double-stream and single-stream transformer blocks" width="1100"/>
11
+ </p>
12
+ <p align="center">
13
+ <em>Architecture of RotEx: content and style streams, depth conditioning, and illumination-dependent expert routing.</em>
14
+ </p>
15
+
16
+ > The Hugging Face commands below describe the intended weight layout; they become usable once the checkpoints are uploaded.
17
+
18
+ ## Introduction
19
+
20
+ **RotEx** is a transformer-based image-to-image framework for synthesizing scenes under varying illumination conditions. It combines content latents, depth information, style features, and a continuous time-of-day variable to generate illumination variations of an input scene.
21
+
22
+ The model operates in the latent space of a FLUX autoencoder. DINOv2 supplies style features, Depth Anything V2 supplies depth maps, and time-conditioned mixture-of-experts modules adapt the synthesis process to the requested illumination. The release includes both multi-step inference and a distilled one-step inference path.
23
+
24
+ ### Key Features
25
+
26
+ - **Continuous illumination control:** Specify a time-of-day value and map it to an angle through `phi = hour * 2Ο€ / 24`.
27
+ - **Illumination-dependent experts:** Route style and image features through experts conditioned on the illumination angle.
28
+ - **Content and depth conditioning:** Use an encoded content image and its estimated depth map during synthesis.
29
+ - **Two inference modes:** Use a full checkpoint with a multi-step Heun solver, or a distilled checkpoint with a single model evaluation.
30
+ - **Image and video processing:** Generate image sequences across hours or vary the target hour across the frames of a video.
31
+ - **Reference-style conditioning:** Use a separate style image to condition illumination synthesis.
32
+
33
+ ## Demo
34
+
35
+ ### Illumination Variation
36
+
37
+ <p align="center">
38
+ <img src="demo/illumination_variation_space.jpg" alt="Scene synthesis across the illumination variation space" width="1000"/>
39
+ </p>
40
+ <p align="center">
41
+ <em>Illustration of the illumination variation space.</em>
42
+ </p>
43
+
44
+ ### Downstream Trajectory Comparison
45
+
46
+ <p align="center">
47
+ <img src="demo/VLA_Traj.png" alt="AutoVLA trajectory comparison under different image translation methods" width="1100"/>
48
+ </p>
49
+ <p align="center">
50
+ <em>Qualitative comparison of AutoVLA rollouts using daylight reference images and images produced by different translation methods.</em>
51
+ </p>
52
+
53
+ ## Installation and Requirements
54
+
55
+ The supplied inference code uses CUDA and imports FlashAttention directly. Use a compatible NVIDIA GPU and a Linux environment with the CUDA toolkit required by your FlashAttention build.
56
+
57
+ | Component | Requirement |
58
+ | --- | --- |
59
+ | Python | 3.10 or later |
60
+ | PyTorch | 2.6.0 or later, as specified in `requirements.txt` |
61
+ | torchvision | A version compatible with the installed PyTorch release; `requirements.txt` specifies 0.19.0 or later |
62
+ | FlashAttention | 2.0.0 or later, with a build compatible with PyTorch, CUDA, and the GPU |
63
+ | Default inference precision | `torch.bfloat16` |
64
+ | Default image resolution | 384 Γ— 384 |
65
+
66
+ ### 1. Clone the Repository
67
+
68
+ ```shell
69
+ git clone https://github.com/val-utehy/RotEx.git
70
+ cd RotEx
71
+ ```
72
+
73
+ All commands below run from the directory containing `config.py`, `flux/`, and the inference scripts. If using this code inside the BenchRotEx workspace, that directory is `B/RotEx`.
74
+
75
+ ### 2. Create an Environment
76
+
77
+ ```shell
78
+ conda create -n rotex python=3.10 -y
79
+ conda activate rotex
80
+ python -m pip install --upgrade pip
81
+ ```
82
+
83
+ ### 3. Install PyTorch and Dependencies
84
+
85
+ Install a matching CUDA-enabled PyTorch and torchvision pair using the [official PyTorch installation instructions](https://pytorch.org/get-started/locally/). Ensure the selected versions satisfy the requirements above.
86
+
87
+ After installing PyTorch:
88
+
89
+ ```shell
90
+ python -m pip install packaging ninja psutil
91
+ python -m pip install "flash-attn>=2.0.0" --no-build-isolation
92
+ python -m pip install -r requirements.txt
93
+ ```
94
+
95
+ See the [FlashAttention installation instructions](https://github.com/Dao-AILab/flash-attention#installation-and-features) for supported CUDA/GPU combinations and build requirements.
96
+
97
+ Verify the environment:
98
+
99
+ ```shell
100
+ python -c "import torch, flash_attn; print('PyTorch:', torch.__version__); print('CUDA:', torch.cuda.is_available()); print('BF16:', torch.cuda.is_bf16_supported())"
101
+ ```
102
+
103
+ ### 4. Prepare the Auxiliary Models
104
+
105
+ The inference scripts load these pretrained components:
106
+
107
+ | Component | Source | Local behavior |
108
+ | --- | --- | --- |
109
+ | FLUX autoencoder | [black-forest-labs/FLUX.1-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev), `ae.safetensors` | Loaded from `checkpoints/ae.safetensors`; downloaded there if missing |
110
+ | DINOv2 Base | [facebook/dinov2-base](https://huggingface.co/facebook/dinov2-base) | Downloaded through Transformers and stored in the Hugging Face cache |
111
+ | Depth Anything V2 Small | [depth-anything/Depth-Anything-V2-Small-hf](https://huggingface.co/depth-anything/Depth-Anything-V2-Small-hf) | Downloaded through Transformers and stored in the Hugging Face cache |
112
+
113
+ An internet connection is needed for the first download. If access to an auxiliary model requires authentication or approval, complete the access process on its model page and log in with `hf auth login`.
114
+
115
+ To download the autoencoder explicitly:
116
+
117
+ ```shell
118
+ hf download black-forest-labs/FLUX.1-dev ae.safetensors --local-dir checkpoints
119
+ ```
120
+
121
+ ## Pretrained Models
122
+
123
+ RotEx weights will be distributed through [Hugging Face](https://huggingface.co/val-utehy/RotEx). Download the checkpoint matching the inference mode you intend to run.
124
+
125
+ | Checkpoint | Purpose | Expected local path |
126
+ | --- | --- | --- |
127
+ | Full model, best checkpoint | Multi-step image and video inference | `checkpoints/full/best.pt` |
128
+ | Full model, final checkpoint | Alternative full-model checkpoint | `checkpoints/full/last.pt` |
129
+ | Distilled model, best checkpoint | One-step inference | `distill_checkpoints/best_distill.pt` |
130
+
131
+ Once the model repository is published with this directory structure, download the required weights from the project root:
132
+
133
+ ```shell
134
+ hf download val-utehy/RotEx checkpoints/full/best.pt --local-dir .
135
+ hf download val-utehy/RotEx distill_checkpoints/best_distill.pt --local-dir .
136
+ ```
137
+
138
+ The final full-model checkpoint is optional:
139
+
140
+ ```shell
141
+ hf download val-utehy/RotEx checkpoints/full/last.pt --local-dir .
142
+ ```
143
+
144
+ The `hf` command is provided by `huggingface_hub`. See the [Hugging Face CLI guide](https://huggingface.co/docs/huggingface_hub/guides/cli) for download and authentication options.
145
+
146
+ Additional placement instructions are available in [checkpoints/README.md](checkpoints/README.md), [checkpoints/full/README.md](checkpoints/full/README.md), and [distill_checkpoints/README.md](distill_checkpoints/README.md).
147
+
148
+ > [!NOTE]
149
+ > The scripts still contain paths from the original development environment. Pass explicit paths to the multi-step scripts and update the configuration constants in `infer_1step_style.py`. The model configuration is defined in `config.py`; these scripts do not use an `opt.pkl` file.
150
+
151
+ ## Usage Guide
152
+
153
+ ### 1. Model Training
154
+
155
+ This release contains model components and inference scripts. A standalone training entry point and a distillation training entry point are not included in this directory.
156
+
157
+ ### 2. Choosing an Inference Script
158
+
159
+ | Script | Checkpoint | Style input | Output |
160
+ | --- | --- | --- | --- |
161
+ | `infer_1step_style.py` | Distilled | One fixed reference image | One PNG per image and hour for each configured input/output pair; hours 1–24 by default |
162
+ | `infer_multistep.py` | Full | Every image in a style directory | One labeled strip per content/style pair; hours 0–24 by default |
163
+ | `infer_multistep_vid.py` | Full | One fixed reference image | One MP4 with the target hour interpolated across frames |
164
+
165
+ All image-directory scripts read `.jpg`, `.jpeg`, and `.png` files directly inside the configured folders. They do not search subdirectories recursively.
166
+
167
+ ### 3. Multi-Step Image Inference
168
+
169
+ Prepare separate content and style directories, for example:
170
+
171
+ ```text
172
+ inputs/
173
+ β”œβ”€β”€ content/
174
+ β”‚ β”œβ”€β”€ scene_001.png
175
+ β”‚ └── scene_002.jpg
176
+ └── style/
177
+ └── night_reference.jpg
178
+ ```
179
+
180
+ Run:
181
+
182
+ ```shell
183
+ python infer_multistep.py \
184
+ --checkpoint checkpoints/full/best.pt \
185
+ --content_dir inputs/content \
186
+ --style_dir inputs/style \
187
+ --output_dir outputs/multistep \
188
+ --denoise_steps 50 \
189
+ --image_size 384 \
190
+ --depth_size 48 \
191
+ --num_seeds 1 \
192
+ --device cuda \
193
+ --dtype bfloat16
194
+ ```
195
+
196
+ Each content image is combined with every style image. Results are saved as `<content_stem>__sty_<style_stem>.png`. Each result is a horizontal strip containing the content image, style reference, depth visualization, and generated images for hours 0 through 24. Existing output files are skipped.
197
+
198
+ | Argument | Default | Description |
199
+ | --- | --- | --- |
200
+ | `--denoise_steps` | `50` | Number of Heun integration steps; each step evaluates the model twice |
201
+ | `--image_size` | `384` | Square image resolution used for content and style preprocessing |
202
+ | `--depth_size` | `48` | Depth-map resolution supplied to the model |
203
+ | `--num_seeds` | `1` | Number of independently sampled outputs to average per hour, in decoded image space |
204
+ | `--device` | `cuda` | Device used for inference |
205
+ | `--dtype` | `bfloat16` | Use `bfloat16` for the supplied default GPU path |
206
+
207
+ The hour list is set inside `run_inference()` and is not exposed as a command-line argument. The legacy `--noise_alpha` argument is unused; initialization is sampled from Gaussian noise. The image solver clamps latents to `[-20, 20]` during integration and `[-15, 15]` before decoding.
208
+
209
+ ### 4. One-Step Image Inference with a Fixed Style
210
+
211
+ Open `infer_1step_style.py` and configure the distilled checkpoint, style image, and dataset paths:
212
+
213
+ ```python
214
+ CHECKPOINT = "distill_checkpoints/best_distill.pt"
215
+ FIXED_STYLE_PATH = "inputs/style/night_reference.jpg"
216
+ DATA_PAIRS = [
217
+ {"input": "inputs/content", "output": "outputs/1step_style"},
218
+ ]
219
+ HOURS = list(range(1, 25))
220
+ ```
221
+
222
+ Run:
223
+
224
+ ```shell
225
+ python infer_1step_style.py
226
+ ```
227
+
228
+ The reference style is encoded once and reused for all configured image folders. Outputs follow the `<image_stem>_hXX.png` naming convention. The script writes to the configured output paths and can overwrite existing images with the same names.
229
+
230
+ ### 5. Multi-Step Video Inference
231
+
232
+ ```shell
233
+ python infer_multistep_vid.py \
234
+ --checkpoint checkpoints/full/best.pt \
235
+ --video_path inputs/video.mp4 \
236
+ --style_path inputs/style/night_reference.jpg \
237
+ --output_dir outputs/video \
238
+ --denoise_steps 50 \
239
+ --image_size 384 \
240
+ --depth_size 48 \
241
+ --max_height 384 \
242
+ --hour_start 0 \
243
+ --hour_end 24 \
244
+ --device cuda \
245
+ --dtype bfloat16 \
246
+ --no_audio
247
+ ```
248
+
249
+ The script encodes the style reference once, reuses a base noise tensor across frames, and interpolates the target hour from `--hour_start` to `--hour_end` over the input video. The output is named `<video_stem>_chronflux.mp4` and uses the input video's reported frame rate.
250
+
251
+ `--image_size` controls the square resolution used for model inference. `--max_height` controls output resizing, with output dimensions aligned to multiples of 16. The video solver uses latent clamps of `[-8, 8]` during integration and `[-6, 6]` before decoding, which differ from the image solver.
252
+
253
+ The example produces a silent video. Audio transfer is optional and requires MoviePy, which is not included in `requirements.txt`; the script falls back to saving the video without audio if transfer fails.
254
+
255
+ ### 6. Illumination Control and Reproducibility
256
+
257
+ The scripts map hours to the model's illumination angle as follows:
258
+
259
+ ```python
260
+ phi = hour * (2 * math.pi / 24.0)
261
+ ```
262
+
263
+ Hours 0 and 24 correspond to the endpoints of one full cycle. The image scripts use discrete hour lists, while the video script supplies intermediate hour values across frames. To render fractional hours in an image script, edit its hour list; the one-step filename formatting must also be adapted if the values are not integers.
264
+
265
+ The image scripts sample new Gaussian noise for each output and do not expose a seed argument. Repeated runs can therefore produce different results. Increasing `--num_seeds` in the multi-step image script averages decoded outputs and increases computation; it is separate from increasing the number of solver steps.
266
+
267
+ ## Repository Structure
268
+
269
+ ```text
270
+ RotEx/
271
+ β”œβ”€β”€ README.md
272
+ β”œβ”€β”€ config.py
273
+ β”œβ”€β”€ requirements.txt
274
+ β”œβ”€β”€ infer_1step_style.py
275
+ β”œβ”€β”€ infer_multistep.py
276
+ β”œβ”€β”€ infer_multistep_vid.py
277
+ β”œβ”€β”€ flux/ # Model, autoencoder, encoders, and utilities
278
+ β”œβ”€β”€ demo/ # Architecture and qualitative figures
279
+ β”œβ”€β”€ checkpoints/
280
+ β”‚ β”œβ”€β”€ README.md
281
+ β”‚ β”œβ”€β”€ ae.safetensors # Download separately
282
+ β”‚ └── full/
283
+ β”‚ β”œβ”€β”€ README.md
284
+ β”‚ β”œβ”€β”€ best.pt # Download from the RotEx model repository
285
+ β”‚ └── last.pt # Optional alternative full checkpoint
286
+ └── distill_checkpoints/
287
+ β”œβ”€β”€ README.md
288
+ └── best_distill.pt # Download from the RotEx model repository
289
+ ```
290
+
291
+ The Python implementation retains internal names such as `ChronFlux` and the `q1_paper` configuration preset. These are the identifiers used by the current inference scripts.
292
+
293
+ ## Citation
294
+
295
+ Paper details and the official BibTeX entry will be added when available.
296
+
297
+ ## References
298
+
299
+ - [FLUX](https://github.com/black-forest-labs/flux)
300
+ - [DINOv2](https://github.com/facebookresearch/dinov2)
301
+ - [Depth Anything V2](https://github.com/DepthAnything/Depth-Anything-V2)
302
+ - [FlashAttention](https://github.com/Dao-AILab/flash-attention)
303
+
304
+ ## License
305
+
306
+ A project license has not yet been included in this release. The license will be specified when the repository is published. Consult the respective upstream repositories and model cards for the terms governing pretrained components.
checkpoints/README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pretrained Checkpoints
2
+
3
+ RotEx model weights will be hosted on **Hugging Face**:
4
+
5
+ [RotEx model repository](https://huggingface.co/val-utehy/RotEx)
6
+
7
+ The commands below assume the uploaded files preserve the directory structure shown here.
8
+
9
+ ## Directory Layout
10
+
11
+ ```text
12
+ checkpoints/
13
+ β”œβ”€β”€ README.md
14
+ β”œβ”€β”€ ae.safetensors
15
+ └── full/
16
+ β”œβ”€β”€ README.md
17
+ β”œβ”€β”€ best.pt
18
+ └── last.pt
19
+ ```
20
+
21
+ `best.pt` and `last.pt` are full-model checkpoints for `infer_multistep.py` and `infer_multistep_vid.py`. Use the distilled model in [`../distill_checkpoints/`](../distill_checkpoints/README.md) for `infer_1step_style.py`.
22
+
23
+ ## Download the Full Model
24
+
25
+ Run from the project root, the directory containing `config.py`:
26
+
27
+ ```shell
28
+ hf download val-utehy/RotEx checkpoints/full/best.pt --local-dir .
29
+ ```
30
+
31
+ Optionally download the final full-model checkpoint:
32
+
33
+ ```shell
34
+ hf download val-utehy/RotEx checkpoints/full/last.pt --local-dir .
35
+ ```
36
+
37
+ Pass `--checkpoint checkpoints/full/best.pt` to either multi-step inference script. See [`full/README.md`](full/README.md) for examples.
38
+
39
+ ## Download the Autoencoder
40
+
41
+ The FLUX autoencoder is distributed separately by its original provider:
42
+
43
+ [black-forest-labs/FLUX.1-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev)
44
+
45
+ ```shell
46
+ hf download black-forest-labs/FLUX.1-dev ae.safetensors --local-dir checkpoints
47
+ ```
48
+
49
+ If authentication is required, complete the model's access process and run `hf auth login` first. The inference loader also attempts to download `ae.safetensors` automatically when it is absent from this directory.
50
+
51
+ DINOv2 and Depth Anything V2 are loaded through Transformers into the Hugging Face cache; their weights do not need to be copied into this folder.
52
+
53
+ See the [main README](../README.md) for installation and inference instructions.
checkpoints/full/README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Full-Model Weights
2
+
3
+ Download full-model checkpoints from the [RotEx Hugging Face repository](https://huggingface.co/val-utehy/RotEx).
4
+
5
+ Downloads become available after the files are uploaded.
6
+
7
+ | File | Use |
8
+ | --- | --- |
9
+ | `best.pt` | Best full-model checkpoint |
10
+ | `last.pt` | Final full-model checkpoint; optional alternative |
11
+
12
+ Run from the project root:
13
+
14
+ ```shell
15
+ hf download val-utehy/RotEx checkpoints/full/best.pt --local-dir .
16
+ ```
17
+
18
+ To download the alternative checkpoint:
19
+
20
+ ```shell
21
+ hf download val-utehy/RotEx checkpoints/full/last.pt --local-dir .
22
+ ```
23
+
24
+ The expected local paths are `checkpoints/full/best.pt` and `checkpoints/full/last.pt`.
25
+
26
+ ## Image Inference
27
+
28
+ ```shell
29
+ python infer_multistep.py \
30
+ --checkpoint checkpoints/full/best.pt \
31
+ --content_dir inputs/content \
32
+ --style_dir inputs/style \
33
+ --output_dir outputs/multistep
34
+ ```
35
+
36
+ ## Video Inference
37
+
38
+ ```shell
39
+ python infer_multistep_vid.py \
40
+ --checkpoint checkpoints/full/best.pt \
41
+ --video_path inputs/video.mp4 \
42
+ --style_path inputs/style/night_reference.jpg \
43
+ --output_dir outputs/video \
44
+ --no_audio
45
+ ```
46
+
47
+ These checkpoints are intended for the multi-step scripts. For one-step inference, download `distill_checkpoints/best_distill.pt` and follow the [distilled-checkpoint instructions](../../distill_checkpoints/README.md).
48
+
49
+ See the [main README](../../README.md) for dependencies, auxiliary models, and inference options.
checkpoints/full/best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b24f810ca16eb2a6d1ab5eed60d5ea0ba9ead1d0ebeb827e1daaf77305670c7b
3
+ size 8396220961
checkpoints/full/last.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b24f810ca16eb2a6d1ab5eed60d5ea0ba9ead1d0ebeb827e1daaf77305670c7b
3
+ size 8396220961
config.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration system for enhanced RIFT model.
3
+ """
4
+ from dataclasses import dataclass, field
5
+ from typing import Literal
6
+
7
+ @dataclass
8
+ class EncoderConfig:
9
+ type: Literal["siglip", "dinov2", "evaclip"] = "dinov2"
10
+ model_size: Literal["small", "base", "large"] = "base"
11
+ output_dim: int = 512
12
+ depth_aware: bool = False
13
+
14
+ @dataclass
15
+ class LossConfig:
16
+ """
17
+ Configuration for loss functions and weights.
18
+ """
19
+ recon_weight: float = 10.0 # Charbonnier Loss
20
+ color_match_weight: float = 5.0 # ColorMatchingLoss
21
+ day_preserve_weight: float = 3.0 # DayPreservationLoss
22
+ moe_weight: float = 0.05 # MoE Load Balancing
23
+ brightness_cont_weight: float = 0.1
24
+ color_cont_weight: float = 0.1
25
+
26
+ use_charbonnier: bool = True
27
+
28
+ use_vgg: bool = True
29
+ vgg_weight: float = 0.5
30
+ # Tuning guide:
31
+ # 0.1 β†’ subtle sharpening, safe
32
+ # 0.5 β†’ noticeable edge/texture improvement ← default
33
+
34
+ use_lpips: bool = False
35
+ lpips_weight: float = 0.0
36
+
37
+ use_l1: bool = False
38
+ use_gan: bool = False
39
+ gan_weight: float = 0.0
40
+
41
+ # Legacy (disabled)
42
+ use_smooth_transition: bool = False
43
+ smooth_weight: float = 0.0
44
+ use_content_preservation: bool = False
45
+ content_weight: float = 0.0
46
+ use_brightness_preservation: bool = False
47
+ brightness_weight: float = 0.0
48
+ min_brightness: float = 0.0
49
+ max_brightness: float = 0.0
50
+ charbonnier_eps: float = 1e-4
51
+
52
+ @dataclass
53
+ class AttentionConfig:
54
+ use_cross_attention: bool = True
55
+ use_adain: bool = True
56
+ use_depth_attention: bool = True
57
+ use_multi_scale_attention: bool = False
58
+
59
+ @dataclass
60
+ class MoEConfig:
61
+ enabled: bool = True
62
+ num_experts: int = 4
63
+ top_k: int = 2
64
+ temperature: float = 0.3
65
+ noise_std: float = 0.1
66
+ load_balance_coef: float = 0.01
67
+
68
+ @dataclass
69
+ class SamplingConfig:
70
+ method: Literal["euler", "heun", "consistency"] = "heun"
71
+ num_steps: int = 30
72
+ use_flow_matching: bool = True
73
+ use_consistency: bool = False
74
+ use_cfg: bool = True
75
+ cfg_scale: float = 7.5
76
+ sigma_min: float = 0.002
77
+ sigma_max: float = 80.0
78
+
79
+ @dataclass
80
+ class ModelConfig:
81
+ in_channels: int = 16
82
+ hidden_size: int = 1024
83
+ num_double_layers: int = 8
84
+ num_single_layers: int = 16
85
+ num_heads: int = 16
86
+ style_dim: int = 512
87
+ use_text: bool = False
88
+ text_dim: int = 1152
89
+ encoder: EncoderConfig = field(default_factory=EncoderConfig)
90
+ loss: LossConfig = field(default_factory=LossConfig)
91
+ attention: AttentionConfig = field(default_factory=AttentionConfig)
92
+ moe: MoEConfig = field(default_factory=MoEConfig)
93
+ sampling: SamplingConfig = field(default_factory=SamplingConfig)
94
+
95
+ @dataclass
96
+ class TrainingConfig:
97
+ content_dir: str = "/workspace/ChronFLUX/rafael_ds_ver2/train/day"
98
+ style_dir: str = "/workspace/ChronFLUX/rafael_ds_ver2/train/night"
99
+ image_size: int = 384
100
+ batch_size: int = 1
101
+ grad_accum_steps: int = 4
102
+ epochs: int = 43
103
+ lr_max: float = 1e-7
104
+ lr_min: float = 1e-5
105
+ weight_decay: float = 1e-4
106
+ save_interval_epochs: int = 1
107
+ device: str = "cuda"
108
+ dtype: str = "bfloat16"
109
+
110
+ def get_q1_paper_config() -> ModelConfig:
111
+ return ModelConfig()
112
+
113
+ PRESETS = {
114
+ "q1_paper": get_q1_paper_config,
115
+ }
116
+
117
+ def get_config(preset: str = "q1_paper") -> ModelConfig:
118
+ return PRESETS.get(preset, get_q1_paper_config)()
demo/RotEx_Proposed.png ADDED

Git LFS Details

  • SHA256: eb143632412ff5ed25ba2d1ff04594a1dff1f616f6c9814f93b02cf5ef442e3c
  • Pointer size: 132 Bytes
  • Size of remote file: 3.8 MB
demo/VLA_Traj.png ADDED

Git LFS Details

  • SHA256: 71d7900c6fcf9137430c826f58e8967034fd8722c220126df408ee12dfa28360
  • Pointer size: 131 Bytes
  • Size of remote file: 543 kB
demo/illumination_variation_space.jpg ADDED

Git LFS Details

  • SHA256: 0974531cb0ca0280a4af10f1c549c55fd3d3ec863ba267d2d611078eadb650c6
  • Pointer size: 132 Bytes
  • Size of remote file: 1.38 MB
distill_checkpoints/README.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Distilled One-Step Weights
2
+
3
+ Download the distilled checkpoint from the [RotEx Hugging Face repository](https://huggingface.co/val-utehy/RotEx).
4
+
5
+ Downloads become available after the checkpoint is uploaded.
6
+
7
+ Run from the project root, the directory containing `config.py`:
8
+
9
+ ```shell
10
+ hf download val-utehy/RotEx distill_checkpoints/best_distill.pt --local-dir .
11
+ ```
12
+
13
+ The expected local path is:
14
+
15
+ ```text
16
+ distill_checkpoints/best_distill.pt
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Set the checkpoint constant in `infer_1step_style.py`:
22
+
23
+ ```python
24
+ CHECKPOINT = "distill_checkpoints/best_distill.pt"
25
+ ```
26
+
27
+ Configure the input/output paths in the script, then run:
28
+
29
+ ```shell
30
+ python infer_1step_style.py
31
+ ```
32
+
33
+ `infer_1step_style.py` requires a fixed reference image configured through `FIXED_STYLE_PATH` and input/output folders configured through `DATA_PAIRS`.
34
+
35
+ The script also needs the FLUX autoencoder, DINOv2, and Depth Anything V2 described in the [main README](../README.md). Full-model weights for multi-step inference belong in [`../checkpoints/full/`](../checkpoints/full/README.md).
distill_checkpoints/best_distill.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee1b4ef1320479571288b9f00cbd4ddd89e6e7829e0f9d1d104546f821d487ab
3
+ size 2798766921
flux/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ try:
2
+ from ._version import (
3
+ version as __version__,
4
+ version_tuple,
5
+ )
6
+ except ImportError:
7
+ __version__ = "unknown"
8
+ version_tuple = (0, 0, "unknown", "noinfo")
9
+
10
+ from pathlib import Path
11
+
12
+ PACKAGE = __package__.replace("_", "-")
13
+ PACKAGE_ROOT = Path(__file__).parent
flux/chronflux.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+ from einops import rearrange, repeat
4
+ from flux.modules.layers import EmbedND, timestep_embedding, MLPEmbedder
5
+ from flux.utils.math import attention, apply_rope
6
+ from flux.modules.attention import AdaIN1d, DepthControlledAttention
7
+ from flux.modules.moe import MoEStyleAdapter, MoEFeatureProcessor
8
+ import torch.utils.checkpoint as checkpoint
9
+ from typing import Optional, Dict, Any
10
+
11
+ class SwiGLU(nn.Module):
12
+ def __init__(self, in_features, hidden_features=None, out_features=None):
13
+ super().__init__()
14
+ out_features = out_features or in_features
15
+ hidden_features = hidden_features or in_features
16
+ hidden_features = int(hidden_features * 2 / 3)
17
+ self.w1 = nn.Linear(in_features, hidden_features)
18
+ self.w2 = nn.Linear(in_features, hidden_features)
19
+ self.w3 = nn.Linear(hidden_features, out_features)
20
+ self.silu = nn.SiLU()
21
+
22
+ def forward(self, x):
23
+ x1 = self.w1(x)
24
+ x2 = self.w2(x)
25
+ hidden = self.silu(x1) * x2
26
+ return self.w3(hidden)
27
+
28
+ class AdaLNModulation(nn.Module):
29
+ def __init__(self, hidden_size, components=6):
30
+ super().__init__()
31
+ self.silu = nn.SiLU()
32
+ self.linear = nn.Linear(hidden_size, components * hidden_size, bias=True)
33
+ self.components = components
34
+ nn.init.zeros_(self.linear.weight)
35
+ nn.init.zeros_(self.linear.bias)
36
+
37
+ def forward(self, vec):
38
+ chunk = self.linear(self.silu(vec))
39
+ return chunk.chunk(self.components, dim=1)
40
+
41
+ class QKNorm(nn.Module):
42
+ def __init__(self, dim: int):
43
+ super().__init__()
44
+ self.query_norm = nn.LayerNorm(dim, elementwise_affine=True, eps=1e-6)
45
+ self.key_norm = nn.LayerNorm(dim, elementwise_affine=True, eps=1e-6)
46
+
47
+ def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
48
+ return self.query_norm(q), self.key_norm(k)
49
+
50
+ class ChronFluxDoubleStreamBlock(nn.Module):
51
+ def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, use_depth_attn=False):
52
+ super().__init__()
53
+ self.hidden_size = hidden_size
54
+ self.num_heads = num_heads
55
+ self.head_dim = hidden_size // num_heads
56
+ self.use_depth_attn = use_depth_attn
57
+ self.img_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
58
+ self.img_qkv = nn.Linear(hidden_size, hidden_size * 3)
59
+ self.img_proj = nn.Linear(hidden_size, hidden_size)
60
+ self.sty_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
61
+ self.sty_qkv = nn.Linear(hidden_size, hidden_size * 3)
62
+ self.sty_proj = nn.Linear(hidden_size, hidden_size)
63
+ self.img_qk_norm = QKNorm(self.head_dim)
64
+ self.sty_qk_norm = QKNorm(self.head_dim)
65
+ self.img_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
66
+ self.img_mlp = SwiGLU(hidden_size, int(hidden_size * mlp_ratio))
67
+ self.sty_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
68
+ self.sty_mlp = SwiGLU(hidden_size, int(hidden_size * mlp_ratio))
69
+ self.adaLN = AdaLNModulation(hidden_size, components=12)
70
+ if use_depth_attn:
71
+ self.depth_attn = DepthControlledAttention(hidden_size, num_heads=num_heads)
72
+ self.use_adain = False
73
+
74
+ def set_adain(self, use_adain, style_dim):
75
+ self.use_adain = use_adain
76
+ if use_adain:
77
+ self.adain = AdaIN1d(self.hidden_size, style_dim)
78
+
79
+ def forward(self, img, sty, vec, pe, depth_map=None, style_vec=None):
80
+ if self.use_adain and style_vec is not None:
81
+ adain_style = style_vec
82
+ if style_vec.dim() == 3:
83
+ adain_style = style_vec.mean(dim=1)
84
+ img = self.adain(img, adain_style)
85
+
86
+ params = self.adaLN(vec)
87
+ (shift_i_a, scale_i_a, gate_i_a, shift_i_m, scale_i_m, gate_i_m,
88
+ shift_s_a, scale_s_a, gate_s_a, shift_s_m, scale_s_m, gate_s_m) = params
89
+
90
+ img_mod = self.img_norm1(img) * (1 + scale_i_a.unsqueeze(1)) + shift_i_a.unsqueeze(1)
91
+ sty_mod = self.sty_norm1(sty) * (1 + scale_s_a.unsqueeze(1)) + shift_s_a.unsqueeze(1)
92
+ qkv_i = self.img_qkv(img_mod)
93
+ qkv_s = self.sty_qkv(sty_mod)
94
+ q_i, k_i, v_i = rearrange(qkv_i, "B L (K H D) -> K B H L D", K=3, H=self.num_heads)
95
+ q_s, k_s, v_s = rearrange(qkv_s, "B L (K H D) -> K B H L D", K=3, H=self.num_heads)
96
+ q_i, k_i = self.img_qk_norm(q_i, k_i)
97
+ q_s, k_s = self.sty_qk_norm(q_s, k_s)
98
+ dtype = img.dtype
99
+ q_i, k_i, v_i = q_i.to(dtype), k_i.to(dtype), v_i.to(dtype)
100
+ q_s, k_s, v_s = q_s.to(dtype), k_s.to(dtype), v_s.to(dtype)
101
+ L_s_tokens = sty.shape[1]
102
+ pe_s = pe[:, :, :L_s_tokens, ...]
103
+ pe_i = pe[:, :, L_s_tokens:, ...]
104
+ q_i, k_i = apply_rope(q_i, k_i, pe_i)
105
+ q_s, k_s = apply_rope(q_s, k_s, pe_s)
106
+ q_i, k_i = q_i.to(dtype), k_i.to(dtype)
107
+ q_s, k_s = q_s.to(dtype), k_s.to(dtype)
108
+ q_i = rearrange(q_i, "B H L D -> B L H D")
109
+ k_i = rearrange(k_i, "B H L D -> B L H D")
110
+ v_i = rearrange(v_i, "B H L D -> B L H D")
111
+ q_s = rearrange(q_s, "B H L D -> B L H D")
112
+ k_s = rearrange(k_s, "B H L D -> B L H D")
113
+ v_s = rearrange(v_s, "B H L D -> B L H D")
114
+ full_k = torch.cat([k_s, k_i], dim=1)
115
+ full_v = torch.cat([v_s, v_i], dim=1)
116
+ img_attn_out = self.img_proj(attention(q_i, full_k, full_v))
117
+ sty_attn_out = self.sty_proj(attention(q_s, full_k, full_v))
118
+ img = img + gate_i_a.unsqueeze(1) * img_attn_out
119
+ sty = sty + gate_s_a.unsqueeze(1) * sty_attn_out
120
+ if self.use_depth_attn and depth_map is not None:
121
+ img = img + self.depth_attn(img, depth_map)
122
+ img_mod = self.img_norm2(img) * (1 + scale_i_m.unsqueeze(1)) + shift_i_m.unsqueeze(1)
123
+ img = img + gate_i_m.unsqueeze(1) * self.img_mlp(img_mod)
124
+ sty_mod = self.sty_norm2(sty) * (1 + scale_s_m.unsqueeze(1)) + shift_s_m.unsqueeze(1)
125
+ sty = sty + gate_s_m.unsqueeze(1) * self.sty_mlp(sty_mod)
126
+ return img, sty
127
+
128
+ class ChronFluxSingleStreamBlock(nn.Module):
129
+ def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, use_moe=False, moe_params: Optional[Dict[str, Any]] = None):
130
+ super().__init__()
131
+ self.hidden_size = hidden_size
132
+ self.num_heads = num_heads
133
+ self.head_dim = hidden_size // num_heads
134
+ self.use_moe = use_moe
135
+ self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
136
+ self.qkv = nn.Linear(hidden_size, hidden_size * 3)
137
+ self.proj = nn.Linear(hidden_size, hidden_size)
138
+ self.qk_norm = QKNorm(self.head_dim)
139
+ self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
140
+ if use_moe:
141
+ params = moe_params or {}
142
+ num_experts = params.get('num_experts', 3) # Default safe value
143
+ # Extract only valid args for MoEFeatureProcessor
144
+ valid_keys = ['custom_centers', 'custom_widths']
145
+ kwargs = {k: params[k] for k in valid_keys if k in params}
146
+
147
+ self.moe_processor = MoEFeatureProcessor(hidden_size, num_experts=num_experts, **kwargs)
148
+ self.mlp = None
149
+ else:
150
+ self.mlp = SwiGLU(hidden_size, int(hidden_size * mlp_ratio))
151
+ self.moe_processor = None
152
+ self.adaLN = AdaLNModulation(hidden_size, components=6)
153
+
154
+ def forward(self, x, vec, pe, phi=None):
155
+ shift_a, scale_a, gate_a, shift_m, scale_m, gate_m = self.adaLN(vec)
156
+ x_mod = self.norm1(x) * (1 + scale_a.unsqueeze(1)) + shift_a.unsqueeze(1)
157
+ qkv = self.qkv(x_mod)
158
+ q, k, v = rearrange(qkv, "B L (K H D) -> K B H L D", K=3, H=self.num_heads)
159
+ q, k = self.qk_norm(q, k)
160
+ dtype = x.dtype
161
+ q, k, v = q.to(dtype), k.to(dtype), v.to(dtype)
162
+ q, k = apply_rope(q, k, pe)
163
+ q, k = q.to(dtype), k.to(dtype)
164
+ q = rearrange(q, "B H L D -> B L H D")
165
+ k = rearrange(k, "B H L D -> B L H D")
166
+ v = rearrange(v, "B H L D -> B L H D")
167
+ x_attn = self.proj(attention(q, k, v))
168
+ x = x + gate_a.unsqueeze(1) * x_attn
169
+ x_mod = self.norm2(x) * (1 + scale_m.unsqueeze(1)) + shift_m.unsqueeze(1)
170
+
171
+ aux_loss = 0.0
172
+ if self.use_moe and phi is not None:
173
+ moe_out, aux = self.moe_processor(x_mod, phi, return_aux=True)
174
+ x = x + gate_m.unsqueeze(1) * moe_out
175
+ if aux is not None:
176
+ aux_loss = aux['load_balance_loss']
177
+ else:
178
+ x = x + gate_m.unsqueeze(1) * self.mlp(x_mod)
179
+
180
+ return x, aux_loss
181
+
182
+ class ChronFlux(nn.Module):
183
+ def __init__(self, in_channels=16, hidden_size=1024, num_double_layers=8,
184
+ num_single_layers=16, num_heads=16, style_dim=512,
185
+ use_text=False, text_dim=1152,
186
+ use_moe=True, use_depth_attn=True, use_adain=False,
187
+ moe_params: Optional[Dict[str, Any]] = None):
188
+ super().__init__()
189
+ self.in_channels = in_channels
190
+ self.gradient_checkpointing = False
191
+ self.use_text = use_text
192
+ self.style_dim = style_dim
193
+ self.use_moe = use_moe
194
+ self.use_depth_attn = use_depth_attn
195
+ self.use_adain = use_adain
196
+ self.img_in = nn.Linear(16 * 3, hidden_size)
197
+ self.depth_proj = nn.Conv2d(1, 16, kernel_size=1)
198
+ self.style_in = nn.Linear(style_dim, hidden_size)
199
+ self.default_style = nn.Parameter(torch.randn(1, style_dim))
200
+ self.time_mlp = nn.Sequential(
201
+ nn.Linear(256, hidden_size),
202
+ nn.SiLU(),
203
+ nn.Linear(hidden_size, hidden_size)
204
+ )
205
+ if self.use_text:
206
+ self.txt_mlp = nn.Sequential(
207
+ nn.Linear(text_dim, hidden_size),
208
+ nn.SiLU(),
209
+ nn.Linear(hidden_size, hidden_size)
210
+ )
211
+ self.style_adapter = nn.Sequential(
212
+ nn.Linear(text_dim, hidden_size),
213
+ nn.SiLU(),
214
+ nn.Linear(hidden_size, style_dim)
215
+ )
216
+ nn.init.zeros_(self.style_adapter[-1].weight)
217
+ nn.init.zeros_(self.style_adapter[-1].bias)
218
+ self.phi_mlp = nn.Sequential(
219
+ nn.Linear(5, hidden_size),
220
+ nn.SiLU(),
221
+ nn.Linear(hidden_size, hidden_size)
222
+ )
223
+
224
+ # Pass MoE params
225
+ if self.use_moe:
226
+ params = moe_params or {}
227
+ num_experts = params.get('num_experts', 3)
228
+ valid_keys = ['custom_centers', 'custom_widths']
229
+ kwargs = {k: params[k] for k in valid_keys if k in params}
230
+ self.moe_style_adapter = MoEStyleAdapter(style_dim, hidden_dim=style_dim * 2, num_experts=num_experts, **kwargs)
231
+
232
+ self.vector_in = MLPEmbedder(hidden_size, hidden_size)
233
+ self.pe_embedder = EmbedND(dim=hidden_size // num_heads, theta=10000, axes_dim=[16, 24, 24])
234
+ self.double_blocks = nn.ModuleList()
235
+ for _ in range(num_double_layers):
236
+ block = ChronFluxDoubleStreamBlock(hidden_size, num_heads, use_depth_attn=use_depth_attn)
237
+ block.set_adain(use_adain, style_dim)
238
+ self.double_blocks.append(block)
239
+ self.single_blocks = nn.ModuleList([
240
+ ChronFluxSingleStreamBlock(
241
+ hidden_size, num_heads,
242
+ use_moe=(use_moe and i >= num_single_layers // 2),
243
+ moe_params=moe_params
244
+ )
245
+ for i in range(num_single_layers)
246
+ ])
247
+ self.final_norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
248
+ self.final_layer = nn.Linear(hidden_size, in_channels)
249
+
250
+ def enable_gradient_checkpointing(self):
251
+ self.gradient_checkpointing = True
252
+
253
+ def forward(self, x_t, content_latent, depth_map, style_vec, timesteps, phi, txt_emb=None):
254
+ B, C, H, W = x_t.shape
255
+ moe_loss = torch.tensor(0.0, device=x_t.device, dtype=x_t.dtype)
256
+
257
+ content_normalized = content_latent
258
+ depth_small = torch.nn.functional.interpolate(depth_map, size=(H, W), mode="nearest")
259
+ depth_emb = self.depth_proj(depth_small)
260
+ img_cat = torch.cat([x_t, content_normalized, depth_emb], dim=1)
261
+ img_flat = rearrange(img_cat, 'b c h w -> b (h w) c')
262
+ img = self.img_in(img_flat)
263
+
264
+ if style_vec is None:
265
+ style_vec = self.default_style.expand(B, -1).to(x_t.dtype)
266
+ if self.use_text and txt_emb is not None:
267
+ style_delta = self.style_adapter(txt_emb)
268
+ style_vec = style_vec + style_delta
269
+
270
+ if self.use_moe:
271
+ style_vec, aux = self.moe_style_adapter(style_vec, phi, return_aux=True)
272
+ if aux is not None:
273
+ moe_loss += aux['load_balance_loss']
274
+
275
+ style = self.style_in(style_vec)
276
+ if style.dim() == 2:
277
+ style = style.unsqueeze(1)
278
+ L_s = style.shape[1]
279
+
280
+ t_emb = timestep_embedding(timesteps, 256).to(x_t.device).to(x_t.dtype)
281
+ t_vec = self.time_mlp(t_emb)
282
+ phi_norm = (phi % (2 * torch.pi)) / (2 * torch.pi)
283
+ phi_input = torch.cat([
284
+ torch.sin(phi.unsqueeze(1)),
285
+ torch.cos(phi.unsqueeze(1)),
286
+ torch.sin(2 * phi.unsqueeze(1)),
287
+ torch.cos(2 * phi.unsqueeze(1)),
288
+ phi_norm.unsqueeze(1)
289
+ ], dim=1)
290
+ phi_vec = self.phi_mlp(phi_input)
291
+ vec_input = t_vec + phi_vec
292
+ if self.use_text and txt_emb is not None:
293
+ txt_vec = self.txt_mlp(txt_emb)
294
+ vec_input = vec_input + txt_vec
295
+ vec = self.vector_in(vec_input)
296
+
297
+ img_ids = torch.zeros(H, W, 3).to(x_t.device)
298
+ img_ids[..., 1] = img_ids[..., 1] + torch.arange(H, device=x_t.device)[:, None]
299
+ img_ids[..., 2] = img_ids[..., 2] + torch.arange(W, device=x_t.device)[None, :]
300
+ if self.training:
301
+ img_ids += torch.randn_like(img_ids) * 0.5
302
+ img_ids = repeat(img_ids, "h w c -> b (h w) c", b=B)
303
+ pe = self.pe_embedder(img_ids)
304
+ dtype = pe.dtype
305
+ device = pe.device
306
+ pe_dim = pe.shape[-3]
307
+ style_pe = torch.zeros(B, 1, L_s, pe_dim, 2, 2, device=device, dtype=dtype)
308
+ full_pe = torch.cat([style_pe, pe], dim=2)
309
+
310
+ for block in self.double_blocks:
311
+ if self.gradient_checkpointing and self.training:
312
+ img, style = checkpoint.checkpoint(block, img, style, vec, full_pe, depth_map, style_vec, use_reentrant=False)
313
+ else:
314
+ img, style = block(img, style, vec, full_pe, depth_map, style_vec=style_vec)
315
+
316
+ x = torch.cat([style, img], dim=1)
317
+
318
+ for block in self.single_blocks:
319
+ if self.gradient_checkpointing and self.training:
320
+ x, block_loss = checkpoint.checkpoint(block, x, vec, full_pe, phi, use_reentrant=False)
321
+ else:
322
+ x, block_loss = block(x, vec, full_pe, phi)
323
+ moe_loss += block_loss
324
+
325
+ x = x[:, style.shape[1]:, :]
326
+ x = self.final_norm(x)
327
+ x = self.final_layer(x)
328
+ x = rearrange(x, 'b (h w) c -> b c h w', h=H, w=W)
329
+
330
+ return x, moe_loss
flux/modules/__init__.py ADDED
File without changes
flux/modules/adaptive_blocks.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Adaptive Flux Blocks with enhanced AdaLN integration.
3
+ These blocks serve as building blocks for the SwiftFlux architecture with
4
+ advanced style transfer control and optional MoE-compatible components.
5
+ Author: https://github.com/datnguyen-tien204
6
+ """
7
+ import torch
8
+ from torch import nn
9
+ from einops import rearrange
10
+ from flux.modules.normalization import AdaLNModulation
11
+ from flux.utils.math import attention, apply_rope
12
+ from flux.utils.logger_utils import ConsoleLogger
13
+
14
+
15
+ # Constants
16
+ SWIGLU_HIDDEN_RATIO = 2 / 3 # Hidden dimension ratio for SwiGLU
17
+
18
+
19
+ class QKNorm(nn.Module):
20
+ """Query-Key normalization for stable attention"""
21
+ def __init__(self, dim: int):
22
+ super().__init__()
23
+ self.query_norm = nn.LayerNorm(dim, elementwise_affine=True, eps=1e-6)
24
+ self.key_norm = nn.LayerNorm(dim, elementwise_affine=True, eps=1e-6)
25
+
26
+ def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
27
+ return self.query_norm(q), self.key_norm(k)
28
+
29
+
30
+ class SwiGLU(nn.Module):
31
+ """SwiGLU activation for efficient FFN"""
32
+ def __init__(self, in_features, hidden_features=None, out_features=None):
33
+ super().__init__()
34
+ out_features = out_features or in_features
35
+ hidden_features = hidden_features or in_features
36
+ hidden_features = int(hidden_features * SWIGLU_HIDDEN_RATIO)
37
+
38
+ self.w1 = nn.Linear(in_features, hidden_features)
39
+ self.w2 = nn.Linear(in_features, hidden_features)
40
+ self.w3 = nn.Linear(hidden_features, out_features)
41
+ self.silu = nn.SiLU()
42
+
43
+ def forward(self, x):
44
+ x1 = self.w1(x)
45
+ x2 = self.w2(x)
46
+ hidden = self.silu(x1) * x2
47
+ return self.w3(hidden)
48
+
49
+
50
+ class AdaptiveFluxBlock(nn.Module):
51
+ """
52
+ Base Adaptive Flux Block with AdaLN integration.
53
+ Serves as foundation for both double-stream and single-stream variants.
54
+
55
+ Features:
56
+ - AdaLN for layer-wise style and conditioning control
57
+ - QKNorm for attention stability
58
+ - SwiGLU for efficient feed-forward
59
+ - Optional sparse activation support for MoE compatibility
60
+ """
61
+ def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, use_sparse=False):
62
+ super().__init__()
63
+ self.hidden_size = hidden_size
64
+ self.num_heads = num_heads
65
+ self.head_dim = hidden_size // num_heads
66
+ self.use_sparse = use_sparse
67
+ # Note: adaLN should be initialized in subclasses
68
+
69
+ def apply_adaptive_norm(self, x, scale, shift):
70
+ """Apply AdaLN normalization with scale and shift"""
71
+ return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
72
+
73
+
74
+ class AdaptiveDoubleStreamBlock(AdaptiveFluxBlock):
75
+ """
76
+ Adaptive Double-Stream Block with enhanced AdaLN.
77
+ Processes image and style streams separately with cross-attention.
78
+
79
+ Improvements:
80
+ - Dedicated AdaLN for both streams
81
+ - Enhanced normalization for better style consistency
82
+ - Support for sparse activation patterns
83
+ """
84
+ def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, use_sparse=False):
85
+ super().__init__(hidden_size, num_heads, mlp_ratio, use_sparse)
86
+
87
+ # Image stream components
88
+ self.img_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
89
+ self.img_qkv = nn.Linear(hidden_size, hidden_size * 3)
90
+ self.img_proj = nn.Linear(hidden_size, hidden_size)
91
+ self.img_qk_norm = QKNorm(self.head_dim)
92
+
93
+ self.img_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
94
+ self.img_mlp = SwiGLU(hidden_size, int(hidden_size * mlp_ratio))
95
+
96
+ # Style stream components
97
+ self.sty_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
98
+ self.sty_qkv = nn.Linear(hidden_size, hidden_size * 3)
99
+ self.sty_proj = nn.Linear(hidden_size, hidden_size)
100
+ self.sty_qk_norm = QKNorm(self.head_dim)
101
+
102
+ self.sty_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
103
+ self.sty_mlp = SwiGLU(hidden_size, int(hidden_size * mlp_ratio))
104
+
105
+ # AdaLN with 12 components (6 for img, 6 for style)
106
+ self.adaLN = AdaLNModulation(hidden_size, components=12)
107
+
108
+ def forward(self, img, sty, vec, pe):
109
+ """
110
+ Forward pass with dual-stream processing
111
+
112
+ Args:
113
+ img: Image features [B, L_img, D]
114
+ sty: Style features [B, L_sty, D]
115
+ vec: Conditioning vector [B, D]
116
+ pe: Position embeddings
117
+
118
+ Returns:
119
+ Updated (img, sty) features
120
+ """
121
+ # Generate all modulation parameters
122
+ params = self.adaLN(vec)
123
+ (shift_i_a, scale_i_a, gate_i_a, shift_i_m, scale_i_m, gate_i_m,
124
+ shift_s_a, scale_s_a, gate_s_a, shift_s_m, scale_s_m, gate_s_m) = params
125
+
126
+ # Image stream - attention
127
+ img_mod = self.apply_adaptive_norm(self.img_norm1(img), scale_i_a, shift_i_a)
128
+ sty_mod = self.apply_adaptive_norm(self.sty_norm1(sty), scale_s_a, shift_s_a)
129
+
130
+ qkv_i = self.img_qkv(img_mod)
131
+ qkv_s = self.sty_qkv(sty_mod)
132
+
133
+ q_i, k_i, v_i = rearrange(qkv_i, "B L (K H D) -> K B L H D", K=3, H=self.num_heads)
134
+ q_s, k_s, v_s = rearrange(qkv_s, "B L (K H D) -> K B L H D", K=3, H=self.num_heads)
135
+
136
+ q_i, k_i = self.img_qk_norm(q_i, k_i)
137
+ q_s, k_s = self.sty_qk_norm(q_s, k_s)
138
+
139
+ # Preserve dtype for Flash Attention
140
+ dtype = img.dtype
141
+ q_i, k_i, v_i = q_i.to(dtype), k_i.to(dtype), v_i.to(dtype)
142
+ q_s, k_s, v_s = q_s.to(dtype), k_s.to(dtype), v_s.to(dtype)
143
+
144
+ # Apply RoPE if position embeddings provided
145
+ if pe is not None:
146
+ pe = pe.transpose(1, 2)
147
+ pe_s = pe[:, :1, ...]
148
+ pe_i = pe[:, 1:, ...]
149
+
150
+ q_i, k_i = apply_rope(q_i, k_i, pe_i)
151
+ q_s, k_s = apply_rope(q_s, k_s, pe_s)
152
+
153
+ q_i, k_i = q_i.to(dtype), k_i.to(dtype)
154
+ q_s, k_s = q_s.to(dtype), k_s.to(dtype)
155
+
156
+ # Cross-attention between streams
157
+ full_k = torch.cat([k_s, k_i], dim=1)
158
+ full_v = torch.cat([v_s, v_i], dim=1)
159
+
160
+ img_attn_out = self.img_proj(attention(q_i, full_k, full_v))
161
+ sty_attn_out = self.sty_proj(attention(q_s, full_k, full_v))
162
+
163
+ # Apply gating and residual
164
+ img = img + gate_i_a.unsqueeze(1) * img_attn_out
165
+ sty = sty + gate_s_a.unsqueeze(1) * sty_attn_out
166
+
167
+ # MLP blocks with AdaLN
168
+ img_mod = self.apply_adaptive_norm(self.img_norm2(img), scale_i_m, shift_i_m)
169
+ img = img + gate_i_m.unsqueeze(1) * self.img_mlp(img_mod)
170
+
171
+ sty_mod = self.apply_adaptive_norm(self.sty_norm2(sty), scale_s_m, shift_s_m)
172
+ sty = sty + gate_s_m.unsqueeze(1) * self.sty_mlp(sty_mod)
173
+
174
+ return img, sty
175
+
176
+
177
+ class AdaptiveSingleStreamBlock(AdaptiveFluxBlock):
178
+ """
179
+ Adaptive Single-Stream Block with enhanced AdaLN.
180
+ Processes unified features with self-attention.
181
+
182
+ Improvements:
183
+ - Enhanced AdaLN control for unified stream
184
+ - Better style propagation through the network
185
+ - Optional sparse activation for MoE
186
+ """
187
+ def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, use_sparse=False):
188
+ super().__init__(hidden_size, num_heads, mlp_ratio, use_sparse)
189
+
190
+ self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
191
+ self.qkv = nn.Linear(hidden_size, hidden_size * 3)
192
+ self.proj = nn.Linear(hidden_size, hidden_size)
193
+ self.qk_norm = QKNorm(self.head_dim)
194
+
195
+ self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
196
+ self.mlp = SwiGLU(hidden_size, int(hidden_size * mlp_ratio))
197
+
198
+ # AdaLN with 6 components (scale, shift, gate for attention and MLP)
199
+ self.adaLN = AdaLNModulation(hidden_size, components=6)
200
+
201
+ def forward(self, x, vec, pe):
202
+ """
203
+ Forward pass with single-stream processing
204
+
205
+ Args:
206
+ x: Input features [B, L, D]
207
+ vec: Conditioning vector [B, D]
208
+ pe: Position embeddings
209
+
210
+ Returns:
211
+ Updated features
212
+ """
213
+ shift_a, scale_a, gate_a, shift_m, scale_m, gate_m = self.adaLN(vec)
214
+
215
+ # Attention with AdaLN
216
+ x_mod = self.apply_adaptive_norm(self.norm1(x), scale_a, shift_a)
217
+ qkv = self.qkv(x_mod)
218
+ q, k, v = rearrange(qkv, "B L (K H D) -> K B L H D", K=3, H=self.num_heads)
219
+ q, k = self.qk_norm(q, k)
220
+
221
+ dtype = x.dtype
222
+ q, k, v = q.to(dtype), k.to(dtype), v.to(dtype)
223
+
224
+ # Apply RoPE if position embeddings provided
225
+ if pe is not None:
226
+ pe_reshaped = pe.transpose(1, 2)
227
+ q, k = apply_rope(q, k, pe_reshaped)
228
+ q, k = q.to(dtype), k.to(dtype)
229
+
230
+ x_attn = self.proj(attention(q, k, v))
231
+ x = x + gate_a.unsqueeze(1) * x_attn
232
+
233
+ # MLP with AdaLN
234
+ x_mod = self.apply_adaptive_norm(self.norm2(x), scale_m, shift_m)
235
+ x = x + gate_m.unsqueeze(1) * self.mlp(x_mod)
236
+
237
+ return x
238
+
239
+
240
+ # For backward compatibility, create aliases
241
+ SwiftDoubleStreamBlock = AdaptiveDoubleStreamBlock
242
+ SwiftSingleStreamBlock = AdaptiveSingleStreamBlock
243
+
244
+
245
+ __all__ = [
246
+ 'AdaptiveFluxBlock',
247
+ 'AdaptiveDoubleStreamBlock',
248
+ 'AdaptiveSingleStreamBlock',
249
+ 'SwiftDoubleStreamBlock',
250
+ 'SwiftSingleStreamBlock',
251
+ 'QKNorm',
252
+ 'SwiGLU',
253
+ ]
flux/modules/advanced_encoders.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced encoder modules for enhanced feature extraction.
3
+ Supports DINOv2, EVA-CLIP, and other state-of-the-art vision encoders.
4
+ Author: https://github.com/datnguyen-tien204
5
+ """
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ from transformers import AutoModel, AutoImageProcessor, AutoImageProcessor, AutoModelForDepthEstimation
10
+ from typing import Optional, Literal
11
+ from flux.utils.logger_utils import ConsoleLogger
12
+
13
+ class DepthEstimator:
14
+ def __init__(self, device):
15
+ self.device = device
16
+ ConsoleLogger.info("Loading Depth Anything V2 Small...")
17
+ self.proc = AutoImageProcessor.from_pretrained("depth-anything/Depth-Anything-V2-Small-hf")
18
+ self.model = AutoModelForDepthEstimation.from_pretrained("depth-anything/Depth-Anything-V2-Small-hf").to(device).eval()
19
+
20
+ @torch.no_grad()
21
+ def predict(self, images, size=384):
22
+ imgs_255 = ((images * 0.5 + 0.5) * 255).clamp(0, 255).to(torch.uint8)
23
+ inputs = self.proc(images=imgs_255, return_tensors="pt", size={"height": 518, "width": 518}).to(self.device)
24
+ depth = self.model(**inputs).predicted_depth
25
+ depth = F.interpolate(depth.unsqueeze(1), size=(size, size), mode="bicubic")
26
+ for i in range(depth.shape[0]):
27
+ depth[i] = (depth[i] - depth[i].min()) / (depth[i].max() - depth[i].min() + 1e-6)
28
+ return depth
29
+
30
+ class DINOv2Encoder(nn.Module):
31
+ """
32
+ DINOv2 encoder for enhanced feature extraction.
33
+ Better semantic understanding and structural preservation.
34
+ """
35
+ def __init__(self, device: str, output_dim: int = 512,
36
+ model_size: Literal["small", "base", "large"] = "base"):
37
+ super().__init__()
38
+ self.device = device
39
+ self.output_dim = output_dim
40
+
41
+ # Select model based on size
42
+ model_names = {
43
+ "small": "facebook/dinov2-small",
44
+ "base": "facebook/dinov2-base",
45
+ "large": "facebook/dinov2-large"
46
+ }
47
+ model_name = model_names[model_size]
48
+
49
+ print(f"Loading DINOv2 encoder: {model_name}")
50
+ self.model = AutoModel.from_pretrained(model_name).to(device)
51
+ self.model.eval()
52
+
53
+ # Get hidden size from model config
54
+ hidden_size = self.model.config.hidden_size
55
+
56
+ # Projection layer to match output_dim
57
+ self.proj = nn.Linear(hidden_size, output_dim, bias=False).to(device)
58
+
59
+ def forward(self, x: torch.Tensor, return_spatial: bool = False) -> torch.Tensor:
60
+ """
61
+ Args:
62
+ x: Input images [B, 3, H, W] in range [-1, 1]
63
+ return_spatial: Whether to return all patch tokens [B, L, D]
64
+ Returns:
65
+ features: [B, output_dim] or [B, L, output_dim]
66
+ """
67
+ # Normalize to [0, 1] for DINOv2
68
+ x_normalized = (x + 1.0) / 2.0
69
+
70
+ # Resize to 224x224 (DINOv2 standard input size)
71
+ x_resized = F.interpolate(x_normalized, size=(224, 224),
72
+ mode='bilinear', align_corners=False)
73
+
74
+ with torch.no_grad():
75
+ outputs = self.model(pixel_values=x_resized, return_dict=True)
76
+ if return_spatial:
77
+ # Return all patch tokens (excluding CLS if possible, though Flux handle sequences well)
78
+ features = outputs.last_hidden_state
79
+ else:
80
+ # Use CLS token (first token) as global feature
81
+ features = outputs.last_hidden_state[:, 0]
82
+
83
+ return self.proj(features)
84
+
85
+
86
+ class EVACLIPEncoder(nn.Module):
87
+ """
88
+ EVA-CLIP encoder for enhanced vision-language feature extraction.
89
+ State-of-the-art performance on visual understanding tasks.
90
+ """
91
+ def __init__(self, device: str, output_dim: int = 512,
92
+ model_size: Literal["base", "large"] = "large"):
93
+ super().__init__()
94
+ self.device = device
95
+ self.output_dim = output_dim
96
+
97
+ # EVA-CLIP models
98
+ model_names = {
99
+ "base": "BAAI/EVA-CLIP-8B",
100
+ "large": "BAAI/EVA-CLIP-18B"
101
+ }
102
+
103
+ # Note: Using a more accessible model for now
104
+ # In production, use actual EVA-CLIP if available
105
+ model_name = "laion/CLIP-ViT-H-14-laion2B-s32B-b79K"
106
+
107
+ print(f"Loading EVA-CLIP style encoder: {model_name}")
108
+ try:
109
+ from transformers import CLIPVisionModel
110
+ self.model = CLIPVisionModel.from_pretrained(model_name).to(device)
111
+ except Exception as e:
112
+ print(f"Warning: Could not load {model_name}, falling back to openai/clip-vit-large-patch14")
113
+ from transformers import CLIPVisionModel
114
+ self.model = CLIPVisionModel.from_pretrained("openai/clip-vit-large-patch14").to(device)
115
+
116
+ self.model.eval()
117
+
118
+ # Get hidden size
119
+ hidden_size = self.model.config.hidden_size
120
+
121
+ # Projection layer
122
+ self.proj = nn.Linear(hidden_size, output_dim, bias=False).to(device)
123
+
124
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
125
+ """
126
+ Args:
127
+ x: Input images [B, 3, H, W] in range [-1, 1]
128
+ Returns:
129
+ features: [B, output_dim]
130
+ """
131
+ # Normalize to [0, 1]
132
+ x_normalized = (x + 1.0) / 2.0
133
+
134
+ # Resize to 224x224
135
+ x_resized = F.interpolate(x_normalized, size=(224, 224),
136
+ mode='bilinear', align_corners=False)
137
+
138
+ with torch.no_grad():
139
+ outputs = self.model(pixel_values=x_resized, return_dict=True)
140
+ features = outputs.pooler_output
141
+
142
+ return self.proj(features)
143
+
144
+
145
+ class DepthAwareEncoder(nn.Module):
146
+ """
147
+ Depth-aware feature encoder that combines visual features with depth information.
148
+ """
149
+ def __init__(self, base_encoder: nn.Module, depth_channels: int = 1,
150
+ output_dim: int = 512):
151
+ super().__init__()
152
+ self.base_encoder = base_encoder
153
+ self.output_dim = output_dim
154
+
155
+ # Depth processing branch
156
+ self.depth_conv = nn.Sequential(
157
+ nn.Conv2d(depth_channels, 32, kernel_size=3, stride=2, padding=1),
158
+ nn.ReLU(inplace=True),
159
+ nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
160
+ nn.ReLU(inplace=True),
161
+ nn.AdaptiveAvgPool2d(1)
162
+ )
163
+
164
+ # Fusion layer
165
+ self.fusion = nn.Sequential(
166
+ nn.Linear(output_dim + 64, output_dim),
167
+ nn.ReLU(inplace=True),
168
+ nn.Linear(output_dim, output_dim)
169
+ )
170
+
171
+ def forward(self, x: torch.Tensor, depth: Optional[torch.Tensor] = None) -> torch.Tensor:
172
+ """
173
+ Args:
174
+ x: Input images [B, 3, H, W]
175
+ depth: Depth maps [B, 1, H, W] (optional)
176
+ Returns:
177
+ features: [B, output_dim]
178
+ """
179
+ # Get base visual features
180
+ visual_features = self.base_encoder(x)
181
+
182
+ if depth is None:
183
+ return visual_features
184
+
185
+ # Process depth
186
+ depth_features = self.depth_conv(depth)
187
+ depth_features = depth_features.view(depth_features.size(0), -1)
188
+
189
+ # Concatenate and fuse
190
+ combined = torch.cat([visual_features, depth_features], dim=1)
191
+ fused_features = self.fusion(combined)
192
+
193
+ return fused_features
194
+
195
+
196
+ class EncoderFactory:
197
+ """
198
+ Factory class for creating different types of encoders.
199
+ """
200
+ @staticmethod
201
+ def create_encoder(
202
+ encoder_type: Literal["siglip", "dinov2", "evaclip"],
203
+ device: str,
204
+ output_dim: int = 512,
205
+ depth_aware: bool = False,
206
+ **kwargs
207
+ ) -> nn.Module:
208
+ """
209
+ Create an encoder of the specified type.
210
+
211
+ Args:
212
+ encoder_type: Type of encoder to create
213
+ device: Device to load the model on
214
+ output_dim: Output dimension of the encoder
215
+ depth_aware: Whether to wrap encoder with depth-aware processing
216
+ **kwargs: Additional arguments for specific encoders
217
+
218
+ Returns:
219
+ encoder: Configured encoder module
220
+ """
221
+ if encoder_type == "siglip":
222
+ from transformers import SiglipVisionModel
223
+
224
+ class SigLIPStyleEncoder(nn.Module):
225
+ def __init__(self, device, output_dim=512):
226
+ super().__init__()
227
+ model_name = "google/siglip-so400m-patch14-384"
228
+ self.model = SiglipVisionModel.from_pretrained(model_name).to(device)
229
+ self.model.eval()
230
+ self.proj = nn.Linear(self.model.config.hidden_size, output_dim, bias=False).to(device)
231
+
232
+ def forward(self, x):
233
+ x_resized = F.interpolate(x, size=(384, 384), mode='bilinear', align_corners=False)
234
+ with torch.no_grad():
235
+ outputs = self.model(pixel_values=x_resized)
236
+ features = outputs.last_hidden_state.mean(dim=1)
237
+ return self.proj(features)
238
+
239
+ encoder = SigLIPStyleEncoder(device, output_dim)
240
+
241
+ elif encoder_type == "dinov2":
242
+ model_size = kwargs.get("model_size", "base")
243
+ encoder = DINOv2Encoder(device, output_dim, model_size)
244
+
245
+ elif encoder_type == "evaclip":
246
+ model_size = kwargs.get("model_size", "large")
247
+ encoder = EVACLIPEncoder(device, output_dim, model_size)
248
+
249
+ else:
250
+ raise ValueError(f"Unknown encoder type: {encoder_type}")
251
+
252
+ # Wrap with depth-aware processing if requested
253
+ if depth_aware:
254
+ encoder = DepthAwareEncoder(encoder, depth_channels=1, output_dim=output_dim)
255
+
256
+ return encoder
flux/modules/attention.py ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced attention mechanisms for better semantic and style alignment.
3
+ Includes Cross-Attention, AdaIN, and Depth-Controlled Attention.
4
+ Author: https://github.com/datnguyen-tien204
5
+ """
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ from einops import rearrange
10
+ from typing import Optional
11
+
12
+
13
+ class AdaptiveInstanceNorm2d(nn.Module):
14
+ """
15
+ Adaptive Instance Normalization (AdaIN) for style transfer.
16
+ Aligns mean and variance of content features to match style features.
17
+ """
18
+ def __init__(self, num_features: int, style_dim: int):
19
+ """
20
+ Args:
21
+ num_features: Number of feature channels
22
+ style_dim: Dimension of style vector
23
+ """
24
+ super().__init__()
25
+ self.num_features = num_features
26
+
27
+ # Style modulation parameters
28
+ self.style_scale = nn.Linear(style_dim, num_features)
29
+ self.style_shift = nn.Linear(style_dim, num_features)
30
+
31
+ # Initialize to identity transformation
32
+ nn.init.constant_(self.style_scale.weight, 0)
33
+ nn.init.constant_(self.style_scale.bias, 1)
34
+ nn.init.constant_(self.style_shift.weight, 0)
35
+ nn.init.constant_(self.style_shift.bias, 0)
36
+
37
+ def forward(self, x: torch.Tensor, style: torch.Tensor) -> torch.Tensor:
38
+ """
39
+ Args:
40
+ x: Input features [B, C, H, W]
41
+ style: Style vector [B, style_dim]
42
+ Returns:
43
+ normalized: Style-adapted features [B, C, H, W]
44
+ """
45
+ # Instance normalization
46
+ mean = x.mean(dim=[2, 3], keepdim=True)
47
+ std = x.std(dim=[2, 3], keepdim=True) + 1e-5
48
+ normalized = (x - mean) / std
49
+
50
+ # Style modulation
51
+ scale = self.style_scale(style).unsqueeze(-1).unsqueeze(-1)
52
+ shift = self.style_shift(style).unsqueeze(-1).unsqueeze(-1)
53
+
54
+ return normalized * scale + shift
55
+
56
+
57
+ class AdaIN1d(nn.Module):
58
+ """
59
+ AdaIN for 1D features (sequence data).
60
+ """
61
+ def __init__(self, num_features: int, style_dim: int):
62
+ super().__init__()
63
+ self.num_features = num_features
64
+
65
+ self.norm = nn.LayerNorm(num_features, elementwise_affine=False)
66
+ self.style_scale = nn.Linear(style_dim, num_features)
67
+ self.style_shift = nn.Linear(style_dim, num_features)
68
+
69
+ nn.init.constant_(self.style_scale.weight, 0)
70
+ nn.init.constant_(self.style_scale.bias, 1)
71
+ nn.init.constant_(self.style_shift.weight, 0)
72
+ nn.init.constant_(self.style_shift.bias, 0)
73
+
74
+ def forward(self, x: torch.Tensor, style: torch.Tensor) -> torch.Tensor:
75
+ """
76
+ Args:
77
+ x: Input features [B, L, C]
78
+ style: Style vector [B, style_dim] OR [B, L_s, style_dim]
79
+ Returns:
80
+ normalized: Style-adapted features [B, L, C]
81
+ """
82
+ if style.dim() == 3:
83
+ style = style.mean(dim=1)
84
+
85
+ normalized = self.norm(x)
86
+ scale = self.style_scale(style).unsqueeze(1)
87
+ shift = self.style_shift(style).unsqueeze(1)
88
+
89
+ return normalized * scale + shift
90
+
91
+
92
+
93
+ class CrossAttention(nn.Module):
94
+ """
95
+ Cross-Attention mechanism for semantic alignment between content and style.
96
+ """
97
+ def __init__(self, query_dim: int, key_dim: int, num_heads: int = 8,
98
+ dropout: float = 0.0):
99
+ """
100
+ Args:
101
+ query_dim: Dimension of query features
102
+ key_dim: Dimension of key/value features
103
+ num_heads: Number of attention heads
104
+ dropout: Dropout probability
105
+ """
106
+ super().__init__()
107
+ self.num_heads = num_heads
108
+ self.head_dim = query_dim // num_heads
109
+ assert query_dim % num_heads == 0, "query_dim must be divisible by num_heads"
110
+
111
+ self.query = nn.Linear(query_dim, query_dim)
112
+ self.key = nn.Linear(key_dim, query_dim)
113
+ self.value = nn.Linear(key_dim, query_dim)
114
+
115
+ self.proj = nn.Linear(query_dim, query_dim)
116
+ self.dropout = nn.Dropout(dropout)
117
+
118
+ def forward(self, query: torch.Tensor, key: torch.Tensor,
119
+ value: Optional[torch.Tensor] = None,
120
+ mask: Optional[torch.Tensor] = None) -> torch.Tensor:
121
+ """
122
+ Args:
123
+ query: Query features [B, L_q, query_dim]
124
+ key: Key features [B, L_k, key_dim]
125
+ value: Value features [B, L_k, key_dim] (if None, uses key)
126
+ mask: Attention mask [B, L_q, L_k] (optional)
127
+ Returns:
128
+ output: Attended features [B, L_q, query_dim]
129
+ """
130
+ if value is None:
131
+ value = key
132
+
133
+ B, L_q, _ = query.shape
134
+ L_k = key.shape[1]
135
+
136
+ # Linear projections
137
+ q = self.query(query)
138
+ k = self.key(key)
139
+ v = self.value(value)
140
+
141
+ # Reshape for multi-head attention
142
+ q = rearrange(q, 'b l (h d) -> b h l d', h=self.num_heads)
143
+ k = rearrange(k, 'b l (h d) -> b h l d', h=self.num_heads)
144
+ v = rearrange(v, 'b l (h d) -> b h l d', h=self.num_heads)
145
+
146
+ # Scaled dot-product attention
147
+ scale = self.head_dim ** -0.5
148
+ attn = torch.matmul(q, k.transpose(-2, -1)) * scale
149
+
150
+ if mask is not None:
151
+ attn = attn.masked_fill(mask.unsqueeze(1) == 0, float('-inf'))
152
+
153
+ attn = F.softmax(attn, dim=-1)
154
+ attn = self.dropout(attn)
155
+
156
+ # Apply attention to values
157
+ out = torch.matmul(attn, v)
158
+ out = rearrange(out, 'b h l d -> b l (h d)')
159
+
160
+ # Output projection
161
+ out = self.proj(out)
162
+
163
+ return out
164
+
165
+
166
+ class DepthControlledAttention(nn.Module):
167
+ """
168
+ Depth-controlled attention mechanism for structure-aware feature learning.
169
+ Uses depth information to modulate attention patterns.
170
+ """
171
+ def __init__(self, feature_dim: int, depth_dim: int = 64, num_heads: int = 8):
172
+ """
173
+ Args:
174
+ feature_dim: Dimension of input features
175
+ depth_dim: Dimension of depth features
176
+ num_heads: Number of attention heads
177
+ """
178
+ super().__init__()
179
+ self.num_heads = num_heads
180
+ self.head_dim = feature_dim // num_heads
181
+
182
+ # Process depth information
183
+ self.depth_encoder = nn.Sequential(
184
+ nn.Conv2d(1, depth_dim, kernel_size=3, padding=1),
185
+ nn.ReLU(inplace=True),
186
+ nn.Conv2d(depth_dim, depth_dim, kernel_size=3, padding=1)
187
+ )
188
+
189
+ # Depth-modulated attention
190
+ self.qkv = nn.Linear(feature_dim, feature_dim * 3)
191
+ self.depth_gate = nn.Linear(depth_dim, feature_dim)
192
+ self.proj = nn.Linear(feature_dim, feature_dim)
193
+
194
+ def forward(self, x: torch.Tensor, depth: torch.Tensor) -> torch.Tensor:
195
+ """
196
+ Args:
197
+ x: Input features [B, L, C]
198
+ depth: Depth map [B, 1, H, W]
199
+ Returns:
200
+ output: Depth-modulated features [B, L, C]
201
+ """
202
+ B, L, C = x.shape
203
+
204
+ # Encode depth
205
+ depth_feat = self.depth_encoder(depth) # [B, depth_dim, H, W]
206
+ H, W = depth_feat.shape[-2:]
207
+ depth_feat = rearrange(depth_feat, 'b c h w -> b (h w) c')
208
+
209
+ # Generate Q, K, V
210
+ qkv = self.qkv(x)
211
+ q, k, v = rearrange(qkv, 'b l (three h d) -> three b h l d',
212
+ three=3, h=self.num_heads)
213
+
214
+ # Compute attention
215
+ scale = self.head_dim ** -0.5
216
+ attn = torch.matmul(q, k.transpose(-2, -1)) * scale
217
+
218
+ # Modulate attention with depth
219
+ if depth_feat.shape[1] == L:
220
+ depth_gate = self.depth_gate(depth_feat)
221
+ depth_gate = rearrange(depth_gate, 'b l (h d) -> b h l d', h=self.num_heads)
222
+ depth_mask = torch.sigmoid(depth_gate.mean(dim=-1, keepdim=True))
223
+ attn = attn * depth_mask
224
+
225
+ attn = F.softmax(attn, dim=-1)
226
+
227
+ # Apply attention
228
+ out = torch.matmul(attn, v)
229
+ out = rearrange(out, 'b h l d -> b l (h d)')
230
+ out = self.proj(out)
231
+
232
+ return out
233
+
234
+
235
+ class StyleAttentionBlock(nn.Module):
236
+ """
237
+ Combined attention block with AdaIN and Cross-Attention.
238
+ """
239
+ def __init__(self, feature_dim: int, style_dim: int, num_heads: int = 8):
240
+ super().__init__()
241
+
242
+ # Layer normalization
243
+ self.norm1 = nn.LayerNorm(feature_dim)
244
+ self.norm2 = nn.LayerNorm(feature_dim)
245
+
246
+ # Cross-attention for style conditioning
247
+ self.cross_attn = CrossAttention(feature_dim, style_dim, num_heads)
248
+
249
+ # AdaIN for style transfer
250
+ self.adain = AdaIN1d(feature_dim, style_dim)
251
+
252
+ # Feed-forward network
253
+ self.ffn = nn.Sequential(
254
+ nn.Linear(feature_dim, feature_dim * 4),
255
+ nn.GELU(),
256
+ nn.Linear(feature_dim * 4, feature_dim)
257
+ )
258
+
259
+ def forward(self, x: torch.Tensor, style: torch.Tensor,
260
+ style_features: Optional[torch.Tensor] = None) -> torch.Tensor:
261
+ """
262
+ Args:
263
+ x: Input features [B, L, C]
264
+ style: Style vector [B, style_dim]
265
+ style_features: Optional style feature sequence [B, L_s, style_dim]
266
+ Returns:
267
+ output: Style-conditioned features [B, L, C]
268
+ """
269
+ # Cross-attention with style features
270
+ if style_features is not None:
271
+ attn_out = self.cross_attn(self.norm1(x), style_features)
272
+ x = x + attn_out
273
+
274
+ # AdaIN style transfer
275
+ x = self.adain(x, style)
276
+
277
+ # Feed-forward
278
+ x = x + self.ffn(self.norm2(x))
279
+
280
+ return x
281
+
282
+
283
+ class MultiScaleAttention(nn.Module):
284
+ """
285
+ Multi-scale attention for capturing features at different resolutions.
286
+ """
287
+ def __init__(self, feature_dim: int, num_scales: int = 3, num_heads: int = 8):
288
+ super().__init__()
289
+ self.num_scales = num_scales
290
+
291
+ self.attentions = nn.ModuleList([
292
+ nn.MultiheadAttention(feature_dim, num_heads, batch_first=True)
293
+ for _ in range(num_scales)
294
+ ])
295
+
296
+ self.fusion = nn.Linear(feature_dim * num_scales, feature_dim)
297
+
298
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
299
+ """
300
+ Args:
301
+ x: Input features [B, L, C]
302
+ Returns:
303
+ output: Multi-scale attended features [B, L, C]
304
+ """
305
+ B, L, C = x.shape
306
+
307
+ outputs = []
308
+ for i, attn in enumerate(self.attentions):
309
+ # Downsample for different scales
310
+ if i > 0:
311
+ scale = 2 ** i
312
+ # Simple downsampling by reshaping
313
+ L_down = L // scale
314
+ if L_down > 0:
315
+ x_down = x[:, ::scale, :]
316
+ else:
317
+ x_down = x
318
+ out, _ = attn(x_down, x_down, x_down)
319
+ # Upsample back
320
+ if out.shape[1] != L:
321
+ out = F.interpolate(out.transpose(1, 2), size=L, mode='linear',
322
+ align_corners=False).transpose(1, 2)
323
+ else:
324
+ out, _ = attn(x, x, x)
325
+
326
+ outputs.append(out)
327
+
328
+ # Concatenate and fuse
329
+ multi_scale = torch.cat(outputs, dim=-1)
330
+ fused = self.fusion(multi_scale)
331
+
332
+ return fused
flux/modules/autoencoder.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flux project: https://github.com/black-forest-labs/flux/blob/main/src/flux/modules/autoencoder.py
2
+ from dataclasses import dataclass
3
+ import torch
4
+ from einops import rearrange
5
+ from torch import Tensor, nn
6
+
7
+ @dataclass
8
+ class AutoEncoderParams:
9
+ resolution: int
10
+ in_channels: int
11
+ ch: int
12
+ out_ch: int
13
+ ch_mult: list[int]
14
+ num_res_blocks: int
15
+ z_channels: int
16
+ scale_factor: float
17
+ shift_factor: float
18
+
19
+ def swish(x: Tensor) -> Tensor:
20
+ return x * torch.sigmoid(x)
21
+
22
+ class AttnBlock(nn.Module):
23
+ def __init__(self, in_channels: int):
24
+ super().__init__()
25
+ self.in_channels = in_channels
26
+ self.norm = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
27
+ self.q = nn.Conv2d(in_channels, in_channels, kernel_size=1)
28
+ self.k = nn.Conv2d(in_channels, in_channels, kernel_size=1)
29
+ self.v = nn.Conv2d(in_channels, in_channels, kernel_size=1)
30
+ self.proj_out = nn.Conv2d(in_channels, in_channels, kernel_size=1)
31
+
32
+ def attention(self, h_: Tensor) -> Tensor:
33
+ h_ = self.norm(h_)
34
+ q = self.q(h_)
35
+ k = self.k(h_)
36
+ v = self.v(h_)
37
+ b, c, h, w = q.shape
38
+ q = rearrange(q, "b c h w -> b 1 (h w) c").contiguous()
39
+ k = rearrange(k, "b c h w -> b 1 (h w) c").contiguous()
40
+ v = rearrange(v, "b c h w -> b 1 (h w) c").contiguous()
41
+ h_ = nn.functional.scaled_dot_product_attention(q, k, v)
42
+ return rearrange(h_, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b)
43
+
44
+ def forward(self, x: Tensor) -> Tensor:
45
+ return x + self.proj_out(self.attention(x))
46
+
47
+ class ResnetBlock(nn.Module):
48
+ def __init__(self, in_channels: int, out_channels: int):
49
+ super().__init__()
50
+ self.in_channels = in_channels
51
+ out_channels = in_channels if out_channels is None else out_channels
52
+ self.out_channels = out_channels
53
+ self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
54
+ self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
55
+ self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
56
+ self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
57
+ if self.in_channels != self.out_channels:
58
+ self.nin_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
59
+
60
+ def forward(self, x):
61
+ h = x
62
+ h = self.norm1(h)
63
+ h = swish(h)
64
+ h = self.conv1(h)
65
+ h = self.norm2(h)
66
+ h = swish(h)
67
+ h = self.conv2(h)
68
+ if self.in_channels != self.out_channels:
69
+ x = self.nin_shortcut(x)
70
+ return x + h
71
+
72
+ class Downsample(nn.Module):
73
+ def __init__(self, in_channels: int):
74
+ super().__init__()
75
+ self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0)
76
+
77
+ def forward(self, x: Tensor):
78
+ pad = (0, 1, 0, 1)
79
+ x = nn.functional.pad(x, pad, mode="constant", value=0)
80
+ x = self.conv(x)
81
+ return x
82
+
83
+ class Upsample(nn.Module):
84
+ def __init__(self, in_channels: int):
85
+ super().__init__()
86
+ self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
87
+
88
+ def forward(self, x: Tensor):
89
+ x = nn.functional.interpolate(x, scale_factor=2.0, mode="nearest")
90
+ x = self.conv(x)
91
+ return x
92
+
93
+ class Encoder(nn.Module):
94
+ def __init__(self, resolution: int, in_channels: int, ch: int, ch_mult: list[int], num_res_blocks: int, z_channels: int):
95
+ super().__init__()
96
+ self.ch = ch
97
+ self.num_resolutions = len(ch_mult)
98
+ self.num_res_blocks = num_res_blocks
99
+ self.resolution = resolution
100
+ self.in_channels = in_channels
101
+ self.conv_in = nn.Conv2d(in_channels, self.ch, kernel_size=3, stride=1, padding=1)
102
+ curr_res = resolution
103
+ in_ch_mult = (1,) + tuple(ch_mult)
104
+ self.in_ch_mult = in_ch_mult
105
+ self.down = nn.ModuleList()
106
+ block_in = self.ch
107
+ for i_level in range(self.num_resolutions):
108
+ block = nn.ModuleList()
109
+ attn = nn.ModuleList()
110
+ block_in = ch * in_ch_mult[i_level]
111
+ block_out = ch * ch_mult[i_level]
112
+ for _ in range(self.num_res_blocks):
113
+ block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
114
+ block_in = block_out
115
+ down = nn.Module()
116
+ down.block = block
117
+ down.attn = attn
118
+ if i_level != self.num_resolutions - 1:
119
+ down.downsample = Downsample(block_in)
120
+ curr_res = curr_res // 2
121
+ self.down.append(down)
122
+ self.mid = nn.Module()
123
+ self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
124
+ self.mid.attn_1 = AttnBlock(block_in)
125
+ self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
126
+ self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
127
+ self.conv_out = nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1)
128
+
129
+ def forward(self, x: Tensor) -> Tensor:
130
+ hs = [self.conv_in(x)]
131
+ for i_level in range(self.num_resolutions):
132
+ for i_block in range(self.num_res_blocks):
133
+ h = self.down[i_level].block[i_block](hs[-1])
134
+ if len(self.down[i_level].attn) > 0:
135
+ h = self.down[i_level].attn[i_block](h)
136
+ hs.append(h)
137
+ if i_level != self.num_resolutions - 1:
138
+ hs.append(self.down[i_level].downsample(hs[-1]))
139
+ h = hs[-1]
140
+ h = self.mid.block_1(h)
141
+ h = self.mid.attn_1(h)
142
+ h = self.mid.block_2(h)
143
+ h = self.norm_out(h)
144
+ h = swish(h)
145
+ h = self.conv_out(h)
146
+ return h
147
+
148
+ class Decoder(nn.Module):
149
+ def __init__(self, ch: int, out_ch: int, ch_mult: list[int], num_res_blocks: int, in_channels: int, resolution: int, z_channels: int):
150
+ super().__init__()
151
+ self.ch = ch
152
+ self.num_resolutions = len(ch_mult)
153
+ self.num_res_blocks = num_res_blocks
154
+ self.resolution = resolution
155
+ self.in_channels = in_channels
156
+ self.ffactor = 2 ** (self.num_resolutions - 1)
157
+ block_in = ch * ch_mult[self.num_resolutions - 1]
158
+ curr_res = resolution // 2 ** (self.num_resolutions - 1)
159
+ self.z_shape = (1, z_channels, curr_res, curr_res)
160
+ self.conv_in = nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1)
161
+ self.mid = nn.Module()
162
+ self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
163
+ self.mid.attn_1 = AttnBlock(block_in)
164
+ self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
165
+ self.up = nn.ModuleList()
166
+ for i_level in reversed(range(self.num_resolutions)):
167
+ block = nn.ModuleList()
168
+ attn = nn.ModuleList()
169
+ block_out = ch * ch_mult[i_level]
170
+ for _ in range(self.num_res_blocks + 1):
171
+ block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
172
+ block_in = block_out
173
+ up = nn.Module()
174
+ up.block = block
175
+ up.attn = attn
176
+ if i_level != 0:
177
+ up.upsample = Upsample(block_in)
178
+ curr_res = curr_res * 2
179
+ self.up.insert(0, up)
180
+ self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
181
+ self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1)
182
+
183
+ def forward(self, z: Tensor) -> Tensor:
184
+ upscale_dtype = next(self.up.parameters()).dtype
185
+ h = self.conv_in(z)
186
+ h = self.mid.block_1(h)
187
+ h = self.mid.attn_1(h)
188
+ h = self.mid.block_2(h)
189
+ h = h.to(upscale_dtype)
190
+ for i_level in reversed(range(self.num_resolutions)):
191
+ for i_block in range(self.num_res_blocks + 1):
192
+ h = self.up[i_level].block[i_block](h)
193
+ if len(self.up[i_level].attn) > 0:
194
+ h = self.up[i_level].attn[i_block](h)
195
+ if i_level != 0:
196
+ h = self.up[i_level].upsample(h)
197
+ h = self.norm_out(h)
198
+ h = swish(h)
199
+ h = self.conv_out(h)
200
+ return h
201
+
202
+ class DiagonalGaussian(nn.Module):
203
+ def __init__(self, sample: bool = True, chunk_dim: int = 1):
204
+ super().__init__()
205
+ self.sample = sample
206
+ self.chunk_dim = chunk_dim
207
+
208
+ def forward(self, z: Tensor) -> Tensor:
209
+ mean, logvar = torch.chunk(z, 2, dim=self.chunk_dim)
210
+ if self.sample:
211
+ std = torch.exp(0.5 * logvar)
212
+ return mean + std * torch.randn_like(mean)
213
+ else:
214
+ return mean
215
+
216
+ class AutoEncoder(nn.Module):
217
+ def __init__(self, params: AutoEncoderParams, sample_z: bool = False):
218
+ super().__init__()
219
+ self.params = params
220
+ self.encoder = Encoder(
221
+ resolution=params.resolution,
222
+ in_channels=params.in_channels,
223
+ ch=params.ch,
224
+ ch_mult=params.ch_mult,
225
+ num_res_blocks=params.num_res_blocks,
226
+ z_channels=params.z_channels,
227
+ )
228
+ self.decoder = Decoder(
229
+ resolution=params.resolution,
230
+ in_channels=params.in_channels,
231
+ ch=params.ch,
232
+ out_ch=params.out_ch,
233
+ ch_mult=params.ch_mult,
234
+ num_res_blocks=params.num_res_blocks,
235
+ z_channels=params.z_channels,
236
+ )
237
+ self.reg = DiagonalGaussian(sample=sample_z)
238
+ self.scale_factor = params.scale_factor
239
+ self.shift_factor = params.shift_factor
240
+
241
+ def encode(self, x: Tensor) -> Tensor:
242
+ z = self.reg(self.encoder(x))
243
+ z = self.scale_factor * (z - self.shift_factor)
244
+ return z
245
+
246
+ def decode(self, z: Tensor) -> Tensor:
247
+ z = z / self.scale_factor + self.shift_factor
248
+ return self.decoder(z)
249
+
250
+ def forward(self, x: Tensor) -> Tensor:
251
+ return self.decode(self.encode(x))
flux/modules/conditioner.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flux Project: https://github.com/black-forest-labs/flux/blob/main/src/flux/modules/conditioner.py
2
+ from torch import Tensor, nn
3
+ from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5Tokenizer
4
+
5
+ class HFEmbedder(nn.Module):
6
+ def __init__(self, version: str, max_length: int, **hf_kwargs):
7
+ super().__init__()
8
+ self.is_clip = version.startswith("openai")
9
+ self.max_length = max_length
10
+ self.output_key = "pooler_output" if self.is_clip else "last_hidden_state"
11
+ if self.is_clip:
12
+ self.tokenizer: CLIPTokenizer = CLIPTokenizer.from_pretrained(version, max_length=max_length)
13
+ self.hf_module: CLIPTextModel = CLIPTextModel.from_pretrained(version, **hf_kwargs)
14
+ else:
15
+ self.tokenizer: T5Tokenizer = T5Tokenizer.from_pretrained(version, max_length=max_length)
16
+ self.hf_module: T5EncoderModel = T5EncoderModel.from_pretrained(version, **hf_kwargs)
17
+ self.hf_module = self.hf_module.eval().requires_grad_(False)
18
+
19
+ def forward(self, text: list[str]) -> Tensor:
20
+ batch_encoding = self.tokenizer(
21
+ text,
22
+ truncation=True,
23
+ max_length=self.max_length,
24
+ return_length=False,
25
+ return_overflowing_tokens=False,
26
+ padding="max_length",
27
+ return_tensors="pt",
28
+ )
29
+ outputs = self.hf_module(
30
+ input_ids=batch_encoding["input_ids"].to(self.hf_module.device),
31
+ attention_mask=None,
32
+ output_hidden_states=False,
33
+ )
34
+ return outputs[self.output_key].bfloat16()
flux/modules/image_embedders.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flux Project: https://github.com/black-forest-labs/flux/blob/main/src/flux/modules/image_embedders.py
2
+ import cv2
3
+ import numpy as np
4
+ import torch
5
+ from einops import rearrange, repeat
6
+ from PIL import Image
7
+ from safetensors.torch import load_file as load_sft
8
+ from torch import nn
9
+ from transformers import AutoModelForDepthEstimation, AutoProcessor, SiglipImageProcessor, SiglipVisionModel
10
+ from flux.util import print_load_warning
11
+
12
+ class DepthImageEncoder:
13
+ depth_model_name = "LiheYoung/depth-anything-large-hf"
14
+
15
+ def __init__(self, device):
16
+ self.device = device
17
+ self.depth_model = AutoModelForDepthEstimation.from_pretrained(self.depth_model_name).to(device)
18
+ self.processor = AutoProcessor.from_pretrained(self.depth_model_name)
19
+
20
+ def __call__(self, img: torch.Tensor) -> torch.Tensor:
21
+ hw = img.shape[-2:]
22
+ img = torch.clamp(img, -1.0, 1.0)
23
+ img_byte = ((img + 1.0) * 127.5).byte()
24
+ img = self.processor(img_byte, return_tensors="pt")["pixel_values"]
25
+ depth = self.depth_model(img.to(self.device)).predicted_depth
26
+ depth = repeat(depth, "b h w -> b 3 h w")
27
+ depth = torch.nn.functional.interpolate(depth, hw, mode="bicubic", antialias=True)
28
+ depth = depth / 127.5 - 1.0
29
+ return depth
30
+
31
+ class CannyImageEncoder:
32
+ def __init__(self, device, min_t: int = 50, max_t: int = 200):
33
+ self.device = device
34
+ self.min_t = min_t
35
+ self.max_t = max_t
36
+
37
+ def __call__(self, img: torch.Tensor) -> torch.Tensor:
38
+ assert img.shape[0] == 1, "Only batch size 1 is supported"
39
+ img = rearrange(img[0], "c h w -> h w c")
40
+ img = torch.clamp(img, -1.0, 1.0)
41
+ img_np = ((img + 1.0) * 127.5).numpy().astype(np.uint8)
42
+ canny = cv2.Canny(img_np, self.min_t, self.max_t)
43
+ canny = torch.from_numpy(canny).float() / 127.5 - 1.0
44
+ canny = rearrange(canny, "h w -> 1 1 h w")
45
+ canny = repeat(canny, "b 1 ... -> b 3 ...")
46
+ return canny.to(self.device)
47
+
48
+ class ReduxImageEncoder(nn.Module):
49
+ siglip_model_name = "google/siglip-so400m-patch14-384"
50
+
51
+ def __init__(self, device, redux_path: str, redux_dim: int = 1152, txt_in_features: int = 4096, dtype=torch.bfloat16):
52
+ super().__init__()
53
+ self.redux_dim = redux_dim
54
+ self.device = device if isinstance(device, torch.device) else torch.device(device)
55
+ self.dtype = dtype
56
+ with self.device:
57
+ self.redux_up = nn.Linear(redux_dim, txt_in_features * 3, dtype=dtype)
58
+ self.redux_down = nn.Linear(txt_in_features * 3, txt_in_features, dtype=dtype)
59
+ sd = load_sft(redux_path, device=str(device))
60
+ missing, unexpected = self.load_state_dict(sd, strict=False, assign=True)
61
+ print_load_warning(missing, unexpected)
62
+ self.siglip = SiglipVisionModel.from_pretrained(self.siglip_model_name).to(dtype=dtype)
63
+ self.normalize = SiglipImageProcessor.from_pretrained(self.siglip_model_name)
64
+
65
+ def __call__(self, x: Image.Image) -> torch.Tensor:
66
+ imgs = self.normalize.preprocess(images=[x], do_resize=True, return_tensors="pt", do_convert_rgb=True)
67
+ _encoded_x = self.siglip(**imgs.to(device=self.device, dtype=self.dtype)).last_hidden_state
68
+ projected_x = self.redux_down(nn.functional.silu(self.redux_up(_encoded_x)))
69
+ return projected_x
flux/modules/layers.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Modified: https://github.com/datnguyen-tien204
3
+ Flux Project: https://github.com/black-forest-labs/flux/blob/main/src/flux/modules/layers.py
4
+ """
5
+ import math
6
+ import torch
7
+ from torch import nn, Tensor
8
+ from einops import rearrange
9
+ from flux.utils.math import attention, rope
10
+
11
+
12
+ class EmbedND(nn.Module):
13
+ def __init__(self, dim: int, theta: int, axes_dim: list[int]):
14
+ super().__init__()
15
+ self.dim = dim
16
+ self.theta = theta
17
+ self.axes_dim = axes_dim
18
+
19
+ def forward(self, ids: Tensor) -> Tensor:
20
+ n_axes = ids.shape[-1]
21
+ emb = torch.cat(
22
+ [rope(ids[..., i], self.axes_dim[i], self.theta) for i in range(n_axes)],
23
+ dim=-3,
24
+ )
25
+ return emb.unsqueeze(1)
26
+
27
+
28
+ def timestep_embedding(t: Tensor, dim, max_period=10000, time_factor: float = 1000.0):
29
+ t = time_factor * t
30
+ half = dim // 2
31
+ freqs = torch.exp(-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half).to(t.device)
32
+ args = t[:, None].float() * freqs[None]
33
+ embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
34
+ if dim % 2:
35
+ embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
36
+ if torch.is_floating_point(t):
37
+ embedding = embedding.to(t)
38
+ return embedding
39
+
40
+
41
+ class MLPEmbedder(nn.Module):
42
+ def __init__(self, in_dim: int, hidden_dim: int):
43
+ super().__init__()
44
+ self.in_layer = nn.Linear(in_dim, hidden_dim, bias=True)
45
+ self.silu = nn.SiLU()
46
+ self.out_layer = nn.Linear(hidden_dim, hidden_dim, bias=True)
47
+
48
+ def forward(self, x: Tensor) -> Tensor:
49
+ return self.out_layer(self.silu(self.in_layer(x)))
50
+
51
+
52
+ class RMSNorm(torch.nn.Module):
53
+ def __init__(self, dim: int):
54
+ super().__init__()
55
+ self.scale = nn.Parameter(torch.ones(dim))
56
+
57
+ def forward(self, x: Tensor):
58
+ x_dtype = x.dtype
59
+ x = x.float()
60
+ rrms = torch.rsqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + 1e-6)
61
+
62
+ # --- DEBUG PRINT ---
63
+ if x.shape[-1] != self.scale.shape[0]:
64
+ print(f"!!! SHAPE MISMATCH ERROR !!!")
65
+ print(f"Input x shape: {x.shape}")
66
+ print(f"Scale shape: {self.scale.shape}")
67
+ # -------------------
68
+
69
+ return (x * rrms).to(dtype=x_dtype) * self.scale
70
+
71
+
72
+ class QKNorm(torch.nn.Module):
73
+ def __init__(self, dim: int):
74
+ super().__init__()
75
+ self.query_norm = RMSNorm(dim)
76
+ self.key_norm = RMSNorm(dim)
77
+
78
+ def forward(self, q: Tensor, k: Tensor, v: Tensor) -> tuple[Tensor, Tensor]:
79
+ q = self.query_norm(q)
80
+ k = self.key_norm(k)
81
+ return q.to(v), k.to(v)
82
+
83
+
84
+ class SelfAttention(nn.Module):
85
+ def __init__(self, dim: int, num_heads: int = 8, qkv_bias: bool = False):
86
+ super().__init__()
87
+ self.num_heads = num_heads
88
+ head_dim = dim // num_heads
89
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
90
+ self.norm = QKNorm(head_dim)
91
+ self.proj = nn.Linear(dim, dim)
92
+
93
+ def forward(self, x: Tensor, pe: Tensor) -> Tensor:
94
+ qkv = self.qkv(x)
95
+ q, k, v = rearrange(qkv, "B L (K H D) -> K B H L D", K=3, H=self.num_heads)
96
+ q, k = self.norm(q, k, v)
97
+ x = attention(q, k, v, pe=pe)
98
+ x = self.proj(x)
99
+ return x
flux/modules/losses.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Perceptual and adversarial loss modules for enhanced training.
3
+ Includes LPIPS, VGG perceptual loss, and PatchGAN discriminator.
4
+ Author: https://github.com/datnguyen-tien204
5
+ """
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ from torchvision.models import vgg16, VGG16_Weights
10
+ from torchvision import transforms, models
11
+ from typing import List, Optional
12
+ import lpips
13
+
14
+
15
+ class CharbonnierLoss(nn.Module):
16
+ def __init__(self, eps=1e-4):
17
+ super().__init__()
18
+ self.eps = eps
19
+ def forward(self, x, y):
20
+ diff = x - y
21
+ return torch.mean(torch.sqrt(diff * diff + self.eps * self.eps))
22
+
23
+
24
+ class DayPreservationLoss(nn.Module):
25
+ def __init__(self): super().__init__()
26
+ def forward(self, pred_img, day_img, hour_val):
27
+ if isinstance(hour_val, torch.Tensor):
28
+ weights = []
29
+ for h in hour_val:
30
+ h = h.item() % 24.0
31
+ if 11.0 <= h <= 13.0: w = 1.0
32
+ elif 10.0 <= h < 11.0: w = (h - 10.0)
33
+ elif 13.0 < h <= 14.0: w = (14.0 - h)
34
+ else: w = 0.0
35
+ weights.append(w)
36
+ weight_tensor = torch.tensor(weights, device=pred_img.device, dtype=pred_img.dtype).view(-1, 1, 1, 1)
37
+ if weight_tensor.sum() > 0:
38
+ return (torch.abs(pred_img - day_img) * weight_tensor).mean()
39
+ return torch.tensor(0.0, device=pred_img.device)
40
+
41
+ class PerceptualLoss(nn.Module):
42
+ def __init__(self, device):
43
+ super().__init__()
44
+ vgg = models.vgg16(weights=models.VGG16_Weights.IMAGENET1K_V1).features.to(device).eval()
45
+ for param in vgg.parameters(): param.requires_grad = False
46
+ self.slice1 = nn.Sequential(*list(vgg[:4])).to(device)
47
+ self.slice2 = nn.Sequential(*list(vgg[4:9])).to(device)
48
+ self.normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
49
+
50
+ def forward(self, x, y):
51
+ x_norm = self.normalize((x + 1.0) / 2.0)
52
+ y_norm = self.normalize((y + 1.0) / 2.0)
53
+ x_f1 = self.slice1(x_norm)
54
+ y_f1 = self.slice1(y_norm)
55
+ x_f2 = self.slice2(x_f1)
56
+ y_f2 = self.slice2(y_f1)
57
+ return (F.l1_loss(x_f1, y_f1) * 0.5 + F.l1_loss(x_f2, y_f2) * 0.5) * 0.01
58
+
59
+ class ColorMatchingLoss(nn.Module):
60
+ def __init__(self, device, use_perceptual=True):
61
+ super().__init__()
62
+ self.use_perceptual = use_perceptual
63
+ if use_perceptual: self.perceptual_loss = PerceptualLoss(device)
64
+ def forward(self, pred, target):
65
+ pred_01, target_01 = (pred + 1) / 2, (target + 1) / 2
66
+ mean_loss = F.mse_loss(pred_01.mean([2,3]), target_01.mean([2,3]))
67
+ std_loss = F.mse_loss(pred_01.std([2,3]), target_01.std([2,3]))
68
+
69
+ eps = 1e-3
70
+ p_c, t_c = pred_01.clamp(eps, 1), target_01.clamp(eps, 1)
71
+ def ratios(x): return (torch.log(x[:,0:1])-torch.log(x[:,1:2])).clamp(-3,3), (torch.log(x[:,0:1])-torch.log(x[:,2:3])).clamp(-3,3), (torch.log(x[:,1:2])-torch.log(x[:,2:3])).clamp(-3,3)
72
+ prg, prb, pgb = ratios(p_c)
73
+ trg, trb, tgb = ratios(t_c)
74
+ ratio_loss = (F.mse_loss(prg, trg) + F.mse_loss(prb, trb) + F.mse_loss(pgb, tgb)) / 3
75
+
76
+ loss = mean_loss * 1.0 + std_loss * 0.5 + ratio_loss * 0.3
77
+ if self.use_perceptual: loss += self.perceptual_loss(pred, target)
78
+ return loss
79
+
80
+ class ContinuityLoss(nn.Module):
81
+ def __init__(self, max_rate=0.15):
82
+ super().__init__()
83
+ self.max_rate = max_rate
84
+
85
+ def forward(self, outputs, phis):
86
+ # outputs: [N, 3, H, W] - N can be batch size OR temporal buffer size
87
+ if outputs.shape[0] < 2: return torch.tensor(0.0, device=outputs.device)
88
+
89
+ # Sort by phi to ensure temporal order
90
+ idx = torch.argsort(phis)
91
+ out, phi = outputs[idx], phis[idx]
92
+
93
+ # Calculate Brightness
94
+ b = 0.299*out[:,0]+0.587*out[:,1]+0.114*out[:,2]
95
+
96
+ # Calculate diffs
97
+ diff_phi = (phi[1:] - phi[:-1])
98
+
99
+ # Filter out cases where phi is too close (avoid division by zero)
100
+ mask = diff_phi > 1e-4
101
+ if not mask.any(): return torch.tensor(0.0, device=outputs.device)
102
+
103
+ # Rate of change: |dB| / dPhi
104
+ dB = torch.abs(b[1:] - b[:-1])
105
+ dB = dB[mask] # [K, H, W]
106
+ dphi = diff_phi[mask].view(-1,1,1) # [K,1,1]
107
+
108
+ rate = dB / dphi
109
+ return torch.mean(F.relu(rate - self.max_rate).clamp(0, 0.5)**2)
110
+
111
+ class ColorContinuityLoss(nn.Module):
112
+ def __init__(self, max_change=0.10):
113
+ super().__init__()
114
+ self.max_change = max_change
115
+
116
+ def forward(self, outputs, phis):
117
+ if outputs.shape[0] < 2: return torch.tensor(0.0, device=outputs.device)
118
+
119
+ idx = torch.argsort(phis)
120
+ out, phi = outputs[idx], phis[idx]
121
+
122
+ diff_phi = (phi[1:] - phi[:-1])
123
+ mask = diff_phi > 1e-4
124
+ if not mask.any(): return torch.tensor(0.0, device=outputs.device)
125
+
126
+ # Rate of change: |dColor| / dPhi
127
+ rate = torch.abs(out[1:] - out[:-1])[mask] / diff_phi[mask].view(-1,1,1,1)
128
+
129
+ loss = 0
130
+ for c in range(3):
131
+ loss += torch.mean(F.relu(rate[:,c] - self.max_change).clamp(0, 0.3)**2)
132
+ return loss / 3
flux/modules/moe.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Mixture-of-Experts (MoE) module for time-based style control.
3
+ Implements sparse expert selection based on time intervals.
4
+ Author: https://github.com/datnguyen-tien204
5
+ """
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+ from typing import Tuple, Optional, List
11
+ import math
12
+
13
+ class Expert(nn.Module):
14
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
15
+ super().__init__()
16
+ self.network = nn.Sequential(
17
+ nn.Linear(input_dim, hidden_dim),
18
+ nn.SiLU(),
19
+ nn.Linear(hidden_dim, hidden_dim),
20
+ nn.SiLU(),
21
+ nn.Linear(hidden_dim, output_dim)
22
+ )
23
+
24
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
25
+ return self.network(x)
26
+
27
+ class TimeBasedGatingNetwork(nn.Module):
28
+ def __init__(self, num_experts: int, input_dim: int = 2, temperature: float = 0.2,
29
+ custom_centers: Optional[List[float]] = None,
30
+ custom_widths: Optional[List[float]] = None):
31
+ super().__init__()
32
+ self.num_experts = num_experts
33
+ self.temperature = temperature
34
+
35
+ if custom_centers is not None:
36
+ centers_rad = [c * (2 * math.pi / 24.0) for c in custom_centers]
37
+ self.register_buffer('expert_centers', torch.tensor(centers_rad))
38
+ else:
39
+ centers = [i * (2 * math.pi / num_experts) for i in range(num_experts)]
40
+ self.register_buffer('expert_centers', torch.tensor(centers))
41
+
42
+ if custom_widths is not None:
43
+ widths_rad = [(w * (2 * math.pi / 24.0)) / 2.0 for w in custom_widths]
44
+ init_log_widths = torch.log(torch.tensor(widths_rad) + 1e-5)
45
+ self.expert_log_widths = nn.Parameter(init_log_widths)
46
+ else:
47
+ self.expert_log_widths = nn.Parameter(torch.zeros(num_experts))
48
+
49
+ self.min_width = 0.1
50
+
51
+ def forward(self, phi: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
52
+ phi = phi.view(-1, 1)
53
+ centers = self.expert_centers.view(1, -1)
54
+
55
+ diff = torch.remainder(phi - centers + math.pi, 2 * math.pi) - math.pi
56
+
57
+ widths = F.softplus(self.expert_log_widths).view(1, -1) + self.min_width
58
+
59
+ logits = -diff ** 2 / (2 * widths ** 2)
60
+ weights = F.softmax(logits / self.temperature, dim=-1)
61
+
62
+ return weights, logits
63
+
64
+ class MixtureOfExperts(nn.Module):
65
+ def __init__(self, input_dim, hidden_dim, output_dim, num_experts, k=2, temperature=0.2, noise_std=0.1,
66
+ custom_centers=None, custom_widths=None):
67
+ super().__init__()
68
+ self.num_experts = num_experts
69
+ self.k = k
70
+ self.noise_std = noise_std
71
+
72
+ self.experts = nn.ModuleList([
73
+ Expert(input_dim, hidden_dim, output_dim) for _ in range(num_experts)
74
+ ])
75
+
76
+ self.gate = TimeBasedGatingNetwork(
77
+ num_experts, input_dim=2, temperature=temperature,
78
+ custom_centers=custom_centers, custom_widths=custom_widths
79
+ )
80
+ self.register_buffer('load_balance_loss_coef', torch.tensor(0.01))
81
+
82
+ def _add_noise(self, logits):
83
+ if self.training and self.noise_std > 0:
84
+ noise = torch.randn_like(logits) * self.noise_std
85
+ return logits + noise
86
+ return logits
87
+
88
+ def _compute_load_balance_loss(self, weights):
89
+ mean_weights = weights.mean(dim=0)
90
+ target = torch.ones_like(mean_weights) / self.num_experts
91
+ loss = F.mse_loss(mean_weights, target)
92
+ return loss * self.load_balance_loss_coef
93
+
94
+ def forward(self, x, phi, return_aux=False):
95
+ is_sequence = x.dim() == 3
96
+ if is_sequence:
97
+ B, L, C = x.shape
98
+ x = x.reshape(B * L, C)
99
+ phi = phi.unsqueeze(1).expand(B, L).reshape(B * L)
100
+
101
+ weights, gate_logits = self.gate(phi)
102
+
103
+ if self.training:
104
+ gate_logits = self._add_noise(gate_logits)
105
+ weights = F.softmax(gate_logits / self.gate.temperature, dim=-1)
106
+
107
+ if self.k < self.num_experts:
108
+ top_k_weights, top_k_indices = torch.topk(weights, self.k, dim=-1)
109
+ top_k_weights = top_k_weights / top_k_weights.sum(dim=-1, keepdim=True)
110
+ sparse_weights = torch.zeros_like(weights)
111
+ sparse_weights.scatter_(1, top_k_indices, top_k_weights)
112
+ weights = sparse_weights
113
+
114
+ expert_outputs = torch.stack([e(x) for e in self.experts], dim=1)
115
+ output = torch.einsum('be,beo->bo', weights, expert_outputs)
116
+
117
+ if is_sequence:
118
+ output = output.reshape(B, L, -1)
119
+ weights = weights.reshape(B, L, -1).mean(dim=1)
120
+
121
+ aux = None
122
+ if return_aux:
123
+ aux = {'load_balance_loss': self._compute_load_balance_loss(weights)}
124
+
125
+ return output, aux
126
+
127
+ class MoEStyleAdapter(nn.Module):
128
+ def __init__(self, style_dim, hidden_dim, num_experts, custom_centers=None, custom_widths=None):
129
+ super().__init__()
130
+ self.moe = MixtureOfExperts(
131
+ style_dim, hidden_dim, style_dim, num_experts, k=2, temperature=0.2, noise_std=0.1,
132
+ custom_centers=custom_centers, custom_widths=custom_widths
133
+ )
134
+ def forward(self, style, phi, return_aux=False):
135
+ adapted, aux = self.moe(style, phi, return_aux=return_aux)
136
+ return adapted + style, aux
137
+
138
+ class MoEFeatureProcessor(nn.Module):
139
+ def __init__(self, feature_dim, num_experts, expand_ratio=4, custom_centers=None, custom_widths=None):
140
+ super().__init__()
141
+ self.moe = MixtureOfExperts(
142
+ feature_dim, feature_dim * expand_ratio, feature_dim, num_experts, k=2, temperature=0.2, noise_std=0.1,
143
+ custom_centers=custom_centers, custom_widths=custom_widths
144
+ )
145
+ self.norm = nn.LayerNorm(feature_dim)
146
+ def forward(self, x, phi, return_aux=False):
147
+ normed = self.norm(x)
148
+ processed, aux = self.moe(normed, phi, return_aux=return_aux)
149
+ return x + processed, aux
flux/modules/normalization.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Adaptive Normalization Modules for ChronFlux
3
+ Includes AdaIN (Adaptive Instance Normalization) and AdaLN (Adaptive Layer Normalization)
4
+ """
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+
9
+
10
+ NIGHT_SKY_INTENSITY = -0.95
11
+ WARM_BIAS_RGB = [0.2, 0.0, -0.2]
12
+
13
+
14
+ class AdaIN(nn.Module):
15
+ """
16
+ Adaptive Instance Normalization (AdaIN) for style transfer.
17
+ Used for generating pseudo-style targets during training with extensive augmentation.
18
+
19
+ Reference: Huang & Belongie, "Arbitrary Style Transfer in Real-time with Adaptive Instance Normalization"
20
+ """
21
+ def __init__(self, eps=1e-5):
22
+ super().__init__()
23
+ self.eps = eps
24
+
25
+ def calc_mean_std(self, feat):
26
+ size = feat.size()
27
+ N, C = size[:2]
28
+ feat_view = feat.view(N, C, -1)
29
+
30
+ feat_std, feat_mean = torch.std_mean(feat_view, dim=2, unbiased=False)
31
+
32
+ feat_std = (feat_std + self.eps).view(N, C, 1, 1)
33
+ feat_mean = feat_mean.view(N, C, 1, 1)
34
+
35
+ return feat_mean, feat_std
36
+
37
+ def forward(self, content, style):
38
+ """
39
+ Apply AdaIN: transfers style statistics to content
40
+
41
+ Args:
42
+ content: Content features [B, C, H, W]
43
+ style: Style features [B, C, H, W]
44
+
45
+ Returns:
46
+ Stylized content with style's mean and std
47
+ """
48
+ size = content.size()
49
+ content_mean, content_std = self.calc_mean_std(content)
50
+ style_mean, style_std = self.calc_mean_std(style)
51
+
52
+ normalized = (content - content_mean) / content_std
53
+ stylized = normalized * style_std + style_mean
54
+
55
+ return stylized
56
+
57
+
58
+ class AdaINTransfer(nn.Module):
59
+ """
60
+ Extended AdaIN for pseudo-style target generation with augmentation support.
61
+ Used specifically for training pipeline to create robust style targets.
62
+ """
63
+ def __init__(self, eps=1e-5):
64
+ super().__init__()
65
+ self.adain = AdaIN(eps=eps)
66
+
67
+ self.register_buffer(
68
+ 'warm_bias_template',
69
+ torch.tensor(WARM_BIAS_RGB, dtype=torch.float32).view(1, 3, 1, 1)
70
+ )
71
+
72
+ def adaptive_night_transfer(self, content, style, sky_mask=None):
73
+ """
74
+ Transfer night style to content with optional sky masking
75
+
76
+ Args:
77
+ content: Day images [B, C, H, W]
78
+ style: Night images [B, C, H, W]
79
+ sky_mask: Binary mask for sky regions [B, 1, H, W]
80
+
81
+ Returns:
82
+ Night-styled content
83
+ """
84
+ target = self.adain(content, style)
85
+
86
+ if sky_mask is not None:
87
+ target = target * (1 - sky_mask) + (NIGHT_SKY_INTENSITY * sky_mask)
88
+
89
+ return target.clamp(-0.95, 0.95)
90
+
91
+ def adaptive_sunset_transfer(self, content, style_night, sky_mask=None):
92
+ """
93
+ Create sunset style by channel manipulation of night style
94
+
95
+ Args:
96
+ content: Day images [B, C, H, W]
97
+ style_night: Night images for base style [B, C, H, W]
98
+ sky_mask: Binary mask for sky regions [B, 1, H, W]
99
+
100
+ Returns:
101
+ Sunset-styled content with warm tones
102
+ """
103
+ content_mean, content_std = self.adain.calc_mean_std(content)
104
+ style_mean, style_std = self.adain.calc_mean_std(style_night)
105
+
106
+ mu_swap = style_mean.clone()
107
+ mu_swap[:, 0] = style_mean[:, 2]
108
+ mu_swap[:, 2] = style_mean[:, 0]
109
+
110
+ std_swap = style_std.clone()
111
+ std_swap[:, 0] = style_std[:, 2]
112
+ std_swap[:, 2] = style_std[:, 0]
113
+
114
+ B = content.shape[0]
115
+ warm_bias = self.warm_bias_template.to(content.dtype).expand(B, 3, 1, 1)
116
+
117
+ mu_final = mu_swap * 0.7 + warm_bias * 0.3
118
+ std_final = std_swap * 1.4
119
+
120
+ target = (content - content_mean) / content_std * std_final + mu_final
121
+
122
+ if sky_mask is not None:
123
+ sky_color = mu_final.view(B, 3, 1, 1)
124
+ target = target * (1 - sky_mask) + (target * 0.4 + sky_color * 0.6) * sky_mask
125
+
126
+ return target.clamp(-0.95, 0.95)
127
+
128
+
129
+ class AdaLNModulation(nn.Module):
130
+ """
131
+ Adaptive Layer Normalization (AdaLN) Modulation.
132
+ Generates scale, shift, and gate parameters for layer-wise adaptive normalization.
133
+ Provides better control over style consistency and layer-wise adjustments.
134
+ """
135
+ def __init__(self, hidden_size, components=6):
136
+ super().__init__()
137
+ self.silu = nn.SiLU()
138
+ self.linear = nn.Linear(hidden_size, components * hidden_size, bias=True)
139
+ self.components = components
140
+
141
+ nn.init.zeros_(self.linear.weight)
142
+ nn.init.zeros_(self.linear.bias)
143
+
144
+ def forward(self, vec):
145
+ """
146
+ Generate modulation parameters
147
+
148
+ Args:
149
+ vec: Conditioning vector [B, hidden_size]
150
+
151
+ Returns:
152
+ Tuple of (shift, scale, gate) parameters for each component
153
+ """
154
+ chunk = self.linear(self.silu(vec))
155
+ return chunk.chunk(self.components, dim=1)
156
+
157
+
158
+ class AdaLNBlock(nn.Module):
159
+ """
160
+ Base AdaLN normalization block with scale, shift, and gate.
161
+ Can be used as a building block for various architectures.
162
+ """
163
+ def __init__(self, hidden_size, use_gate=True):
164
+ super().__init__()
165
+ self.norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
166
+ self.use_gate = use_gate
167
+
168
+ def forward(self, x, scale, shift, gate=None):
169
+ """
170
+ Apply AdaLN: normalize, then scale and shift, optionally gate
171
+
172
+ Args:
173
+ x: Input features [B, L, D]
174
+ scale: Scale parameter [B, D]
175
+ shift: Shift parameter [B, D]
176
+ gate: Optional gate parameter [B, D]
177
+
178
+ Returns:
179
+ Modulated features
180
+ """
181
+ x_norm = self.norm(x)
182
+ x_mod = x_norm * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
183
+
184
+ if self.use_gate and gate is not None:
185
+ x_mod = gate.unsqueeze(1) * x_mod
186
+
187
+ return x_mod
188
+
189
+
190
+ class AdaLNZero(nn.Module):
191
+ """
192
+ AdaLN-Zero: AdaLN with zero-initialized output scaling.
193
+ Particularly useful for residual connections and stable training.
194
+ """
195
+ def __init__(self, hidden_size):
196
+ super().__init__()
197
+ self.norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
198
+ self.scale = nn.Parameter(torch.zeros(hidden_size))
199
+ self.shift = nn.Parameter(torch.zeros(hidden_size))
200
+
201
+ def forward(self, x, modulation_scale, modulation_shift):
202
+ """
203
+ Apply AdaLN with zero-initialization
204
+
205
+ Args:
206
+ x: Input features [B, L, D]
207
+ modulation_scale: Additional scale from conditioning [B, D]
208
+ modulation_shift: Additional shift from conditioning [B, D]
209
+
210
+ Returns:
211
+ Modulated features
212
+ """
213
+ x_norm = self.norm(x)
214
+ x_mod = x_norm * (self.scale + modulation_scale.unsqueeze(1)) + (self.shift + modulation_shift.unsqueeze(1))
215
+ return x_mod
216
+
217
+
218
+ class StyleAdaLN(nn.Module):
219
+ """
220
+ Style-aware AdaLN that combines style and conditioning vectors.
221
+ Particularly useful for style transfer with additional controls.
222
+ """
223
+ def __init__(self, hidden_size, style_dim):
224
+ super().__init__()
225
+ self.style_proj = nn.Linear(style_dim, hidden_size)
226
+ self.adaln = AdaLNModulation(hidden_size, components=2)
227
+ self.norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
228
+
229
+ def forward(self, x, style_vec, cond_vec=None):
230
+ """
231
+ Apply style-aware normalization
232
+
233
+ Args:
234
+ x: Input features [B, L, D]
235
+ style_vec: Style vector [B, style_dim]
236
+ cond_vec: Optional additional conditioning [B, D]
237
+
238
+ Returns:
239
+ Style-modulated features
240
+ """
241
+ style_emb = self.style_proj(style_vec)
242
+
243
+ if cond_vec is not None:
244
+ combined = style_emb + cond_vec
245
+ else:
246
+ combined = style_emb
247
+
248
+ scale, shift = self.adaln(combined)
249
+
250
+ x_norm = self.norm(x)
251
+ x_mod = x_norm * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
252
+
253
+ return x_mod
254
+
255
+
256
+ __all__ = [
257
+ 'AdaIN',
258
+ 'AdaINTransfer',
259
+ 'AdaLNModulation',
260
+ 'AdaLNBlock',
261
+ 'AdaLNZero',
262
+ 'StyleAdaLN',
263
+ ]
flux/modules/sampling.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ State-of-the-art sampling techniques (2024-2025).
3
+ Includes Flow Matching with Optimal Transport and Consistency Models.
4
+ """
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+ from typing import Optional, Callable
9
+ import math
10
+
11
+
12
+ class FlowMatching:
13
+ """
14
+ Flow Matching with Optimal Transport for improved trajectory learning.
15
+ Based on recent advances in continuous normalizing flows.
16
+ """
17
+ def __init__(self, sigma_min: float = 0.001):
18
+ """
19
+ Args:
20
+ sigma_min: Minimum noise level for numerical stability
21
+ """
22
+ self.sigma_min = sigma_min
23
+
24
+ def compute_optimal_transport_path(self, x0: torch.Tensor, x1: torch.Tensor,
25
+ t: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
26
+ """
27
+ Compute optimal transport path from noise (x0) to data (x1).
28
+
29
+ Args:
30
+ x0: Source distribution (noise) [B, ...]
31
+ x1: Target distribution (data) [B, ...]
32
+ t: Time steps [B] in range [0, 1]
33
+ Returns:
34
+ x_t: Interpolated samples
35
+ v_t: Vector field (velocity)
36
+ """
37
+ t = t.view(-1, *([1] * (x0.dim() - 1)))
38
+
39
+ # Optimal transport interpolation (linear for Gaussian)
40
+ x_t = (1 - t) * x0 + t * x1
41
+
42
+ # Velocity is the derivative: v = dx/dt = x1 - x0
43
+ v_t = x1 - x0
44
+
45
+ return x_t, v_t
46
+
47
+ def compute_conditional_flow(self, x0: torch.Tensor, x1: torch.Tensor,
48
+ t: torch.Tensor, sigma: Optional[float] = None) -> tuple[torch.Tensor, torch.Tensor]:
49
+ """
50
+ Compute conditional flow with added noise for robustness.
51
+
52
+ Args:
53
+ x0: Noise [B, ...]
54
+ x1: Data [B, ...]
55
+ t: Time [B]
56
+ sigma: Optional noise level (defaults to sigma_min * (1-t))
57
+ Returns:
58
+ x_t: Noisy interpolation
59
+ v_t: Target velocity
60
+ """
61
+ if sigma is None:
62
+ sigma = self.sigma_min * (1 - t.view(-1, *([1] * (x0.dim() - 1))))
63
+ else:
64
+ sigma = sigma * torch.ones_like(t).view(-1, *([1] * (x0.dim() - 1)))
65
+
66
+ # Conditional path
67
+ x_t, v_t = self.compute_optimal_transport_path(x0, x1, t)
68
+
69
+ # Add Gaussian noise for conditional flow matching
70
+ eps = torch.randn_like(x_t)
71
+ x_t = x_t + sigma * eps
72
+
73
+ return x_t, v_t
74
+
75
+ def sample(self, model: Callable, x_init: torch.Tensor,
76
+ num_steps: int = 50, **model_kwargs) -> torch.Tensor:
77
+ """
78
+ Sample using Euler method for ODE solving.
79
+
80
+ Args:
81
+ model: Velocity prediction model
82
+ x_init: Initial noise [B, ...]
83
+ num_steps: Number of integration steps
84
+ **model_kwargs: Additional arguments for model
85
+ Returns:
86
+ x_final: Generated samples
87
+ """
88
+ x = x_init
89
+ dt = 1.0 / num_steps
90
+
91
+ for step in range(num_steps):
92
+ t_current = torch.full((x.shape[0],), step / num_steps,
93
+ device=x.device, dtype=x.dtype)
94
+
95
+ # Predict velocity
96
+ with torch.no_grad():
97
+ v = model(x, timesteps=t_current, **model_kwargs)
98
+
99
+ # Euler step
100
+ x = x + v * dt
101
+
102
+ return x
103
+
104
+ def sample_heun(self, model: Callable, x_init: torch.Tensor,
105
+ num_steps: int = 50, **model_kwargs) -> torch.Tensor:
106
+ """
107
+ Higher-order sampling using Heun's method (2nd order).
108
+ More accurate but requires 2x model evaluations.
109
+
110
+ Args:
111
+ model: Velocity prediction model
112
+ x_init: Initial noise
113
+ num_steps: Number of steps
114
+ **model_kwargs: Model arguments
115
+ Returns:
116
+ x_final: Generated samples
117
+ """
118
+ x = x_init
119
+ dt = 1.0 / num_steps
120
+
121
+ for step in range(num_steps):
122
+ t_current = torch.full((x.shape[0],), step / num_steps,
123
+ device=x.device, dtype=x.dtype)
124
+
125
+ # First stage
126
+ with torch.no_grad():
127
+ v1 = model(x, timesteps=t_current, **model_kwargs)
128
+
129
+ x_temp = x + v1 * dt
130
+
131
+ # Second stage
132
+ t_next = torch.full((x.shape[0],), (step + 1) / num_steps,
133
+ device=x.device, dtype=x.dtype)
134
+
135
+ with torch.no_grad():
136
+ v2 = model(x_temp, timesteps=t_next, **model_kwargs)
137
+
138
+ # Heun's method: average of two velocities
139
+ x = x + (v1 + v2) * 0.5 * dt
140
+
141
+ return x
142
+
143
+
144
+ class ConsistencyModel:
145
+ """
146
+ Consistency Models for fast, single-step generation.
147
+ Based on "Consistency Models" (Song et al., 2023).
148
+ """
149
+ def __init__(self, sigma_min: float = 0.002, sigma_max: float = 80.0,
150
+ rho: float = 7.0):
151
+ """
152
+ Args:
153
+ sigma_min: Minimum noise level
154
+ sigma_max: Maximum noise level
155
+ rho: Schedule parameter
156
+ """
157
+ self.sigma_min = sigma_min
158
+ self.sigma_max = sigma_max
159
+ self.rho = rho
160
+
161
+ def get_schedule(self, num_steps: int, device: torch.device) -> torch.Tensor:
162
+ """
163
+ Get noise schedule for consistency training.
164
+
165
+ Args:
166
+ num_steps: Number of discretization steps
167
+ device: Device for tensors
168
+ Returns:
169
+ sigmas: Noise levels [num_steps + 1]
170
+ """
171
+ rho_inv = 1.0 / self.rho
172
+ steps = torch.arange(num_steps + 1, device=device)
173
+ sigmas = (self.sigma_max ** rho_inv +
174
+ steps / num_steps * (self.sigma_min ** rho_inv - self.sigma_max ** rho_inv)) ** self.rho
175
+ return sigmas
176
+
177
+ def consistency_function(self, model: Callable, x: torch.Tensor,
178
+ sigma: torch.Tensor, **model_kwargs) -> torch.Tensor:
179
+ """
180
+ Apply consistency function: maps (x, sigma) to denoised output.
181
+
182
+ Args:
183
+ model: Denoising model
184
+ x: Noisy input
185
+ sigma: Noise level
186
+ **model_kwargs: Model arguments
187
+ Returns:
188
+ denoised: Consistency function output
189
+ """
190
+ # Skip connection for sigma_min
191
+ c_skip = self.sigma_min ** 2 / (sigma ** 2 + self.sigma_min ** 2)
192
+ c_out = sigma * self.sigma_min / (sigma ** 2 + self.sigma_min ** 2).sqrt()
193
+
194
+ # Predict denoised output
195
+ with torch.no_grad():
196
+ # Convert sigma to timestep for model
197
+ t = sigma / self.sigma_max
198
+ model_out = model(x, timesteps=t, **model_kwargs)
199
+
200
+ # Apply consistency function
201
+ denoised = c_skip * x + c_out * model_out
202
+
203
+ return denoised
204
+
205
+ def sample_onestep(self, model: Callable, shape: tuple,
206
+ device: torch.device, dtype: torch.dtype,
207
+ **model_kwargs) -> torch.Tensor:
208
+ """
209
+ Single-step generation from noise to data.
210
+
211
+ Args:
212
+ model: Consistency model
213
+ shape: Output shape
214
+ device: Device
215
+ dtype: Data type
216
+ **model_kwargs: Model arguments
217
+ Returns:
218
+ samples: Generated samples
219
+ """
220
+ # Sample from prior (high noise)
221
+ x = torch.randn(*shape, device=device, dtype=dtype) * self.sigma_max
222
+ sigma = torch.full((shape[0],), self.sigma_max, device=device, dtype=dtype)
223
+
224
+ # One-step denoising
225
+ samples = self.consistency_function(model, x, sigma, **model_kwargs)
226
+
227
+ return samples
228
+
229
+ def sample_multistep(self, model: Callable, shape: tuple,
230
+ num_steps: int, device: torch.device,
231
+ dtype: torch.dtype, **model_kwargs) -> torch.Tensor:
232
+ """
233
+ Multi-step generation for better quality.
234
+
235
+ Args:
236
+ model: Consistency model
237
+ shape: Output shape
238
+ num_steps: Number of steps
239
+ device: Device
240
+ dtype: Data type
241
+ **model_kwargs: Model arguments
242
+ Returns:
243
+ samples: Generated samples
244
+ """
245
+ # Get noise schedule
246
+ sigmas = self.get_schedule(num_steps, device)
247
+
248
+ # Start from maximum noise
249
+ x = torch.randn(*shape, device=device, dtype=dtype) * self.sigma_max
250
+
251
+ # Iterative refinement
252
+ for i in range(num_steps, 0, -1):
253
+ sigma_current = sigmas[i]
254
+ sigma_next = sigmas[i - 1]
255
+
256
+ # Denoise to current level
257
+ x = self.consistency_function(
258
+ model, x,
259
+ torch.full((shape[0],), sigma_current, device=device, dtype=dtype),
260
+ **model_kwargs
261
+ )
262
+
263
+ # Add noise for next iteration (except last)
264
+ if i > 1:
265
+ noise = torch.randn_like(x)
266
+ x = x + noise * (sigma_next ** 2 - self.sigma_min ** 2).sqrt()
267
+
268
+ return x
269
+
270
+
271
+ class ClassifierFreeGuidance:
272
+ """
273
+ Enhanced Classifier-Free Guidance for better conditioning.
274
+ """
275
+ def __init__(self, guidance_scale: float = 7.5, min_scale: float = 1.0):
276
+ """
277
+ Args:
278
+ guidance_scale: Guidance strength
279
+ min_scale: Minimum guidance scale
280
+ """
281
+ self.guidance_scale = guidance_scale
282
+ self.min_scale = min_scale
283
+
284
+ def apply_guidance(self, cond_out: torch.Tensor, uncond_out: torch.Tensor,
285
+ guidance_scale: Optional[float] = None) -> torch.Tensor:
286
+ """
287
+ Apply classifier-free guidance to model outputs.
288
+
289
+ Args:
290
+ cond_out: Conditional model output
291
+ uncond_out: Unconditional model output
292
+ guidance_scale: Optional override for guidance scale
293
+ Returns:
294
+ guided_out: Guided output
295
+ """
296
+ scale = guidance_scale if guidance_scale is not None else self.guidance_scale
297
+ scale = max(scale, self.min_scale)
298
+
299
+ # CFG formula: out = uncond + scale * (cond - uncond)
300
+ guided_out = uncond_out + scale * (cond_out - uncond_out)
301
+
302
+ return guided_out
303
+
304
+ def apply_dynamic_guidance(self, cond_out: torch.Tensor, uncond_out: torch.Tensor,
305
+ t: torch.Tensor, min_t: float = 0.0,
306
+ max_t: float = 1.0) -> torch.Tensor:
307
+ """
308
+ Apply time-dependent guidance that decreases over time.
309
+
310
+ Args:
311
+ cond_out: Conditional output
312
+ uncond_out: Unconditional output
313
+ t: Time steps [B]
314
+ min_t: Time to start decreasing guidance
315
+ max_t: Time to reach minimum guidance
316
+ Returns:
317
+ guided_out: Dynamically guided output
318
+ """
319
+ # Linearly decrease guidance scale from max to min over time
320
+ t_normalized = (t - min_t) / (max_t - min_t)
321
+ t_normalized = t_normalized.clamp(0, 1)
322
+
323
+ # Scale goes from guidance_scale to min_scale
324
+ scale = self.guidance_scale + (self.min_scale - self.guidance_scale) * t_normalized
325
+ scale = scale.view(-1, *([1] * (cond_out.dim() - 1)))
326
+
327
+ guided_out = uncond_out + scale * (cond_out - uncond_out)
328
+
329
+ return guided_out
330
+
331
+
332
+ class AdaptiveSampler:
333
+ """
334
+ Adaptive sampling that combines multiple techniques for optimal quality/speed.
335
+ """
336
+ def __init__(self, use_flow_matching: bool = True,
337
+ use_consistency: bool = False,
338
+ use_cfg: bool = True):
339
+ self.use_flow_matching = use_flow_matching
340
+ self.use_consistency = use_consistency
341
+ self.use_cfg = use_cfg
342
+
343
+ if use_flow_matching:
344
+ self.flow_matching = FlowMatching()
345
+ if use_consistency:
346
+ self.consistency_model = ConsistencyModel()
347
+ if use_cfg:
348
+ self.cfg = ClassifierFreeGuidance()
349
+
350
+ def sample(self, model: Callable, x_init: torch.Tensor,
351
+ num_steps: int = 50, method: str = "heun",
352
+ cfg_scale: Optional[float] = None,
353
+ **model_kwargs) -> torch.Tensor:
354
+ """
355
+ Adaptive sampling with multiple techniques.
356
+
357
+ Args:
358
+ model: Model for sampling
359
+ x_init: Initial noise
360
+ num_steps: Number of steps
361
+ method: Sampling method ("euler", "heun", "consistency")
362
+ cfg_scale: Optional CFG scale
363
+ **model_kwargs: Model arguments
364
+ Returns:
365
+ samples: Generated samples
366
+ """
367
+ if method == "consistency" and self.use_consistency:
368
+ return self.consistency_model.sample_multistep(
369
+ model, x_init.shape, num_steps,
370
+ x_init.device, x_init.dtype, **model_kwargs
371
+ )
372
+ elif method == "heun" and self.use_flow_matching:
373
+ return self.flow_matching.sample_heun(
374
+ model, x_init, num_steps, **model_kwargs
375
+ )
376
+ else: # euler
377
+ return self.flow_matching.sample(
378
+ model, x_init, num_steps, **model_kwargs
379
+ )
flux/utils/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import numpy as np
4
+ from flux.utils import logger_utils
flux/utils/chronflux_datasets.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Author: https://github.com/datnguyen-tien204
3
+ """
4
+
5
+ import os
6
+ import numpy as np
7
+ from PIL import Image
8
+
9
+ import torch
10
+ from torch.utils.data import Dataset
11
+ from torchvision import transforms
12
+
13
+
14
+ class DayNightDataset(Dataset):
15
+ def __init__(self, day_dir, night_dir, size=384):
16
+ self.day_files = [os.path.join(day_dir, f) for f in os.listdir(day_dir) if f.endswith(('.jpg', '.png'))]
17
+ self.night_files = [os.path.join(night_dir, f) for f in os.listdir(night_dir) if f.endswith(('.jpg', '.png'))]
18
+ self.transform = transforms.Compose(
19
+ [transforms.Resize((size, size)), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])])
20
+
21
+ def __len__(self): return len(self.day_files)
22
+
23
+ def __getitem__(self, idx):
24
+ day_img = Image.open(self.day_files[idx]).convert('RGB')
25
+ night_img = Image.open(np.random.choice(self.night_files)).convert('RGB')
26
+ return self.transform(day_img), self.transform(night_img)
flux/utils/logger_utils.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ConsoleLogger:
2
+ HEADER = '\033[95m'
3
+ BLUE = '\033[94m'
4
+ CYAN = '\033[96m'
5
+ GREEN = '\033[92m'
6
+ WARNING = '\033[93m'
7
+ FAIL = '\033[91m'
8
+ ENDC = '\033[0m'
9
+ BOLD = '\033[1m'
10
+ UNDERLINE = '\033[4m'
11
+
12
+ @staticmethod
13
+ def info(msg): print(f"{ConsoleLogger.GREEN}{ConsoleLogger.BOLD}[INFO]{ConsoleLogger.ENDC} {msg}")
14
+ @staticmethod
15
+ def step(msg): print(f"{ConsoleLogger.CYAN}[STEP]{ConsoleLogger.ENDC} {msg}")
16
+ @staticmethod
17
+ def warn(msg): print(f"{ConsoleLogger.WARNING}{ConsoleLogger.BOLD}[WARN]{ConsoleLogger.ENDC} {msg}")
18
+ @staticmethod
19
+ def section(msg): print(f"\n{ConsoleLogger.HEADER}{ConsoleLogger.BOLD}{'='*60}\n{msg}\n{'='*60}{ConsoleLogger.ENDC}")
flux/utils/math.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Author: https://github.com/datnguyen-tien204
3
+ """
4
+
5
+ import torch
6
+ from einops import rearrange
7
+ from torch import Tensor
8
+ from flash_attn import flash_attn_func
9
+
10
+
11
+ def attention(q: Tensor, k: Tensor, v: Tensor) -> Tensor:
12
+ # Ensure tensors are in bf16 or fp16 for FlashAttention =====
13
+ original_dtype = q.dtype
14
+ if q.dtype not in [torch.float16, torch.bfloat16]:
15
+ q = q.to(torch.bfloat16)
16
+ k = k.to(torch.bfloat16)
17
+ v = v.to(torch.bfloat16)
18
+ # ======================================================================
19
+
20
+ x = flash_attn_func(q, k, v, dropout_p=0.0, softmax_scale=None, causal=False)
21
+ x = rearrange(x, "B L H D -> B L (H D)")
22
+
23
+ # Convert back to original dtype if needed =====
24
+ if original_dtype not in [torch.float16, torch.bfloat16]:
25
+ x = x.to(original_dtype)
26
+ # ==========================================================
27
+
28
+ return x
29
+
30
+
31
+ def rope(pos: Tensor, dim: int, theta: int) -> Tensor:
32
+ assert dim % 2 == 0
33
+ scale = torch.arange(0, dim, 2, dtype=torch.float32, device=pos.device) / dim
34
+ omega = 1.0 / (theta ** scale)
35
+ out = torch.einsum("...n,d->...nd", pos, omega)
36
+ out = torch.stack([torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1)
37
+ out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2)
38
+ return out.float()
39
+
40
+
41
+ def apply_rope(xq: Tensor, xk: Tensor, freqs_cis: Tensor) -> tuple[Tensor, Tensor]:
42
+ xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2)
43
+ xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
44
+
45
+ xq_out = freqs_cis[..., 0] * xq_[..., 0] + freqs_cis[..., 1] * xq_[..., 1]
46
+ xk_out = freqs_cis[..., 0] * xk_[..., 0] + freqs_cis[..., 1] * xk_[..., 1]
47
+
48
+ return xq_out.reshape(*xq.shape).type_as(xq), xk_out.reshape(*xk.shape).type_as(xk)
flux/utils/target_generation.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Author: https://github.com/datnguyen-tien204
3
+ """
4
+ import numpy as np
5
+ import torch
6
+ import math
7
+
8
+ PI = math.pi
9
+
10
+ def symmetric_brightness_curve(hour):
11
+ return 0.035 + (1.0 - 0.035) * (0.5 - 0.5 * np.cos(2 * PI * (hour / 24.0)))
12
+
13
+
14
+ def rgb_curve_original(hour):
15
+ t = (hour % 24.0) / 24.0
16
+ sunset = np.exp(-((t - 0.75) / 0.08) ** 2)
17
+ sunrise = np.exp(-((t - 0.25) / 0.08) ** 2)
18
+ night = np.exp(-((t - 0.0) / 0.10) ** 2) + np.exp(-((t - 1.0) / 0.10) ** 2)
19
+ noon = np.exp(-((t - 0.5) / 0.12) ** 2)
20
+ return [1.0 + 0.4 * sunset + 0.22 * sunrise - 0.35 * night,
21
+ 1.0 - 0.1 * sunset - 0.06 * night,
22
+ 1.0 - 0.5 * sunset + 0.3 * night + 0.15 * noon]
23
+
24
+
25
+ def create_target_final_v2(day_img, style_img, depth_map, hour_val, adain_module, style_stats):
26
+ device, dtype, B = day_img.device, day_img.dtype, day_img.shape[0]
27
+ img_original = (day_img + 1.0) / 2.0
28
+ target_list = []
29
+
30
+ for i in range(B):
31
+ h = hour_val[i].item()
32
+ bright = symmetric_brightness_curve(h)
33
+ rgb = rgb_curve_original(h)
34
+
35
+ tint = torch.tensor(rgb, device=device, dtype=dtype).view(1, 3, 1, 1)
36
+ tint = tint * (0.6 + 0.4 * style_stats["mean"][i].view(1, 3, 1, 1))
37
+
38
+ img_i = img_original[i:i + 1]
39
+ target_base = img_i * tint
40
+
41
+ if bright < 0.8:
42
+ adain_out = (adain_module(day_img[i:i + 1], style_img[i:i + 1]) + 1.0) / 2.0
43
+ night_int = (1.0 - bright) ** 0.8 * 0.85
44
+
45
+ d_i = depth_map[i:i + 1]
46
+ d_norm = (d_i - d_i.min()) / (d_i.max() - d_i.min() + 1e-6)
47
+ sky_mask = torch.sigmoid((0.15 - d_norm) * 10.0)
48
+
49
+ blend = night_int * (0.7 + 0.3 * sky_mask)
50
+ target_mixed = target_base * (1 - blend) + adain_out * blend
51
+ else:
52
+ target_mixed = target_base
53
+ sky_mask = torch.zeros_like(target_mixed[:, :1])
54
+
55
+ target_calc = target_mixed * bright
56
+ if bright < 0.20:
57
+ target_calc += img_i.mean(1, keepdim=True) * tint * 0.03
58
+ if bright < 0.8: target_calc *= (1.0 - sky_mask * 0.5)
59
+
60
+ noon_w = torch.tensor(np.exp(-(abs(h - 12.0) ** 2) / 8.0), device=device, dtype=dtype)
61
+ target_list.append(target_calc * (1 - noon_w) + img_i * noon_w)
62
+
63
+ return (torch.cat(target_list, dim=0) * 2.0 - 1.0).clamp(-1, 1)
flux/utils/util.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ import torch
5
+ from einops import rearrange
6
+ from PIL import Image
7
+ from safetensors.torch import load_file as load_sft
8
+ from huggingface_hub import hf_hub_download
9
+ from flux.modules.autoencoder import AutoEncoder, AutoEncoderParams
10
+
11
+ CHECKPOINTS_DIR = Path("checkpoints")
12
+ CHECKPOINTS_DIR.mkdir(exist_ok=True)
13
+
14
+
15
+ @dataclass
16
+ class VAEConfig:
17
+ repo_id: str
18
+ repo_ae: str
19
+ ae_params: AutoEncoderParams
20
+
21
+
22
+ flux_vae_config = VAEConfig(
23
+ repo_id="black-forest-labs/FLUX.1-dev",
24
+ repo_ae="ae.safetensors",
25
+ ae_params=AutoEncoderParams(
26
+ resolution=64,
27
+ in_channels=3,
28
+ ch=128,
29
+ out_ch=3,
30
+ ch_mult=[1, 2, 4, 4],
31
+ num_res_blocks=2,
32
+ z_channels=16,
33
+ scale_factor=0.3611,
34
+ shift_factor=0.1159,
35
+ ),
36
+ )
37
+
38
+
39
+ def get_checkpoint_path(filename: str, repo_id: str) -> Path:
40
+ local_path = CHECKPOINTS_DIR / filename
41
+ if not local_path.exists():
42
+ print(f"Downloading {filename} from {repo_id}...")
43
+ try:
44
+ downloaded_path = hf_hub_download(repo_id=repo_id, filename=filename, local_dir=CHECKPOINTS_DIR)
45
+ return Path(downloaded_path)
46
+ except Exception as e:
47
+ print(f"Failed to download: {e}")
48
+ return local_path
49
+
50
+
51
+ def load_ae(device: str | torch.device = "cuda") -> AutoEncoder:
52
+ print("Init AE")
53
+ ckpt_path = get_checkpoint_path(flux_vae_config.repo_ae, flux_vae_config.repo_id)
54
+
55
+ if ckpt_path.exists():
56
+ print(f"Loading AE checkpoint: {ckpt_path}")
57
+ with torch.device("meta"):
58
+ ae = AutoEncoder(flux_vae_config.ae_params)
59
+ sd = load_sft(str(ckpt_path), device=str(device))
60
+ ae.load_state_dict(sd, strict=False, assign=True)
61
+ else:
62
+ raise RuntimeError("AE Checkpoint is missing! Model cannot train with random VAE.")
63
+
64
+ return ae
65
+
66
+
67
+ def save_image(x: torch.Tensor, path: str):
68
+ x = x.clamp(-1, 1)
69
+ x = rearrange(x[0], "c h w -> h w c")
70
+ img = Image.fromarray((127.5 * (x + 1.0)).cpu().byte().numpy())
71
+ img.save(path, quality=95, subsampling=0)
72
+
73
+
74
+ def print_load_warning(missing: list[str], unexpected: list[str]):
75
+ if len(missing) > 0 and len(unexpected) > 0:
76
+ print(f"Parameters missing: {len(missing)}. Unexpected: {len(unexpected)}")
infer_1step_style.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import math
4
+ import torch
5
+ import torch.nn.functional as F
6
+ import numpy as np
7
+ from tqdm import tqdm
8
+ from PIL import Image
9
+ from torchvision import transforms
10
+
11
+ sys.path.insert(0, "/workspace/ChronFLUX/ChronFlux-v1")
12
+
13
+ from flux.utils.util import load_ae
14
+ from flux.chronflux import ChronFlux
15
+ from flux.modules.advanced_encoders import EncoderFactory, DepthEstimator
16
+ from config import get_config
17
+
18
+ PI = math.pi
19
+
20
+ # ===== MODEL =====
21
+ CHECKPOINT = "/workspace/ChronFLUX/ChronFlux-v1/distill_checkpoints/rift_1step_epoch_5.pt"
22
+
23
+ # ===== DATA (FIXED MAPPING) =====
24
+ DATA_PAIRS = [
25
+ {
26
+ "input": "/workspace/ChronFLUX/ChronFlux-v1/EvaluationDS/ChronFLUX-Bench/SDSD/outdoor_png/clear",
27
+ "output": "/workspace/ChronFLUX/ChronFlux-v1/EvaluationDS/DS-BenchFinal/SDSD/outdoor_png/dark_gen",
28
+ },
29
+ {
30
+ "input": "/workspace/ChronFLUX/ChronFlux-v1/EvaluationDS/ChronFLUX-Bench/SDSD/indoor_png/clear",
31
+ "output": "/workspace/ChronFLUX/ChronFlux-v1/EvaluationDS/DS-BenchFinal/SDSD/indoor_png/dark_gen",
32
+ },
33
+ {
34
+ "input": "/workspace/ChronFLUX/ChronFlux-v1/EvaluationDS/ChronFLUX-Bench/SMID/clear",
35
+ "output": "/workspace/ChronFLUX/ChronFlux-v1/EvaluationDS/DS-BenchFinal/SMID/dark_gen",
36
+ },
37
+ ]
38
+
39
+ # ===== STYLE =====
40
+ FIXED_STYLE_PATH = "/workspace/ChronFLUX/rafael_ds_ver2/train/night/frame_000002.jpg"
41
+
42
+ # ===== PARAMS =====
43
+ HOURS = list(range(1, 25))
44
+
45
+ IMAGE_SIZE = 384
46
+ DEPTH_SIZE = 48
47
+ DEVICE = "cuda"
48
+ DTYPE = torch.bfloat16
49
+
50
+
51
+ def load_model(ckpt_path, model_config, device, dtype):
52
+ model = ChronFlux(
53
+ in_channels=16,
54
+ hidden_size=1024,
55
+ num_double_layers=8,
56
+ num_single_layers=16,
57
+ num_heads=16,
58
+ style_dim=512,
59
+ use_moe=True,
60
+ use_depth_attn=True,
61
+ use_adain=True,
62
+ moe_params={"num_experts": model_config.moe.num_experts}
63
+ ).to(device).to(dtype)
64
+
65
+ state = torch.load(ckpt_path, map_location="cpu")
66
+ if isinstance(state, dict) and "model_state_dict" in state:
67
+ state = state["model_state_dict"]
68
+
69
+ model.load_state_dict(state, strict=True)
70
+ model.eval().requires_grad_(False)
71
+
72
+ print(f"[INFO] Loaded checkpoint: {ckpt_path}")
73
+ return model
74
+
75
+
76
+ @torch.no_grad()
77
+ def infer_1step(model, ae, content_lat, depth_small, style_vec, phi, device, dtype):
78
+ t = torch.tensor([0.0], device=device, dtype=dtype)
79
+ z = torch.randn_like(content_lat)
80
+
81
+ v, _ = model(z, content_lat, depth_small, style_vec, t, phi)
82
+ z_final = (z + v * 1.0).clamp(-8, 8)
83
+
84
+ return ae.decode(z_final).clamp(-1, 1)
85
+
86
+
87
+ def save_tensor(tensor, path):
88
+ img = tensor.squeeze(0).cpu().float()
89
+ img = ((img + 1.0) / 2.0).clamp(0, 1)
90
+ img = (img.permute(1, 2, 0).numpy() * 255).astype(np.uint8)
91
+ Image.fromarray(img).save(path)
92
+
93
+
94
+ def main():
95
+ model_config = get_config("q1_paper")
96
+
97
+ print("[INFO] Loading AutoEncoder...")
98
+ ae = load_ae(DEVICE).to(DEVICE).to(DTYPE).eval().requires_grad_(False)
99
+
100
+ print("[INFO] Loading Style Encoder (DINOv2)...")
101
+ style_enc = EncoderFactory.create_encoder(
102
+ "dinov2", DEVICE, 512, False, model_size="base"
103
+ ).to(DEVICE).to(DTYPE).eval().requires_grad_(False)
104
+
105
+ print("[INFO] Loading Depth Estimator...")
106
+ depth_est = DepthEstimator(DEVICE)
107
+
108
+ print("[INFO] Loading distilled model...")
109
+ model = load_model(CHECKPOINT, model_config, DEVICE, DTYPE)
110
+
111
+ transform = transforms.Compose([
112
+ transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),
113
+ transforms.ToTensor(),
114
+ transforms.Normalize([0.5], [0.5])
115
+ ])
116
+
117
+ print("[INFO] Encoding style image...")
118
+ style_t = transform(
119
+ Image.open(FIXED_STYLE_PATH).convert("RGB")
120
+ ).unsqueeze(0).to(DEVICE).to(DTYPE)
121
+
122
+ with torch.no_grad():
123
+ style_vec = style_enc(style_t, return_spatial=True)
124
+
125
+ print(f"[INFO] Style vector shape: {style_vec.shape}")
126
+
127
+ # ===== LOOP DATA PAIRS =====
128
+ for pair in DATA_PAIRS:
129
+ content_dir = pair["input"]
130
+ output_dir = pair["output"]
131
+
132
+ os.makedirs(output_dir, exist_ok=True)
133
+
134
+ if not os.path.exists(content_dir):
135
+ print(f"[WARNING] Missing folder: {content_dir}")
136
+ continue
137
+
138
+ content_files = sorted([
139
+ f for f in os.listdir(content_dir)
140
+ if f.lower().endswith((".jpg", ".jpeg", ".png"))
141
+ ])
142
+
143
+ if not content_files:
144
+ print(f"[WARNING] No images in: {content_dir}")
145
+ continue
146
+
147
+ print(f"\n[DATASET]")
148
+ print(f"[INPUT ] {content_dir}")
149
+ print(f"[OUTPUT] {output_dir}")
150
+ print(f"[INFO ] {len(content_files)} images")
151
+
152
+ for fname in tqdm(content_files, desc=os.path.basename(content_dir)):
153
+ stem = os.path.splitext(fname)[0]
154
+
155
+ try:
156
+ content_tensor = transform(
157
+ Image.open(os.path.join(content_dir, fname)).convert("RGB")
158
+ ).unsqueeze(0).to(DEVICE).to(DTYPE)
159
+
160
+ with torch.no_grad():
161
+ depth_full = depth_est.predict(
162
+ content_tensor.float(), IMAGE_SIZE
163
+ ).to(DTYPE)
164
+
165
+ depth_small = F.interpolate(
166
+ depth_full,
167
+ (DEPTH_SIZE, DEPTH_SIZE),
168
+ mode="nearest"
169
+ )
170
+
171
+ content_lat = ae.encode(content_tensor)
172
+
173
+ # ===== LOOP HOURS =====
174
+ for h in HOURS:
175
+ phi = torch.tensor(
176
+ [h * (2 * PI / 24.0)],
177
+ device=DEVICE,
178
+ dtype=DTYPE
179
+ )
180
+
181
+ out = infer_1step(
182
+ model,
183
+ ae,
184
+ content_lat,
185
+ depth_small,
186
+ style_vec,
187
+ phi,
188
+ DEVICE,
189
+ DTYPE
190
+ )
191
+
192
+ save_path = os.path.join(
193
+ output_dir,
194
+ f"{stem}_h{h:02d}.png"
195
+ )
196
+
197
+ save_tensor(out, save_path)
198
+
199
+ except Exception as e:
200
+ print(f"\n[ERROR] {fname}: {e}")
201
+ import traceback
202
+ traceback.print_exc()
203
+
204
+ print(f"[DONE] {content_dir}: {len(content_files)*len(HOURS)} files")
205
+
206
+ print("\nπŸŽ‰ ALL DATASETS DONE")
207
+
208
+
209
+ if __name__ == "__main__":
210
+ main()
infer_multistep.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import argparse
4
+ import math
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from torchvision import transforms
8
+ from torchvision.utils import save_image
9
+ from PIL import Image, ImageDraw, ImageFont
10
+ import numpy as np
11
+ from tqdm import tqdm
12
+
13
+ sys.path.insert(0, "/workspace/RIFT-9226")
14
+
15
+ from flux.utils.util import load_ae
16
+ from flux.chronflux import ChronFlux
17
+ from flux.modules.advanced_encoders import EncoderFactory, DepthEstimator
18
+ from config import get_config
19
+
20
+ PI = np.pi
21
+
22
+
23
+ # ─────────────────────────────────────────────────────────────────────────────
24
+ # Helpers
25
+ # ─────────────────────────────────────────────────────────────────────────────
26
+
27
+ def add_label(tensor_img, text):
28
+ img = (tensor_img.clamp(-1, 1) + 1) / 2
29
+ img = img.squeeze(0).permute(1, 2, 0).cpu().float().numpy()
30
+ img = (img * 255).astype(np.uint8)
31
+ pil_img = Image.fromarray(img)
32
+ draw = ImageDraw.Draw(pil_img)
33
+ try:
34
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 22)
35
+ except Exception:
36
+ font = ImageFont.load_default()
37
+ x, y = 6, 6
38
+ for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
39
+ draw.text((x + dx, y + dy), text, fill="black", font=font)
40
+ draw.text((x, y), text, fill="white", font=font)
41
+ out = np.array(pil_img).astype(np.float32) / 255.0
42
+ out = torch.from_numpy(out).permute(2, 0, 1).unsqueeze(0)
43
+ return out * 2 - 1
44
+
45
+
46
+ def load_model(checkpoint_path, model_config, device, dtype):
47
+ model = ChronFlux(
48
+ in_channels=16,
49
+ hidden_size=1024,
50
+ num_double_layers=8,
51
+ num_single_layers=16,
52
+ num_heads=16,
53
+ style_dim=512,
54
+ use_moe=True,
55
+ use_depth_attn=True,
56
+ use_adain=True,
57
+ moe_params={"num_experts": model_config.moe.num_experts}
58
+ ).to(device).to(dtype)
59
+
60
+ state = torch.load(checkpoint_path, map_location="cpu")
61
+ if "model_state_dict" in state:
62
+ state = state["model_state_dict"]
63
+ model.load_state_dict(state, strict=True)
64
+ model.eval()
65
+ return model
66
+
67
+
68
+ def get_cosine_schedule(num_steps):
69
+ return [1.0 - math.cos((i / num_steps) * math.pi / 2.0) for i in range(num_steps)]
70
+
71
+
72
+ # ─────────────────────────────────────────────────────────────────────────────
73
+ # Core denoising loop (shared by single-seed and multi-seed paths)
74
+ # ─────────────────────────────────────────────────────────────────────────────
75
+
76
+ def denoise(
77
+ model, z_init, content_lat, depth_small, style_vec,
78
+ phi, device, dtype, denoise_steps,
79
+ latent_clamp=20.0, # clamp during ODE integration
80
+ latent_clamp_final=15.0, # tighter clamp right before decode
81
+ ):
82
+ """
83
+ Heun 2nd-order rectified-flow ODE solver.
84
+
85
+ Returns the final latent (NOT yet decoded).
86
+ Caller is responsible for ae.decode(z).
87
+ """
88
+ t_schedule = get_cosine_schedule(denoise_steps)
89
+ z = z_init.clone()
90
+
91
+ for i in range(denoise_steps):
92
+ t_val = t_schedule[i]
93
+ dt = (t_schedule[i + 1] - t_schedule[i]) if i < denoise_steps - 1 else (1.0 - t_schedule[i])
94
+
95
+ t_cur = torch.tensor([t_val], device=device, dtype=dtype)
96
+
97
+ # ── Euler predictor ─────────────────────────────────────────────────
98
+ v1, _ = model(z, content_lat, depth_small, style_vec, t_cur, phi)
99
+ z_mid = (z + v1 * dt).clamp(-latent_clamp, latent_clamp)
100
+
101
+ # ── Heun corrector ───────────────────────────────────────────────────
102
+ t_next_val = t_schedule[i + 1] if i < denoise_steps - 1 else 1.0
103
+ t_next = torch.tensor([t_next_val], device=device, dtype=dtype)
104
+ v2, _ = model(z_mid, content_lat, depth_small, style_vec, t_next, phi)
105
+
106
+ z = z + (v1 + v2) * 0.5 * dt
107
+ z = z.clamp(-latent_clamp, latent_clamp) # keep ODE stable
108
+
109
+ # ── Final pre-decode clamp ───────────────────────────────────────────────
110
+ z = z.clamp(-latent_clamp_final, latent_clamp_final)
111
+ return z
112
+
113
+
114
+ # ─────────────────────────────────────────────────────────────────────────────
115
+ # Per-image inference
116
+ # ─────────────────────────────────────────────────────────────────────────────
117
+
118
+ def run_inference(
119
+ model, ae, style_enc, depth_est,
120
+ content_path, style_path,
121
+ device, dtype,
122
+ denoise_steps, image_size, depth_size,
123
+ noise_alpha=0.92, # [DEPRECATED] kept for signature compatibility
124
+ num_seeds=1, # >1 β†’ average over multiple trajectories
125
+ hours=None, # list of hours to render; default = 0..24
126
+ ):
127
+ """
128
+ Returns a strip tensor:
129
+ [content | style | depth | hour_0 | hour_1 | ... | hour_24]
130
+ """
131
+ if hours is None:
132
+ hours = list(range(25)) # 0 h … 24 h
133
+
134
+ transform = transforms.Compose([
135
+ transforms.Resize((image_size, image_size)),
136
+ transforms.ToTensor(),
137
+ transforms.Normalize([0.5], [0.5])
138
+ ])
139
+
140
+ content_tensor = transform(Image.open(content_path).convert("RGB")).unsqueeze(0).to(device).to(dtype)
141
+ style_tensor = transform(Image.open(style_path).convert("RGB")).unsqueeze(0).to(device).to(dtype)
142
+
143
+ with torch.no_grad():
144
+ # ── Encode once ─────────────────────────────────────────────────────
145
+ depth_full = depth_est.predict(content_tensor.float(), image_size).to(dtype)
146
+ depth_small = F.interpolate(depth_full, size=(depth_size, depth_size), mode="nearest")
147
+
148
+ content_lat = ae.encode(content_tensor)
149
+
150
+ style_vec = style_enc(style_tensor, return_spatial=True)
151
+
152
+ depth_vis = (depth_full.repeat(1, 3, 1, 1) - 0.5) / 0.5
153
+
154
+ generated = []
155
+ for h in hours:
156
+ phi_val = h * (2 * PI / 24.0)
157
+ phi = torch.tensor([phi_val], device=device, dtype=dtype)
158
+
159
+ if num_seeds == 1:
160
+ # ── Single trajectory ────────────────────────────────────────
161
+ z_init = torch.randn_like(content_lat)
162
+
163
+ z_final = denoise(
164
+ model, z_init, content_lat, depth_small, style_vec,
165
+ phi, device, dtype, denoise_steps
166
+ )
167
+ out = ae.decode(z_final).clamp(-1, 1)
168
+
169
+ else:
170
+ decoded_sum = torch.zeros(1, 3, image_size, image_size, device=device, dtype=torch.float32)
171
+ for _ in range(num_seeds):
172
+ z_init = torch.randn_like(content_lat)
173
+
174
+ z_final = denoise(
175
+ model, z_init, content_lat, depth_small, style_vec,
176
+ phi, device, dtype, denoise_steps
177
+ )
178
+ decoded_sum += ae.decode(z_final).clamp(-1, 1).float()
179
+ out = (decoded_sum / num_seeds).clamp(-1, 1)
180
+
181
+ generated.append(add_label(out.float(), f"{h}h").to(device))
182
+
183
+ content_labeled = add_label(content_tensor.float(), "Content").to(device)
184
+ style_labeled = add_label(style_tensor.float(), "Style").to(device)
185
+ depth_labeled = add_label(depth_vis.float(), "Depth").to(device)
186
+
187
+ strip = torch.cat([content_labeled, style_labeled, depth_labeled] + generated, dim=3)
188
+ return strip
189
+
190
+
191
+ # ─────────────────────────────────────────────────────────────────────────────
192
+ # CLI entry-point
193
+ # ─────────────────────────────────────────────────────────────────────────────
194
+
195
+ def main():
196
+ parser = argparse.ArgumentParser(description="ChronFlux 24-hour inference")
197
+ parser.add_argument("--checkpoint", type=str, default="/workspace/RIFT-9226/checkpoints/rift_epoch_30.pt")
198
+ parser.add_argument("--content_dir", type=str, default="/workspace/RIFT/rafael_ds_ver2/img_test/cnt")
199
+ parser.add_argument("--style_dir", type=str, default="/workspace/RIFT/rafael_ds_ver2/img_test/sty")
200
+ parser.add_argument("--output_dir", type=str, default="/workspace/RIFT-9226/inference_outputs")
201
+ parser.add_argument("--denoise_steps", type=int, default=50,
202
+ help="More steps = better quality. 50 is a good default; try 75 for maximum quality.")
203
+ parser.add_argument("--image_size", type=int, default=384)
204
+ parser.add_argument("--depth_size", type=int, default=48)
205
+ parser.add_argument("--noise_alpha", type=float, default=0.85,
206
+ help="[DEPRECATED] No longer used. Model requires pure noise init.")
207
+ parser.add_argument("--num_seeds", type=int, default=1,
208
+ help="Average over N random seeds (slow but reduces noise artifacts).")
209
+ parser.add_argument("--device", type=str, default="cuda")
210
+ parser.add_argument("--dtype", type=str, default="bfloat16")
211
+ args = parser.parse_args()
212
+
213
+ device = args.device
214
+ dtype = torch.bfloat16 if args.dtype == "bfloat16" else torch.float32
215
+
216
+ os.makedirs(args.output_dir, exist_ok=True)
217
+
218
+ print(f"[INFO] Loading config...")
219
+ model_config = get_config("q1_paper")
220
+
221
+ print(f"[INFO] Loading AutoEncoder...")
222
+ ae = load_ae(device).to(device).to(dtype).eval().requires_grad_(False)
223
+
224
+ print(f"[INFO] Loading Style Encoder (DINOv2)...")
225
+ style_enc = EncoderFactory.create_encoder(
226
+ "dinov2", device, 512, False, model_size="base"
227
+ ).to(device).to(dtype).eval().requires_grad_(False)
228
+
229
+ print(f"[INFO] Loading Depth Estimator...")
230
+ depth_est = DepthEstimator(device)
231
+
232
+ print(f"[INFO] Loading ChronFlux from {args.checkpoint}...")
233
+ model = load_model(args.checkpoint, model_config, device, dtype)
234
+ model.requires_grad_(False)
235
+
236
+ print(f"[INFO] denoise_steps={args.denoise_steps} | num_seeds={args.num_seeds} | Pure Noise Init: ENABLED")
237
+
238
+ content_files = sorted([
239
+ f for f in os.listdir(args.content_dir)
240
+ if f.lower().endswith((".jpg", ".jpeg", ".png"))
241
+ ])
242
+ style_files = sorted([
243
+ f for f in os.listdir(args.style_dir)
244
+ if f.lower().endswith((".jpg", ".jpeg", ".png"))
245
+ ])
246
+
247
+ if not content_files:
248
+ print("[ERROR] No content images found.")
249
+ return
250
+ if not style_files:
251
+ print("[ERROR] No style images found.")
252
+ return
253
+
254
+ print(f"[INFO] Found {len(content_files)} content images and {len(style_files)} style images.")
255
+
256
+ for content_file in tqdm(content_files, desc="Content images"):
257
+ content_path = os.path.join(args.content_dir, content_file)
258
+ content_stem = os.path.splitext(content_file)[0]
259
+
260
+ for style_file in style_files:
261
+ style_path = os.path.join(args.style_dir, style_file)
262
+ style_stem = os.path.splitext(style_file)[0]
263
+
264
+ out_name = f"{content_stem}__sty_{style_stem}.png"
265
+ out_path = os.path.join(args.output_dir, out_name)
266
+
267
+ if os.path.exists(out_path):
268
+ print(f"[SKIP] {out_name} already exists.")
269
+ continue
270
+
271
+ try:
272
+ strip = run_inference(
273
+ model=model,
274
+ ae=ae,
275
+ style_enc=style_enc,
276
+ depth_est=depth_est,
277
+ content_path=content_path,
278
+ style_path=style_path,
279
+ device=device,
280
+ dtype=dtype,
281
+ denoise_steps=args.denoise_steps,
282
+ image_size=args.image_size,
283
+ depth_size=args.depth_size,
284
+ noise_alpha=args.noise_alpha,
285
+ num_seeds=args.num_seeds,
286
+ )
287
+
288
+ save_image(strip, out_path, normalize=True, value_range=(-1, 1))
289
+ print(f"[SAVED] {out_path}")
290
+
291
+ except Exception as e:
292
+ print(f"[ERROR] Failed on {content_file} x {style_file}: {e}")
293
+ import traceback
294
+ traceback.print_exc()
295
+
296
+ print(f"\n[DONE] All outputs saved to: {args.output_dir}")
297
+
298
+
299
+ if __name__ == "__main__":
300
+ main()
infer_multistep_vid.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import argparse
4
+ import math
5
+ import torch
6
+ import torch.nn.functional as F
7
+ import cv2
8
+ import numpy as np
9
+ from tqdm import tqdm
10
+ from PIL import Image
11
+ from torchvision import transforms
12
+
13
+ try:
14
+ from moviepy import VideoFileClip
15
+ MOVIEPY_AVAILABLE = True
16
+ except ImportError:
17
+ MOVIEPY_AVAILABLE = False
18
+
19
+ sys.path.insert(0, "/workspace/ChronFLUX")
20
+
21
+ from flux.utils.util import load_ae
22
+ from flux.chronflux import ChronFlux
23
+ from flux.modules.advanced_encoders import EncoderFactory, DepthEstimator
24
+ from config import get_config
25
+
26
+ PI = math.pi
27
+
28
+
29
+ def load_model(checkpoint_path, model_config, device, dtype):
30
+ model = ChronFlux(
31
+ in_channels=16,
32
+ hidden_size=1024,
33
+ num_double_layers=8,
34
+ num_single_layers=16,
35
+ num_heads=16,
36
+ style_dim=512,
37
+ use_moe=True,
38
+ use_depth_attn=True,
39
+ use_adain=True,
40
+ moe_params={"num_experts": model_config.moe.num_experts}
41
+ ).to(device).to(dtype)
42
+
43
+ state = torch.load(checkpoint_path, map_location="cpu")
44
+ if "model_state_dict" in state:
45
+ state = state["model_state_dict"]
46
+ model.load_state_dict(state, strict=True)
47
+ model.eval()
48
+ model.requires_grad_(False)
49
+ return model
50
+
51
+
52
+ def get_cosine_schedule(num_steps):
53
+ ts = []
54
+ for i in range(num_steps):
55
+ t_val = 1.0 - math.cos((i / num_steps) * math.pi / 2.0)
56
+ ts.append(t_val)
57
+ return ts
58
+
59
+
60
+ def get_safe_resolution(width, height, max_height=720):
61
+ if height <= max_height and width % 16 == 0 and height % 16 == 0:
62
+ return width, height
63
+ ratio = max_height / height
64
+ new_width = int(width * ratio)
65
+ new_height = max_height
66
+ if new_width % 16 != 0:
67
+ new_width = new_width - (new_width % 16)
68
+ if new_height % 16 != 0:
69
+ new_height = new_height - (new_height % 16)
70
+ return new_width, new_height
71
+
72
+
73
+ def tensor_to_numpy_bgr(tensor):
74
+ img = tensor.clamp(-1, 1)
75
+ img = (img + 1.0) / 2.0
76
+ img = img.squeeze(0).permute(1, 2, 0).cpu().float().numpy()
77
+ img = (img * 255).astype(np.uint8)
78
+ img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
79
+ return img_bgr
80
+
81
+
82
+ def inference_single_frame(
83
+ model, ae, depth_est,
84
+ frame_tensor,
85
+ style_vec_cached,
86
+ phi, device, dtype,
87
+ denoise_steps, depth_size,
88
+ base_noise
89
+ ):
90
+ with torch.no_grad():
91
+ image_size = frame_tensor.shape[-1]
92
+
93
+ depth_full = depth_est.predict(frame_tensor.float(), image_size).to(dtype)
94
+ depth_small = F.interpolate(depth_full, size=(depth_size, depth_size), mode="nearest")
95
+
96
+ # content_gray = transforms.functional.rgb_to_grayscale(frame_tensor, num_output_channels=3)
97
+ # content_lat = ae.encode(content_gray)
98
+ content_lat = ae.encode(frame_tensor)
99
+
100
+ style_vec = style_vec_cached
101
+
102
+ t_schedule = get_cosine_schedule(denoise_steps)
103
+
104
+ z = base_noise.clone()
105
+
106
+ for i in range(denoise_steps):
107
+ t_val = t_schedule[i]
108
+ if i < denoise_steps - 1:
109
+ dt = t_schedule[i + 1] - t_schedule[i]
110
+ else:
111
+ dt = 1.0 - t_schedule[i]
112
+
113
+ t = torch.tensor([t_val], device=device, dtype=dtype)
114
+
115
+ v1, _ = model(z, content_lat, depth_small, style_vec, t, phi)
116
+ z_mid = (z + v1 * dt).clamp(-8, 8)
117
+
118
+ if i < denoise_steps - 1:
119
+ t_next_val = t_schedule[i + 1]
120
+ else:
121
+ t_next_val = 1.0
122
+ t_next = torch.tensor([t_next_val], device=device, dtype=dtype)
123
+ v2, _ = model(z_mid, content_lat, depth_small, style_vec, t_next, phi)
124
+
125
+ z = z + (v1 + v2) * 0.5 * dt
126
+ z = z.clamp(-8, 8)
127
+
128
+ z = z.clamp(-6, 6)
129
+ out = ae.decode(z).clamp(-1, 1)
130
+ return out
131
+
132
+
133
+ def main():
134
+ parser = argparse.ArgumentParser(description="ChronFlux Video Inference - Day/Night Cycle (Fast)")
135
+ parser.add_argument("--checkpoint", type=str, required=True)
136
+ parser.add_argument("--video_path", type=str, required=True)
137
+ parser.add_argument("--style_path", type=str, required=True, help="αΊ’nh style ban Δ‘Γͺm")
138
+ parser.add_argument("--output_dir", type=str, default="/workspace/ChronFLUX/video_outputs")
139
+ parser.add_argument("--denoise_steps", type=int, default=50)
140
+ parser.add_argument("--image_size", type=int, default=384)
141
+ parser.add_argument("--depth_size", type=int, default=48)
142
+ parser.add_argument("--max_height", type=int, default=384)
143
+ parser.add_argument("--hour_start", type=float, default=0.0)
144
+ parser.add_argument("--hour_end", type=float, default=24.0)
145
+ parser.add_argument("--cycle", action="store_true")
146
+ parser.add_argument("--no_audio", action="store_true")
147
+ parser.add_argument("--device", type=str, default="cuda")
148
+ parser.add_argument("--dtype", type=str, default="bfloat16")
149
+ args = parser.parse_args()
150
+
151
+ device = args.device
152
+ dtype = torch.bfloat16 if args.dtype == "bfloat16" else torch.float32
153
+
154
+ os.makedirs(args.output_dir, exist_ok=True)
155
+
156
+ print("[INFO] Loading config...")
157
+ model_config = get_config("q1_paper")
158
+
159
+ print("[INFO] Loading AutoEncoder...")
160
+ ae = load_ae(device).to(device).to(dtype).eval().requires_grad_(False)
161
+
162
+ print("[INFO] Loading Style Encoder (DINOv2)...")
163
+ style_enc = EncoderFactory.create_encoder(
164
+ "dinov2", device, 512, False, model_size="base"
165
+ ).to(device).to(dtype).eval().requires_grad_(False)
166
+
167
+ print("[INFO] Loading Depth Estimator...")
168
+ depth_est = DepthEstimator(device)
169
+
170
+ print(f"[INFO] Loading ChronFlux from {args.checkpoint}...")
171
+ model = load_model(args.checkpoint, model_config, device, dtype)
172
+
173
+ transform = transforms.Compose([
174
+ transforms.Resize((args.image_size, args.image_size)),
175
+ transforms.ToTensor(),
176
+ transforms.Normalize([0.5], [0.5])
177
+ ])
178
+
179
+ print("[INFO] Loading & encoding style image (cached for all frames)...")
180
+ style_pil = Image.open(args.style_path).convert("RGB")
181
+ style_tensor = transform(style_pil).unsqueeze(0).to(device).to(dtype)
182
+ with torch.no_grad():
183
+ style_vec_cached = style_enc(style_tensor, return_spatial=True)
184
+ print(f"[INFO] Style vector cached: {style_vec_cached.shape}")
185
+ # ─────────────────────────────────────────────────────────────────────────
186
+
187
+ print("[INFO] Opening video...")
188
+ cap = cv2.VideoCapture(args.video_path)
189
+ if not cap.isOpened():
190
+ raise IOError(f"KhΓ΄ng thể mở video: {args.video_path}")
191
+
192
+ fps = cap.get(cv2.CAP_PROP_FPS)
193
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
194
+ orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
195
+ orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
196
+
197
+ proc_w, proc_h = get_safe_resolution(orig_w, orig_h, args.max_height)
198
+ print(f"[INFO] Video: {orig_w}x{orig_h} β†’ resize {proc_w}x{proc_h}, {fps:.2f} FPS, {total_frames} frames")
199
+ print(f"[INFO] Hour range: {args.hour_start}h β†’ {args.hour_end}h, denoise_steps={args.denoise_steps} (Heun)")
200
+ print(f"[INFO] Speedup: style_enc skipped for {total_frames - 1} frames")
201
+
202
+ video_name = os.path.splitext(os.path.basename(args.video_path))[0]
203
+ temp_path = os.path.join(args.output_dir, f"{video_name}_temp.mp4")
204
+ output_path = os.path.join(args.output_dir, f"{video_name}_chronflux.mp4")
205
+
206
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
207
+ writer = cv2.VideoWriter(temp_path, fourcc, fps, (proc_w, proc_h))
208
+
209
+ frame_idx = 0
210
+ pbar = tqdm(total=total_frames, desc="Processing frames")
211
+
212
+ base_noise = None
213
+
214
+ while True:
215
+ ret, frame_bgr = cap.read()
216
+ if not ret:
217
+ break
218
+
219
+ frame_resized = cv2.resize(frame_bgr, (proc_w, proc_h), interpolation=cv2.INTER_AREA)
220
+ frame_rgb = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)
221
+ frame_pil = Image.fromarray(frame_rgb)
222
+ frame_tensor_inf = transform(frame_pil).unsqueeze(0).to(device).to(dtype)
223
+
224
+ if base_noise is None:
225
+ with torch.no_grad():
226
+ content_gray = transforms.functional.rgb_to_grayscale(frame_tensor_inf, num_output_channels=3)
227
+ first_lat = ae.encode(content_gray)
228
+ base_noise = torch.randn_like(first_lat)
229
+ print(f"[INFO] Base noise initialized: {base_noise.shape}")
230
+
231
+ if args.cycle:
232
+ progress = (frame_idx % total_frames) / max(total_frames - 1, 1)
233
+ if (frame_idx // total_frames) % 2 == 0:
234
+ hour = args.hour_start + progress * (args.hour_end - args.hour_start)
235
+ else:
236
+ hour = args.hour_end - progress * (args.hour_end - args.hour_start)
237
+ else:
238
+ progress = frame_idx / max(total_frames - 1, 1)
239
+ hour = args.hour_start + progress * (args.hour_end - args.hour_start)
240
+
241
+ phi_val = hour * (2 * PI / 24.0)
242
+ phi = torch.tensor([phi_val], device=device, dtype=dtype)
243
+
244
+ out = inference_single_frame(
245
+ model=model,
246
+ ae=ae,
247
+ depth_est=depth_est,
248
+ frame_tensor=frame_tensor_inf,
249
+ style_vec_cached=style_vec_cached,
250
+ phi=phi,
251
+ device=device,
252
+ dtype=dtype,
253
+ denoise_steps=args.denoise_steps,
254
+ depth_size=args.depth_size,
255
+ base_noise=base_noise
256
+ )
257
+
258
+ out_bgr = tensor_to_numpy_bgr(out)
259
+ out_bgr_resized = cv2.resize(out_bgr, (proc_w, proc_h), interpolation=cv2.INTER_LINEAR)
260
+ writer.write(out_bgr_resized)
261
+
262
+ frame_idx += 1
263
+ pbar.update(1)
264
+ pbar.set_postfix({"hour": f"{hour:.1f}h", "phi": f"{phi_val:.3f}"})
265
+
266
+ cap.release()
267
+ writer.release()
268
+ pbar.close()
269
+ print(f"[INFO] Finished processing {frame_idx} frames.")
270
+
271
+ if not args.no_audio and MOVIEPY_AVAILABLE:
272
+ print("[INFO] Adding audio from original video...")
273
+ try:
274
+ original_clip = VideoFileClip(args.video_path)
275
+ generated_clip = VideoFileClip(temp_path)
276
+ if original_clip.audio is not None:
277
+ final_clip = generated_clip.set_audio(original_clip.audio)
278
+ final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", logger="bar")
279
+ original_clip.close()
280
+ generated_clip.close()
281
+ os.remove(temp_path)
282
+ print("[INFO] Audio added successfully.")
283
+ else:
284
+ original_clip.close()
285
+ generated_clip.close()
286
+ os.rename(temp_path, output_path)
287
+ print("[INFO] No audio track found in original video.")
288
+ except Exception as e:
289
+ print(f"[WARN] Could not add audio: {e}")
290
+ os.rename(temp_path, output_path)
291
+ else:
292
+ if not MOVIEPY_AVAILABLE and not args.no_audio:
293
+ print("[WARN] moviepy not installed, skipping audio.")
294
+ os.rename(temp_path, output_path)
295
+
296
+ print(f"\n[DONE] Output saved to: {output_path}")
297
+
298
+
299
+ if __name__ == "__main__":
300
+ main()
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch>=2.6.0
2
+ torchvision>=0.19.0
3
+ numpy>=1.24.0
4
+ pillow>=10.3.0
5
+ tqdm
6
+ einops
7
+ safetensors
8
+ transformers>=4.48.0
9
+ accelerate
10
+ opencv-python
11
+ sentencepiece
12
+ huggingface_hub
13
+ timm>=0.9.0
14
+ lpips
15
+ flash-attn>=2.0.0