Offexb commited on
Commit
04b5cae
·
verified ·
1 Parent(s): 6efa079

Upload 8 files

Browse files
Files changed (5) hide show
  1. STYLE_TRAINING.md +16 -7
  2. ZEROGPU_TRAINING.md +3 -0
  3. app.py +44 -25
  4. requirements.txt +14 -10
  5. train_style_lora.py +1 -1
STYLE_TRAINING.md CHANGED
@@ -1,10 +1,19 @@
1
- # Custom Style LoRA
2
 
3
- Use only images you own or are licensed to train on.
 
 
 
 
4
 
5
- Put training images in `training_data/`. `train_style_lora.py` validates them and
6
- creates a manifest. Use a current Diffusers LoRA training workflow compatible
7
- with your GPU to actually train the weights.
 
 
 
 
 
 
8
 
9
- The LoRA teaches the target visual style/character patterns. The expression
10
- engine and reference-image adapter handle expression/pose control separately.
 
1
+ # Real Style LoRA workflow
2
 
3
+ 1. Keep the 10 reference images in `training_data/`.
4
+ 2. Run `python prepare_dataset.py`.
5
+ 3. Run `python train_style_lora.py` on a CUDA GPU.
6
+ 4. The official Diffusers SDXL DreamBooth-LoRA trainer writes safetensors weights into `weights/`.
7
+ 5. Start `app.py`. The app auto-loads the local LoRA if `weights/pytorch_lora_weights.safetensors` exists.
8
 
9
+ ### Environment tuning
10
+ - `MAX_TRAIN_STEPS=1200`
11
+ - `LEARNING_RATE=1e-4`
12
+ - `LORA_RANK=16`
13
+ - `GRAD_ACCUM=4`
14
+ - `TRAIN_RESOLUTION=1024`
15
+ - `TRAIN_BATCH_SIZE=1`
16
+ - `MODEL_ID=stabilityai/stable-diffusion-xl-base-1.0`
17
+ - `HF_TOKEN=<your token>` if the Space needs authenticated model access
18
 
19
+ The 10 images are a small dataset. They can teach a recognizable style, but a larger set with more poses/characters generally gives a more robust style LoRA.
 
ZEROGPU_TRAINING.md CHANGED
@@ -9,3 +9,6 @@ The Space UI must connect a Gradio button to `train_style_lora.train_chunk`.
9
  Run the button repeatedly until the status reports completion.
10
 
11
  A full SDXL LoRA run is not guaranteed to fit into a single ZeroGPU call or daily quota.
 
 
 
 
9
  Run the button repeatedly until the status reports completion.
10
 
11
  A full SDXL LoRA run is not guaranteed to fit into a single ZeroGPU call or daily quota.
12
+
13
+
14
+ Note: the trainer downloads the maintained SDXL DreamBooth LoRA script from the Diffusers examples/dreambooth path.
app.py CHANGED
@@ -1,7 +1,9 @@
1
  import os, random, gc, json, re
2
  from pathlib import Path
 
3
  import gradio as gr
4
  import torch
 
5
  from PIL import Image
6
  from rembg import remove, new_session
