#!/usr/bin/env python
"""Unofficial demo app for https://github.com/cloneofsimo/lora.
The code in this repo is partly adapted from the following repository:
https://huggingface.co/spaces/multimodalart/dreambooth-training/tree/a00184917aa273c6d8adab08d5deb9b39b997938
The license of the original code is MIT, which is specified in the README.md.
"""
from __future__ import annotations
import os
import pathlib
import gradio as gr
import torch
from inference import InferencePipeline
from trainer import Trainer
from uploader import upload
TITLE = '# LoRA + StableDiffusion Training UI'
DESCRIPTION = 'This is an unofficial demo for [https://github.com/cloneofsimo/lora](https://github.com/cloneofsimo/lora).'
ORIGINAL_SPACE_ID = 'hysts/LoRA-SD-training'
SPACE_ID = os.getenv('SPACE_ID', ORIGINAL_SPACE_ID)
SHARED_UI_WARNING = f'''# Attention - This Space doesn't work in this shared UI. You can duplicate and use it with a paid private T4 GPU.
'''
if os.getenv('SYSTEM') == 'spaces' and SPACE_ID != ORIGINAL_SPACE_ID:
SETTINGS = f'Settings'
else:
SETTINGS = 'Settings'
CUDA_NOT_AVAILABLE_WARNING = f'''# Attention - Running on CPU.
You can assign a GPU in the {SETTINGS} tab if you are running this on HF Spaces.
"T4 small" is sufficient to run this demo.
'''
def show_warning(warning_text: str) -> gr.Blocks:
with gr.Blocks() as demo:
with gr.Box():
gr.Markdown(warning_text)
return demo
def update_output_files() -> dict:
paths = sorted(pathlib.Path('results').glob('*.pt'))
paths = [path.as_posix() for path in paths] # type: ignore
return gr.update(value=paths or None)
def create_training_demo(trainer: Trainer,
pipe: InferencePipeline) -> gr.Blocks:
with gr.Blocks() as demo:
base_model = gr.Dropdown(
choices=['stabilityai/stable-diffusion-2-1-base'],
value='stabilityai/stable-diffusion-2-1-base',
label='Base Model',
visible=False)
resolution = gr.Dropdown(choices=['512'],
value='512',
label='Resolution',
visible=False)
with gr.Row():
with gr.Box():
gr.Markdown('Training Data')
concept_images = gr.Files(label='Images for your concept')
concept_prompt = gr.Textbox(label='Concept Prompt',
max_lines=1)
gr.Markdown('''
- Upload images of the style you are planning on training on.
- For a concept prompt, use a unique, made up word to avoid collisions.
''')
with gr.Box():
gr.Markdown('Training Parameters')
num_training_steps = gr.Number(
label='Number of Training Steps', value=1000, precision=0)
learning_rate = gr.Number(label='Learning Rate', value=0.0001)
train_text_encoder = gr.Checkbox(label='Train Text Encoder',
value=True)
learning_rate_text = gr.Number(
label='Learning Rate for Text Encoder', value=0.00005)
gradient_accumulation = gr.Number(
label='Number of Gradient Accumulation',
value=1,
precision=0)
fp16 = gr.Checkbox(label='FP16', value=True)
use_8bit_adam = gr.Checkbox(label='Use 8bit Adam', value=True)
gr.Markdown('''
- It will take about 8 minutes to train for 1000 steps with a T4 GPU.
- You may want to try a small number of steps first, like 1, to see if everything works fine in your environment.
- Note that your trained models will be deleted when the second training is started. You can upload your trained model in the "Upload" tab.
''')
run_button = gr.Button('Start Training')
with gr.Box():
with gr.Row():
check_status_button = gr.Button('Check Training Status')
with gr.Column():
with gr.Box():
gr.Markdown('Message')
training_status = gr.Markdown()
output_files = gr.Files(label='Trained Weight Files')
run_button.click(fn=pipe.clear)
run_button.click(fn=trainer.run,
inputs=[
base_model,
resolution,
concept_images,
concept_prompt,
num_training_steps,
learning_rate,
train_text_encoder,
learning_rate_text,
gradient_accumulation,
fp16,
use_8bit_adam,
],
outputs=[
training_status,
output_files,
],
queue=False)
check_status_button.click(fn=trainer.check_if_running,
inputs=None,
outputs=training_status,
queue=False)
check_status_button.click(fn=update_output_files,
inputs=None,
outputs=output_files,
queue=False)
return demo
def find_weight_files() -> list[str]:
curr_dir = pathlib.Path(__file__).parent
paths = sorted(curr_dir.rglob('*.pt'))
paths = [path for path in paths if not path.stem.endswith('.text_encoder')]
return [path.relative_to(curr_dir).as_posix() for path in paths]
def reload_lora_weight_list() -> dict:
return gr.update(choices=find_weight_files())
def create_inference_demo(pipe: InferencePipeline) -> gr.Blocks:
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
base_model = gr.Dropdown(
choices=['stabilityai/stable-diffusion-2-1-base'],
value='stabilityai/stable-diffusion-2-1-base',
label='Base Model',
visible=False)
reload_button = gr.Button('Reload Weight List')
lora_weight_name = gr.Dropdown(choices=find_weight_files(),
value='lora/lora_disney.pt',
label='LoRA Weight File')
prompt = gr.Textbox(
label='Prompt',
max_lines=1,
placeholder='Example: "style of sks, baby lion"')
alpha = gr.Slider(label='Alpha',
minimum=0,
maximum=2,
step=0.05,
value=1)
alpha_for_text = gr.Slider(label='Alpha for Text Encoder',
minimum=0,
maximum=2,
step=0.05,
value=1)
seed = gr.Slider(label='Seed',
minimum=0,
maximum=100000,
step=1,
value=1)
with gr.Accordion('Other Parameters', open=False):
num_steps = gr.Slider(label='Number of Steps',
minimum=0,
maximum=100,
step=1,
value=50)
guidance_scale = gr.Slider(label='CFG Scale',
minimum=0,
maximum=50,
step=0.1,
value=7)
run_button = gr.Button('Generate')
gr.Markdown('''
- Models with names starting with "lora/" are the pretrained models provided in the [original repo](https://github.com/cloneofsimo/lora), and the ones with names starting with "results/" are your trained models.
- After training, you can press "Reload Weight List" button to load your trained model names.
- The pretrained models for "disney", "illust" and "pop" are trained with the concept prompt "style of sks".
- The pretrained model for "kiriko" is trained with the concept prompt "game character bnha". For this model, the text encoder is also trained.
''')
with gr.Column():
result = gr.Image(label='Result')
reload_button.click(fn=reload_lora_weight_list,
inputs=None,
outputs=lora_weight_name)
prompt.submit(fn=pipe.run,
inputs=[
base_model,
lora_weight_name,
prompt,
alpha,
alpha_for_text,
seed,
num_steps,
guidance_scale,
],
outputs=result,
queue=False)
run_button.click(fn=pipe.run,
inputs=[
base_model,
lora_weight_name,
prompt,
alpha,
alpha_for_text,
seed,
num_steps,
guidance_scale,
],
outputs=result,
queue=False)
return demo
def create_upload_demo() -> gr.Blocks:
with gr.Blocks() as demo:
model_name = gr.Textbox(label='Model Name')
hf_token = gr.Textbox(
label='Hugging Face Token (with write permission)')
upload_button = gr.Button('Upload')
with gr.Box():
gr.Markdown('Message')
result = gr.Markdown()
gr.Markdown('''
- You can upload your trained model to your private Model repo (i.e. https://huggingface.co/{your_username}/{model_name}).
- You can find your Hugging Face token [here](https://huggingface.co/settings/tokens).
''')
upload_button.click(fn=upload,
inputs=[model_name, hf_token],
outputs=result)
return demo
pipe = InferencePipeline()
trainer = Trainer()
with gr.Blocks(css='style.css') as demo:
if os.getenv('IS_SHARED_UI'):
show_warning(SHARED_UI_WARNING)
if not torch.cuda.is_available():
show_warning(CUDA_NOT_AVAILABLE_WARNING)
gr.Markdown(TITLE)
gr.Markdown(DESCRIPTION)
with gr.Tabs():
with gr.TabItem('Train'):
create_training_demo(trainer, pipe)
with gr.TabItem('Test'):
create_inference_demo(pipe)
with gr.TabItem('Upload'):
create_upload_demo()
demo.queue(default_enabled=False).launch(share=False)