Dylan Deshler commited on
Commit
95e3962
·
1 Parent(s): 9dd435f

autoregressive

Browse files
Files changed (2) hide show
  1. backend.py +52 -15
  2. real_backend.py +87 -54
backend.py CHANGED
@@ -77,6 +77,16 @@ N_CHUNKS = 24 # fixed sequence length (measures) -- matches training
77
  # positions the model never trained on. Continuation therefore SLIDES a fixed
78
  # 24-measure window instead of growing it.
79
  CONTINUE_MEASURES = 8
 
 
 
 
 
 
 
 
 
 
80
  # Style embedding width. The DiT is now conditioned on CLAP embeddings
81
  # (clap.CLAP proj_dim=512), NOT the old 128-d contrast.py vectors. Both stage
82
  # checkpoints carry style_dim=512 in their model_args; keep this in sync.
@@ -131,26 +141,53 @@ def shift_window(c: "Conditioning", n_new: int) -> "Conditioning":
131
  """Slide a conditioning window forward by `n_new` measures.
132
 
133
  The first T-n_new slots become the OLD window's tail -- those are the
134
- measures the model will be shown as fixed context -- and the n_new slots
135
- exposed at the end inherit the old tail's conditioning, so a continuation
136
- carries the character of the bars it is following instead of jumping. The
137
- per-measure `active` flags travel with their signals, so a section you had
138
- running free keeps running free.
139
-
140
- That inheritance is a starting point, not a verdict: the new measures are
141
- ordinary editable slots, so shape them and hit Regenerate if the
142
- continuation should go somewhere the previous eight bars didn't."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  T = c.T
144
  n_new = max(1, min(int(n_new), T - 1))
145
  n_ctx = T - n_new
 
 
146
  new = c.copy()
147
  for s in Conditioning._NPZ_SIGNALS:
148
  arr, old = getattr(new, s), getattr(c, s)
149
  arr[:n_ctx] = old[n_new:]
150
- arr[n_ctx:] = old[n_ctx:]
 
 
 
151
  for s in ALL_SIGNALS:
152
- new.active[s][:n_ctx] = c.active[s][n_new:]
153
- new.active[s][n_ctx:] = c.active[s][n_ctx:]
 
154
  return new
155
 
156
 
@@ -294,7 +331,7 @@ class Track:
294
  # (guidance 3.0 matches training's `{i}_cfg.wav`). MockBackend ignores it.
295
  guidance: float = 3.0
296
  # Cached model latents (B=1, T, N, C) from the last render. Used by
297
- # RealBackend for RePaint-style region inpainting; ignored by MockBackend.
298
  latents: Optional[np.ndarray] = None
299
 
300
  # ---- continuation ------------------------------------------------------
@@ -778,8 +815,8 @@ class MockBackend(ModelBackend):
778
 
779
  def regenerate_region(self, track, start, end, seed, n_steps=100):
780
  """Mock inpaint: keep audio outside [start,end), re-render inside with a
781
- new seed. The real backend resamples only those latents (RePaint-style)
782
- and re-injects known latents each Euler step -- see RealBackend notes."""
783
  tr = track.copy()
784
  s, e = self._range(start, end, tr.cond.T)
785
  full, bounds = self._synthesize(tr.cond, seed=seed) # fresh noise render
 
77
  # positions the model never trained on. Continuation therefore SLIDES a fixed
78
  # 24-measure window instead of growing it.
79
  CONTINUE_MEASURES = 8
80
+
81
+ # Noise level the already-written measures are held at while the new ones are
82
+ # sampled. 0.0 == hand the past over perfectly clean, which is what makes the
83
+ # re-decoded context bit-match the audio already in the mix. Diffusion Forcing's
84
+ # stabilization result is that a SMALL nonzero level makes long autoregressive
85
+ # rollouts more robust (the model stops trusting its own artifacts), so if
86
+ # repeated Continues harden into a loop or drift, try 0.02-0.1. See
87
+ # real_backend.fm_df_sample.
88
+ CONTINUE_CTX_NOISE = 0.0
89
+
90
  # Style embedding width. The DiT is now conditioned on CLAP embeddings
91
  # (clap.CLAP proj_dim=512), NOT the old 128-d contrast.py vectors. Both stage
92
  # checkpoints carry style_dim=512 in their model_args; keep this in sync.
 
141
  """Slide a conditioning window forward by `n_new` measures.
142
 
143
  The first T-n_new slots become the OLD window's tail -- those are the