7
  from diffusers import (
@@ -18,21 +20,21 @@ from transformers import CLIPVisionModelWithProjection
18
  # ============================================================
19
 
20
  MODEL_ID = os.getenv("MODEL_ID", "stabilityai/stable-diffusion-xl-base-1.0")
21
- LORA_REPO = os.getenv("LORA_REPO", "")
22
- LORA_WEIGHT = os.getenv("LORA_WEIGHT", "")
23
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
- DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
25
 
26
  # Fixed identity/style contract. Keep this stable across generations.
27
  STYLE_LOCK = """
28
- high-end stylized 3D animated character asset,
29
  premium polished character design, expressive stylized face,
30
  clean sculpted facial forms, large readable eyes,
31
  clear eyebrows and mouth shapes, detailed clothing,
32
  clean hands and fingers, strong silhouette,
33
  full body from head to toe, entire feet visible,
34
  entire arms and hands visible, centered character,
35
- consistent proportions, professional production character sheet quality,
 
 
36
  isolated character, transparent-background asset look,
37
  neutral balanced studio lighting, clean edges, sharp details,
38
  no environment, no props unless requested, no text, no logo, no watermark
@@ -40,7 +42,9 @@ no environment, no props unless requested, no text, no logo, no watermark
40
 
41
  NEGATIVE = """
42
  cropped, cut off head, cut off feet, half body, close-up,
43
- duplicate character, two characters, extra arms, extra legs,
 
 
44
  extra fingers, fused fingers, missing fingers, deformed hands,
45
  bad anatomy, malformed face, asymmetrical eyes, floating limbs,
46
  background scene, landscape, room, street, text, letters, logo,
@@ -190,25 +194,32 @@ _lora_loaded = False
190
 
191
  def load_models():
192
  global _pipe, _rmbg, _lora_loaded
 
 
193
  if _pipe is None:
 
 
194
  _pipe = AutoPipelineForText2Image.from_pretrained(
195
- MODEL_ID, torch_dtype=DTYPE, use_safetensors=True
196
  )
197
  if LORA_REPO and LORA_WEIGHT:
198
  try:
199
- _pipe.load_lora_weights(LORA_REPO, weight_name=LORA_WEIGHT, adapter_name="character_style")
 
 
 
 
 
 
200
  _pipe.set_adapters(["character_style"], adapter_weights=[float(os.getenv("LORA_SCALE","0.75"))])
201
  _lora_loaded = True
202
  except Exception as e:
203
  print("LoRA load failed:", e)
204
 
205
- if DEVICE == "cuda":
206
- _pipe = _pipe.to("cuda")
207
- try: _pipe.enable_xformers_memory_efficient_attention()
208
- except Exception: pass
209
- _pipe.enable_attention_slicing()
210
- else:
211
- _pipe = _pipe.to("cpu")
212
  if _rmbg is None:
213
  _rmbg = new_session("u2net_human_seg")
214
  return _pipe, _rmbg
@@ -242,16 +253,21 @@ EXTRA DIRECTION:
242
  {extra or "None"}
243
 
244
  OUTPUT:
245
- single full-body character, centered, head-to-toe, isolated asset
 
 
 
 
246
  """.strip()
247
 
248
- def generate(character, expression, consistency, reference, strength, extra, steps, guidance, seed):
 
249
  if not character or not character.strip():
250
  raise gr.Error("Character description डालना जरूरी है।")
251
  pipe, rmbg = load_models()
252
 
253
  seed = random.randint(0, 2147483647) if seed is None or int(seed) < 0 else int(seed)
254
- generator = torch.Generator(device=DEVICE).manual_seed(seed)
255
  prompt = make_prompt(character, expression, consistency, reference, extra)
256
 
257
  # IP-Adapter is the reference-image pathway when the installed diffusers
@@ -259,7 +275,7 @@ def generate(character, expression, consistency, reference, strength, extra, ste
259
  # combinations differ between Spaces.
260
  kwargs = dict(
261
  prompt=prompt, negative_prompt=NEGATIVE,
262
- width=1024, height=1024,
263
  num_inference_steps=int(steps),
264
  guidance_scale=float(guidance),
265
  generator=generator
@@ -306,7 +322,7 @@ button.primary{background:linear-gradient(100deg,#7357ff,#22d4ff)!important;bord
306
  .output{border-radius:22px!important;min-height:650px!important}
307
  """
308
 
309
- with gr.Blocks(title="AI Character Studio ULTRA", css=CSS, theme=gr.themes.Base()) as demo:
310
  gr.HTML("""
311
  <div class="hero">
312
  <div style="color:#a9b3c7;font-size:12px;letter-spacing:2px;margin-bottom:8px">AI CHARACTER STUDIO • ULTRA</div>
@@ -353,8 +369,10 @@ with gr.Blocks(title="AI Character Studio ULTRA", css=CSS, theme=gr.themes.Base(
353
  guidance = gr.Slider(4,10,7,step=.5,label="Guidance")
354
 
355
  seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
356
- generate = gr.Button("✨ GENERATE HIGH-QUALITY CHARACTER", variant="primary", elem_id="generate")
357
  clear = gr.Button("Clear GPU Cache")
 
 
358
  status = gr.Textbox(label="System Status", interactive=False)
359
 
360
  with gr.Column(scale=7):
@@ -362,12 +380,13 @@ with gr.Blocks(title="AI Character Studio ULTRA", css=CSS, theme=gr.themes.Base(
362
  used_seed = gr.Number(label="Seed Used", interactive=False)
363
  prompt_view = gr.Textbox(label="Resolved Prompt", lines=14, interactive=False)
364
 
365
- generate.click(
366
- generate,
367
  [character, expression, consistency, reference, strength, extra, steps, guidance, seed],
368
  [output, used_seed, prompt_view]
369
  )
370
  clear.click(clear_cache, outputs=status)
 
371
 
372
  if __name__ == "__main__":
373
- demo.queue(max_size=20).launch()
 
1
  import os, random, gc, json, re
2
  from pathlib import Path
3
+ import spaces # must be imported before torch for ZeroGPU startup detection
4
  import gradio as gr
5
  import torch
6
+ from train_style_lora import train_chunk
7
  from PIL import Image
8
  from rembg import remove, new_session
9
  from diffusers import (
 
20
  # ============================================================
21
 
22
  MODEL_ID = os.getenv("MODEL_ID", "stabilityai/stable-diffusion-xl-base-1.0")
23
+ LORA_REPO = os.getenv("LORA_REPO", str(Path(__file__).parent / "weights"))
24
+ LORA_WEIGHT = os.getenv("LORA_WEIGHT", "offex3dstyle.safetensors")
 
 
25
 
26
  # Fixed identity/style contract. Keep this stable across generations.
27
  STYLE_LOCK = """
28
+ offex3dstyle, high-end stylized 3D animated character asset,
29
  premium polished character design, expressive stylized face,
30
  clean sculpted facial forms, large readable eyes,
31
  clear eyebrows and mouth shapes, detailed clothing,
32
  clean hands and fingers, strong silhouette,
33
  full body from head to toe, entire feet visible,
34
  entire arms and hands visible, centered character,
35
+ consistent proportions, professional production character asset quality,
36
+ ONE AND ONLY ONE CHARACTER, a single person/character only,
37
+ no character sheet, no turnaround sheet, no multiple views, no collage, no thumbnails, no duplicate character,
38
  isolated character, transparent-background asset look,
39
  neutral balanced studio lighting, clean edges, sharp details,
40
  no environment, no props unless requested, no text, no logo, no watermark
 
42
 
43
  NEGATIVE = """
44
  cropped, cut off head, cut off feet, half body, close-up,
45
+ character sheet, character turnaround, turnaround sheet, model sheet, expression sheet, multiple views, multiple poses, collage, grid, contact sheet, headshot, bust portrait, portrait crop, close-up,
46
+ duplicate character, two characters, three characters, multiple characters, extra character, repeated character,
47
+ extra arms, extra legs,
48
  extra fingers, fused fingers, missing fingers, deformed hands,
49
  bad anatomy, malformed face, asymmetrical eyes, floating limbs,
50
  background scene, landscape, room, street, text, letters, logo,
 
194
 
195
  def load_models():
196
  global _pipe, _rmbg, _lora_loaded
197
+ device = "cuda" if torch.cuda.is_available() else "cpu"
198
+ dtype = torch.float16 if device == "cuda" else torch.float32
199
  if _pipe is None:
200
+ if device != "cuda":
201
+ raise RuntimeError("GPU allocation is required for generation. Please retry while the ZeroGPU function is running.")
202
  _pipe = AutoPipelineForText2Image.from_pretrained(
203
+ MODEL_ID, torch_dtype=dtype, use_safetensors=True
204
  )
205
  if LORA_REPO and LORA_WEIGHT:
206
  try:
207
+ # Load the locally trained style LoRA when the weight file exists.
208
+ weight_path = Path(LORA_REPO) / LORA_WEIGHT
209
+ if not weight_path.exists() and Path(LORA_REPO).is_file():
210
+ weight_path = Path(LORA_REPO)
211
+ if not weight_path.exists():
212
+ raise FileNotFoundError(f"Style LoRA not found: {weight_path}")
213
+ _pipe.load_lora_weights(str(weight_path.parent), weight_name=weight_path.name, adapter_name="character_style")
214
  _pipe.set_adapters(["character_style"], adapter_weights=[float(os.getenv("LORA_SCALE","0.75"))])
215
  _lora_loaded = True
216
  except Exception as e:
217
  print("LoRA load failed:", e)
218
 
219
+ _pipe = _pipe.to(device)
220
+ try: _pipe.enable_xformers_memory_efficient_attention()
221
+ except Exception: pass
222
+ _pipe.enable_attention_slicing()
 
 
 
223
  if _rmbg is None:
224
  _rmbg = new_session("u2net_human_seg")
225
  return _pipe, _rmbg
 
253
  {extra or "None"}
254
 
255
  OUTPUT:
256
+ ONE SINGLE FULL-BODY CHARACTER ONLY.
257
+ This is a single-character asset, not a character sheet and not a portrait collection.
258
+ Exactly one single character in the entire image, centered and occupying most of the canvas.
259
+ Full-body standing pose from top of head to soles of both feet, one person only, large enough to fill the vertical frame, both feet visible, both arms visible, isolated on a clean transparent-style background.
260
+ Do NOT show a headshot, bust, portrait crop, front/back/side turnaround, multiple views, additional poses, smaller copies, duplicate copies, thumbnails, character sheet, expression sheet, collage, grid, contact sheet, or any second character.
261
  """.strip()
262
 
263
+ @spaces.GPU(duration=120, size="large")
264
+ def generate_character(character, expression, consistency, reference, strength, extra, steps, guidance, seed):
265
  if not character or not character.strip():
266
  raise gr.Error("Character description डालना जरूरी है।")
267
  pipe, rmbg = load_models()
268
 
269
  seed = random.randint(0, 2147483647) if seed is None or int(seed) < 0 else int(seed)
270
+ generator = torch.Generator(device="cuda").manual_seed(seed)
271
  prompt = make_prompt(character, expression, consistency, reference, extra)
272
 
273
  # IP-Adapter is the reference-image pathway when the installed diffusers
 
275
  # combinations differ between Spaces.
276
  kwargs = dict(
277
  prompt=prompt, negative_prompt=NEGATIVE,
278
+ width=832, height=1216,
279
  num_inference_steps=int(steps),
280
  guidance_scale=float(guidance),
281
  generator=generator
 
322
  .output{border-radius:22px!important;min-height:650px!important}
323
  """
324
 
325
+ with gr.Blocks(title="AI Character Studio ULTRA") as demo:
326
  gr.HTML("""
327
  <div class="hero">
328
  <div style="color:#a9b3c7;font-size:12px;letter-spacing:2px;margin-bottom:8px">AI CHARACTER STUDIO • ULTRA</div>
 
369
  guidance = gr.Slider(4,10,7,step=.5,label="Guidance")
370
 
371
  seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
372
+ generate_btn = gr.Button("✨ GENERATE HIGH-QUALITY CHARACTER", variant="primary", elem_id="generate")
373
  clear = gr.Button("Clear GPU Cache")
374
+ train_lora = gr.Button("🔥 TRAIN LORA CHUNK (ZeroGPU)", variant="primary")
375
+ train_status = gr.Textbox(label="LoRA Training Status", interactive=False, lines=3)
376
  status = gr.Textbox(label="System Status", interactive=False)
377
 
378
  with gr.Column(scale=7):
 
380
  used_seed = gr.Number(label="Seed Used", interactive=False)
381
  prompt_view = gr.Textbox(label="Resolved Prompt", lines=14, interactive=False)
382
 
383
+ generate_btn.click(
384
+ generate_character,
385
  [character, expression, consistency, reference, strength, extra, steps, guidance, seed],
386
  [output, used_seed, prompt_view]
387
  )
388
  clear.click(clear_cache, outputs=status)
389
+ train_lora.click(train_chunk, outputs=train_status)
390
 
391
  if __name__ == "__main__":
392
+ demo.queue(max_size=20).launch(css=CSS, theme=gr.themes.Base())
requirements.txt CHANGED
@@ -1,10 +1,14 @@
1
- gradio>=5.38.0
2
- diffusers>=0.35.1
3
- transformers>=4.53.0
4
- accelerate>=1.8.1
5
- safetensors>=0.5.3
6
- Pillow>=11.0.0
7
- rembg[gpu]>=2.0.67
8
- onnxruntime-gpu>=1.22.0
9
- numpy<2
10
- peft>=0.15.0
 
 
 
 
 
1
+ # SDXL generation + real LoRA training
2
+ accelerate>=1.2.0
3
+ diffusers>=0.31.0
4
+ transformers>=4.46.0
5
+ peft>=0.13.0
6
+ safetensors>=0.4.5
7
+ huggingface_hub>=0.26.0
8
+ bitsandbytes>=0.45.0
9
+ gradio>=5.0.0
10
+ Pillow>=10.0.0
11
+ rembg>=2.0.59
12
+ onnxruntime-gpu>=1.19.0; platform_system == "Linux"
13
+ spaces>=0.36.0
14
+ torchvision>=0.20.0
train_style_lora.py CHANGED
@@ -31,7 +31,7 @@ HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HF_HUB_TOKEN", "")
31
  TRAIN_SCRIPT_URL = os.getenv(
32
  "DIFFUSERS_TRAIN_SCRIPT_URL",
33
  "https://raw.githubusercontent.com/huggingface/diffusers/main/"
34
- "examples/advanced_diffusion_training/train_dreambooth_lora_sdxl.py",
35
  )
36
 
37
  # Keep each ZeroGPU request bounded. Run Train again to continue the next chunk.
 
31
  TRAIN_SCRIPT_URL = os.getenv(
32
  "DIFFUSERS_TRAIN_SCRIPT_URL",
33
  "https://raw.githubusercontent.com/huggingface/diffusers/main/"
34
+ "examples/dreambooth/train_dreambooth_lora_sdxl.py",
35
  )
36
 
37
  # Keep each ZeroGPU request bounded. Run Train again to continue the next chunk.