Skip to content

Commit 0b7ec77

Browse files
committed
fix: make LayerDiff SDE sampling reproducible
1 parent 044e865 commit 0b7ec77

2 files changed

Lines changed: 123 additions & 3 deletions

File tree

module/see_through/vendor/modules/layerdiffuse/diffusers_kdiffusion_sdxl.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,16 @@ def cache_tag_embeds(self, unload_textencoders=True):
285285
gc.collect()
286286
torch.cuda.empty_cache()
287287

288-
def denoise_func(self, latents, add_text_embeds, add_time_ids, prompt_embeds, c_concat, num_inference_steps=50):
288+
def denoise_func(
289+
self,
290+
latents,
291+
add_text_embeds,
292+
add_time_ids,
293+
prompt_embeds,
294+
c_concat,
295+
num_inference_steps=50,
296+
generator=None,
297+
):
289298

290299
# 4. Prepare timesteps
291300
device = self.unet.device
@@ -324,7 +333,13 @@ def denoise_func(self, latents, add_text_embeds, add_time_ids, prompt_embeds, c_
324333

325334
# compute the previous noisy sample x_t -> x_t-1
326335
latents_dtype = latents.dtype
327-
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
336+
latents = self.scheduler.step(
337+
noise_pred,
338+
t,
339+
latents,
340+
generator=generator,
341+
return_dict=False,
342+
)[0]
328343
if latents.dtype != latents_dtype:
329344
if torch.backends.mps.is_available():
330345
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
@@ -469,7 +484,13 @@ def __call__(
469484

470485
# compute the previous noisy sample x_t -> x_t-1
471486
latents_dtype = latents.dtype
472-
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
487+
latents = self.scheduler.step(
488+
noise_pred,
489+
t,
490+
latents,
491+
generator=generator,
492+
return_dict=False,
493+
)[0]
473494
if latents.dtype != latents_dtype:
474495
if torch.backends.mps.is_available():
475496
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272

tests/test_see_through_embedding_cache.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,48 @@ def cpu(self):
5959
return self
6060

6161

62+
class _StochasticScheduler:
63+
init_noise_sigma = 1.0
64+
65+
def set_timesteps(self, num_inference_steps, *, device):
66+
self.timesteps = torch.arange(num_inference_steps - 1, -1, -1, device=device)
67+
68+
def scale_model_input(self, sample, timestep):
69+
return sample
70+
71+
def step(self, model_output, timestep, sample, *, generator=None, return_dict=True):
72+
variance_noise = torch.randn(
73+
sample.shape,
74+
generator=generator,
75+
device=sample.device,
76+
dtype=sample.dtype,
77+
)
78+
return (sample + variance_noise,)
79+
80+
81+
def _make_stochastic_layerdiff_pipeline(module):
82+
class ZeroUnet(module.UNetFrameConditionModel):
83+
device = torch.device("cpu")
84+
dtype = torch.float32
85+
config = types.SimpleNamespace(time_cond_proj_dim=None)
86+
87+
def __call__(self, sample, *args, **kwargs):
88+
return (torch.zeros_like(sample.narrow(-3, 0, 4)),)
89+
90+
pipeline = module.KDiffusionStableDiffusionXLPipeline(
91+
vae=object(),
92+
text_encoder=object(),
93+
tokenizer=object(),
94+
text_encoder_2=object(),
95+
tokenizer_2=object(),
96+
unet=ZeroUnet(),
97+
scheduler=_StochasticScheduler(),
98+
trans_vae=None,
99+
)
100+
pipeline.vae_scale_factor = 8
101+
return pipeline
102+
103+
62104
def _package(name: str) -> types.ModuleType:
63105
module = types.ModuleType(name)
64106
module.__path__ = []
@@ -84,11 +126,20 @@ def _load_layerdiff_vendor_module(monkeypatch):
84126
"StableDiffusionXLImg2ImgPipeline",
85127
"CLIPVisionModelWithProjection",
86128
"CLIPImageProcessor",
129+
"randn_tensor",
130+
"retrieve_timesteps",
87131
]
88132
pipeline_module.torch = torch
89133
pipeline_module.StableDiffusionXLImg2ImgPipeline = _FakeDiffusionPipeline
90134
pipeline_module.CLIPVisionModelWithProjection = type("CLIPVisionModelWithProjection", (), {})
91135
pipeline_module.CLIPImageProcessor = type("CLIPImageProcessor", (), {})
136+
pipeline_module.randn_tensor = torch.randn
137+
138+
def retrieve_timesteps(scheduler, num_inference_steps, device, **kwargs):
139+
scheduler.set_timesteps(num_inference_steps, device=device)
140+
return scheduler.timesteps, num_inference_steps
141+
142+
pipeline_module.retrieve_timesteps = retrieve_timesteps
92143

93144
diffusers = _package("diffusers")
94145
for name in (
@@ -248,6 +299,54 @@ def fake_encode(prompts):
248299
assert empty_cache_calls == [True]
249300

250301

302+
def test_layerdiff_seed_controls_scheduler_variance_noise(monkeypatch):
303+
module = _load_layerdiff_vendor_module(monkeypatch)
304+
305+
def run_with_global_seed(global_seed):
306+
torch.manual_seed(global_seed)
307+
pipeline = _make_stochastic_layerdiff_pipeline(module)
308+
return pipeline(
309+
num_inference_steps=3,
310+
guidance_scale=1.0,
311+
generator=torch.Generator(device="cpu").manual_seed(42),
312+
prompt_embeds=torch.zeros((1, 1, 1)),
313+
pooled_prompt_embeds=torch.zeros((1, 1)),
314+
c_concat=torch.zeros((1, 4, 2, 2)),
315+
)
316+
317+
with torch.random.fork_rng():
318+
first = run_with_global_seed(100)
319+
second = run_with_global_seed(999)
320+
321+
assert torch.equal(first, second)
322+
323+
324+
def test_layerdiff_denoise_func_seed_controls_scheduler_variance_noise(monkeypatch):
325+
module = _load_layerdiff_vendor_module(monkeypatch)
326+
327+
def run_with_global_seed(global_seed):
328+
torch.manual_seed(global_seed)
329+
generator = torch.Generator(device="cpu").manual_seed(42)
330+
pipeline = _make_stochastic_layerdiff_pipeline(module)
331+
pipeline._guidance_scale = 1.0
332+
initial_latents = torch.randn((1, 1, 4, 2, 2), generator=generator)
333+
return pipeline.denoise_func(
334+
initial_latents,
335+
add_text_embeds=torch.zeros((1, 1)),
336+
add_time_ids=torch.zeros((1, 1)),
337+
prompt_embeds=torch.zeros((1, 1, 1)),
338+
c_concat=torch.zeros((1, 1, 4, 2, 2)),
339+
num_inference_steps=3,
340+
generator=generator,
341+
)
342+
343+
with torch.random.fork_rng():
344+
first = run_with_global_seed(100)
345+
second = run_with_global_seed(999)
346+
347+
assert torch.equal(first, second)
348+
349+
251350
def test_marigold_caches_empty_embedding_before_unloading_text_encoder(monkeypatch):
252351
module = _load_marigold_vendor_module(monkeypatch)
253352
unet = module.UNetFrameConditionModel()

0 commit comments

Comments
 (0)