Merge pull request #126 from filipstrand/img2img-special-case
Img2img special case fix
This commit is contained in:
commit
dca6c0acea
@ -12,6 +12,8 @@ class BeforeLoopCallback(Protocol):
|
|||||||
self,
|
self,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
|
latents: mx.array,
|
||||||
|
config: RuntimeConfig,
|
||||||
canny_image: PIL.Image.Image | None = None,
|
canny_image: PIL.Image.Image | None = None,
|
||||||
) -> None: # fmt: off
|
) -> None: # fmt: off
|
||||||
...
|
...
|
||||||
@ -20,9 +22,9 @@ class BeforeLoopCallback(Protocol):
|
|||||||
class InLoopCallback(Protocol):
|
class InLoopCallback(Protocol):
|
||||||
def call_in_loop(
|
def call_in_loop(
|
||||||
self,
|
self,
|
||||||
|
t: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
step: int,
|
|
||||||
latents: mx.array,
|
latents: mx.array,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
time_steps: tqdm
|
time_steps: tqdm
|
||||||
@ -33,9 +35,9 @@ class InLoopCallback(Protocol):
|
|||||||
class InterruptCallback(Protocol):
|
class InterruptCallback(Protocol):
|
||||||
def call_interrupt(
|
def call_interrupt(
|
||||||
self,
|
self,
|
||||||
|
t: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
step: int,
|
|
||||||
latents: mx.array,
|
latents: mx.array,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
time_steps: tqdm
|
time_steps: tqdm
|
||||||
|
|||||||
@ -11,29 +11,33 @@ class Callbacks:
|
|||||||
def before_loop(
|
def before_loop(
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
|
latents: mx.array,
|
||||||
|
config: RuntimeConfig,
|
||||||
canny_image: PIL.Image.Image | None = None,
|
canny_image: PIL.Image.Image | None = None,
|
||||||
): # fmt: off
|
): # fmt: off
|
||||||
for subscriber in CallbackRegistry.before_loop_callbacks():
|
for subscriber in CallbackRegistry.before_loop_callbacks():
|
||||||
subscriber.call_before_loop(
|
subscriber.call_before_loop(
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
|
latents=latents,
|
||||||
|
config=config,
|
||||||
canny_image=canny_image,
|
canny_image=canny_image,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def in_loop(
|
def in_loop(
|
||||||
|
t: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
step: int,
|
|
||||||
latents: mx.array,
|
latents: mx.array,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
time_steps: tqdm
|
time_steps: tqdm
|
||||||
): # fmt: off
|
): # fmt: off
|
||||||
for subscriber in CallbackRegistry.in_loop_callbacks():
|
for subscriber in CallbackRegistry.in_loop_callbacks():
|
||||||
subscriber.call_in_loop(
|
subscriber.call_in_loop(
|
||||||
|
t=t,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
step=step,
|
|
||||||
latents=latents,
|
latents=latents,
|
||||||
config=config,
|
config=config,
|
||||||
time_steps=time_steps,
|
time_steps=time_steps,
|
||||||
@ -41,18 +45,18 @@ class Callbacks:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def interruption(
|
def interruption(
|
||||||
|
t: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
step: int,
|
|
||||||
latents: mx.array,
|
latents: mx.array,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
time_steps: tqdm
|
time_steps: tqdm
|
||||||
): # fmt: off
|
): # fmt: off
|
||||||
for subscriber in CallbackRegistry.interrupt_callbacks():
|
for subscriber in CallbackRegistry.interrupt_callbacks():
|
||||||
subscriber.call_interrupt(
|
subscriber.call_interrupt(
|
||||||
|
t=t,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
step=step,
|
|
||||||
latents=latents,
|
latents=latents,
|
||||||
config=config,
|
config=config,
|
||||||
time_steps=time_steps,
|
time_steps=time_steps,
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import mlx.core as mx
|
||||||
import PIL.Image
|
import PIL.Image
|
||||||
|
|
||||||
from mflux import ImageUtil
|
from mflux import ImageUtil
|
||||||
from mflux.callbacks.callback import BeforeLoopCallback
|
from mflux.callbacks.callback import BeforeLoopCallback
|
||||||
|
from mflux.config.runtime_config import RuntimeConfig
|
||||||
|
|
||||||
|
|
||||||
class CannyImageSaver(BeforeLoopCallback):
|
class CannyImageSaver(BeforeLoopCallback):
|
||||||
@ -15,8 +17,10 @@ class CannyImageSaver(BeforeLoopCallback):
|
|||||||
self,
|
self,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
|
latents: mx.array,
|
||||||
|
config: RuntimeConfig,
|
||||||
canny_image: PIL.Image.Image | None = None,
|
canny_image: PIL.Image.Image | None = None,
|
||||||
) -> None: # fmt: off
|
) -> None:
|
||||||
base, ext = os.path.splitext(self.path)
|
base, ext = os.path.splitext(self.path)
|
||||||
ImageUtil.save_image(
|
ImageUtil.save_image(
|
||||||
image=canny_image,
|
image=canny_image,
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import mlx.core as mx
|
import mlx.core as mx
|
||||||
|
import PIL.Image
|
||||||
import tqdm
|
import tqdm
|
||||||
|
|
||||||
from mflux import StopImageGenerationException
|
from mflux import StopImageGenerationException
|
||||||
from mflux.callbacks.callback import InLoopCallback, InterruptCallback
|
from mflux.callbacks.callback import BeforeLoopCallback, InLoopCallback, InterruptCallback
|
||||||
from mflux.config.runtime_config import RuntimeConfig
|
from mflux.config.runtime_config import RuntimeConfig
|
||||||
from mflux.post_processing.array_util import ArrayUtil
|
from mflux.post_processing.array_util import ArrayUtil
|
||||||
from mflux.post_processing.image_util import ImageUtil
|
from mflux.post_processing.image_util import ImageUtil
|
||||||
|
|
||||||
|
|
||||||
class StepwiseHandler(InLoopCallback, InterruptCallback):
|
class StepwiseHandler(BeforeLoopCallback, InLoopCallback, InterruptCallback):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
flux,
|
flux,
|
||||||
@ -23,17 +24,65 @@ class StepwiseHandler(InLoopCallback, InterruptCallback):
|
|||||||
if self.output_dir:
|
if self.output_dir:
|
||||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def call_in_loop(
|
def call_before_loop(
|
||||||
self,
|
self,
|
||||||
seed: int,
|
seed: int,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
|
latents: mx.array,
|
||||||
|
config: RuntimeConfig,
|
||||||
|
canny_image: PIL.Image.Image | None = None,
|
||||||
|
) -> None: # fmt: off
|
||||||
|
self._save_image(
|
||||||
|
step=config.init_time_step,
|
||||||
|
seed=seed,
|
||||||
|
prompt=prompt,
|
||||||
|
latents=latents,
|
||||||
|
config=config,
|
||||||
|
time_steps=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def call_in_loop(
|
||||||
|
self,
|
||||||
|
t: int,
|
||||||
|
seed: int,
|
||||||
|
prompt: str,
|
||||||
|
latents: mx.array,
|
||||||
|
config: RuntimeConfig,
|
||||||
|
time_steps: tqdm
|
||||||
|
) -> None: # fmt: off
|
||||||
|
self._save_image(
|
||||||
|
step=t + 1,
|
||||||
|
seed=seed,
|
||||||
|
prompt=prompt,
|
||||||
|
latents=latents,
|
||||||
|
config=config,
|
||||||
|
time_steps=time_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
def call_interrupt(
|
||||||
|
self,
|
||||||
|
t: int,
|
||||||
|
seed: int,
|
||||||
|
prompt: str,
|
||||||
|
latents: mx.array,
|
||||||
|
config: RuntimeConfig,
|
||||||
|
time_steps: tqdm
|
||||||
|
) -> None: # fmt: off
|
||||||
|
self._save_composite(seed=seed)
|
||||||
|
raise StopImageGenerationException(f"Stopping image generation at step {t + 1}/{len(time_steps)}")
|
||||||
|
|
||||||
|
def _save_image(
|
||||||
|
self,
|
||||||
step: int,
|
step: int,
|
||||||
|
seed: int,
|
||||||
|
prompt: str,
|
||||||
latents: mx.array,
|
latents: mx.array,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
time_steps: tqdm
|
time_steps: tqdm
|
||||||
) -> None: # fmt: off
|
) -> None: # fmt: off
|
||||||
unpack_latents = ArrayUtil.unpack_latents(latents=latents, height=config.height, width=config.width)
|
unpack_latents = ArrayUtil.unpack_latents(latents=latents, height=config.height, width=config.width)
|
||||||
stepwise_decoded = self.flux.vae.decode(unpack_latents)
|
stepwise_decoded = self.flux.vae.decode(unpack_latents)
|
||||||
|
generation_time = time_steps.format_dict["elapsed"] if time_steps is not None else 0
|
||||||
stepwise_img = ImageUtil.to_image(
|
stepwise_img = ImageUtil.to_image(
|
||||||
decoded_latents=stepwise_decoded,
|
decoded_latents=stepwise_decoded,
|
||||||
config=config,
|
config=config,
|
||||||
@ -42,28 +91,15 @@ class StepwiseHandler(InLoopCallback, InterruptCallback):
|
|||||||
quantization=self.flux.bits,
|
quantization=self.flux.bits,
|
||||||
lora_paths=self.flux.lora_paths,
|
lora_paths=self.flux.lora_paths,
|
||||||
lora_scales=self.flux.lora_scales,
|
lora_scales=self.flux.lora_scales,
|
||||||
generation_time=time_steps.format_dict["elapsed"],
|
generation_time=generation_time,
|
||||||
)
|
)
|
||||||
self.step_wise_images.append(stepwise_img)
|
|
||||||
|
|
||||||
stepwise_img.save(
|
stepwise_img.save(
|
||||||
path=self.output_dir / f"seed_{seed}_step{step}of{len(time_steps)}.png",
|
path=self.output_dir / f"seed_{seed}_step{step}of{config.num_inference_steps}.png",
|
||||||
export_json_metadata=False,
|
export_json_metadata=False,
|
||||||
)
|
)
|
||||||
|
self.step_wise_images.append(stepwise_img)
|
||||||
self._save_composite(seed=seed)
|
self._save_composite(seed=seed)
|
||||||
|
|
||||||
def call_interrupt(
|
|
||||||
self,
|
|
||||||
seed: int,
|
|
||||||
prompt: str,
|
|
||||||
step: int,
|
|
||||||
latents: mx.array,
|
|
||||||
config: RuntimeConfig,
|
|
||||||
time_steps: tqdm
|
|
||||||
) -> None: # fmt: off
|
|
||||||
self._save_composite(seed=seed)
|
|
||||||
raise StopImageGenerationException(f"Stopping image generation at step {step + 1}/{len(time_steps)}")
|
|
||||||
|
|
||||||
def _save_composite(self, seed: int) -> None:
|
def _save_composite(self, seed: int) -> None:
|
||||||
if self.step_wise_images:
|
if self.step_wise_images:
|
||||||
composite_img = ImageUtil.to_composite_image(self.step_wise_images)
|
composite_img = ImageUtil.to_composite_image(self.step_wise_images)
|
||||||
|
|||||||
@ -53,14 +53,16 @@ class RuntimeConfig:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def init_time_step(self) -> int:
|
def init_time_step(self) -> int:
|
||||||
if self.config.init_image_path is None:
|
is_txt2img = self.config.init_image_path is None or self.config.init_image_strength == 0.0
|
||||||
# For text-to-image, always begin at time step 0.
|
|
||||||
|
if is_txt2img:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
# For image-to-image: higher strength means we skip more steps.
|
# 1. Clamp strength to [0, 1]
|
||||||
strength = max(0.0, min(1.0, self.config.init_image_strength))
|
strength = max(0.0, min(1.0, self.config.init_image_strength))
|
||||||
t = max(1, int(self.num_inference_steps * strength))
|
|
||||||
return t
|
# 2. Return start time in [1, floor(num_steps * strength)]
|
||||||
|
return max(1, int(self.num_inference_steps * strength))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def controlnet_strength(self) -> float | None:
|
def controlnet_strength(self) -> float | None:
|
||||||
|
|||||||
@ -87,10 +87,12 @@ class Flux1Controlnet(nn.Module):
|
|||||||
Callbacks.before_loop(
|
Callbacks.before_loop(
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
|
latents=latents,
|
||||||
|
config=config,
|
||||||
canny_image=canny_image
|
canny_image=canny_image
|
||||||
) # fmt: off
|
) # fmt: off
|
||||||
|
|
||||||
for gen_step, t in enumerate(time_steps, 1):
|
for t in time_steps:
|
||||||
try:
|
try:
|
||||||
# 4.t Compute controlnet samples
|
# 4.t Compute controlnet samples
|
||||||
controlnet_block_samples, controlnet_single_block_samples = self.transformer_controlnet(
|
controlnet_block_samples, controlnet_single_block_samples = self.transformer_controlnet(
|
||||||
@ -119,9 +121,9 @@ class Flux1Controlnet(nn.Module):
|
|||||||
|
|
||||||
# (Optional) Call subscribes at end of loop
|
# (Optional) Call subscribes at end of loop
|
||||||
Callbacks.in_loop(
|
Callbacks.in_loop(
|
||||||
|
t=t,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
step=gen_step,
|
|
||||||
latents=latents,
|
latents=latents,
|
||||||
config=config,
|
config=config,
|
||||||
time_steps=time_steps,
|
time_steps=time_steps,
|
||||||
@ -132,9 +134,9 @@ class Flux1Controlnet(nn.Module):
|
|||||||
|
|
||||||
except KeyboardInterrupt: # noqa: PERF203
|
except KeyboardInterrupt: # noqa: PERF203
|
||||||
Callbacks.interruption(
|
Callbacks.interruption(
|
||||||
|
t=t,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
step=gen_step,
|
|
||||||
latents=latents,
|
latents=latents,
|
||||||
config=config,
|
config=config,
|
||||||
time_steps=time_steps,
|
time_steps=time_steps,
|
||||||
|
|||||||
@ -79,10 +79,12 @@ class Flux1(nn.Module):
|
|||||||
# (Optional) Call subscribers for beginning of loop
|
# (Optional) Call subscribers for beginning of loop
|
||||||
Callbacks.before_loop(
|
Callbacks.before_loop(
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt
|
prompt=prompt,
|
||||||
|
latents=latents,
|
||||||
|
config=config,
|
||||||
) # fmt: off
|
) # fmt: off
|
||||||
|
|
||||||
for gen_step, t in enumerate(time_steps, 1):
|
for t in time_steps:
|
||||||
try:
|
try:
|
||||||
# 3.t Predict the noise
|
# 3.t Predict the noise
|
||||||
noise = self.transformer(
|
noise = self.transformer(
|
||||||
@ -99,9 +101,9 @@ class Flux1(nn.Module):
|
|||||||
|
|
||||||
# (Optional) Call subscribes at end of loop
|
# (Optional) Call subscribes at end of loop
|
||||||
Callbacks.in_loop(
|
Callbacks.in_loop(
|
||||||
|
t=t,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
step=gen_step,
|
|
||||||
latents=latents,
|
latents=latents,
|
||||||
config=config,
|
config=config,
|
||||||
time_steps=time_steps,
|
time_steps=time_steps,
|
||||||
@ -112,9 +114,9 @@ class Flux1(nn.Module):
|
|||||||
|
|
||||||
except KeyboardInterrupt: # noqa: PERF203
|
except KeyboardInterrupt: # noqa: PERF203
|
||||||
Callbacks.interruption(
|
Callbacks.interruption(
|
||||||
|
t=t,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
step=gen_step,
|
|
||||||
latents=latents,
|
latents=latents,
|
||||||
config=config,
|
config=config,
|
||||||
time_steps=time_steps,
|
time_steps=time_steps,
|
||||||
|
|||||||
@ -26,6 +26,7 @@ def main():
|
|||||||
# 2. Register the optional callbacks
|
# 2. Register the optional callbacks
|
||||||
if args.stepwise_image_output_dir:
|
if args.stepwise_image_output_dir:
|
||||||
handler = StepwiseHandler(flux=flux, output_dir=args.stepwise_image_output_dir)
|
handler = StepwiseHandler(flux=flux, output_dir=args.stepwise_image_output_dir)
|
||||||
|
CallbackRegistry.register_before_loop(handler)
|
||||||
CallbackRegistry.register_in_loop(handler)
|
CallbackRegistry.register_in_loop(handler)
|
||||||
CallbackRegistry.register_interrupt(handler)
|
CallbackRegistry.register_interrupt(handler)
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ def main():
|
|||||||
CallbackRegistry.register_before_loop(CannyImageSaver(path=args.output))
|
CallbackRegistry.register_before_loop(CannyImageSaver(path=args.output))
|
||||||
if args.stepwise_image_output_dir:
|
if args.stepwise_image_output_dir:
|
||||||
handler = StepwiseHandler(flux=flux, output_dir=args.stepwise_image_output_dir)
|
handler = StepwiseHandler(flux=flux, output_dir=args.stepwise_image_output_dir)
|
||||||
|
CallbackRegistry.register_before_loop(handler)
|
||||||
CallbackRegistry.register_in_loop(handler)
|
CallbackRegistry.register_in_loop(handler)
|
||||||
CallbackRegistry.register_interrupt(handler)
|
CallbackRegistry.register_interrupt(handler)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user