144
+ measures handed to the model as fixed context, so their conditioning has to
145
+ travel with their latents or the model is told one thing and shown another.
146
+ Values and per-measure `active` flags both shift.
147
+
148
+ The n_new slots exposed at the end are the ones being WRITTEN, and what goes
149
+ in them is a real decision:
150
+
151
+ style, bpm -- carried forward from the last context measure, active flag
152
+ included. These are properties of the piece, not
153
+ descriptions of a particular bar; a continuation should
154
+ stay in the same style and the same tempo unless asked
155
+ otherwise, and bpm additionally has to hold a real number
156
+ because it sets the decoded measure duration.
157
+
158
+ chroma, rms, density, zcr, flatness -- turned OFF (free). These describe
159
+ what a bar actually contains, and nobody knows that for a
160
+ bar that has not been written yet. Copying the previous
161
+ eight bars' values forward would assert the new bars have
162
+ the same chords, the same energy and the same texture as
163
+ the ones they follow, which is both a claim we cannot make
164
+ and a direct instruction to repeat. Freeing them swaps in
165
+ the null token and clears the stage-2 presence channel,
166
+ which is in-distribution (training dropped signals per
167
+ token, p_ind_uncond=0.5 at stage 2) and lets the frozen
168
+ context latents decide the continuation instead.
169
+
170
+ The freed values still get back-filled from the render by
171
+ `_measure_render`, so after a Continue the curves show what the model
172
+ actually played and are immediately editable -- shape them and hit
173
+ Regenerate to push the new bars somewhere else."""
174
  T = c.T
175
  n_new = max(1, min(int(n_new), T - 1))
176
  n_ctx = T - n_new
177
+ last = n_ctx - 1 # last context measure, post-shift
178
+ carry = ("style", "bpm")
179
  new = c.copy()
180
  for s in Conditioning._NPZ_SIGNALS:
181
  arr, old = getattr(new, s), getattr(c, s)
182
  arr[:n_ctx] = old[n_new:]
183
+ # seed every new slot from the last context measure. For style/bpm that
184
+ # IS the conditioning; for the freed signals it is just a placeholder so
185
+ # the arrays hold something plausible until the render overwrites them.
186
+ arr[n_ctx:] = arr[last]
187
  for s in ALL_SIGNALS:
188
+ a = new.active[s]
189
+ a[:n_ctx] = c.active[s][n_new:]
190
+ a[n_ctx:] = a[last] if s in carry else False
191
  return new
192
 
193
 
 
331
  # (guidance 3.0 matches training's `{i}_cfg.wav`). MockBackend ignores it.
332
  guidance: float = 3.0
333
  # Cached model latents (B=1, T, N, C) from the last render. Used by
334
+ # RealBackend to freeze measures during region regen / continuation.
335
  latents: Optional[np.ndarray] = None
336
 
337
  # ---- continuation ------------------------------------------------------
 
815
 
816
  def regenerate_region(self, track, start, end, seed, n_steps=100):
817
  """Mock inpaint: keep audio outside [start,end), re-render inside with a
