flaxdiff 0.2.9__tar.gz → 0.2.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/PKG-INFO +2 -4
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/README.md +0 -2
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/inference/utils.py +1 -1
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/hilbert.py +25 -20
- flaxdiff-0.2.11/flaxdiff/models/ssm_dit.py +560 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/trainer/general_diffusion_trainer.py +20 -10
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/trainer/simple_trainer.py +14 -12
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff.egg-info/PKG-INFO +2 -4
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff.egg-info/SOURCES.txt +1 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/pyproject.toml +2 -2
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/benchmark_decord.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/dataloaders.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/dataset_map.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/online_loader.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/audio_utils.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/av_example.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/av_utils.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/base.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/images.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/utils.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/videos.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/data/sources/voxceleb2.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/inference/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/inference/pipeline.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/inputs/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/inputs/encoders.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/common.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/images.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/inception.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/psnr.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/ssim.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/metrics/utils.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/attention.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/autoencoder.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/diffusers.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/simple_autoenc.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/common.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/favor_fastattn.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/general.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/simple_dit.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/simple_mmdit.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/simple_unet.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/simple_vit.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/unet_3d.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/unet_3d_blocks.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/models/vit_common.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/predictors/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/common.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/ddim.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/ddpm.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/euler.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/heun_sampler.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/multistep_dpm.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/samplers/rk4_sampler.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/common.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/continuous.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/cosine.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/discrete.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/exp.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/karras.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/linear.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/schedulers/sqrt.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/trainer/__init__.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/trainer/autoencoder_trainer.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/trainer/diffusion_trainer.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff/utils.py +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff.egg-info/dependency_links.txt +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff.egg-info/requires.txt +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/flaxdiff.egg-info/top_level.txt +0 -0
- {flaxdiff-0.2.9 → flaxdiff-0.2.11}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flaxdiff
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
4
4
|
Summary: A versatile and easy to understand Diffusion library
|
|
5
5
|
Author-email: Ashish Kumar Singh <ashishkmr472@gmail.com>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: flax>=0.8.4
|
|
9
9
|
Requires-Dist: jax>=0.4.28
|
|
@@ -22,8 +22,6 @@ Requires-Dist: python-dotenv
|
|
|
22
22
|
|
|
23
23
|
# 
|
|
24
24
|
|
|
25
|
-
**This project is being used for the UMD Course project MSML 605: MLOps**
|
|
26
|
-
|
|
27
25
|
**This project is partially supported by [Google TPU Research Cloud](https://sites.research.google/trc/about/). I would like to thank the Google Cloud TPU team for providing me with the resources to train the bigger text-conditional models in multi-host distributed settings.**
|
|
28
26
|
|
|
29
27
|
## A Versatile and simple Diffusion Library
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# 
|
|
2
2
|
|
|
3
|
-
**This project is being used for the UMD Course project MSML 605: MLOps**
|
|
4
|
-
|
|
5
3
|
**This project is partially supported by [Google TPU Research Cloud](https://sites.research.google/trc/about/). I would like to thank the Google Cloud TPU team for providing me with the resources to train the bigger text-conditional models in multi-host distributed settings.**
|
|
6
4
|
|
|
7
5
|
## A Versatile and simple Diffusion Library
|
|
@@ -121,8 +121,8 @@ def parse_config(config, overrides=None):
|
|
|
121
121
|
'uvit': UViT,
|
|
122
122
|
'diffusers_unet_simple': FlaxUNet2DConditionModel,
|
|
123
123
|
'simple_dit': SimpleDiT,
|
|
124
|
-
'simple_uvit': SimpleUDiT,
|
|
125
124
|
'simple_mmdit': SimpleMMDiT,
|
|
125
|
+
'simple_udit': SimpleUDiT,
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
# Map all the leaves of the model config, converting strings to appropriate types
|
|
@@ -241,7 +241,6 @@ def hilbert_unpatchify(x: jnp.ndarray, inv_idx: jnp.ndarray, patch_size: int, H:
|
|
|
241
241
|
target_shape = (B, total_patches_expected, patch_dim)
|
|
242
242
|
|
|
243
243
|
# Create indices for gathering from x (Hilbert order h) based on inv_idx (map k -> h)
|
|
244
|
-
# inv_idx contains the 'h' index for each 'k' index.
|
|
245
244
|
# Clamp invalid indices (-1) to 0; we'll mask these results later.
|
|
246
245
|
# Values must be < N (the actual number of patches in x).
|
|
247
246
|
h_indices_for_gather = jnp.maximum(inv_idx, 0) # Shape [total_patches_expected]
|
|
@@ -320,26 +319,37 @@ def visualize_hilbert_curve(H: int, W: int, patch_size: int, figsize=(12, 5)):
|
|
|
320
319
|
orig_grid = np.arange(H_P * W_P).reshape((H_P, W_P))
|
|
321
320
|
im0 = axes[0].imshow(orig_grid, cmap='viridis', aspect='auto')
|
|
322
321
|
axes[0].set_title(f"Original Grid ({H_P}x{W_P})\n(Row-Major Order)")
|
|
323
|
-
#
|
|
324
|
-
for r in range(H_P):
|
|
325
|
-
for c in range(W_P):
|
|
326
|
-
axes[0].text(c, r, f'{orig_grid[r, c]}', ha='center', va='center', color='white' if orig_grid[r,c] < (H_P*W_P)/2 else 'black', fontsize=8)
|
|
322
|
+
# Remove text labels for indices
|
|
327
323
|
axes[0].set_xticks(np.arange(W_P))
|
|
328
324
|
axes[0].set_yticks(np.arange(H_P))
|
|
329
325
|
axes[0].set_xticklabels(np.arange(W_P))
|
|
330
326
|
axes[0].set_yticklabels(np.arange(H_P))
|
|
331
327
|
plt.colorbar(im0, ax=axes[0], fraction=0.046, pad=0.04, label="Row-Major Index")
|
|
332
328
|
|
|
329
|
+
# Draw the row-major scanning line
|
|
330
|
+
if H_P * W_P > 1:
|
|
331
|
+
row_major_coords_y = []
|
|
332
|
+
row_major_coords_x = []
|
|
333
|
+
for r_idx in range(H_P * W_P):
|
|
334
|
+
r = r_idx // W_P
|
|
335
|
+
c = r_idx % W_P
|
|
336
|
+
row_major_coords_y.append(r + 0.5) # Cell center
|
|
337
|
+
row_major_coords_x.append(c + 0.5) # Cell center
|
|
338
|
+
axes[0].plot(row_major_coords_x, row_major_coords_y, color='red', linestyle='-', linewidth=1.5, alpha=0.8)
|
|
339
|
+
axes[0].plot(row_major_coords_x[0], row_major_coords_y[0], 'go', markersize=5, label='Start') # Smaller marker
|
|
340
|
+
axes[0].plot(row_major_coords_x[-1], row_major_coords_y[-1], 'mo', markersize=5, label='End') # Smaller marker
|
|
341
|
+
axes[0].legend(fontsize='x-small')
|
|
342
|
+
|
|
333
343
|
# --- Plot 2: Hilbert Curve Ordering ---
|
|
334
344
|
# Mask unmapped cells for visualization
|
|
335
345
|
masked_grid = np.ma.masked_where(grid == -1, grid)
|
|
336
346
|
im1 = axes[1].imshow(masked_grid, cmap=cmap, aspect='auto', vmin=0, vmax=max(0, len(idx)-1))
|
|
337
347
|
axes[1].set_title(f"Hilbert Curve Ordering ({len(idx)} points)")
|
|
338
348
|
# Add text labels for Hilbert indices
|
|
339
|
-
for r in range(H_P):
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
# for r in range(H_P):
|
|
350
|
+
# for c in range(W_P):
|
|
351
|
+
# if grid[r,c] != -1:
|
|
352
|
+
# axes[1].text(c, r, f'{int(grid[r, c])}', ha='center', va='center', color='black', fontsize=8)
|
|
343
353
|
axes[1].set_xticks(np.arange(W_P))
|
|
344
354
|
axes[1].set_yticks(np.arange(H_P))
|
|
345
355
|
axes[1].set_xticklabels(np.arange(W_P))
|
|
@@ -355,22 +365,17 @@ def visualize_hilbert_curve(H: int, W: int, patch_size: int, figsize=(12, 5)):
|
|
|
355
365
|
for i in range(len(idx)):
|
|
356
366
|
if i in row_col_map:
|
|
357
367
|
coords.append(row_col_map[i])
|
|
358
|
-
# Fallback (slower):
|
|
359
|
-
# row_indices, col_indices = np.where(grid == i)
|
|
360
|
-
# if len(row_indices) > 0:
|
|
361
|
-
# coords.append((row_indices[0], col_indices[0]))
|
|
362
368
|
|
|
363
369
|
if coords:
|
|
364
370
|
# Get coordinates for plotting (centers of cells)
|
|
365
|
-
y_coords = [r + 0.5 for r, c in coords]
|
|
366
|
-
x_coords = [c + 0.5 for r, c in coords]
|
|
367
|
-
axes[1].plot(x_coords, y_coords, color='
|
|
371
|
+
y_coords = [r + 0.5 for r, c in coords] # Cell center
|
|
372
|
+
x_coords = [c + 0.5 for r, c in coords] # Cell center
|
|
373
|
+
axes[1].plot(x_coords, y_coords, color='red', linestyle='-', linewidth=1.5, alpha=0.8) # Ensure Hilbert curve is red
|
|
368
374
|
# Mark start point
|
|
369
|
-
axes[1].plot(x_coords[0], y_coords[0], 'go', markersize=
|
|
375
|
+
axes[1].plot(x_coords[0], y_coords[0], 'go', markersize=5, label='Start') # Smaller marker
|
|
370
376
|
# Mark end point
|
|
371
|
-
axes[1].plot(x_coords[-1], y_coords[-1], 'mo', markersize=
|
|
372
|
-
axes[1].legend(fontsize='small')
|
|
373
|
-
|
|
377
|
+
axes[1].plot(x_coords[-1], y_coords[-1], 'mo', markersize=5, label='End') # Smaller marker
|
|
378
|
+
axes[1].legend(fontsize='x-small')
|
|
374
379
|
|
|
375
380
|
plt.tight_layout()
|
|
376
381
|
return fig
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SSM-based DiT Block and Hybrid SSM-Attention DiT for diffusion models.
|
|
3
|
+
|
|
4
|
+
Implements S5 (Simplified State Space) layers as a drop-in replacement for
|
|
5
|
+
attention in DiT blocks. The S5 layer uses diagonal state spaces with
|
|
6
|
+
JAX associative_scan for efficient parallel computation on TPUs.
|
|
7
|
+
|
|
8
|
+
References:
|
|
9
|
+
- Smith et al., "Simplified State Space Layers for Sequence Modeling", ICLR 2023
|
|
10
|
+
- Peebles & Xie, "Scalable Diffusion Models with Transformers", ICCV 2023
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import jax
|
|
14
|
+
import jax.numpy as jnp
|
|
15
|
+
from flax import linen as nn
|
|
16
|
+
from typing import Callable, Any, Optional, Tuple, Sequence, Union
|
|
17
|
+
import einops
|
|
18
|
+
from functools import partial
|
|
19
|
+
|
|
20
|
+
from .vit_common import PatchEmbedding, unpatchify, RotaryEmbedding, RoPEAttention, AdaLNParams
|
|
21
|
+
from .common import kernel_init, FourierEmbedding, TimeProjection
|
|
22
|
+
from .attention import NormalAttention
|
|
23
|
+
from flax.typing import Dtype, PrecisionLike
|
|
24
|
+
|
|
25
|
+
from .hilbert import hilbert_indices, inverse_permutation, hilbert_patchify, hilbert_unpatchify
|
|
26
|
+
from .simple_dit import DiTBlock
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# S5 SSM Layer - Diagonal State Space with Parallel Scan
|
|
31
|
+
# =============================================================================
|
|
32
|
+
|
|
33
|
+
def hippo_initializer(state_dim):
|
|
34
|
+
"""Initialize A matrix using HiPPO-LegS (Legendre State Space) framework.
|
|
35
|
+
|
|
36
|
+
For diagonal S5, we use the diagonal approximation of HiPPO.
|
|
37
|
+
Returns complex diagonal elements that capture long-range dependencies.
|
|
38
|
+
"""
|
|
39
|
+
def init(key, shape, dtype=jnp.float32):
|
|
40
|
+
# Diagonal HiPPO initialization: lambda_n = -(2n+1)/2 + i*pi*n
|
|
41
|
+
n = jnp.arange(state_dim)
|
|
42
|
+
real = -(n + 0.5)
|
|
43
|
+
imag = jnp.pi * n
|
|
44
|
+
# Normalize to unit circle for stability
|
|
45
|
+
lambda_init = real + 1j * imag
|
|
46
|
+
# Discretize with dt=1 for initialization, actual dt is learned
|
|
47
|
+
return lambda_init.astype(jnp.complex64)
|
|
48
|
+
return init
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class S5Layer(nn.Module):
|
|
52
|
+
"""S5 (Simplified State Space) layer with diagonal state matrix.
|
|
53
|
+
|
|
54
|
+
Processes sequences using state space recurrence:
|
|
55
|
+
x_k = A * x_{k-1} + B * u_k
|
|
56
|
+
y_k = Re(C * x_k) + D * u_k
|
|
57
|
+
|
|
58
|
+
Where A is diagonal (complex), enabling efficient parallel scan via
|
|
59
|
+
jax.lax.associative_scan. This is TPU-friendly as it maps to
|
|
60
|
+
prefix-sum operations on the systolic array.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
features: Output dimension (must match input for residual)
|
|
64
|
+
state_dim: Dimension of the hidden state (per feature)
|
|
65
|
+
dt_min: Minimum discretization step (log scale)
|
|
66
|
+
dt_max: Maximum discretization step (log scale)
|
|
67
|
+
dtype: Computation dtype
|
|
68
|
+
precision: JAX precision level
|
|
69
|
+
"""
|
|
70
|
+
features: int
|
|
71
|
+
state_dim: int = 64
|
|
72
|
+
dt_min: float = 0.001
|
|
73
|
+
dt_max: float = 0.1
|
|
74
|
+
dtype: Optional[Dtype] = None
|
|
75
|
+
precision: PrecisionLike = None
|
|
76
|
+
|
|
77
|
+
@nn.compact
|
|
78
|
+
def __call__(self, u):
|
|
79
|
+
"""
|
|
80
|
+
Args:
|
|
81
|
+
u: Input sequence [B, S, F]
|
|
82
|
+
Returns:
|
|
83
|
+
y: Output sequence [B, S, F]
|
|
84
|
+
"""
|
|
85
|
+
B, S, F = u.shape
|
|
86
|
+
|
|
87
|
+
# --- Learnable SSM Parameters ---
|
|
88
|
+
# A: Diagonal state matrix (complex) - initialized with HiPPO
|
|
89
|
+
# We parameterize as log of negative real part for stability
|
|
90
|
+
log_A_real = self.param(
|
|
91
|
+
'log_A_real',
|
|
92
|
+
nn.initializers.normal(stddev=0.5),
|
|
93
|
+
(self.state_dim,)
|
|
94
|
+
)
|
|
95
|
+
A_imag = self.param(
|
|
96
|
+
'A_imag',
|
|
97
|
+
nn.initializers.normal(stddev=0.5),
|
|
98
|
+
(self.state_dim,)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# B: Input-to-state projection [state_dim, F]
|
|
102
|
+
B_re = self.param(
|
|
103
|
+
'B_re',
|
|
104
|
+
nn.initializers.lecun_normal(),
|
|
105
|
+
(self.state_dim, F)
|
|
106
|
+
)
|
|
107
|
+
B_im = self.param(
|
|
108
|
+
'B_im',
|
|
109
|
+
nn.initializers.lecun_normal(),
|
|
110
|
+
(self.state_dim, F)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# C: State-to-output projection [F, state_dim]
|
|
114
|
+
C_re = self.param(
|
|
115
|
+
'C_re',
|
|
116
|
+
nn.initializers.lecun_normal(),
|
|
117
|
+
(F, self.state_dim)
|
|
118
|
+
)
|
|
119
|
+
C_im = self.param(
|
|
120
|
+
'C_im',
|
|
121
|
+
nn.initializers.lecun_normal(),
|
|
122
|
+
(F, self.state_dim)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# D: Skip connection (direct input-to-output)
|
|
126
|
+
D = self.param('D', nn.initializers.ones, (F,))
|
|
127
|
+
|
|
128
|
+
# dt: Discretization timestep (learned, per-feature)
|
|
129
|
+
log_dt = self.param(
|
|
130
|
+
'log_dt',
|
|
131
|
+
lambda key, shape: jax.random.uniform(
|
|
132
|
+
key, shape,
|
|
133
|
+
minval=jnp.log(self.dt_min),
|
|
134
|
+
maxval=jnp.log(self.dt_max)
|
|
135
|
+
),
|
|
136
|
+
(F,)
|
|
137
|
+
)
|
|
138
|
+
dt = jnp.exp(log_dt) # [F]
|
|
139
|
+
|
|
140
|
+
# --- Construct complex A and discretize ---
|
|
141
|
+
# A_diag: complex diagonal [state_dim]
|
|
142
|
+
A_real = -jnp.exp(log_A_real) # Ensure negative real part for stability
|
|
143
|
+
A_diag = A_real + 1j * A_imag # [state_dim]
|
|
144
|
+
|
|
145
|
+
# ZOH discretization: A_bar = exp(A * dt), B_bar = (A_bar - I) * A^{-1} * B
|
|
146
|
+
# For diagonal A, this simplifies element-wise
|
|
147
|
+
# dt broadcasts: [F] -> we use mean dt for state transition
|
|
148
|
+
dt_mean = jnp.mean(dt)
|
|
149
|
+
A_bar = jnp.exp(A_diag * dt_mean) # [state_dim], complex
|
|
150
|
+
|
|
151
|
+
# B as complex: [state_dim, F]
|
|
152
|
+
B_complex = B_re + 1j * B_im
|
|
153
|
+
# Discretized B: element-wise
|
|
154
|
+
B_bar = ((A_bar[:, None] - 1.0) / (A_diag[:, None] + 1e-8)) * B_complex # [state_dim, F]
|
|
155
|
+
|
|
156
|
+
# C as complex: [F, state_dim]
|
|
157
|
+
C_complex = C_re + 1j * C_im
|
|
158
|
+
|
|
159
|
+
# --- Parallel Scan (associative scan for TPU efficiency) ---
|
|
160
|
+
# For diagonal SSM: x_k = A_bar * x_{k-1} + B_bar @ u_k
|
|
161
|
+
# This can be computed via associative scan with the binary operator:
|
|
162
|
+
# (a1, b1) * (a2, b2) = (a1 * a2, a2 * b1 + b2)
|
|
163
|
+
|
|
164
|
+
# Compute per-step inputs: B_bar @ u_k for each timestep
|
|
165
|
+
# u: [B, S, F], B_bar: [state_dim, F]
|
|
166
|
+
# Bu: [B, S, state_dim] (complex)
|
|
167
|
+
u_float = u.astype(jnp.float32)
|
|
168
|
+
Bu = jnp.einsum('bsf,nf->bsn', u_float, B_bar) # [B, S, state_dim]
|
|
169
|
+
|
|
170
|
+
# A_bar is the same for all timesteps: [state_dim]
|
|
171
|
+
# Expand for scan: [B, S, state_dim]
|
|
172
|
+
A_bar_expanded = jnp.broadcast_to(A_bar[None, None, :], (B, S, self.state_dim))
|
|
173
|
+
|
|
174
|
+
# Associative scan elements: (a, b) where a=A_bar, b=Bu
|
|
175
|
+
def binary_operator(e1, e2):
|
|
176
|
+
a1, b1 = e1
|
|
177
|
+
a2, b2 = e2
|
|
178
|
+
return a1 * a2, a2 * b1 + b2
|
|
179
|
+
|
|
180
|
+
# Run parallel scan along sequence dimension (axis=1)
|
|
181
|
+
_, x_states = jax.lax.associative_scan(
|
|
182
|
+
binary_operator,
|
|
183
|
+
(A_bar_expanded, Bu),
|
|
184
|
+
axis=1
|
|
185
|
+
)
|
|
186
|
+
# x_states: [B, S, state_dim] (complex) - hidden states at each step
|
|
187
|
+
|
|
188
|
+
# --- Output computation ---
|
|
189
|
+
# y_k = Re(C @ x_k) + D * u_k
|
|
190
|
+
# C: [F, state_dim], x_states: [B, S, state_dim]
|
|
191
|
+
y_complex = jnp.einsum('fn,bsn->bsf', C_complex, x_states) # [B, S, F]
|
|
192
|
+
y = y_complex.real # Take real part: [B, S, F]
|
|
193
|
+
|
|
194
|
+
# Add skip connection
|
|
195
|
+
y = y + D[None, None, :] * u_float # [B, S, F]
|
|
196
|
+
|
|
197
|
+
# Cast back to input dtype
|
|
198
|
+
if self.dtype is not None:
|
|
199
|
+
y = y.astype(self.dtype)
|
|
200
|
+
else:
|
|
201
|
+
y = y.astype(u.dtype)
|
|
202
|
+
|
|
203
|
+
return y
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# =============================================================================
|
|
207
|
+
# Bidirectional S5 - processes sequence in both directions
|
|
208
|
+
# =============================================================================
|
|
209
|
+
|
|
210
|
+
class BidirectionalS5Layer(nn.Module):
|
|
211
|
+
"""Bidirectional S5 layer - runs forward and backward scans.
|
|
212
|
+
|
|
213
|
+
For diffusion models, spatial patches have no inherent directionality,
|
|
214
|
+
so bidirectional processing captures dependencies in both directions
|
|
215
|
+
along the serialization curve (Hilbert, raster, etc.).
|
|
216
|
+
|
|
217
|
+
Output is the sum of forward and backward scans, projected to features.
|
|
218
|
+
"""
|
|
219
|
+
features: int
|
|
220
|
+
state_dim: int = 64
|
|
221
|
+
dt_min: float = 0.001
|
|
222
|
+
dt_max: float = 0.1
|
|
223
|
+
dtype: Optional[Dtype] = None
|
|
224
|
+
precision: PrecisionLike = None
|
|
225
|
+
|
|
226
|
+
@nn.compact
|
|
227
|
+
def __call__(self, u):
|
|
228
|
+
"""
|
|
229
|
+
Args:
|
|
230
|
+
u: [B, S, F]
|
|
231
|
+
Returns:
|
|
232
|
+
y: [B, S, F]
|
|
233
|
+
"""
|
|
234
|
+
# Forward scan
|
|
235
|
+
y_fwd = S5Layer(
|
|
236
|
+
features=self.features,
|
|
237
|
+
state_dim=self.state_dim,
|
|
238
|
+
dt_min=self.dt_min,
|
|
239
|
+
dt_max=self.dt_max,
|
|
240
|
+
dtype=self.dtype,
|
|
241
|
+
precision=self.precision,
|
|
242
|
+
name="s5_forward"
|
|
243
|
+
)(u)
|
|
244
|
+
|
|
245
|
+
# Backward scan (reverse input, scan, reverse output)
|
|
246
|
+
u_rev = jnp.flip(u, axis=1)
|
|
247
|
+
y_bwd_rev = S5Layer(
|
|
248
|
+
features=self.features,
|
|
249
|
+
state_dim=self.state_dim,
|
|
250
|
+
dt_min=self.dt_min,
|
|
251
|
+
dt_max=self.dt_max,
|
|
252
|
+
dtype=self.dtype,
|
|
253
|
+
precision=self.precision,
|
|
254
|
+
name="s5_backward"
|
|
255
|
+
)(u_rev)
|
|
256
|
+
y_bwd = jnp.flip(y_bwd_rev, axis=1)
|
|
257
|
+
|
|
258
|
+
# Combine forward and backward
|
|
259
|
+
y = y_fwd + y_bwd
|
|
260
|
+
|
|
261
|
+
# Output projection to mix directions
|
|
262
|
+
y = nn.Dense(
|
|
263
|
+
features=self.features,
|
|
264
|
+
dtype=self.dtype,
|
|
265
|
+
precision=self.precision,
|
|
266
|
+
name="out_proj"
|
|
267
|
+
)(y)
|
|
268
|
+
|
|
269
|
+
return y
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# =============================================================================
|
|
273
|
+
# SSMDiTBlock - Drop-in replacement for DiTBlock
|
|
274
|
+
# =============================================================================
|
|
275
|
+
|
|
276
|
+
class SSMDiTBlock(nn.Module):
|
|
277
|
+
"""SSM-based DiT Block that replaces attention with bidirectional S5.
|
|
278
|
+
|
|
279
|
+
Maintains the EXACT same interface as DiTBlock:
|
|
280
|
+
__call__(self, x, conditioning, freqs_cis) -> x
|
|
281
|
+
|
|
282
|
+
The AdaLN modulation, gating, residual connections, and MLP path
|
|
283
|
+
are IDENTICAL to DiTBlock. Only the attention is replaced with S5.
|
|
284
|
+
|
|
285
|
+
Note: freqs_cis (RoPE frequencies) is accepted for interface compatibility
|
|
286
|
+
but not used by the SSM - positional information is implicitly captured
|
|
287
|
+
by the sequential scan along the serialization order (Hilbert/raster/etc).
|
|
288
|
+
"""
|
|
289
|
+
features: int
|
|
290
|
+
num_heads: int # Not used by SSM, kept for interface compat
|
|
291
|
+
rope_emb: RotaryEmbedding # Not used by SSM, kept for interface compat
|
|
292
|
+
state_dim: int = 64
|
|
293
|
+
mlp_ratio: int = 4
|
|
294
|
+
dropout_rate: float = 0.0
|
|
295
|
+
dtype: Optional[Dtype] = None
|
|
296
|
+
precision: PrecisionLike = None
|
|
297
|
+
use_flash_attention: bool = False # Ignored, interface compat
|
|
298
|
+
force_fp32_for_softmax: bool = True # Ignored, interface compat
|
|
299
|
+
norm_epsilon: float = 1e-5
|
|
300
|
+
use_gating: bool = True
|
|
301
|
+
bidirectional: bool = True
|
|
302
|
+
|
|
303
|
+
def setup(self):
|
|
304
|
+
hidden_features = int(self.features * self.mlp_ratio)
|
|
305
|
+
|
|
306
|
+
# AdaLN modulation - IDENTICAL to DiTBlock
|
|
307
|
+
self.ada_params_module = AdaLNParams(
|
|
308
|
+
self.features, dtype=self.dtype, precision=self.precision)
|
|
309
|
+
|
|
310
|
+
# Layer Norms - IDENTICAL to DiTBlock
|
|
311
|
+
self.norm1 = nn.LayerNorm(
|
|
312
|
+
epsilon=self.norm_epsilon, use_scale=False, use_bias=False,
|
|
313
|
+
dtype=self.dtype, name="norm1")
|
|
314
|
+
self.norm2 = nn.LayerNorm(
|
|
315
|
+
epsilon=self.norm_epsilon, use_scale=False, use_bias=False,
|
|
316
|
+
dtype=self.dtype, name="norm2")
|
|
317
|
+
|
|
318
|
+
# S5 SSM layer (replaces attention)
|
|
319
|
+
ssm_cls = BidirectionalS5Layer if self.bidirectional else S5Layer
|
|
320
|
+
self.ssm = ssm_cls(
|
|
321
|
+
features=self.features,
|
|
322
|
+
state_dim=self.state_dim,
|
|
323
|
+
dtype=self.dtype,
|
|
324
|
+
precision=self.precision,
|
|
325
|
+
name="ssm"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# MLP - IDENTICAL to DiTBlock
|
|
329
|
+
self.mlp = nn.Sequential([
|
|
330
|
+
nn.Dense(features=hidden_features, dtype=self.dtype, precision=self.precision),
|
|
331
|
+
nn.gelu,
|
|
332
|
+
nn.Dense(features=self.features, dtype=self.dtype, precision=self.precision)
|
|
333
|
+
])
|
|
334
|
+
|
|
335
|
+
@nn.compact
|
|
336
|
+
def __call__(self, x, conditioning, freqs_cis):
|
|
337
|
+
"""Exact same signature as DiTBlock.__call__"""
|
|
338
|
+
# Get scale/shift/gate parameters - IDENTICAL to DiTBlock
|
|
339
|
+
scale_mlp, shift_mlp, gate_mlp, scale_attn, shift_attn, gate_attn = jnp.split(
|
|
340
|
+
self.ada_params_module(conditioning), 6, axis=-1
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# --- SSM Path (replaces Attention Path) ---
|
|
344
|
+
residual = x
|
|
345
|
+
norm_x = self.norm1(x)
|
|
346
|
+
x_modulated = norm_x * (1 + scale_attn) + shift_attn
|
|
347
|
+
ssm_output = self.ssm(x_modulated)
|
|
348
|
+
|
|
349
|
+
if self.use_gating:
|
|
350
|
+
x = residual + gate_attn * ssm_output
|
|
351
|
+
else:
|
|
352
|
+
x = residual + ssm_output
|
|
353
|
+
|
|
354
|
+
# --- MLP Path --- IDENTICAL to DiTBlock
|
|
355
|
+
residual = x
|
|
356
|
+
norm_x_mlp = self.norm2(x)
|
|
357
|
+
x_mlp_modulated = norm_x_mlp * (1 + scale_mlp) + shift_mlp
|
|
358
|
+
mlp_output = self.mlp(x_mlp_modulated)
|
|
359
|
+
|
|
360
|
+
if self.use_gating:
|
|
361
|
+
x = residual + gate_mlp * mlp_output
|
|
362
|
+
else:
|
|
363
|
+
x = residual + mlp_output
|
|
364
|
+
|
|
365
|
+
return x
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# =============================================================================
|
|
369
|
+
# HybridSSMAttentionDiT - The proposed novel architecture
|
|
370
|
+
# =============================================================================
|
|
371
|
+
|
|
372
|
+
class HybridSSMAttentionDiT(nn.Module):
|
|
373
|
+
"""Hybrid SSM-Attention Diffusion Transformer.
|
|
374
|
+
|
|
375
|
+
Interleaves SSM blocks (for O(n) local processing along Hilbert curve)
|
|
376
|
+
with attention blocks (for O(n^2) global composition) in a configurable
|
|
377
|
+
ratio. Both block types share the same AdaLN conditioning interface.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
block_pattern: List of 'ssm' or 'attn' strings defining the block sequence.
|
|
381
|
+
Example: ['ssm','ssm','ssm','attn'] for 3:1 ratio repeated.
|
|
382
|
+
If None, uses ssm_attention_ratio to auto-generate pattern.
|
|
383
|
+
ssm_attention_ratio: Shorthand ratio like '3:1' (3 SSM per 1 attention).
|
|
384
|
+
Only used if block_pattern is None.
|
|
385
|
+
"""
|
|
386
|
+
output_channels: int = 3
|
|
387
|
+
patch_size: int = 16
|
|
388
|
+
emb_features: int = 768
|
|
389
|
+
num_layers: int = 12
|
|
390
|
+
num_heads: int = 12
|
|
391
|
+
mlp_ratio: int = 4
|
|
392
|
+
ssm_state_dim: int = 64
|
|
393
|
+
dropout_rate: float = 0.0
|
|
394
|
+
dtype: Optional[Dtype] = None
|
|
395
|
+
precision: PrecisionLike = None
|
|
396
|
+
use_flash_attention: bool = False
|
|
397
|
+
force_fp32_for_softmax: bool = True
|
|
398
|
+
norm_epsilon: float = 1e-5
|
|
399
|
+
learn_sigma: bool = False
|
|
400
|
+
use_hilbert: bool = False
|
|
401
|
+
norm_groups: int = 0
|
|
402
|
+
activation: Callable = jax.nn.swish
|
|
403
|
+
block_pattern: Optional[Sequence[str]] = None # e.g., ['ssm','ssm','ssm','attn']
|
|
404
|
+
ssm_attention_ratio: str = "3:1" # e.g., "3:1", "1:1", "all-ssm", "all-attn"
|
|
405
|
+
bidirectional_ssm: bool = True
|
|
406
|
+
|
|
407
|
+
def _build_block_pattern(self):
|
|
408
|
+
"""Generate block pattern from ratio string."""
|
|
409
|
+
if self.block_pattern is not None:
|
|
410
|
+
pattern = list(self.block_pattern)
|
|
411
|
+
elif self.ssm_attention_ratio == "all-ssm":
|
|
412
|
+
pattern = ['ssm'] * self.num_layers
|
|
413
|
+
elif self.ssm_attention_ratio == "all-attn":
|
|
414
|
+
pattern = ['attn'] * self.num_layers
|
|
415
|
+
else:
|
|
416
|
+
parts = self.ssm_attention_ratio.split(':')
|
|
417
|
+
n_ssm, n_attn = int(parts[0]), int(parts[1])
|
|
418
|
+
unit = ['ssm'] * n_ssm + ['attn'] * n_attn
|
|
419
|
+
pattern = (unit * (self.num_layers // len(unit) + 1))[:self.num_layers]
|
|
420
|
+
return pattern
|
|
421
|
+
|
|
422
|
+
def setup(self):
|
|
423
|
+
self.patch_embed = PatchEmbedding(
|
|
424
|
+
patch_size=self.patch_size,
|
|
425
|
+
embedding_dim=self.emb_features,
|
|
426
|
+
dtype=self.dtype,
|
|
427
|
+
precision=self.precision
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if self.use_hilbert:
|
|
431
|
+
self.hilbert_proj = nn.Dense(
|
|
432
|
+
features=self.emb_features,
|
|
433
|
+
dtype=self.dtype,
|
|
434
|
+
precision=self.precision,
|
|
435
|
+
name="hilbert_projection"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Time embedding
|
|
439
|
+
self.time_embed = nn.Sequential([
|
|
440
|
+
FourierEmbedding(features=self.emb_features),
|
|
441
|
+
TimeProjection(features=self.emb_features * self.mlp_ratio),
|
|
442
|
+
nn.Dense(features=self.emb_features, dtype=self.dtype, precision=self.precision)
|
|
443
|
+
])
|
|
444
|
+
|
|
445
|
+
# Text context projection
|
|
446
|
+
self.text_proj = nn.Dense(
|
|
447
|
+
features=self.emb_features, dtype=self.dtype,
|
|
448
|
+
precision=self.precision, name="text_context_proj")
|
|
449
|
+
|
|
450
|
+
# RoPE (used by attention blocks, passed through SSM blocks)
|
|
451
|
+
self.rope = RotaryEmbedding(
|
|
452
|
+
dim=self.emb_features // self.num_heads,
|
|
453
|
+
max_seq_len=4096, dtype=self.dtype)
|
|
454
|
+
|
|
455
|
+
# Build hybrid block sequence
|
|
456
|
+
pattern = self._build_block_pattern()
|
|
457
|
+
blocks = []
|
|
458
|
+
for i, block_type in enumerate(pattern):
|
|
459
|
+
if block_type == 'ssm':
|
|
460
|
+
blocks.append(SSMDiTBlock(
|
|
461
|
+
features=self.emb_features,
|
|
462
|
+
num_heads=self.num_heads,
|
|
463
|
+
rope_emb=self.rope,
|
|
464
|
+
state_dim=self.ssm_state_dim,
|
|
465
|
+
mlp_ratio=self.mlp_ratio,
|
|
466
|
+
dropout_rate=self.dropout_rate,
|
|
467
|
+
dtype=self.dtype,
|
|
468
|
+
precision=self.precision,
|
|
469
|
+
norm_epsilon=self.norm_epsilon,
|
|
470
|
+
bidirectional=self.bidirectional_ssm,
|
|
471
|
+
name=f"ssm_block_{i}"
|
|
472
|
+
))
|
|
473
|
+
else: # 'attn'
|
|
474
|
+
blocks.append(DiTBlock(
|
|
475
|
+
features=self.emb_features,
|
|
476
|
+
num_heads=self.num_heads,
|
|
477
|
+
rope_emb=self.rope,
|
|
478
|
+
mlp_ratio=self.mlp_ratio,
|
|
479
|
+
dropout_rate=self.dropout_rate,
|
|
480
|
+
dtype=self.dtype,
|
|
481
|
+
precision=self.precision,
|
|
482
|
+
use_flash_attention=self.use_flash_attention,
|
|
483
|
+
force_fp32_for_softmax=self.force_fp32_for_softmax,
|
|
484
|
+
norm_epsilon=self.norm_epsilon,
|
|
485
|
+
name=f"dit_block_{i}"
|
|
486
|
+
))
|
|
487
|
+
self.blocks = blocks
|
|
488
|
+
|
|
489
|
+
# Final layer
|
|
490
|
+
self.final_norm = nn.LayerNorm(
|
|
491
|
+
epsilon=self.norm_epsilon, dtype=self.dtype, name="final_norm")
|
|
492
|
+
|
|
493
|
+
output_dim = self.patch_size * self.patch_size * self.output_channels
|
|
494
|
+
if self.learn_sigma:
|
|
495
|
+
output_dim *= 2
|
|
496
|
+
|
|
497
|
+
self.final_proj = nn.Dense(
|
|
498
|
+
features=output_dim,
|
|
499
|
+
dtype=self.dtype,
|
|
500
|
+
precision=self.precision,
|
|
501
|
+
kernel_init=nn.initializers.zeros,
|
|
502
|
+
name="final_proj"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
@nn.compact
|
|
506
|
+
def __call__(self, x, temb, textcontext=None):
|
|
507
|
+
"""Exact same signature as SimpleDiT.__call__"""
|
|
508
|
+
B, H, W, C = x.shape
|
|
509
|
+
assert H % self.patch_size == 0 and W % self.patch_size == 0
|
|
510
|
+
|
|
511
|
+
H_P = H // self.patch_size
|
|
512
|
+
W_P = W // self.patch_size
|
|
513
|
+
|
|
514
|
+
# 1. Patch Embedding (identical to SimpleDiT)
|
|
515
|
+
if self.use_hilbert:
|
|
516
|
+
patches_raw, hilbert_inv_idx = hilbert_patchify(x, self.patch_size)
|
|
517
|
+
patches = self.hilbert_proj(patches_raw)
|
|
518
|
+
else:
|
|
519
|
+
patches = self.patch_embed(x)
|
|
520
|
+
hilbert_inv_idx = None
|
|
521
|
+
|
|
522
|
+
num_patches = patches.shape[1]
|
|
523
|
+
x_seq = patches
|
|
524
|
+
|
|
525
|
+
# 2. Conditioning (identical to SimpleDiT)
|
|
526
|
+
t_emb = self.time_embed(temb)
|
|
527
|
+
cond_emb = t_emb
|
|
528
|
+
if textcontext is not None:
|
|
529
|
+
text_emb = self.text_proj(textcontext)
|
|
530
|
+
text_emb_pooled = jnp.mean(text_emb, axis=1)
|
|
531
|
+
cond_emb = cond_emb + text_emb_pooled
|
|
532
|
+
|
|
533
|
+
# 3. RoPE frequencies
|
|
534
|
+
freqs_cos, freqs_sin = self.rope(seq_len=num_patches)
|
|
535
|
+
|
|
536
|
+
# 4. Hybrid blocks (SSM and attention interleaved)
|
|
537
|
+
for block in self.blocks:
|
|
538
|
+
x_seq = block(x_seq, conditioning=cond_emb, freqs_cis=(freqs_cos, freqs_sin))
|
|
539
|
+
|
|
540
|
+
# 5. Final output (identical to SimpleDiT)
|
|
541
|
+
x_out = self.final_norm(x_seq)
|
|
542
|
+
x_out = self.final_proj(x_out)
|
|
543
|
+
|
|
544
|
+
# 6. Unpatchify
|
|
545
|
+
if self.use_hilbert:
|
|
546
|
+
if self.learn_sigma:
|
|
547
|
+
x_mean, x_logvar = jnp.split(x_out, 2, axis=-1)
|
|
548
|
+
x_image = hilbert_unpatchify(x_mean, hilbert_inv_idx, self.patch_size, H, W, self.output_channels)
|
|
549
|
+
return x_image
|
|
550
|
+
else:
|
|
551
|
+
x_image = hilbert_unpatchify(x_out, hilbert_inv_idx, self.patch_size, H, W, self.output_channels)
|
|
552
|
+
return x_image
|
|
553
|
+
else:
|
|
554
|
+
if self.learn_sigma:
|
|
555
|
+
x_mean, x_logvar = jnp.split(x_out, 2, axis=-1)
|
|
556
|
+
x = unpatchify(x_mean, channels=self.output_channels)
|
|
557
|
+
return x
|
|
558
|
+
else:
|
|
559
|
+
x = unpatchify(x_out, channels=self.output_channels)
|
|
560
|
+
return x
|
|
@@ -427,7 +427,7 @@ class GeneralDiffusionTrainer(DiffusionTrainer):
|
|
|
427
427
|
process_index = jax.process_index()
|
|
428
428
|
generate_samples = val_step_fn
|
|
429
429
|
|
|
430
|
-
val_ds = iter(val_ds) if val_ds else None
|
|
430
|
+
val_ds = iter(val_ds()) if val_ds else None
|
|
431
431
|
print(f"Validation loop started for process index {process_index} with {global_device_count} devices.")
|
|
432
432
|
# Evaluation step
|
|
433
433
|
try:
|
|
@@ -475,10 +475,11 @@ class GeneralDiffusionTrainer(DiffusionTrainer):
|
|
|
475
475
|
metrics = {k: np.mean(v) for k, v in metrics.items()}
|
|
476
476
|
# Update the best validation metrics
|
|
477
477
|
for key, value in metrics.items():
|
|
478
|
-
|
|
479
|
-
|
|
478
|
+
final_key = f"val/{key}"
|
|
479
|
+
if final_key not in self.best_val_metrics:
|
|
480
|
+
self.best_val_metrics[final_key] = value
|
|
480
481
|
else:
|
|
481
|
-
self.best_val_metrics[
|
|
482
|
+
self.best_val_metrics[final_key] = min(self.best_val_metrics[final_key], value)
|
|
482
483
|
# Log the best validation metrics
|
|
483
484
|
if getattr(self, 'wandb', None) is not None and self.wandb:
|
|
484
485
|
# Log the metrics
|
|
@@ -488,6 +489,13 @@ class GeneralDiffusionTrainer(DiffusionTrainer):
|
|
|
488
489
|
self.wandb.log({
|
|
489
490
|
f"val/{key}": value,
|
|
490
491
|
}, step=current_step)
|
|
492
|
+
# Log the best validation metrics
|
|
493
|
+
for key, value in self.best_val_metrics.items():
|
|
494
|
+
if isinstance(value, jnp.ndarray):
|
|
495
|
+
value = np.array(value)
|
|
496
|
+
self.wandb.log({
|
|
497
|
+
f"best_{key}": value,
|
|
498
|
+
}, step=current_step)
|
|
491
499
|
print(f"Validation metrics for process index {process_index}: {metrics}")
|
|
492
500
|
|
|
493
501
|
# Close validation dataset iterator
|
|
@@ -622,10 +630,10 @@ class GeneralDiffusionTrainer(DiffusionTrainer):
|
|
|
622
630
|
if not runs:
|
|
623
631
|
raise ValueError("No runs found in wandb.")
|
|
624
632
|
print(f"Getting best runs from wandb {self.wandb.id}...")
|
|
625
|
-
runs = sorted(runs, key=lambda x: x.summary.get(metric, float('inf')))
|
|
633
|
+
runs = sorted(runs, key=lambda x: x.summary.get(f"best_{metric}", float('inf')))
|
|
626
634
|
best_runs = runs[:top_k]
|
|
627
|
-
lower_bound = best_runs[-1].summary.get(metric, float('inf'))
|
|
628
|
-
upper_bound = best_runs[0].summary.get(metric, float('inf'))
|
|
635
|
+
lower_bound = best_runs[-1].summary.get(f"best_{metric}", float('inf'))
|
|
636
|
+
upper_bound = best_runs[0].summary.get(f"best_{metric}", float('inf'))
|
|
629
637
|
print(f"Best runs from wandb {self.wandb.id}:")
|
|
630
638
|
for run in best_runs:
|
|
631
639
|
print(f"\t\tRun ID: {run.id}, Metric: {run.summary.get(metric, float('inf'))}")
|
|
@@ -649,19 +657,21 @@ class GeneralDiffusionTrainer(DiffusionTrainer):
|
|
|
649
657
|
best_runs, bounds = self.__get_best_general_runs__(metric=metric, top_k=top_k)
|
|
650
658
|
|
|
651
659
|
# Determine if lower or higher values are better (for loss, lower is better)
|
|
652
|
-
is_lower_better =
|
|
660
|
+
is_lower_better = True
|
|
653
661
|
|
|
654
662
|
# Check if current run is one of the best
|
|
655
663
|
if metric == "train/best_loss":
|
|
656
664
|
current_run_metric = self.best_loss
|
|
657
665
|
elif metric in self.best_val_metrics:
|
|
666
|
+
print(f"Fetching best validation metric {metric} from local")
|
|
658
667
|
current_run_metric = self.best_val_metrics[metric]
|
|
659
668
|
else:
|
|
660
669
|
current_run_metric = self.wandb.summary.get(metric, float('inf') if is_lower_better else float('-inf'))
|
|
661
|
-
|
|
670
|
+
|
|
671
|
+
print(f"Current run {self.wandb.id} metric: {current_run_metric}, Best bounds: {bounds}")
|
|
662
672
|
# Check based on bounds
|
|
663
673
|
if (is_lower_better and current_run_metric < bounds[1]) or (not is_lower_better and current_run_metric > bounds[0]):
|
|
664
|
-
print(f"Current run {self.wandb.id} meets performance criteria.")
|
|
674
|
+
print(f"Current run {self.wandb.id} meets performance criteria. Current metric: {current_run_metric}, Best bounds: {bounds}")
|
|
665
675
|
is_best = (is_lower_better and current_run_metric < bounds[0]) or (not is_lower_better and current_run_metric > bounds[1])
|
|
666
676
|
return True, is_best
|
|
667
677
|
|
|
@@ -600,7 +600,7 @@ class SimpleTrainer:
|
|
|
600
600
|
|
|
601
601
|
def fit(self, data, train_steps_per_epoch, epochs, train_step_args={}, val_steps_per_epoch=5, validation_step_args={}):
|
|
602
602
|
train_ds = iter(data['train']())
|
|
603
|
-
val_ds = data.get('val', data.get('test', None))
|
|
603
|
+
val_ds = data.get('val', data.get('test', None))
|
|
604
604
|
train_step = self._define_train_step(**train_step_args)
|
|
605
605
|
val_step = self._define_validation_step(**validation_step_args)
|
|
606
606
|
train_state = self.state
|
|
@@ -642,6 +642,19 @@ class SimpleTrainer:
|
|
|
642
642
|
self.rngstate = rng_state
|
|
643
643
|
total_time = end_time - start_time
|
|
644
644
|
avg_time_per_step = total_time / train_steps_per_epoch
|
|
645
|
+
|
|
646
|
+
if val_steps_per_epoch > 0:
|
|
647
|
+
print(f"Validation started for process index {process_index}")
|
|
648
|
+
# Validation step
|
|
649
|
+
self.validation_loop(
|
|
650
|
+
train_state,
|
|
651
|
+
val_step,
|
|
652
|
+
val_ds,
|
|
653
|
+
val_steps_per_epoch,
|
|
654
|
+
current_step,
|
|
655
|
+
)
|
|
656
|
+
print(colored(f"Validation done on process index {process_index}", PROCESS_COLOR_MAP[process_index]))
|
|
657
|
+
|
|
645
658
|
avg_loss = epoch_loss / train_steps_per_epoch
|
|
646
659
|
if avg_loss < self.best_loss:
|
|
647
660
|
self.best_loss = avg_loss
|
|
@@ -659,17 +672,6 @@ class SimpleTrainer:
|
|
|
659
672
|
}, step=current_step)
|
|
660
673
|
print(colored(f"\n\tEpoch {current_epoch} completed. Avg Loss: {avg_loss}, Time: {total_time:.2f}s, Best Loss: {self.best_loss}", 'green'))
|
|
661
674
|
|
|
662
|
-
if val_steps_per_epoch > 0:
|
|
663
|
-
print(f"Validation started for process index {process_index}")
|
|
664
|
-
# Validation step
|
|
665
|
-
self.validation_loop(
|
|
666
|
-
train_state,
|
|
667
|
-
val_step,
|
|
668
|
-
val_ds,
|
|
669
|
-
val_steps_per_epoch,
|
|
670
|
-
current_step,
|
|
671
|
-
)
|
|
672
|
-
print(colored(f"Validation done on process index {process_index}", PROCESS_COLOR_MAP[process_index]))
|
|
673
675
|
|
|
674
676
|
self.save(epochs)#
|
|
675
677
|
return self.state
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flaxdiff
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
4
4
|
Summary: A versatile and easy to understand Diffusion library
|
|
5
5
|
Author-email: Ashish Kumar Singh <ashishkmr472@gmail.com>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: flax>=0.8.4
|
|
9
9
|
Requires-Dist: jax>=0.4.28
|
|
@@ -22,8 +22,6 @@ Requires-Dist: python-dotenv
|
|
|
22
22
|
|
|
23
23
|
# 
|
|
24
24
|
|
|
25
|
-
**This project is being used for the UMD Course project MSML 605: MLOps**
|
|
26
|
-
|
|
27
25
|
**This project is partially supported by [Google TPU Research Cloud](https://sites.research.google/trc/about/). I would like to thank the Google Cloud TPU team for providing me with the resources to train the bigger text-conditional models in multi-host distributed settings.**
|
|
28
26
|
|
|
29
27
|
## A Versatile and simple Diffusion Library
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flaxdiff"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.11"
|
|
8
8
|
description = "A versatile and easy to understand Diffusion library"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -26,7 +26,7 @@ dependencies = [
|
|
|
26
26
|
"rich",
|
|
27
27
|
"python-dotenv",
|
|
28
28
|
]
|
|
29
|
-
license = "MIT"
|
|
29
|
+
license = {text = "MIT"}
|
|
30
30
|
|
|
31
31
|
[tool.setuptools.packages.find]
|
|
32
32
|
include = ["flaxdiff*"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|