flaxdiff 0.2.10__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.
Files changed (77) hide show
  1. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/PKG-INFO +2 -4
  2. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/README.md +0 -2
  3. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/hilbert.py +25 -20
  4. flaxdiff-0.2.11/flaxdiff/models/ssm_dit.py +560 -0
  5. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff.egg-info/PKG-INFO +2 -4
  6. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff.egg-info/SOURCES.txt +1 -0
  7. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/pyproject.toml +2 -2
  8. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/__init__.py +0 -0
  9. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/__init__.py +0 -0
  10. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/benchmark_decord.py +0 -0
  11. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/dataloaders.py +0 -0
  12. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/dataset_map.py +0 -0
  13. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/online_loader.py +0 -0
  14. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/audio_utils.py +0 -0
  15. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/av_example.py +0 -0
  16. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/av_utils.py +0 -0
  17. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/base.py +0 -0
  18. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/images.py +0 -0
  19. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/utils.py +0 -0
  20. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/videos.py +0 -0
  21. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/data/sources/voxceleb2.py +0 -0
  22. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/inference/__init__.py +0 -0
  23. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/inference/pipeline.py +0 -0
  24. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/inference/utils.py +0 -0
  25. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/inputs/__init__.py +0 -0
  26. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/inputs/encoders.py +0 -0
  27. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/__init__.py +0 -0
  28. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/common.py +0 -0
  29. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/images.py +0 -0
  30. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/inception.py +0 -0
  31. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/psnr.py +0 -0
  32. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/ssim.py +0 -0
  33. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/metrics/utils.py +0 -0
  34. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/__init__.py +0 -0
  35. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/attention.py +0 -0
  36. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/__init__.py +0 -0
  37. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/autoencoder.py +0 -0
  38. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/diffusers.py +0 -0
  39. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/autoencoder/simple_autoenc.py +0 -0
  40. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/common.py +0 -0
  41. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/favor_fastattn.py +0 -0
  42. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/general.py +0 -0
  43. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/simple_dit.py +0 -0
  44. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/simple_mmdit.py +0 -0
  45. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/simple_unet.py +0 -0
  46. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/simple_vit.py +0 -0
  47. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/unet_3d.py +0 -0
  48. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/unet_3d_blocks.py +0 -0
  49. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/models/vit_common.py +0 -0
  50. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/predictors/__init__.py +0 -0
  51. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/__init__.py +0 -0
  52. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/common.py +0 -0
  53. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/ddim.py +0 -0
  54. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/ddpm.py +0 -0
  55. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/euler.py +0 -0
  56. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/heun_sampler.py +0 -0
  57. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/multistep_dpm.py +0 -0
  58. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/samplers/rk4_sampler.py +0 -0
  59. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/__init__.py +0 -0
  60. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/common.py +0 -0
  61. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/continuous.py +0 -0
  62. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/cosine.py +0 -0
  63. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/discrete.py +0 -0
  64. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/exp.py +0 -0
  65. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/karras.py +0 -0
  66. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/linear.py +0 -0
  67. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/schedulers/sqrt.py +0 -0
  68. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/trainer/__init__.py +0 -0
  69. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/trainer/autoencoder_trainer.py +0 -0
  70. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/trainer/diffusion_trainer.py +0 -0
  71. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/trainer/general_diffusion_trainer.py +0 -0
  72. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/trainer/simple_trainer.py +0 -0
  73. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff/utils.py +0 -0
  74. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff.egg-info/dependency_links.txt +0 -0
  75. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff.egg-info/requires.txt +0 -0
  76. {flaxdiff-0.2.10 → flaxdiff-0.2.11}/flaxdiff.egg-info/top_level.txt +0 -0
  77. {flaxdiff-0.2.10 → 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.10
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-Expression: MIT
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
  # ![](images/logo.jpeg "FlaxDiff")
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
  # ![](images/logo.jpeg "FlaxDiff")
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
@@ -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
- # Add text labels for indices
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
- for c in range(W_P):
341
- if grid[r,c] != -1:
342
- axes[1].text(c, r, f'{int(grid[r, c])}', ha='center', va='center', color='black', fontsize=8)
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='black', linestyle='-', linewidth=1.5, alpha=0.8)
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=8, label='Start (Idx 0)') # Green circle
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=8, label=f'End (Idx {len(idx)-1})') # Magenta circle
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
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flaxdiff
3
- Version: 0.2.10
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-Expression: MIT
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
  # ![](images/logo.jpeg "FlaxDiff")
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
@@ -42,6 +42,7 @@ flaxdiff/models/simple_dit.py
42
42
  flaxdiff/models/simple_mmdit.py
43
43
  flaxdiff/models/simple_unet.py
44
44
  flaxdiff/models/simple_vit.py
45
+ flaxdiff/models/ssm_dit.py
45
46
  flaxdiff/models/unet_3d.py
46
47
  flaxdiff/models/unet_3d_blocks.py
47
48
  flaxdiff/models/vit_common.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flaxdiff"
7
- version = "0.2.10"
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