818
+ new seed. The real backend resamples only those latents, holding the
819
+ kept measures clean at noise level ~0 -- see RealBackend notes."""
820
  tr = track.copy()
821
  s, e = self._range(start, end, tr.cond.T)
822
  full, bounds = self._synthesize(tr.cond, seed=seed) # fresh noise render
real_backend.py CHANGED
@@ -90,7 +90,8 @@ import numpy as np
90
 
91
  from backend import (
92
  ModelBackend, Track, Conditioning,
93
- ALL_SIGNALS, CONTINUE_MEASURES, EDITABLE_SIGNALS, N_CHUNKS, STYLE_DIM,
 
94
  TARGET_SIG, SR,
95
  bpm_curve, eval_shape, shift_window,
96
  )
@@ -127,13 +128,13 @@ def _crossfade_segments(segment_a, segment_b, sample_rate, crossfade_ms=15):
127
 
128
 
129
  # =============================================================================
130
- # Inpainting sampler (RePaint-style), standalone so diffusion_forcing.py is
131
- # untouched. Works with the same FM diffusion + get_prediction/CFG machinery.
132
  # =============================================================================
133
- def fm_inpaint_sample(
134
  diffusion, # FM instance (net wrapper's .diffusion)
135
  net, # the raw DiT (wrapper.net)
136
- known_x0, # (B, T, N, C) clean latents to preserve outside the mask
137
  keep_mask, # (B, T, 1, 1) bool: True where latents are FROZEN (kept)
138
  n_steps,
139
  net_kwargs=None,
@@ -144,15 +145,45 @@ def fm_inpaint_sample(
144
  rescale_phi=0.0,
145
  cfg_mode="independent",
146
  t_dist="uniform",
147
- n_resample=1, # RePaint resampling steps per timestep (1 == plain)
148
  ):
149
- """RePaint-style masked FM sampling.
150
-
151
- At every Euler step the KNOWN region is overwritten with the forward-noised
152
- ground-truth latents at the current noise level, so only the masked region
153
- is actually generated while staying coherent with the fixed context. Setting
154
- n_resample > 1 does RePaint's harmonization (renoise/redenoise) which helps
155
- the boundary blend.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  Returns x0-space latents (B, T, N, C)."""
158
  import torch
@@ -170,42 +201,41 @@ def fm_inpaint_sample(
170
  t_steps[0] = 1.0
171
  t_steps[-1] = 0.0
172
 
173
- keep = keep_mask.to(dev)
 
 
174
 
175
- def _noised_known(t_scalar):
176
- # FM forward process: x_t = alpha(t)*x0 + sigma(t)*noise (per add_noise)
177
- tt = t_scalar.view(1, 1, 1, 1).expand(B, T, N, 1)
178
- eps = torch.randn_like(known_x0)
179
- return diffusion.alpha(tt) * known_x0 + diffusion.sigma(tt) * eps
 
 
 
 
180
 
181
  with torch.no_grad():
182
- # clamp the known region at the initial (fully-noised) level too
183
- x_t = torch.where(keep, _noised_known(t_steps[0]), x_t)
184
  for i in range(n_steps):
185
  t_cur, t_next = t_steps[i], t_steps[i + 1]
186
- for r in range(n_resample):
187
- t = t_cur.repeat(x_t.shape[0], x_t.shape[1])
188
- neg_v = diffusion.get_prediction(
189
- net, x_t, t,
190
- net_kwargs=net_kwargs,
191
- uncond_net_kwargs=uncond_net_kwargs,
192
- guidance=guidance,
193
- memory_efficient=memory_efficient,
194
- rescale_phi=rescale_phi,
195
- cfg_mode=cfg_mode,
196
- )
197
- x_next = x_t + neg_v * (t_next - t_cur)
198
- # force the known region to the correctly-noised GT at t_next
199
- x_next = torch.where(keep, _noised_known(t_next), x_next)
200
-
201
- if r < n_resample - 1 and t_next > 0:
202
- # RePaint harmonize: jump back up to t_cur and redo
203
- step_up = (diffusion.sigma(t_cur) / diffusion.sigma(t_next)).clamp(max=5.0)
204
- x_t = x_next + torch.randn_like(x_next) * (
205
- diffusion.sigma(t_cur) - diffusion.sigma(t_next))
206
- x_t = torch.where(keep, _noised_known(t_cur), x_t)
207
- else:
208
- x_t = x_next
209
  return x_t
210
 
211
 
@@ -920,7 +950,7 @@ class RealBackend(ModelBackend):
920
  return (out / (np.linalg.norm(out) + 1e-9)).astype(np.float32)
921
 
922
  # =====================================================================
923
- # region regenerate == RePaint inpainting over measure rows [start, end)
924
  # =====================================================================
925
  def regenerate_region(self, track, start, end, seed, n_steps=SAMPLE_STEPS):
926
  import torch
@@ -945,11 +975,12 @@ class RealBackend(ModelBackend):
945
  self._sampling_args(c, getattr(tr, "guidance", None))
946
 
947
  with self._ctx():
948
- y = fm_inpaint_sample(
949
  self.diffusion, self.net, known_x0, keep, n_steps,
950
  net_kwargs=cfg_net_kwargs, uncond_net_kwargs=uncond_net_kwargs,
951
  guidance=guidances, noise=noise, memory_efficient=False,
952
- cfg_mode=cfg_mode, t_dist=self.t_dist, n_resample=1)
 
953
 
954
  bpm_row = torch.from_numpy(
955
  np.asarray(c.bpm, dtype=np.float32))[None].to(self.device)
@@ -978,11 +1009,12 @@ class RealBackend(ModelBackend):
978
  FROZEN latents sampled
979
 
980
  The 24-n_new context measures keep their exact latents from the previous
981
- render and are held fixed through every Euler step -- the same
982
- fm_inpaint_sample machinery region-regen uses, just with the free region
983
- pinned to the end of the window. So the new bars are conditioned on the
984
- actual audio that precedes them, not merely on a similar-looking
985
- conditioning curve.
 
986
 
987
  Only the newly sampled tail is appended to the song; the context
988
  measures are already in the mix and get re-decoded solely because the
@@ -1020,11 +1052,12 @@ class RealBackend(ModelBackend):
1020
  self._sampling_args(c, getattr(tr, "guidance", None))
1021
 
1022
  with self._ctx():
1023
- y = fm_inpaint_sample(
1024
  self.diffusion, self.net, known_x0, keep, n_steps,
1025
  net_kwargs=cfg_net_kwargs, uncond_net_kwargs=uncond_net_kwargs,
1026
  guidance=guidances, noise=noise, memory_efficient=False,
1027
- cfg_mode=cfg_mode, t_dist=self.t_dist, n_resample=1)
 
1028
 
1029
  bpm_row = torch.from_numpy(
1030
  np.asarray(c.bpm, dtype=np.float32))[None].to(self.device)
 
90
 
91
  from backend import (
92
  ModelBackend, Track, Conditioning,
93
+ ALL_SIGNALS, CONTINUE_CTX_NOISE, CONTINUE_MEASURES, EDITABLE_SIGNALS,
94
+ N_CHUNKS, STYLE_DIM,
95
  TARGET_SIG, SR,
96
  bpm_curve, eval_shape, shift_window,
97
  )
 
128
 
129
 
130
  # =============================================================================
131
+ # Masked sampler, DIFFUSION-FORCING style. Standalone so diffusion_forcing.py
132
+ # stays untouched; uses the same FM diffusion + get_prediction/CFG machinery.
133
  # =============================================================================
134
+ def fm_df_sample(
135
  diffusion, # FM instance (net wrapper's .diffusion)
136
  net, # the raw DiT (wrapper.net)
137
+ known_x0, # (B, T, N, C) clean latents to preserve where kept
138
  keep_mask, # (B, T, 1, 1) bool: True where latents are FROZEN (kept)
139
  n_steps,
140
  net_kwargs=None,
 
145
  rescale_phi=0.0,
146
  cfg_mode="independent",
147
  t_dist="uniform",
148
+ ctx_noise=0.0, # noise level to hold the context at; see below
149
  ):
150
+ """Masked FM sampling with a PER-MEASURE noise level.
151
+
152
+ This model is a diffusion-forcing model, and that is the whole point of it.
153
+ FM.loss draws
154
+
155
+ t = torch.sigmoid(torch.randn(B, T)) # (B, T) -- one per measure
156
+
157
+ so every measure was trained at its OWN independently-sampled noise level,
158
+ and MetaConditionalModernDiTV2 modulates per measure to match (`t = t +
159
+ style + bpm`, then `t[:, :, None]` broadcast across that measure's latent
160
+ positions). Attention is fully bidirectional, so a clean measure and a
161
+ pure-noise measure can sit in the same sequence and the clean one is read as
162
+ clean. Conditioning on the past is therefore NATIVE: hand the context
163
+ measures over at t~0 with their real latents and denoise only the rest.
164
+
165
+ That is what this does. The context is pinned to `known_x0` UNCHANGED at
166
+ every Euler step and reported at noise level `ctx_noise`, while the free
167
+ measures walk the schedule from t=1 down to 0.
168
+
169
+ Why not RePaint (which is what this function used to do): RePaint exists
170
+ because ordinary diffusion models accept a single scalar t, so the only way
171
+ to express "this part is known" is to forward-noise the known part to the
172
+ same level as the part being generated and splice it in. Here that is both
173
+ unnecessary and actively destructive -- at the first step (t=1) the context
174
+ gets noised to t=1, i.e. handed over as pure noise, so the model has no past
175
+ to condition on exactly when the global structure is being decided. It also
176
+ redraws that noise every step, so what context does survive flickers. Uniform
177
+ t is in-distribution for training (all-equal is one draw of (B,T) among
178
+ many), but it throws away the one capability that makes rollout work.
179
+
180
+ ctx_noise: 0.0 hands the context over clean. Diffusion Forcing's stabilization
181
+ result is that a SMALL nonzero level on the history makes long autoregressive
182
+ rollouts more robust -- the model treats its own past output as uncertain
183
+ instead of trusting artifacts -- so if repeated Continues drift or harden,
184
+ try ~0.02-0.1 (CONTINUE_CTX_NOISE in backend.py). Held clean at 0.0 the
185
+ context latents are bit-exact, which is what lets the re-decoded context
186
+ match the audio already in the mix.
187
 
