Nate-M commited on
Commit
dfbbb07
·
verified ·
1 Parent(s): 8252bd3

Upload 6 files

Browse files
Files changed (1) hide show
  1. src/generate.py +38 -28
src/generate.py CHANGED
@@ -1,18 +1,17 @@
1
  from __future__ import annotations
 
2
  from dataclasses import dataclass
3
  from pathlib import Path
4
  from typing import List, Optional
5
- import torch
6
  from PIL import Image
 
7
  from .styles import Style
8
  from .utils import timestamp
9
 
10
  IMG_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
11
 
12
- # This file contains the core logic for generating images based on user prompts and styles.
13
- # It defines the GenerateConfig dataclass to hold configuration parameters for the generation process, and the generate_one function that performs the actual image generation using a specified model and style.
14
- # The code also includes helper functions to list images in a folder, load images using PIL, and pick reference images from a style folder based on specified criteria.
15
- # The generate_one function handles the setup of the generation pipeline, including loading the model, preparing reference images if needed, and saving the generated images to the output directory.
16
  def _list_images(folder: Path) -> List[Path]:
17
  if not folder.exists():
18
  return []
@@ -26,13 +25,15 @@ def _list_images(folder: Path) -> List[Path]:
26
  def _load_pil(path: Path) -> Image.Image:
27
  return Image.open(path).convert("RGB")
28
 
29
- # This function is responsible for selecting reference images from the specified style folder to be used as conditioning inputs for the FLUX.2 generation process.
30
  def _pick_reference_images(
31
  style_folder: Path,
32
  max_images: int,
33
  sample_mode: str = "random",
34
- generator: Optional[torch.Generator] = None,
35
  ) -> List[Image.Image]:
 
 
36
  paths = _list_images(style_folder)
37
  if not paths or max_images <= 0:
38
  return []
