Spaces:
Running on Zero
Running on Zero
update
Browse files- .gitignore +55 -0
- README.md +94 -1
- app.py +140 -49
- requirements.txt +3 -6
- requirements_local.txt +13 -11
.gitignore
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
.pytest_cache/
|
| 6 |
+
.mypy_cache/
|
| 7 |
+
.ruff_cache/
|
| 8 |
+
.coverage
|
| 9 |
+
htmlcov/
|
| 10 |
+
|
| 11 |
+
# Virtual environments
|
| 12 |
+
.venv/
|
| 13 |
+
venv/
|
| 14 |
+
env/
|
| 15 |
+
|
| 16 |
+
# Local secrets and machine-specific configuration
|
| 17 |
+
.env
|
| 18 |
+
.env.*
|
| 19 |
+
!.env.example
|
| 20 |
+
*.local
|
| 21 |
+
|
| 22 |
+
# IDE and operating-system files
|
| 23 |
+
.idea/
|
| 24 |
+
.vscode/
|
| 25 |
+
.DS_Store
|
| 26 |
+
Thumbs.db
|
| 27 |
+
|
| 28 |
+
# Hugging Face, Gradio, and experiment caches
|
| 29 |
+
.cache/
|
| 30 |
+
huggingface/
|
| 31 |
+
gradio_cached_examples/
|
| 32 |
+
wandb/
|
| 33 |
+
runs/
|
| 34 |
+
logs/
|
| 35 |
+
|
| 36 |
+
# Helpers downloaded lazily by app.py
|
| 37 |
+
/train_dreambooth_lora_sdxl_advanced.py
|
| 38 |
+
/script.py
|
| 39 |
+
|
| 40 |
+
# Temporary dataset and SpaceRunner directories (UUID names)
|
| 41 |
+
/????????-????-????-????-????????????/
|
| 42 |
+
|
| 43 |
+
# Generated training artifacts
|
| 44 |
+
outputs/
|
| 45 |
+
output/
|
| 46 |
+
checkpoint-*/
|
| 47 |
+
*.safetensors
|
| 48 |
+
*.ckpt
|
| 49 |
+
*.pt
|
| 50 |
+
*.pth
|
| 51 |
+
|
| 52 |
+
# Build and packaging artifacts
|
| 53 |
+
build/
|
| 54 |
+
dist/
|
| 55 |
+
*.egg-info/
|
README.md
CHANGED
|
@@ -14,7 +14,100 @@ hf_oauth_scopes:
|
|
| 14 |
- read-repos
|
| 15 |
- write-repos
|
| 16 |
- manage-repos
|
|
|
|
|
|
|
| 17 |
short_description: Train LoRAs with Ease
|
| 18 |
---
|
| 19 |
|
| 20 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
- read-repos
|
| 15 |
- write-repos
|
| 16 |
- manage-repos
|
| 17 |
+
models:
|
| 18 |
+
- microsoft/Mage-VL
|
| 19 |
short_description: Train LoRAs with Ease
|
| 20 |
---
|
| 21 |
|
| 22 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 23 |
+
|
| 24 |
+
# LoRA Ease: SDXL training on Hugging Face
|
| 25 |
+
|
| 26 |
+
This fork trains LoRA adapters for `stabilityai/stable-diffusion-xl-base-1.0`.
|
| 27 |
+
The maintained Hugging Face Diffusers v0.39.0 advanced SDXL trainer is downloaded
|
| 28 |
+
from a pinned commit the first time training starts. Captions are generated by a
|
| 29 |
+
dedicated Mage-VL Space, while the face-prior dataset is loaded only when needed.
|
| 30 |
+
|
| 31 |
+
## Deploy as a Hugging Face Space
|
| 32 |
+
|
| 33 |
+
Create or duplicate a Gradio Space and push these files to it. The UI itself can
|
| 34 |
+
remain on CPU Basic: when a user starts training, AutoTrain SpaceRunner creates a
|
| 35 |
+
temporary L40S training Space under that user's account and publishes the finished
|
| 36 |
+
LoRA to their model repository.
|
| 37 |
+
|
| 38 |
+
Captioning defaults to `lienthealien/mage-vl-demo`, which uses the Apache-2.0
|
| 39 |
+
`microsoft/Mage-VL` model. If that Space is private, add
|
| 40 |
+
`MAGE_VL_TOKEN` as a secret in this Space's settings. It only needs permission to
|
| 41 |
+
call the Mage-VL Space. `MAGE_VL_SPACE_ID` and `MAGE_VL_API_NAME` can be set as
|
| 42 |
+
Space variables if you use a different duplicate or endpoint. Uploaded images are
|
| 43 |
+
sent to that configured Space for captioning, so use a Space you trust.
|
| 44 |
+
|
| 45 |
+
The user must:
|
| 46 |
+
|
| 47 |
+
1. Have a Hugging Face account with a payment method configured.
|
| 48 |
+
2. Accept the SDXL base-model license on their Hugging Face account.
|
| 49 |
+
3. Provide a Hugging Face **write** token when prompted. It is passed to the
|
| 50 |
+
temporary trainer and is not written into this repository.
|
| 51 |
+
|
| 52 |
+
Training currently targets the `l40sx1` hardware flavor (48 GB VRAM). The UI's
|
| 53 |
+
cost display is an estimate; Hugging Face billing and hardware pricing are the
|
| 54 |
+
source of truth.
|
| 55 |
+
|
| 56 |
+
## Optional local development
|
| 57 |
+
|
| 58 |
+
Local mode remains useful for testing the UI or training on your own NVIDIA GPU.
|
| 59 |
+
Use Python 3.10 or 3.11, create a virtual environment, install a CUDA-enabled
|
| 60 |
+
PyTorch build appropriate for your driver, and then install the project:
|
| 61 |
+
|
| 62 |
+
```powershell
|
| 63 |
+
py -3.11 -m venv .venv
|
| 64 |
+
.venv\Scripts\Activate.ps1
|
| 65 |
+
# Install CUDA PyTorch first: https://pytorch.org/get-started/locally/
|
| 66 |
+
pip install -r requirements_local.txt
|
| 67 |
+
python app.py
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
SDXL training is memory-intensive. The UI defaults to batch size 1, gradient
|
| 71 |
+
accumulation 4, gradient checkpointing, latent caching, fp16, and 1024px. For local
|
| 72 |
+
out-of-memory errors, keep batch size 1 and try 768px; 1024px is preferred when the
|
| 73 |
+
GPU permits.
|
| 74 |
+
|
| 75 |
+
The base model is gated. Before the first run, accept its license on Hugging Face
|
| 76 |
+
and authenticate locally with `hf auth login`.
|
| 77 |
+
|
| 78 |
+
## Training a Daggerfall-like face style
|
| 79 |
+
|
| 80 |
+
If the images show many different people sharing the same visual language, this is
|
| 81 |
+
a **style LoRA**, not a face/identity LoRA:
|
| 82 |
+
|
| 83 |
+
1. Prepare roughly 20-60 varied, clean portraits. Remove menus, text, cursors,
|
| 84 |
+
duplicates, and unrelated UI. Do not mix modern fan art with original game art
|
| 85 |
+
unless you deliberately want both aesthetics.
|
| 86 |
+
2. Crop loosely around each head and shoulders, keeping several face angles,
|
| 87 |
+
ancestries, genders, expressions, backgrounds, and lighting conditions. Do not
|
| 88 |
+
enlarge tiny source images with heavy smoothing; crisp nearest-neighbor or mild
|
| 89 |
+
restoration usually preserves the pixel-art language better.
|
| 90 |
+
3. In the UI choose **style** and keep the concept sentence
|
| 91 |
+
`in the style of TOK`. `TOK` is the trainer's required token abstraction; do
|
| 92 |
+
not replace it with `daggerfall` or another common word.
|
| 93 |
+
4. Caption what should remain controllable, for example:
|
| 94 |
+
`portrait of an elderly elf man, stern expression, dark background, in the style of TOK`.
|
| 95 |
+
Keep the trigger in every caption, but vary subject traits accurately.
|
| 96 |
+
5. Start with rank 32 and 800-1500 steps. Save/test intermediate versions if you
|
| 97 |
+
raise the step count; overtraining shows up as every output copying the training
|
| 98 |
+
backgrounds, poses, or facial proportions.
|
| 99 |
+
|
| 100 |
+
After uploading the images, click **Caption all images with Mage-VL**. The portrait
|
| 101 |
+
instruction asks for controllable character traits while deliberately excluding
|
| 102 |
+
the shared art style; the concept sentence is appended automatically. Review the
|
| 103 |
+
generated text boxes and correct names, species, clothing, or expressions that the
|
| 104 |
+
captioner guessed incorrectly. **Export captions** downloads the reviewed entries
|
| 105 |
+
as ImageFolder-compatible `metadata.jsonl` for backup or offline editing.
|
| 106 |
+
|
| 107 |
+
Use **character** only if all images are the same fictional character. Use **face**
|
| 108 |
+
only for one consistent real or fictional identity; that preset enables prior
|
| 109 |
+
preservation and is not appropriate for learning a whole game's portrait style.
|
| 110 |
+
|
| 111 |
+
The output folder contains `pytorch_lora_weights.safetensors`, which can be loaded
|
| 112 |
+
as an SDXL LoRA by Diffusers, ComfyUI, or compatible Stable Diffusion frontends.
|
| 113 |
+
Only train and distribute assets you have the rights or permission to use.
|
app.py
CHANGED
|
@@ -1,42 +1,79 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
from PIL import Image
|
| 3 |
-
import requests
|
| 4 |
import subprocess
|
| 5 |
import os
|
| 6 |
is_spaces = True if os.environ.get('SPACE_ID') else False
|
| 7 |
-
|
| 8 |
-
import spaces
|
| 9 |
-
from transformers import Blip2Processor, Blip2ForConditionalGeneration
|
| 10 |
from huggingface_hub import snapshot_download, HfApi
|
| 11 |
-
import torch
|
| 12 |
import uuid
|
| 13 |
import shutil
|
| 14 |
import json
|
| 15 |
import random
|
|
|
|
| 16 |
from slugify import slugify
|
| 17 |
import argparse
|
| 18 |
import importlib
|
| 19 |
import sys
|
| 20 |
from pathlib import Path
|
|
|
|
| 21 |
import zipfile
|
|
|
|
| 22 |
|
| 23 |
MAX_IMAGES = 150
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
| 27 |
orchestrator_script_url = "https://huggingface.co/datasets/multimodalart/lora-ease-helper/raw/main/script.py"
|
| 28 |
-
subprocess.run(['wget', '-N', orchestrator_script_url])
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
FACES_DATASET_PATH = snapshot_download(repo_id="multimodalart/faces-prior-preservation", repo_type="dataset")
|
| 33 |
-
#Delete .gitattributes to process things properly
|
| 34 |
-
Path(FACES_DATASET_PATH, '.gitattributes').unlink(missing_ok=True)
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
training_option_settings = {
|
| 42 |
"face": {
|
|
@@ -46,7 +83,7 @@ training_option_settings = {
|
|
| 46 |
"class_prompt": "a photo of a person",
|
| 47 |
"train_steps_multiplier": 75,
|
| 48 |
"file_count": 150,
|
| 49 |
-
"dataset_path":
|
| 50 |
},
|
| 51 |
"style": {
|
| 52 |
"rank": 32,
|
|
@@ -156,7 +193,7 @@ def change_defaults(option, images):
|
|
| 156 |
|
| 157 |
random_files = []
|
| 158 |
if settings["with_prior_preservation"]:
|
| 159 |
-
directory = settings["dataset_path"]
|
| 160 |
file_count = settings["file_count"]
|
| 161 |
files = [os.path.join(directory, file) for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
|
| 162 |
random_files = random.sample(files, min(len(files), file_count))
|
|
@@ -325,21 +362,26 @@ def start_training(
|
|
| 325 |
spacerunner_args = ';'.join(commands)
|
| 326 |
if not os.path.exists(spacerunner_folder):
|
| 327 |
os.makedirs(spacerunner_folder)
|
| 328 |
-
|
|
|
|
|
|
|
| 329 |
shutil.copy("script.py", f"{spacerunner_folder}/script.py")
|
| 330 |
shutil.copytree(dataset_folder, f"{spacerunner_folder}/{dataset_folder}")
|
| 331 |
-
requirements='''
|
| 332 |
-
|
| 333 |
torch
|
| 334 |
-
|
| 335 |
-
transformers=
|
| 336 |
-
accelerate=
|
| 337 |
-
safetensors=
|
| 338 |
prodigyopt==1.0
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
| 343 |
file_path = f'{spacerunner_folder}/requirements.txt'
|
| 344 |
with open(file_path, 'w') as file:
|
| 345 |
file.write(requirements)
|
|
@@ -495,6 +537,8 @@ def start_training_og(
|
|
| 495 |
commands.append(f"--prodigy_beta3={prodigy_beta3}")
|
| 496 |
if use_adam_weight_decay_text_encoder:
|
| 497 |
commands.append(f"--adam_weight_decay_text_encoder={adam_weight_decay_text_encoder}")
|
|
|
|
|
|
|
| 498 |
from train_dreambooth_lora_sdxl_advanced import main as train_main, parse_args as parse_train_args
|
| 499 |
args = parse_train_args(commands)
|
| 500 |
|
|
@@ -503,27 +547,52 @@ def start_training_og(
|
|
| 503 |
return f"Your model has finished training and has been saved to the `{slugged_lora_name}` folder"
|
| 504 |
|
| 505 |
def run_captioning(*inputs):
|
| 506 |
-
model.to("cuda")
|
| 507 |
images = inputs[0]
|
| 508 |
-
training_option = inputs[-
|
|
|
|
| 509 |
final_captions = [""] * MAX_IMAGES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
for index, image in enumerate(images):
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
|
|
|
|
|
|
| 516 |
if training_option == "style":
|
| 517 |
-
final_caption = generated_text
|
| 518 |
else:
|
| 519 |
-
final_caption =
|
| 520 |
final_captions[index] = final_caption
|
| 521 |
yield final_captions
|
| 522 |
|
| 523 |
-
if is_spaces:
|
| 524 |
-
run_captioning = spaces.GPU()(run_captioning)
|
| 525 |
|
| 526 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
def check_token(token):
|
| 528 |
try:
|
| 529 |
api = HfApi(token=token)
|
|
@@ -578,7 +647,9 @@ with gr.Blocks(css=css, theme=theme) as demo:
|
|
| 578 |
with gr.Column(elem_classes=["main_logged"]) as main_ui:
|
| 579 |
lora_name = gr.Textbox(label="The name of your LoRA", info="This has to be a unique name", placeholder="e.g.: Persian Miniature Painting style, Cat Toy")
|
| 580 |
training_option = gr.Radio(
|
| 581 |
-
label="What are you training?",
|
|
|
|
|
|
|
| 582 |
)
|
| 583 |
concept_sentence = gr.Textbox(
|
| 584 |
label="Concept sentence",
|
|
@@ -604,7 +675,19 @@ with gr.Blocks(css=css, theme=theme) as demo:
|
|
| 604 |
To improve the quality of your outputs, you can add a custom caption for each image, describing exactly what is taking place in each of them. Including TOK is mandatory. You can leave things as is if you don't want to include captioning.
|
| 605 |
"""
|
| 606 |
)
|
| 607 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
output_components = [captioning_area]
|
| 609 |
caption_list = []
|
| 610 |
for i in range(1, MAX_IMAGES + 1):
|
|
@@ -653,7 +736,7 @@ To improve the quality of your outputs, you can add a custom caption for each im
|
|
| 653 |
mixed_precision = gr.Dropdown(
|
| 654 |
label="Mixed Precision",
|
| 655 |
choices=["no", "fp16", "bf16"],
|
| 656 |
-
value="
|
| 657 |
)
|
| 658 |
learning_rate = gr.Number(
|
| 659 |
label="UNet Learning rate",
|
|
@@ -753,12 +836,12 @@ To improve the quality of your outputs, you can add a custom caption for each im
|
|
| 753 |
gradient_accumulation_steps = gr.Number(
|
| 754 |
info="If you change this setting, the pricing calculation will be wrong",
|
| 755 |
label="gradient_accumulation_steps",
|
| 756 |
-
value=
|
| 757 |
)
|
| 758 |
train_batch_size = gr.Number(
|
| 759 |
info="If you change this setting, the pricing calculation will be wrong",
|
| 760 |
label="Train batch size",
|
| 761 |
-
value=
|
| 762 |
)
|
| 763 |
num_train_epochs = gr.Number(
|
| 764 |
info="If you change this setting, the pricing calculation will be wrong",
|
|
@@ -1070,9 +1153,17 @@ If you prefer command line, you can run our [training script]({training_script_u
|
|
| 1070 |
)
|
| 1071 |
|
| 1072 |
do_captioning.click(
|
| 1073 |
-
fn=run_captioning,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1074 |
)
|
| 1075 |
#demo.load(fn=swap_opacity, outputs=[main_ui], queue=False, concurrency_limit=50)
|
| 1076 |
if __name__ == "__main__":
|
| 1077 |
demo.queue()
|
| 1078 |
-
demo.launch(share=True)
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import subprocess
|
| 3 |
import os
|
| 4 |
is_spaces = True if os.environ.get('SPACE_ID') else False
|
| 5 |
+
from gradio_client import Client, handle_file
|
|
|
|
|
|
|
| 6 |
from huggingface_hub import snapshot_download, HfApi
|
|
|
|
| 7 |
import uuid
|
| 8 |
import shutil
|
| 9 |
import json
|
| 10 |
import random
|
| 11 |
+
import hashlib
|
| 12 |
from slugify import slugify
|
| 13 |
import argparse
|
| 14 |
import importlib
|
| 15 |
import sys
|
| 16 |
from pathlib import Path
|
| 17 |
+
import tempfile
|
| 18 |
import zipfile
|
| 19 |
+
from urllib.request import urlretrieve
|
| 20 |
|
| 21 |
MAX_IMAGES = 150
|
| 22 |
|
| 23 |
+
DIFFUSERS_VERSION = "v0.39.0"
|
| 24 |
+
DIFFUSERS_COMMIT = "a3608b512ed7248499a44c61d954965ed9bdae4d"
|
| 25 |
+
TRAINING_SCRIPT = Path("train_dreambooth_lora_sdxl_advanced.py")
|
| 26 |
+
training_script_url = f"https://raw.githubusercontent.com/huggingface/diffusers/{DIFFUSERS_COMMIT}/examples/advanced_diffusion_training/{TRAINING_SCRIPT.name}"
|
| 27 |
orchestrator_script_url = "https://huggingface.co/datasets/multimodalart/lora-ease-helper/raw/main/script.py"
|
|
|
|
| 28 |
|
| 29 |
+
MAGE_VL_SPACE_ID = os.environ.get("MAGE_VL_SPACE_ID", "lienthealien/mage-vl-demo")
|
| 30 |
+
MAGE_VL_API_NAME = os.environ.get("MAGE_VL_API_NAME", "/ask_image")
|
| 31 |
+
mage_vl_client = None
|
| 32 |
+
caption_cache = {}
|
| 33 |
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
def ensure_file(url, destination):
|
| 36 |
+
"""Download a pinned helper only when it is actually needed."""
|
| 37 |
+
destination = Path(destination)
|
| 38 |
+
if not destination.exists():
|
| 39 |
+
print(f"Downloading {destination.name} from {url}")
|
| 40 |
+
urlretrieve(url, destination)
|
| 41 |
+
return destination
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def get_face_prior_dataset():
|
| 45 |
+
dataset_path = snapshot_download(
|
| 46 |
+
repo_id="multimodalart/faces-prior-preservation", repo_type="dataset"
|
| 47 |
+
)
|
| 48 |
+
Path(dataset_path, ".gitattributes").unlink(missing_ok=True)
|
| 49 |
+
return dataset_path
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def get_captioner():
|
| 53 |
+
"""Connect to the dedicated Mage-VL Space without loading a VLM here."""
|
| 54 |
+
global mage_vl_client
|
| 55 |
+
if mage_vl_client is None:
|
| 56 |
+
token = os.environ.get("MAGE_VL_TOKEN") or os.environ.get("HF_TOKEN")
|
| 57 |
+
mage_vl_client = Client(MAGE_VL_SPACE_ID, hf_token=token, verbose=False)
|
| 58 |
+
return mage_vl_client
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def caption_with_mage(client, image, instruction):
|
| 62 |
+
with open(image, "rb") as image_file:
|
| 63 |
+
cache_key = hashlib.sha256(image_file.read() + instruction.encode("utf-8")).hexdigest()
|
| 64 |
+
if cache_key in caption_cache:
|
| 65 |
+
return caption_cache[cache_key]
|
| 66 |
+
|
| 67 |
+
result = client.predict(
|
| 68 |
+
handle_file(image),
|
| 69 |
+
instruction,
|
| 70 |
+
160,
|
| 71 |
+
api_name=MAGE_VL_API_NAME,
|
| 72 |
+
)
|
| 73 |
+
caption = result[0] if isinstance(result, (list, tuple)) else result
|
| 74 |
+
caption = str(caption).strip().rstrip(" .,")
|
| 75 |
+
caption_cache[cache_key] = caption
|
| 76 |
+
return caption
|
| 77 |
|
| 78 |
training_option_settings = {
|
| 79 |
"face": {
|
|
|
|
| 83 |
"class_prompt": "a photo of a person",
|
| 84 |
"train_steps_multiplier": 75,
|
| 85 |
"file_count": 150,
|
| 86 |
+
"dataset_path": None
|
| 87 |
},
|
| 88 |
"style": {
|
| 89 |
"rank": 32,
|
|
|
|
| 193 |
|
| 194 |
random_files = []
|
| 195 |
if settings["with_prior_preservation"]:
|
| 196 |
+
directory = settings["dataset_path"] or get_face_prior_dataset()
|
| 197 |
file_count = settings["file_count"]
|
| 198 |
files = [os.path.join(directory, file) for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
|
| 199 |
random_files = random.sample(files, min(len(files), file_count))
|
|
|
|
| 362 |
spacerunner_args = ';'.join(commands)
|
| 363 |
if not os.path.exists(spacerunner_folder):
|
| 364 |
os.makedirs(spacerunner_folder)
|
| 365 |
+
ensure_file(training_script_url, TRAINING_SCRIPT)
|
| 366 |
+
ensure_file(orchestrator_script_url, "script.py")
|
| 367 |
+
shutil.copy(TRAINING_SCRIPT, f"{spacerunner_folder}/trainer.py")
|
| 368 |
shutil.copy("script.py", f"{spacerunner_folder}/script.py")
|
| 369 |
shutil.copytree(dataset_folder, f"{spacerunner_folder}/{dataset_folder}")
|
| 370 |
+
requirements='''diffusers==0.39.0
|
| 371 |
+
peft>=0.11.1
|
| 372 |
torch
|
| 373 |
+
torchvision
|
| 374 |
+
transformers>=4.41.2
|
| 375 |
+
accelerate>=0.31.0
|
| 376 |
+
safetensors>=0.4.3
|
| 377 |
prodigyopt==1.0
|
| 378 |
+
huggingface_hub>=0.34.0
|
| 379 |
+
hf-transfer>=0.1.4
|
| 380 |
+
datasets>=2.20.0
|
| 381 |
+
ftfy
|
| 382 |
+
tensorboard
|
| 383 |
+
Jinja2
|
| 384 |
+
sentencepiece'''
|
| 385 |
file_path = f'{spacerunner_folder}/requirements.txt'
|
| 386 |
with open(file_path, 'w') as file:
|
| 387 |
file.write(requirements)
|
|
|
|
| 537 |
commands.append(f"--prodigy_beta3={prodigy_beta3}")
|
| 538 |
if use_adam_weight_decay_text_encoder:
|
| 539 |
commands.append(f"--adam_weight_decay_text_encoder={adam_weight_decay_text_encoder}")
|
| 540 |
+
ensure_file(training_script_url, TRAINING_SCRIPT)
|
| 541 |
+
importlib.invalidate_caches()
|
| 542 |
from train_dreambooth_lora_sdxl_advanced import main as train_main, parse_args as parse_train_args
|
| 543 |
args = parse_train_args(commands)
|
| 544 |
|
|
|
|
| 547 |
return f"Your model has finished training and has been saved to the `{slugged_lora_name}` folder"
|
| 548 |
|
| 549 |
def run_captioning(*inputs):
|
|
|
|
| 550 |
images = inputs[0]
|
| 551 |
+
training_option = inputs[-2]
|
| 552 |
+
caption_instruction = inputs[-1].strip()
|
| 553 |
final_captions = [""] * MAX_IMAGES
|
| 554 |
+
try:
|
| 555 |
+
caption_client = get_captioner()
|
| 556 |
+
except Exception as exc:
|
| 557 |
+
raise gr.Error(
|
| 558 |
+
f"Could not connect to Mage-VL Space '{MAGE_VL_SPACE_ID}'. "
|
| 559 |
+
"If it is private, add MAGE_VL_TOKEN as a Hugging Face Space secret. "
|
| 560 |
+
f"Details: {exc}"
|
| 561 |
+
) from exc
|
| 562 |
+
|
| 563 |
for index, image in enumerate(images):
|
| 564 |
+
concept_caption = inputs[index + 1].strip()
|
| 565 |
+
try:
|
| 566 |
+
generated_text = caption_with_mage(caption_client, image, caption_instruction)
|
| 567 |
+
except Exception as exc:
|
| 568 |
+
raise gr.Error(
|
| 569 |
+
f"Mage-VL failed while captioning image {index + 1}: {exc}"
|
| 570 |
+
) from exc
|
| 571 |
if training_option == "style":
|
| 572 |
+
final_caption = f"{generated_text}, {concept_caption}"
|
| 573 |
else:
|
| 574 |
+
final_caption = f"{concept_caption}, {generated_text}"
|
| 575 |
final_captions[index] = final_caption
|
| 576 |
yield final_captions
|
| 577 |
|
|
|
|
|
|
|
| 578 |
|
| 579 |
+
def export_captions(images, *captions):
|
| 580 |
+
if not images:
|
| 581 |
+
raise gr.Error("Upload images before exporting captions.")
|
| 582 |
+
|
| 583 |
+
with tempfile.NamedTemporaryFile(
|
| 584 |
+
mode="w", suffix=".jsonl", prefix="lora-captions-", encoding="utf-8", delete=False
|
| 585 |
+
) as caption_file:
|
| 586 |
+
for image, caption in zip(images, captions):
|
| 587 |
+
caption_file.write(
|
| 588 |
+
json.dumps(
|
| 589 |
+
{"file_name": os.path.basename(image), "prompt": (caption or "").strip()},
|
| 590 |
+
ensure_ascii=False,
|
| 591 |
+
)
|
| 592 |
+
+ "\n"
|
| 593 |
+
)
|
| 594 |
+
return caption_file.name
|
| 595 |
+
|
| 596 |
def check_token(token):
|
| 597 |
try:
|
| 598 |
api = HfApi(token=token)
|
|
|
|
| 647 |
with gr.Column(elem_classes=["main_logged"]) as main_ui:
|
| 648 |
lora_name = gr.Textbox(label="The name of your LoRA", info="This has to be a unique name", placeholder="e.g.: Persian Miniature Painting style, Cat Toy")
|
| 649 |
training_option = gr.Radio(
|
| 650 |
+
label="What are you training?",
|
| 651 |
+
info="For a shared game look across many different faces, choose style. Choose character only when every image depicts the same character.",
|
| 652 |
+
choices=["object", "style", "character", "face", "custom"],
|
| 653 |
)
|
| 654 |
concept_sentence = gr.Textbox(
|
| 655 |
label="Concept sentence",
|
|
|
|
| 675 |
To improve the quality of your outputs, you can add a custom caption for each image, describing exactly what is taking place in each of them. Including TOK is mandatory. You can leave things as is if you don't want to include captioning.
|
| 676 |
"""
|
| 677 |
)
|
| 678 |
+
caption_instruction = gr.Textbox(
|
| 679 |
+
label="AI caption instruction",
|
| 680 |
+
value="Write one concise SDXL training caption for this portrait. Describe only visible, controllable content: character species or ancestry, approximate age, gender presentation, expression, hair, clothing, pose, lighting, and background. Do not identify the game, artist, rendering technique, image quality, or art style. Do not speculate about details that are not visible. Return only a natural comma-separated caption with no introduction.",
|
| 681 |
+
info="The LoRA concept sentence is added automatically to every generated caption.",
|
| 682 |
+
lines=3,
|
| 683 |
+
)
|
| 684 |
+
with gr.Row():
|
| 685 |
+
do_captioning = gr.Button("Caption all images with Mage-VL", variant="primary")
|
| 686 |
+
export_caption_button = gr.Button("Export captions")
|
| 687 |
+
exported_captions = gr.File(
|
| 688 |
+
label="Caption metadata (.jsonl)",
|
| 689 |
+
interactive=False,
|
| 690 |
+
)
|
| 691 |
output_components = [captioning_area]
|
| 692 |
caption_list = []
|
| 693 |
for i in range(1, MAX_IMAGES + 1):
|
|
|
|
| 736 |
mixed_precision = gr.Dropdown(
|
| 737 |
label="Mixed Precision",
|
| 738 |
choices=["no", "fp16", "bf16"],
|
| 739 |
+
value="fp16",
|
| 740 |
)
|
| 741 |
learning_rate = gr.Number(
|
| 742 |
label="UNet Learning rate",
|
|
|
|
| 836 |
gradient_accumulation_steps = gr.Number(
|
| 837 |
info="If you change this setting, the pricing calculation will be wrong",
|
| 838 |
label="gradient_accumulation_steps",
|
| 839 |
+
value=4
|
| 840 |
)
|
| 841 |
train_batch_size = gr.Number(
|
| 842 |
info="If you change this setting, the pricing calculation will be wrong",
|
| 843 |
label="Train batch size",
|
| 844 |
+
value=1
|
| 845 |
)
|
| 846 |
num_train_epochs = gr.Number(
|
| 847 |
info="If you change this setting, the pricing calculation will be wrong",
|
|
|
|
| 1153 |
)
|
| 1154 |
|
| 1155 |
do_captioning.click(
|
| 1156 |
+
fn=run_captioning,
|
| 1157 |
+
inputs=[images] + caption_list + [training_option, caption_instruction],
|
| 1158 |
+
outputs=caption_list,
|
| 1159 |
+
)
|
| 1160 |
+
export_caption_button.click(
|
| 1161 |
+
fn=export_captions,
|
| 1162 |
+
inputs=[images] + caption_list,
|
| 1163 |
+
outputs=exported_captions,
|
| 1164 |
+
queue=False,
|
| 1165 |
)
|
| 1166 |
#demo.load(fn=swap_opacity, outputs=[main_ui], queue=False, concurrency_limit=50)
|
| 1167 |
if __name__ == "__main__":
|
| 1168 |
demo.queue()
|
| 1169 |
+
demo.launch(share=True)
|
requirements.txt
CHANGED
|
@@ -1,9 +1,6 @@
|
|
| 1 |
autotrain-advanced
|
| 2 |
numpy==1.26.4
|
| 3 |
-
|
| 4 |
-
huggingface-hub
|
| 5 |
python-slugify
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
torchvision
|
| 9 |
-
spaces
|
|
|
|
| 1 |
autotrain-advanced
|
| 2 |
numpy==1.26.4
|
| 3 |
+
huggingface-hub==0.27.0
|
|
|
|
| 4 |
python-slugify
|
| 5 |
+
gradio>=4.37,<6
|
| 6 |
+
gradio_client>=1.0
|
|
|
|
|
|
requirements_local.txt
CHANGED
|
@@ -1,14 +1,16 @@
|
|
| 1 |
torch
|
| 2 |
-
python-slugify
|
| 3 |
-
uuid
|
| 4 |
-
torch
|
| 5 |
torchvision
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
diffusers==0.
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
prodigyopt==1.0
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
torch
|
|
|
|
|
|
|
|
|
|
| 2 |
torchvision
|
| 3 |
+
python-slugify
|
| 4 |
+
gradio>=4.37,<6
|
| 5 |
+
diffusers==0.39.0
|
| 6 |
+
peft>=0.11.1
|
| 7 |
+
huggingface-hub>=0.34.0
|
| 8 |
+
transformers>=4.41.2
|
| 9 |
+
accelerate>=0.31.0
|
| 10 |
+
safetensors>=0.4.3
|
| 11 |
prodigyopt==1.0
|
| 12 |
+
datasets>=2.20.0
|
| 13 |
+
ftfy
|
| 14 |
+
tensorboard
|
| 15 |
+
Jinja2
|
| 16 |
+
sentencepiece
|