188
  Returns x0-space latents (B, T, N, C)."""
189
  import torch
 
201
  t_steps[0] = 1.0
202
  t_steps[-1] = 0.0
203
 
204
+ keep = keep_mask.to(dev) # (B, T, 1, 1)
205
+ keep_row = keep[:, :, 0, 0] # (B, T) -- for the t vector
206
+ ctx_noise = float(ctx_noise)
207
 
208
+ # The context at level `ctx_noise`. Drawn ONCE, not per step: the context is
209
+ # a fixed thing the model is reading, not a quantity being resampled. At the
210
+ # default 0.0 this is exactly known_x0 (alpha(0)=1, sigma(0)=sigma_min=1e-5).
211
+ if ctx_noise <= 0.0:
212
+ ctx_x = known_x0
213
+ else:
214
+ tt = torch.full((B, T, N, 1), ctx_noise, device=dev, dtype=known_x0.dtype)
215
+ ctx_x = (diffusion.alpha(tt) * known_x0
216
+ + diffusion.sigma(tt) * torch.randn_like(known_x0))
217
 
218
  with torch.no_grad():
219
+ x_t = torch.where(keep, ctx_x, x_t)
 
220
  for i in range(n_steps):
221
  t_cur, t_next = t_steps[i], t_steps[i + 1]
222
+ # per-measure noise level: context sits at ctx_noise, free measures
223
+ # at wherever the schedule currently is.
224
+ t = torch.where(keep_row, torch.full_like(keep_row, ctx_noise,
225
+ dtype=x_t.dtype),
226
+ t_cur.to(x_t.dtype))
227
+ neg_v = diffusion.get_prediction(
228
+ net, x_t, t,
229
+ net_kwargs=net_kwargs,
230
+ uncond_net_kwargs=uncond_net_kwargs,
231
+ guidance=guidance,
232
+ memory_efficient=memory_efficient,
233
+ rescale_phi=rescale_phi,
234
+ cfg_mode=cfg_mode,
235
+ )
236
+ x_t = x_t + neg_v * (t_next - t_cur)
237
+ # the Euler step moved the context too; put it back untouched.
238
+ x_t = torch.where(keep, ctx_x, x_t)
 
 
 
 
 
 
239
  return x_t
240
 
241
 
 
950
  return (out / (np.linalg.norm(out) + 1e-9)).astype(np.float32)
951
 
952
  # =====================================================================
953
+ # region regenerate == masked resample of measure rows [start, end)
954
  # =====================================================================
955
  def regenerate_region(self, track, start, end, seed, n_steps=SAMPLE_STEPS):
956
  import torch
 
975
  self._sampling_args(c, getattr(tr, "guidance", None))
976
 
977
  with self._ctx():
978
+ y = fm_df_sample(
979
  self.diffusion, self.net, known_x0, keep, n_steps,
980
  net_kwargs=cfg_net_kwargs, uncond_net_kwargs=uncond_net_kwargs,
981
  guidance=guidances, noise=noise, memory_efficient=False,
982
+ cfg_mode=cfg_mode, t_dist=self.t_dist,
983
+ ctx_noise=CONTINUE_CTX_NOISE)
984
 
985
  bpm_row = torch.from_numpy(
986
  np.asarray(c.bpm, dtype=np.float32))[None].to(self.device)
 
1009
  FROZEN latents sampled
1010
 
1011
  The 24-n_new context measures keep their exact latents from the previous
1012
+ render and are handed to the model CLEAN, at noise level ~0, while the
1013
+ new measures denoise from t=1 -- diffusion forcing's per-measure noise
1014
+ level, which is what this model was trained for (see fm_df_sample). Same
1015
+ machinery region-regen uses, just with the free region pinned to the end
1016
+ of the window. So the new bars are conditioned on the actual audio that
1017
+ precedes them, not merely on a similar-looking conditioning curve.
1018
 
1019
  Only the newly sampled tail is appended to the song; the context
1020
  measures are already in the mix and get re-decoded solely because the
 
1052
  self._sampling_args(c, getattr(tr, "guidance", None))
1053
 
1054
  with self._ctx():
1055
+ y = fm_df_sample(
1056
  self.diffusion, self.net, known_x0, keep, n_steps,
1057
  net_kwargs=cfg_net_kwargs, uncond_net_kwargs=uncond_net_kwargs,
1058
  guidance=guidances, noise=noise, memory_efficient=False,
1059
+ cfg_mode=cfg_mode, t_dist=self.t_dist,
1060
+ ctx_noise=CONTINUE_CTX_NOISE)
1061
 
1062
  bpm_row = torch.from_numpy(
1063
  np.asarray(c.bpm, dtype=np.float32))[None].to(self.device)