@@ -42,28 +43,26 @@ def _pick_reference_images(
42
  if sample_mode == "first":
43
  chosen = paths[:max_images]
44
  else:
45
- if generator is None:
46
- generator = torch.Generator(device="cpu")
47
- generator.manual_seed(0)
48
  idx = torch.randperm(len(paths), generator=generator).tolist()
49
  chosen = [paths[i] for i in idx[:max_images]]
50
 
51
  return [_load_pil(p) for p in chosen]
52
 
53
- # The generate_one function is the main entry point for generating images based on a user prompt and a specified style.
54
- # It handles the entire generation pipeline, including loading the FLUX.2 model, preparing reference images.
55
  @dataclass
56
  class GenerateConfig:
57
  model_id: str
58
- steps: int = 8
59
  guidance: float = 1.0
60
  height: int = 512
61
  width: int = 512
62
  device: str = "cuda"
63
- torch_dtype: torch.dtype = torch.bfloat16
64
  cpu_offload: bool = True
65
  use_ref_images: bool = True
66
- ref_max_images: int = 10
67
  ref_sample_mode: str = "random"
68
  seed: Optional[int] = None
69
  num_outputs: int = 1
@@ -73,15 +72,27 @@ def _is_flux2(model_id: str) -> bool:
73
  mid = model_id.lower()
74
  return "flux.2" in mid or "flux2" in mid
75
 
76
- # This function loads the FLUX.2 pipeline based on the specified model_id and configuration settings. It checks if the model_id corresponds to a FLUX.2 model and loads the appropriate pipeline class (Flux2KleinPipeline or Flux2Pipeline).
77
- # It also handles moving the pipeline to the correct device (CPU or CUDA) and enabling CPU offload if specified in the configuration.
 
 
 
 
 
 
 
 
 
78
  def _load_flux2_pipeline(model_id: str, cfg: GenerateConfig):
 
79
  from diffusers import Flux2KleinPipeline, Flux2Pipeline
80
 
 
 
81
  if "klein" in model_id.lower():
82
- pipe = Flux2KleinPipeline.from_pretrained(model_id, torch_dtype=cfg.torch_dtype)
83
  else:
84
- pipe = Flux2Pipeline.from_pretrained(model_id, torch_dtype=cfg.torch_dtype)
85
 
86
  if cfg.device == "cuda" and torch.cuda.is_available():
87
  if cfg.cpu_offload:
@@ -93,31 +104,30 @@ def _load_flux2_pipeline(model_id: str, cfg: GenerateConfig):
93
 
94
  return pipe
95
 
96
- # The generate_one function controls the entire image generation process for a single user prompt and style.
97
- # It prepares the FLUX.2 pipeline, selects reference images, and calls the pipeline to generate images based on the user prompt and reference images.
98
  def generate_one(
99
  user_prompt: str,
100
  style: Style,
101
  out_root: Path,
102
  cfg: GenerateConfig,
103
  ) -> List[Path]:
 
 
104
  out_root.mkdir(parents=True, exist_ok=True)
105
 
106
  if not _is_flux2(cfg.model_id):
107
  raise ValueError(
108
- "This generate.py expects a FLUX.2 model_id (e.g., black-forest-labs/FLUX.2-klein-4B). "
109
- f"Got: {cfg.model_id}"
110
  )
111
 
112
  pipe = _load_flux2_pipeline(cfg.model_id, cfg)
113
 
114
  if cfg.seed is None:
115
  generator = None
116
- ref_gen = torch.Generator(device="cpu").manual_seed(0)
117
  else:
118
  gen_device = "cuda" if (cfg.device == "cuda" and torch.cuda.is_available()) else "cpu"
119
  generator = torch.Generator(device=gen_device).manual_seed(int(cfg.seed))
120
- ref_gen = torch.Generator(device="cpu").manual_seed(int(cfg.seed))
121
 
122
  ref_images: Optional[List[Image.Image]] = None
123
  if cfg.use_ref_images:
@@ -125,7 +135,7 @@ def generate_one(
125
  style.folder,
126
  max_images=cfg.ref_max_images,
127
  sample_mode=cfg.ref_sample_mode,
128
- generator=ref_gen,
129
  )
130
  if ref_images:
131
  print(
@@ -135,7 +145,6 @@ def generate_one(
135
  else:
136
  print("FLUX.2 reference images: none found (running txt2img).")
137
 
138
- # Prepare the arguments for the FLUX.2 pipeline call, including the user prompt, generation parameters, and reference images if available.
139
  call_kwargs = dict(
140
  prompt=user_prompt,
141
  height=int(cfg.height),
@@ -145,6 +154,7 @@ def generate_one(
145
  generator=generator,
146
  num_images_per_prompt=max(1, int(cfg.num_outputs)),
147
  )
 
148
  if ref_images:
149
  call_kwargs["image"] = ref_images
150
 
@@ -161,4 +171,4 @@ def generate_one(
161
  img.save(out_path)
162
  paths.append(out_path)
163
 
164
- return paths
 
1
  from __future__ import annotations
2
+
3
  from dataclasses import dataclass
4
  from pathlib import Path
5
  from typing import List, Optional
6
+
7
  from PIL import Image
8
+
9
  from .styles import Style
10
  from .utils import timestamp
11
 
12
  IMG_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
13
 
14
+
 
 
 
15
  def _list_images(folder: Path) -> List[Path]:
16
  if not folder.exists():
17
  return []
 
25
  def _load_pil(path: Path) -> Image.Image:
26
  return Image.open(path).convert("RGB")
27
 
28
+
29
  def _pick_reference_images(
30
  style_folder: Path,
31
  max_images: int,
32
  sample_mode: str = "random",
33
+ seed: Optional[int] = None,
34
  ) -> List[Image.Image]:
35
+ import torch
36
+
37
  paths = _list_images(style_folder)
38
  if not paths or max_images <= 0:
39
  return []
 
43
  if sample_mode == "first":
44
  chosen = paths[:max_images]
45
  else:
46
+ generator = torch.Generator(device="cpu")
47
+ generator.manual_seed(0 if seed is None else int(seed))
 
48
  idx = torch.randperm(len(paths), generator=generator).tolist()
49
  chosen = [paths[i] for i in idx[:max_images]]
50
 
51
  return [_load_pil(p) for p in chosen]
52
 
53
+
 
54
  @dataclass
55
  class GenerateConfig:
56
  model_id: str
57
+ steps: int = 6
58
  guidance: float = 1.0
59
  height: int = 512
60
  width: int = 512
61
  device: str = "cuda"
62
+ torch_dtype_name: str = "bfloat16"
63
  cpu_offload: bool = True
64
  use_ref_images: bool = True
65
+ ref_max_images: int = 4
66
  ref_sample_mode: str = "random"
67
  seed: Optional[int] = None
68
  num_outputs: int = 1
 
72
  mid = model_id.lower()
73
  return "flux.2" in mid or "flux2" in mid
74
 
75
+
76
+ def _resolve_torch_dtype(dtype_name: str):
77
+ import torch
78
+
79
+ if dtype_name == "float16":
80
+ return torch.float16
81
+ if dtype_name == "float32":
82
+ return torch.float32
83
+ return torch.bfloat16
84
+
85
+
86
  def _load_flux2_pipeline(model_id: str, cfg: GenerateConfig):
87
+ import torch
88
  from diffusers import Flux2KleinPipeline, Flux2Pipeline
89
 
90
+ torch_dtype = _resolve_torch_dtype(cfg.torch_dtype_name)
91
+
92
  if "klein" in model_id.lower():
93
+ pipe = Flux2KleinPipeline.from_pretrained(model_id, torch_dtype=torch_dtype)
94
  else:
95
+ pipe = Flux2Pipeline.from_pretrained(model_id, torch_dtype=torch_dtype)
96
 
97
  if cfg.device == "cuda" and torch.cuda.is_available():
98
  if cfg.cpu_offload:
 
104
 
105
  return pipe
106
 
107
+
 
108
  def generate_one(
109
  user_prompt: str,
110
  style: Style,
111
  out_root: Path,
112
  cfg: GenerateConfig,
113
  ) -> List[Path]:
114
+ import torch
115
+
116
  out_root.mkdir(parents=True, exist_ok=True)
117
 
118
  if not _is_flux2(cfg.model_id):
119
  raise ValueError(
120
+ "This generate.py expects a FLUX.2 model_id "
121
+ f"(got: {cfg.model_id})"
122
  )
123
 
124
  pipe = _load_flux2_pipeline(cfg.model_id, cfg)
125
 
126
  if cfg.seed is None:
127
  generator = None
 
128
  else:
129
  gen_device = "cuda" if (cfg.device == "cuda" and torch.cuda.is_available()) else "cpu"
130
  generator = torch.Generator(device=gen_device).manual_seed(int(cfg.seed))
 
131
 
132
  ref_images: Optional[List[Image.Image]] = None
133
  if cfg.use_ref_images:
 
135
  style.folder,
136
  max_images=cfg.ref_max_images,
137
  sample_mode=cfg.ref_sample_mode,
138
+ seed=cfg.seed,
139
  )
140
  if ref_images:
141
  print(
 
145
  else:
146
  print("FLUX.2 reference images: none found (running txt2img).")
147
 
 
148
  call_kwargs = dict(
149
  prompt=user_prompt,
150
  height=int(cfg.height),
 
154
  generator=generator,
155
  num_images_per_prompt=max(1, int(cfg.num_outputs)),
156
  )
157
+
158
  if ref_images:
159
  call_kwargs["image"] = ref_images
160
 
 
171
  img.save(out_path)
172
  paths.append(out_path)
173
 
174
+ return paths