chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:24:13 +08:00
commit 1037506f2e
6050 changed files with 1731598 additions and 0 deletions
+249
View File
@@ -0,0 +1,249 @@
# TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering (ECCV 2024 Oral)
<a href='https://arxiv.org/abs/2311.16465'><img src='https://img.shields.io/badge/Arxiv-2311.16465-red'></a>
<a href='https://github.com/microsoft/unilm/tree/master/textdiffuser-2'><img src='https://img.shields.io/badge/Code-aka.ms/textdiffuser2-yellow'></a>
<a href='https://jingyechen.github.io/textdiffuser2/'><img src='https://img.shields.io/badge/Homepage-link-green'></a>
[![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-TextDiffuser2-blue)](https://huggingface.co/spaces/JingyeChen22/TextDiffuser-2)
<a href='https://discord.gg/HVEcfcwdHv'><img src='https://img.shields.io/badge/Discord-Invitation-purple'></a>
[![Replicate](https://replicate.com/cjwbw/textdiffuser-2/badge)](https://replicate.com/cjwbw/textdiffuser-2)
<img src="assets/readme_images/teaser.jpg" width="100%">
TextDiffuser-2 exhibits enhanced capability powered by language models. In addition to generating text with remarkable accuracy, TextDiffuser-2 provides plausible text layouts and demonstrates a diverse range of text styles.
<img src="assets/readme_images/architecture.jpg" width="100%">
## :star2: Highlights
* We propose **TextDiffuser-2** which utilizes two language models for layout planning and layout encoding, increasing the flexibility and diversity in the process of text rendering.
* **TextDiffuser-2** alleivate several drawbacks in previous methods, such as (1) *limited flexibility and automation*, (2) *constrained capability of layout prediction*, and (3) *Restricted style diversity*.
* **TextDiffuser-2** is capable of handling text-to-image, text-to-image with template, and text inpainting tasks. Moreover, TextDiffuser-2 introduces *an additional feature* - it allows for the editing of generated layouts in a conversational manner.
* ✨ We **release the demo** at [link](https://huggingface.co/spaces/JingyeChen22/TextDiffuser-2). Welcome to use and provide feedbacks.
## :stopwatch: News
- __[2024.08.12]__: TextDiffuser-2 is chosen as oral presentation.
- __[2024.07.01]__: TextDiffuser-2 is accepted to ECCV 2024.
- __[2024.05.19]__: We release the angle/quadrilateral extensions of TextDiffuser-2. Download the checkpoint at [angle](https://drive.google.com/file/d/1zyZT_-iaZQkTO4R2oOT8VQC5WAWIiF5A/view?usp=sharing) or [quadrilateral](https://drive.google.com/file/d/16KcCetsncTfWtM7qQuTSf7Y08RBTDOcV/view?usp=sharing).
- __[2023.12.26]__: Code, model, and demo for the text inpainting task are all released. Welcome to play with it at [link](https://huggingface.co/spaces/JingyeChen22/TextDiffuser-2-Text-Inpainting).
- __[2023.12.12]__: The training and inference code for text-to-image is released. We provide the code for full-parameter training and lora training.
- __[2023.12.10]__: The demo is released at [link](https://huggingface.co/spaces/JingyeChen22/TextDiffuser-2).
- __[2023.11.20]__: The paper is available at [link](https://arxiv.org/pdf/2311.16465.pdf).
## :hammer_and_wrench: Installation
Clone this repo:
```
git clone https://github.com/microsoft/unilm/
cd unilm/tree/master/textdiffuser-2
```
Build up a new environment and install packages as follows:
```
conda create -n textdiffuser2 python=3.8
conda activate textdiffuser2
pip install -r requirements.txt
```
Meanwhile, please install **torch**, **torchvision**, **xformers** that matches the version of system and cuda version (refer to this [link](https://download.pytorch.org/whl/torch_stable.html)). Please also install [flash-attention](https://github.com/Dao-AILab/flash-attention) if you want to train the layout planner using [FastChat](https://github.com/lm-sys/FastChat). We provide the list of packages used in the experiments at [link](./assets/reference_requirements.txt) for your reference.
For training the **text inpainting task**, please install the diffusers package using the command ```pip install https://github.com/JingyeChen/diffusers_td2.git```. Note that the U-Net architecture has been modified for receiving more input features.
<small> If you encounterd an error of *RuntimeError: expected scalar type float Float bu found Half* trigged by *diffusers/models/attention_processor.py*, please use [attention_processor.py](./assets/attention_processor.py) to replace the corresponding file in the installed diffusers library. </small>
## :floppy_disk: Checkpoint
We upload the checkpoints to HuggingFace🤗.
* The checkpoint of layout planner is at [link](https://huggingface.co/JingyeChen22/textdiffuser2_layout_planner).
* The checkpoint of diffusion model (full parameter fine-tuning) is at [link](https://huggingface.co/JingyeChen22/textdiffuser2-full-ft).
* The checkpoint of diffusion model (lora fine-tuning) is at [link](https://huggingface.co/JingyeChen22/textdiffuser2-lora-ft).
Note that we provide the checkpoint with context length 77 as it performs better results when rendering general objects.
## :books: Dataset
The data for training the layout planner is at [link](./data/layout_planner_data_5k.json).
We employ the MARIO-10M dataset for training TextDiffuser-2. Please follow the **Dataset** section at [TextDiffuser](https://github.com/microsoft/unilm/tree/master/textdiffuser) to download the dataset, inluding the **train_dataset_index_file**.
The train_dataset_index_file should be a .txt file, and each line should indicate an index of a training sample.
```txt
06269_062690093
27197_271975251
27197_271978467
...
```
## :steam_locomotive: Train
### Train layout planner
```bash
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 torchrun --nproc_per_node=8 --master_port=20003 fastchat/train/train_mem.py \
--model_name_or_path lmsys/vicuna-7b-v1.5 \
--data_path data/layout_planner_data_5k.json \
--bf16 True \
--output_dir experiment_result \
--num_train_epochs 6 \
--per_device_train_batch_size 2 \
--per_device_eval_batch_size 2 \
--gradient_accumulation_steps 16 \
--evaluation_strategy "no" \
--save_strategy "steps" \
--save_steps 500 \
--save_total_limit 5 \
--learning_rate 2e-5 \
--weight_decay 0. \
--warmup_ratio 0.03 \
--lr_scheduler_type "cosine" \
--logging_steps 1 \
--fsdp "full_shard auto_wrap" \
--fsdp_transformer_layer_cls_to_wrap 'LlamaDecoderLayer' \
--tf32 True \
--model_max_length 2048 \
--gradient_checkpointing True \
--lazy_preprocess True
```
It is normal that the loss curve seems like a staircase:
<img src="assets/readme_images/m1_loss.jpg" width="80%">
### Train diffusion model
For full-parameter training:
```bash
accelerate launch train_textdiffuser2_t2i_full.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_batch_size=18 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-5 \
--max_grad_norm=1 \
--lr_scheduler="constant" \
--lr_warmup_steps=0 \
--output_dir="diffusion_experiment_result" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--index_file_path='/path/to/train_dataset_index.txt' \
--dataset_path='/path/to/laion-ocr-select/' \
--granularity=128 \
--coord_mode="ltrb" \
--max_length=77 \
--resume_from_checkpoint="latest"
```
For LoRA training:
```bash
accelerate launch train_textdiffuser2_t2i_lora.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_batch_size=18 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-4 \
--text_encoder_learning_rate=1e-5 \
--lr_scheduler="constant" \
--output_dir="diffusion_experiment_result" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--index_file_path='/path/to/train_dataset_index.txt' \
--dataset_path='/path/to/laion-ocr-select/' \
--granularity=128 \
--coord_mode="ltrb" \
--max_length=77 \
--resume_from_checkpoint="latest"
```
If you encounter an "out-of-memory" error, please consider reducing the batch size appropriately.
## :firecracker: Inference
For full-parameter inference:
```bash
accelerate launch inference_textdiffuser2_t2i_full.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--mixed_precision="fp16" \
--output_dir="inference_results" \
--enable_xformers_memory_efficient_attention \
--resume_from_checkpoint="JingyeChen22/textdiffuser2-full-ft" \
--granularity=128 \
--max_length=77 \
--coord_mode="ltrb" \
--cfg=7.5 \
--sample_steps=20 \
--seed=43555 \
--m1_model_path="JingyeChen22/textdiffuser2_layout_planner" \
--input_format='prompt' \
--input_prompt='a hotdog with mustard and other toppings on it'
```
For LoRA inference:
```bash
accelerate launch inference_textdiffuser2_t2i_lora.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--output_dir="inference_results" \
--enable_xformers_memory_efficient_attention \
--resume_from_checkpoint="JingyeChen22/textdiffuser2-lora-ft" \
--granularity=128 \
--coord_mode="ltrb" \
--cfg=7.5 \
--sample_steps=50 \
--seed=43555 \
--m1_model_path="JingyeChen22/textdiffuser2_layout_planner" \
--input_format='prompt' \
--input_prompt='a stamp of u.s.a'
```
## :joystick: Demo
TextDiffuser-2 has been deployed on [Hugging Face](https://huggingface.co/spaces/JingyeChen22/TextDiffuser-2). Welcome to play with it! You can also run ```python gradio_demo.py``` to use the demo locally.
<img src="assets/readme_images/demo.jpg" width="100%">
## :love_letter: Acknowledgement
We sincerely thank [AK](https://huggingface.co/akhaliq) and [hysts](https://huggingface.co/hysts) for helping set up the demo. We also feel thankful for the available code/api/demo of [SDXL](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0), [PixArt](https://pixart-alpha.github.io/), [Ideogram](https://ideogram.ai/), [DALLE-3](https://openai.com/dall-e-3), and [GlyphControl](https://huggingface.co/spaces/AIGText/GlyphControl).
## :exclamation: Disclaimer
Please note that the code is intended for academic and research purposes **ONLY**. Any use of the code for generating inappropriate content is **strictly prohibited**. The responsibility for any misuse or inappropriate use of the code lies solely with the users who generated such content, and this code shall not be held liable for any such use.
## :envelope: Contact
For help or issues using TextDiffuser-2, please email Jingye Chen (qwerty.chen@connect.ust.hk), Yupan Huang (huangyp28@mail2.sysu.edu.cn) or submit a GitHub issue.
For other communications related to TextDiffuser-2, please contact Lei Cui (lecu@microsoft.com) or Furu Wei (fuwei@microsoft.com).
## :herb: Citation
If you find TextDiffuser-2 useful in your research, please consider citing:
```
@article{chen2023textdiffuser,
title={TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering},
author={Chen, Jingye and Huang, Yupan and Lv, Tengchao and Cui, Lei and Chen, Qifeng and Wei, Furu},
journal={arXiv preprint arXiv:2311.16465},
year={2023}
}
```
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

@@ -0,0 +1,130 @@
accelerate==0.22.0
aiofiles==23.2.1
aiohttp==3.9.1
aiosignal==1.3.1
altair==5.2.0
anyio==3.7.1
async-timeout==4.0.3
attrs==23.1.0
certifi==2023.11.17
charset-normalizer==3.3.2
click==8.1.7
cmake==3.27.9
contourpy==1.1.1
cycler==0.12.1
datasets==2.11.0
diffusers==0.24.0
dill==0.3.6
einops==0.7.0
exceptiongroup==1.2.0
fastapi==0.104.1
ffmpy==0.3.1
filelock==3.13.1
flash-attn==2.1.2.post3
fonttools==4.46.0
frozenlist==1.4.0
fschat==0.2.26
fsspec==2023.12.0
gradio==3.50.2
gradio_client==0.6.1
h11==0.14.0
httpcore==1.0.2
httpx==0.25.2
huggingface-hub==0.19.4
idna==3.6
importlib-metadata==7.0.0
importlib-resources==6.1.1
Jinja2==3.1.2
jsonschema==4.20.0
jsonschema-specifications==2023.11.2
kiwisolver==1.4.5
lit==17.0.6
markdown-it-py==3.0.0
markdown2==2.4.11
MarkupSafe==2.1.3
matplotlib==3.7.4
mdurl==0.1.2
mpmath==1.3.0
multidict==6.0.4
multiprocess==0.70.14
networkx==3.1
nh3==0.2.14
ninja==1.11.1.1
numpy==1.24.4
nvidia-cublas-cu11==11.10.3.66
nvidia-cublas-cu12==12.1.3.1
nvidia-cuda-cupti-cu11==11.7.101
nvidia-cuda-cupti-cu12==12.1.105
nvidia-cuda-nvrtc-cu11==11.7.99
nvidia-cuda-nvrtc-cu12==12.1.105
nvidia-cuda-runtime-cu11==11.7.99
nvidia-cuda-runtime-cu12==12.1.105
nvidia-cudnn-cu11==8.5.0.96
nvidia-cudnn-cu12==8.9.2.26
nvidia-cufft-cu11==10.9.0.58
nvidia-cufft-cu12==11.0.2.54
nvidia-curand-cu11==10.2.10.91
nvidia-curand-cu12==10.3.2.106
nvidia-cusolver-cu11==11.4.0.1
nvidia-cusolver-cu12==11.4.5.107
nvidia-cusparse-cu11==11.7.4.91
nvidia-cusparse-cu12==12.1.0.106
nvidia-nccl-cu11==2.14.3
nvidia-nccl-cu12==2.18.1
nvidia-nvjitlink-cu12==12.3.101
nvidia-nvtx-cu11==11.7.91
nvidia-nvtx-cu12==12.1.105
opencv-python==4.8.1.78
orjson==3.9.10
packaging==23.2
pandas==2.0.3
peft==0.6.2
Pillow==10.1.0
pkgutil_resolve_name==1.3.10
prompt-toolkit==3.0.41
protobuf==4.25.1
psutil==5.9.6
pyarrow==14.0.1
pydantic==1.10.13
pydub==0.25.1
Pygments==2.17.2
pyparsing==3.1.1
python-dateutil==2.8.2
python-multipart==0.0.6
pytz==2023.3.post1
PyYAML==6.0.1
referencing==0.31.1
regex==2023.10.3
requests==2.31.0
responses==0.18.0
rich==13.7.0
rpds-py==0.13.2
safetensors==0.4.1
semantic-version==2.10.0
sentencepiece==0.1.99
shortuuid==1.0.11
six==1.16.0
sniffio==1.3.0
starlette==0.27.0
svgwrite==1.4.3
sympy==1.12
termcolor==2.4.0
tiktoken==0.5.2
tokenizers==0.13.3
toolz==0.12.0
torch @ https://download.pytorch.org/whl/cu117/torch-2.0.1%2Bcu117-cp38-cp38-linux_x86_64.whl#sha256=bec39e6fe7232f399c6a5cda5785517fec759fc0852e0c31d71a39f7bf6b23b3
torchvision==0.15.2
tqdm==4.66.1
transformers==4.28.1
triton==2.0.0
typing_extensions==4.8.0
tzdata==2023.3
urllib3==2.1.0
uvicorn==0.24.0.post1
wavedrom==2.0.3.post3
wcwidth==0.2.12
websockets==11.0.3
xformers==0.0.21
xxhash==3.4.1
yarl==1.9.3
zipp==3.17.0
+16
View File
@@ -0,0 +1,16 @@
# Configuration for Cog ⚙️
# Reference: https://github.com/replicate/cog/blob/main/docs/yaml.md
build:
gpu: true
python_version: "3.11"
python_packages:
- torch==2.0.1
- torchvision==0.15.2
- diffusers==0.24.0
- accelerate==0.22.0
- datasets==2.11.0
- transformers==4.28.1
- fschat==0.2.26
- sentencepiece==0.1.99
predict: "predict.py:Predictor"
@@ -0,0 +1,33 @@
import json
import random
from PIL import Image, ImageDraw, ImageFont
f = open(f'./layout_planner_data_5k.json')
items = json.load(f)
# print(len(items))
dic = random.sample(items, k=1)
print(dic)
layout = dic[0]['conversations'][1]['value']
print(layout)
blank = Image.new('RGB', (256,256), (0,0,0))
draw = ImageDraw.ImageDraw(blank)
font = ImageFont.truetype('../assets/arial.ttf', 16)
for line in layout.split('\n'):
line = line.strip()
if len(line) == 0:
break
pred = ' '.join(line.split()[:-1])
box = line.split()[-1]
l, t, r, b = [int(i)*2 for i in box.split(',')] # the size of canvas is 256x256
draw.rectangle([(l, t), (r, b)], outline ="red")
draw.text((l, t), pred, font=font)
blank.save('test.jpg')
f.close()
print('Visualizations are successfully saved at ./test.jpg')
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,693 @@
import os
import re
import zipfile
import torch
import gradio as gr
print('hello', gr.__version__)
import numpy as np
import time
from transformers import CLIPTextModel, CLIPTokenizer
from diffusers import AutoencoderKL, DDPMScheduler, StableDiffusionPipeline, UNet2DConditionModel, DiffusionPipeline
from tqdm import tqdm
from PIL import Image
from PIL import Image, ImageDraw, ImageFont
import random
import copy
import string
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + ' ' # len(aphabet) = 95
'''alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
'''
if not os.path.exists('images2'):
os.system('wget https://huggingface.co/datasets/JingyeChen22/TextDiffuser/resolve/main/images2.zip')
with zipfile.ZipFile('images2.zip', 'r') as zip_ref:
zip_ref.extractall('.')
# os.system('nvidia-smi')
os.system('ls')
#### import diffusion models
text_encoder = CLIPTextModel.from_pretrained(
'JingyeChen22/textdiffuser2-full-ft-inpainting', subfolder="text_encoder"
).cuda().half()
tokenizer = CLIPTokenizer.from_pretrained(
'runwayml/stable-diffusion-v1-5', subfolder="tokenizer"
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print('***************')
print(len(tokenizer))
for i in range(520):
tokenizer.add_tokens(['l' + str(i) ]) # left
tokenizer.add_tokens(['t' + str(i) ]) # top
tokenizer.add_tokens(['r' + str(i) ]) # width
tokenizer.add_tokens(['b' + str(i) ]) # height
for c in alphabet:
tokenizer.add_tokens([f'[{c}]'])
print(len(tokenizer))
print('***************')
vae = AutoencoderKL.from_pretrained('runwayml/stable-diffusion-v1-5', subfolder="vae").half().cuda()
unet = UNet2DConditionModel.from_pretrained(
'JingyeChen22/textdiffuser2-full-ft-inpainting', subfolder="unet"
).half().cuda()
text_encoder.resize_token_embeddings(len(tokenizer))
global_dict = {}
#### for interactive
# stack = []
# state = 0
font = ImageFont.truetype("./Arial.ttf", 20)
def skip_fun(i, t, guest_id):
global_dict[guest_id]['state'] = 0
# global state
# state = 0
def exe_undo(i, orig_i, t, guest_id):
global_dict[guest_id]['stack'] = []
global_dict[guest_id]['state'] = 0
return copy.deepcopy(orig_i)
def exe_redo(i, orig_i, t, guest_id):
print('redo ',orig_i)
if type(orig_i) == str:
orig_i = Image.open(orig_i)
# global state
# state = 0
global_dict[guest_id]['state'] = 0
if len(global_dict[guest_id]['stack']) > 0:
global_dict[guest_id]['stack'].pop()
image = copy.deepcopy(orig_i)
draw = ImageDraw.Draw(image)
for items in global_dict[guest_id]['stack']:
# print('now', items)
text_position, t = items
if len(text_position) == 2:
x, y = text_position
text_color = (255, 0, 0)
draw.text((x+2, y), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x-r, y-r)
rightDownPoint = (x+r, y+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
elif len(text_position) == 4:
x0, y0, x1, y1 = text_position
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.rectangle((x0,y0,x1,y1), outline=(255, 0, 0) )
print('stack', global_dict[guest_id]['stack'])
return image
def get_pixels(i, orig_i, radio, t, guest_id, evt: gr.SelectData):
print('hi1 ', i)
print('hi2 ', orig_i)
width, height = Image.open(i).size
# register
if guest_id == '-1': # register for the first time
seed = str(int(time.time()))
global_dict[str(seed)] = {
'state': 0,
'stack': [],
'image_id': [list(Image.open(i).resize((512,512)).getdata())] # an image has been recorded
}
guest_id = str(seed)
else:
seed = guest_id
if type(i) == str:
i = Image.open(i)
i = i.resize((512,512))
images = global_dict[str(seed)]['image_id']
flag = False
for image in images:
if image == list(i.getdata()):
print('find it')
flag = True
break
if not flag:
global_dict[str(seed)]['image_id'] = [list(i.getdata())]
global_dict[str(seed)]['stack'] = []
global_dict[str(seed)]['state'] = 0
orig_i = i
else:
if orig_i is not None:
orig_i = Image.open(orig_i)
orig_i = orig_i.resize((512,512))
else:
orig_i = i
global_dict[guest_id]['stack'] = []
global_dict[guest_id]['state'] = 0
text_position = evt.index
print('hello ', text_position)
if radio == 'Two Points':
if global_dict[guest_id]['state'] == 0:
global_dict[guest_id]['stack'].append(
(text_position, t)
)
print(text_position, global_dict[guest_id]['stack'])
global_dict[guest_id]['state'] = 1
else:
(_, t) = global_dict[guest_id]['stack'].pop()
x, y = _
global_dict[guest_id]['stack'].append(
((x,y,text_position[0],text_position[1]), t)
)
global_dict[guest_id]['state'] = 0
image = copy.deepcopy(orig_i)
draw = ImageDraw.Draw(image)
for items in global_dict[guest_id]['stack']:
text_position, t = items
if len(text_position) == 2:
x, y = text_position
x = int(512 * x / width)
y = int(512 * y / height)
text_color = (255, 0, 0)
draw.text((x+2, y), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x-r, y-r)
rightDownPoint = (x+r, y+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
elif len(text_position) == 4:
x0, y0, x1, y1 = text_position
x0 = int(512 * x0 / width)
x1 = int(512 * x1 / width)
y0 = int(512 * y0 / height)
y1 = int(512 * y1 / height)
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.rectangle((x0,y0,x1,y1), outline=(255, 0, 0) )
elif radio == 'Four Points':
if global_dict[guest_id]['state'] == 0:
global_dict[guest_id]['stack'].append(
(text_position, t)
)
print(text_position, global_dict[guest_id]['stack'])
global_dict[guest_id]['state'] = 1
elif global_dict[guest_id]['state'] == 1:
(_, t) = global_dict[guest_id]['stack'].pop()
x, y = _
global_dict[guest_id]['stack'].append(
((x,y,text_position[0],text_position[1]), t)
)
global_dict[guest_id]['state'] = 2
elif global_dict[guest_id]['state'] == 2:
(_, t) = global_dict[guest_id]['stack'].pop()
x0, y0, x1, y1 = _
global_dict[guest_id]['stack'].append(
((x0, y0, x1, y1,text_position[0],text_position[1]), t)
)
global_dict[guest_id]['state'] = 3
elif global_dict[guest_id]['state'] == 3:
(_, t) = global_dict[guest_id]['stack'].pop()
x0, y0, x1, y1, x2, y2 = _
global_dict[guest_id]['stack'].append(
((x0, y0, x1, y1, x2, y2,text_position[0],text_position[1]), t)
)
global_dict[guest_id]['state'] = 0
image = copy.deepcopy(orig_i)
draw = ImageDraw.Draw(image)
for items in global_dict[guest_id]['stack']:
text_position, t = items
if len(text_position) == 2:
x, y = text_position
x = int(512 * x / width)
y = int(512 * y / height)
text_color = (255, 0, 0)
draw.text((x+2, y), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x-r, y-r)
rightDownPoint = (x+r, y+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
elif len(text_position) == 4:
x0, y0, x1, y1 = text_position
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.line(((x0,y0),(x1,y1)), fill=(255, 0, 0) )
elif len(text_position) == 6:
x0, y0, x1, y1, x2, y2 = text_position
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.line(((x0,y0),(x1,y1)), fill=(255, 0, 0) )
draw.line(((x1,y1),(x2,y2)), fill=(255, 0, 0) )
elif len(text_position) == 8:
x0, y0, x1, y1, x2, y2, x3, y3 = text_position
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.line(((x0,y0),(x1,y1)), fill=(255, 0, 0) )
draw.line(((x1,y1),(x2,y2)), fill=(255, 0, 0) )
draw.line(((x2,y2),(x3,y3)), fill=(255, 0, 0) )
draw.line(((x3,y3),(x0,y0)), fill=(255, 0, 0) )
print('stack', global_dict[guest_id]['stack'])
global_dict[str(seed)]['image_id'].append(list(image.getdata()))
return image, orig_i, seed
font_layout = ImageFont.truetype('./Arial.ttf', 16)
def get_layout_image(ocrs):
blank = Image.new('RGB', (256,256), (0,0,0))
draw = ImageDraw.ImageDraw(blank)
for line in ocrs.split('\n'):
line = line.strip()
if len(line) == 0:
break
pred = ' '.join(line.split()[:-1])
box = line.split()[-1]
l, t, r, b = [int(i)*2 for i in box.split(',')] # the size of canvas is 256x256
draw.rectangle([(l, t), (r, b)], outline ="red")
draw.text((l, t), pred, font=font_layout)
return blank
def to_tensor(image):
if isinstance(image, Image.Image):
image = np.array(image)
elif not isinstance(image, np.ndarray):
raise TypeError("Error")
image = image.astype(np.float32) / 255.0
image = np.transpose(image, (2, 0, 1))
tensor = torch.from_numpy(image)
return tensor
def test_fn(x,y):
print('hello')
def text_to_image(guest_id, i, orig_i, prompt,keywords,positive_prompt,radio,slider_step,slider_guidance,slider_batch,slider_temperature,slider_natural):
# print(type(i))
# exit(0)
print(f'[info] Prompt: {prompt} | Keywords: {keywords} | Radio: {radio} | Steps: {slider_step} | Guidance: {slider_guidance} | Natural: {slider_natural}')
# global stack
# global state
if len(positive_prompt.strip()) != 0:
prompt += positive_prompt
with torch.no_grad():
time1 = time.time()
user_prompt = prompt
if slider_natural:
user_prompt = f'{user_prompt}'
composed_prompt = user_prompt
prompt = tokenizer.encode(user_prompt)
layout_image = None
else:
if guest_id not in global_dict or len(global_dict[guest_id]['stack']) == 0:
if len(keywords.strip()) == 0:
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}'
else:
keywords = keywords.split('/')
keywords = [i.strip() for i in keywords]
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. In addition, we also provide all keywords at random order for reference. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {prompt}. Keywords: {str(keywords)}'
msg = template
conv = get_conversation_template(m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to('cuda') for k, v in inputs.items()}
output_ids = m1_model.generate(
**inputs,
do_sample=True,
temperature=slider_temperature,
repetition_penalty=1.0,
max_new_tokens=512,
)
if m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = m1_tokenizer.decode(
output_ids, skip_special_tokens=True, spaces_between_special_tokens=False
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
layout_image = get_layout_image(outputs)
ocrs = outputs.split('\n')
time2 = time.time()
print(time2-time1)
# user_prompt = prompt
current_ocr = ocrs
ocr_ids = []
print('user_prompt', user_prompt)
print('current_ocr', current_ocr)
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or '###' in ocr or '.com' in ocr:
continue
items = ocr.split()
pred = ' '.join(items[:-1])
box = items[-1]
l,t,r,b = box.split(',')
l,t,r,b = int(l), int(t), int(r), int(b)
ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b)])
char_list = list(pred)
char_list = [f'[{i}]' for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(tokenizer.eos_token_id)
caption_ids = tokenizer(
user_prompt, truncation=True, return_tensors="pt"
).input_ids[0].tolist()
try:
ocr_ids = tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
user_prompt = tokenizer.decode(prompt)
composed_prompt = tokenizer.decode(prompt)
else:
user_prompt += ' <|endoftext|><|startoftext|>'
layout_image = None
image_mask = Image.new('L', (512,512), 0)
draw = ImageDraw.Draw(image_mask)
for items in global_dict[guest_id]['stack']:
position, text = items
# feature_mask
# masked_feature
if len(position) == 2:
x, y = position
x = x // 4
y = y // 4
text_str = ' '.join([f'[{c}]' for c in list(text)])
user_prompt += f' l{x} t{y} {text_str} <|endoftext|>'
elif len(position) == 4:
x0, y0, x1, y1 = position
x0 = x0 // 4
y0 = y0 // 4
x1 = x1 // 4
y1 = y1 // 4
text_str = ' '.join([f'[{c}]' for c in list(text)])
user_prompt += f' l{x0} t{y0} r{x1} b{y1} {text_str} <|endoftext|>'
draw.rectangle((x0*4, y0*4, x1*4, y1*4), fill=1)
print('prompt ', user_prompt)
elif len(position) == 8: # four points
x0, y0, x1, y1, x2, y2, x3, y3 = position
draw.polygon([(x0, y0), (x1, y1), (x2, y2), (x3, y3)], fill=1)
x0 = x0 // 4
y0 = y0 // 4
x1 = x1 // 4
y1 = y1 // 4
x2 = x2 // 4
y2 = y2 // 4
x3 = x3 // 4
y3 = y3 // 4
xmin = min(x0, x1, x2, x3)
ymin = min(y0, y1, y2, y3)
xmax = max(x0, x1, x2, x3)
ymax = max(y0, y1, y2, y3)
text_str = ' '.join([f'[{c}]' for c in list(text)])
user_prompt += f' l{xmin} t{ymin} r{xmax} b{ymax} {text_str} <|endoftext|>'
print('prompt ', user_prompt)
prompt = tokenizer.encode(user_prompt)
composed_prompt = tokenizer.decode(prompt)
prompt = prompt[:77]
while len(prompt) < 77:
prompt.append(tokenizer.pad_token_id)
prompts_cond = prompt
prompts_nocond = [tokenizer.pad_token_id]*77
prompts_cond = [prompts_cond] * slider_batch
prompts_nocond = [prompts_nocond] * slider_batch
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = DDPMScheduler.from_pretrained('runwayml/stable-diffusion-v1-5', subfolder="scheduler")
scheduler.set_timesteps(slider_step)
noise = torch.randn((slider_batch, 4, 64, 64)).to("cuda").half()
input = noise
encoder_hidden_states_cond = text_encoder(prompts_cond)[0].half()
encoder_hidden_states_nocond = text_encoder(prompts_nocond)[0].half()
image_mask = torch.Tensor(np.array(image_mask)).float().half().cuda()
image_mask = image_mask.unsqueeze(0).unsqueeze(0).repeat(slider_batch, 1, 1, 1)
image = Image.open(orig_i).resize((512,512))
image_tensor = to_tensor(image).unsqueeze(0).cuda().sub_(0.5).div_(0.5)
print(f'image_tensor.shape {image_tensor.shape}')
masked_image = image_tensor * (1-image_mask)
masked_feature = vae.encode(masked_image.half()).latent_dist.sample()
masked_feature = masked_feature * vae.config.scaling_factor
masked_feature = masked_feature.half()
print(f'masked_feature.shape {masked_feature.shape}')
feature_mask = torch.nn.functional.interpolate(image_mask, size=(64,64), mode='nearest').cuda()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = unet(sample=input, timestep=t, encoder_hidden_states=encoder_hidden_states_cond[:slider_batch],feature_mask=feature_mask, masked_feature=masked_feature).sample # b, 4, 64, 64
noise_pred_uncond = unet(sample=input, timestep=t, encoder_hidden_states=encoder_hidden_states_nocond[:slider_batch],feature_mask=feature_mask, masked_feature=masked_feature).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + slider_guidance * (noise_pred_cond - noise_pred_uncond) # b, 4, 64, 64
input = scheduler.step(noisy_residual, t, input).prev_sample
del noise_pred_cond
del noise_pred_uncond
torch.cuda.empty_cache()
# decode
input = 1 / vae.config.scaling_factor * input
images = vae.decode(input, return_dict=False)[0]
width, height = 512, 512
results = []
new_image = Image.new('RGB', (2*width, 2*height))
for index, image in enumerate(images.cpu().float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8")).convert('RGB')
results.append(image)
row = index // 2
col = index % 2
new_image.paste(image, (col*width, row*height))
# os.system('nvidia-smi')
torch.cuda.empty_cache()
# os.system('nvidia-smi')
return tuple(results), composed_prompt
with gr.Blocks() as demo:
gr.HTML(
"""
<div style="text-align: center; max-width: 1600px; margin: 20px auto;">
<h2 style="font-weight: 900; font-size: 2.3rem; margin: 0rem">
TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering
</h2>
<h2 style="font-weight: 900; font-size: 1.3rem; margin: 0rem">
(Demo for <b>Text Inpainting</b> 🖼️🖌️)
</h2>
<h2 style="font-weight: 460; font-size: 1.1rem; margin: 0rem">
<a href="https://jingyechen.github.io/">Jingye Chen</a>, <a href="https://hypjudy.github.io/website/">Yupan Huang</a>, <a href="https://scholar.google.com/citations?user=0LTZGhUAAAAJ&hl=en">Tengchao Lv</a>, <a href="https://www.microsoft.com/en-us/research/people/lecu/">Lei Cui</a>, <a href="https://cqf.io/">Qifeng Chen</a>, <a href="https://thegenerality.com/">Furu Wei</a>
</h2>
<h2 style="font-weight: 460; font-size: 1.1rem; margin: 0rem">
HKUST, Sun Yat-sen University, Microsoft Research
</h2>
<h3 style="font-weight: 450; font-size: 1rem; margin: 0rem">
[<a href="https://arxiv.org/abs/2311.16465" style="color:blue;">arXiv</a>]
[<a href="https://github.com/microsoft/unilm/tree/master/textdiffuser-2" style="color:blue;">Code</a>]
[<a href="https://jingyechen.github.io/textdiffuser2/" style="color:blue;">Project Page</a>]
[<a href="https://discord.gg/q7eHPupu" style="color:purple;">Discord</a>]
</h3>
<h2 style="text-align: left; font-weight: 450; font-size: 1rem; margin-top: 0.5rem; margin-bottom: 0.5rem">
TextDiffuser-2 leverages language models to enhance text rendering, achieving greater flexibility. Different from text editing, the text inpainting task aims to add or modify text guided by users, ensuring that the inpainted text has a reasonable style (i.e., no need to match the style of the original text during modification exactly) and is coherent with backgrounds. TextDiffuser-2 offers an <b>improved user experience</b>. Specifically, users only need to type the text they wish to inpaint into the provided input box and then select key points on the Canvas.
</h2>
<h2 style="text-align: left; font-weight: 450; font-size: 1rem; margin-top: 0.5rem; margin-bottom: 0.5rem">
👀 <b>Tips for using this demo</b>: <b>(1)</b> Please carefully read the disclaimer in the below. Current verison can only support English. <b>(2)</b> The <b>prompt is optional</b>. If provided, the generated image may be more accurate. <b>(3)</b> Redo is used to cancel the last keyword, and undo is used to clear all keywords. <b>(4)</b> Current version only supports input image with resolution 512x512. <b>(5)</b> You can use either two points or four points to specify the text box. Using four points can better represent the perspective boxes. <b>(6)</b> Leave "Text to be inpaintd" empty can function as the text removal task. <b>(7)</b> Classifier-free guidance is set to a small value (e.g. 1) in default. It is noticed that a larger cfg may result in chromatic aberration against the background. <b>(8)</b> You can inpaint many text regions at one time. <b>(9)</b> Thanks for reading these tips, shall we start now?
</h2>
<img src="https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/inpainting_blank.jpg" alt="textdiffuser-2">
</div>
""")
with gr.Tab("Text Inpainting"):
with gr.Row():
with gr.Column():
keywords = gr.Textbox(label="(Optional) Keywords. Should be seperated by / (e.g., keyword1/keyword2/...)", placeholder="keyword1/keyword2", visible=False)
positive_prompt = gr.Textbox(label="(Optional) Positive prompt", value="", visible=False)
i = gr.Image(label="Image", type='filepath', value='https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example11.jpg')
orig_i = gr.Image(label="Placeholder", type='filepath', height=512, width=512, visible=False)
radio = gr.Radio(["Two Points", "Four Points"], label="Number of points to represent the text box.", value="Two Points", visible=True)
with gr.Row():
t = gr.Textbox(label="Text to be inpainted", value='Test')
prompt = gr.Textbox(label="(Optional) Prompt.")
with gr.Row():
redo = gr.Button(value='Redo - Cancel the last keyword')
undo = gr.Button(value='Undo - Clear the canvas')
# skip_button = gr.Button(value='Skip - Operate the next keyword')
slider_natural = gr.Checkbox(label="Natural image generation", value=False, info="The text position and content info will not be incorporated.", visible=False)
slider_step = gr.Slider(minimum=1, maximum=50, value=20, step=1, label="Sampling step", info="The sampling step for TextDiffuser-2.")
slider_guidance = gr.Slider(minimum=1, maximum=13, value=1, step=0.5, label="Scale of classifier-free guidance", info="The scale of cfg and is set to 1 in default. Smaller cfg produce stable results.")
slider_batch = gr.Slider(minimum=1, maximum=6, value=4, step=1, label="Batch size", info="The number of images to be sampled.")
slider_temperature = gr.Slider(minimum=0.1, maximum=2, value=1.4, step=0.1, label="Temperature", info="Control the diversity of layout planner. Higher value indicates more diversity.", visible=False)
# slider_seed = gr.Slider(minimum=1, maximum=10000, label="Seed", randomize=True)
button = gr.Button("Generate")
guest_id_box = gr.Textbox(label="guest_id", value=f"-1", visible=False)
i.select(get_pixels,[i,orig_i,radio,t,guest_id_box],[i,orig_i,guest_id_box])
redo.click(exe_redo, [i,orig_i,t,guest_id_box],[i])
undo.click(exe_undo, [i,orig_i,t,guest_id_box],[i])
# skip_button.click(skip_fun, [i,t,guest_id_box])
with gr.Column():
output = gr.Gallery(label='Generated image', rows=2, height=768)
with gr.Accordion("Intermediate results", open=False, visible=False):
gr.Markdown("Composed prompt")
composed_prompt = gr.Textbox(label='')
# gr.Markdown("Layout visualization")
# layout = gr.Image(height=256, width=256)
button.click(text_to_image, inputs=[guest_id_box, i, orig_i, prompt,keywords,positive_prompt, radio,slider_step,slider_guidance,slider_batch,slider_temperature,slider_natural], outputs=[output, composed_prompt])
gr.Markdown("## Image Examples")
template = None
gr.Examples(
[
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example1.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example2.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example3.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example4.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example5.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example7.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example8.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example11.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example12.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example13.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example14.jpg"],
["https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/example15.jpg"],
],
[
i
],
examples_per_page=25,
)
gr.HTML(
"""
<div style="text-align: justify; max-width: 1100px; margin: 20px auto;">
<h3 style="font-weight: 450; font-size: 0.8rem; margin: 0rem">
<b>Version</b>: 1.0
</h3>
<h3 style="font-weight: 450; font-size: 0.8rem; margin: 0rem">
<b>Contact</b>:
For help or issues using TextDiffuser-2, please email Jingye Chen <a href="mailto:qwerty.chen@connect.ust.hk">(qwerty.chen@connect.ust.hk)</a>, Yupan Huang <a href="mailto:huangyp28@mail2.sysu.edu.cn">(huangyp28@mail2.sysu.edu.cn)</a> or submit a GitHub issue. For other communications related to TextDiffuser-2, please contact Lei Cui <a href="mailto:lecu@microsoft.com">(lecu@microsoft.com)</a> or Furu Wei <a href="mailto:fuwei@microsoft.com">(fuwei@microsoft.com)</a>.
</h3>
<h3 style="font-weight: 450; font-size: 0.8rem; margin: 0rem">
<b>Disclaimer</b>:
Please note that the demo is intended for academic and research purposes <b>ONLY</b>. Any use of the demo for generating inappropriate content is strictly prohibited. The responsibility for any misuse or inappropriate use of the demo lies solely with the users who generated such content, and this demo shall not be held liable for any such use.
</h3>
</div>
"""
)
demo.launch()
@@ -0,0 +1,565 @@
import os
import re
import zipfile
import torch
import gradio as gr
print('hello', gr.__version__)
import time
from transformers import CLIPTextModel, CLIPTokenizer
from diffusers import AutoencoderKL, DDPMScheduler, StableDiffusionPipeline, UNet2DConditionModel, DiffusionPipeline, LCMScheduler
from tqdm import tqdm
from PIL import Image
from PIL import Image, ImageDraw, ImageFont
import random
import copy
import string
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + ' ' # len(aphabet) = 95
'''alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
'''
if not os.path.exists('images2'):
os.system('wget https://huggingface.co/datasets/JingyeChen22/TextDiffuser/resolve/main/images2.zip')
with zipfile.ZipFile('images2.zip', 'r') as zip_ref:
zip_ref.extractall('.')
# os.system('nvidia-smi')
os.system('ls')
#### import m1
from fastchat.model import load_model, get_conversation_template
from transformers import AutoTokenizer, AutoModelForCausalLM
m1_model_path = 'JingyeChen22/textdiffuser2_layout_planner'
# m1_model, m1_tokenizer = load_model(
# m1_model_path,
# 'cuda',
# 1,
# None,
# False,
# False,
# revision="main",
# debug=False,
# )
m1_tokenizer = AutoTokenizer.from_pretrained(m1_model_path, use_fast=False)
m1_model = AutoModelForCausalLM.from_pretrained(
m1_model_path, torch_dtype=torch.float16, low_cpu_mem_usage=True
).cuda()
#### import diffusion models
text_encoder = CLIPTextModel.from_pretrained(
'JingyeChen22/textdiffuser2-full-ft', subfolder="text_encoder"
).cuda().half()
tokenizer = CLIPTokenizer.from_pretrained(
'runwayml/stable-diffusion-v1-5', subfolder="tokenizer"
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print('***************')
print(len(tokenizer))
for i in range(520):
tokenizer.add_tokens(['l' + str(i) ]) # left
tokenizer.add_tokens(['t' + str(i) ]) # top
tokenizer.add_tokens(['r' + str(i) ]) # width
tokenizer.add_tokens(['b' + str(i) ]) # height
for c in alphabet:
tokenizer.add_tokens([f'[{c}]'])
print(len(tokenizer))
print('***************')
vae = AutoencoderKL.from_pretrained('runwayml/stable-diffusion-v1-5', subfolder="vae").half().cuda()
unet = UNet2DConditionModel.from_pretrained(
'JingyeChen22/textdiffuser2-full-ft', subfolder="unet"
).half().cuda()
text_encoder.resize_token_embeddings(len(tokenizer))
#### load lcm components
model_id = "lambdalabs/sd-pokemon-diffusers"
lcm_lora_id = "latent-consistency/lcm-lora-sdv1-5"
pipe = DiffusionPipeline.from_pretrained(model_id, unet=copy.deepcopy(unet), tokenizer=tokenizer, text_encoder=copy.deepcopy(text_encoder), torch_dtype=torch.float16)
pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
pipe.load_lora_weights(lcm_lora_id)
pipe.to(device="cuda")
global_dict = {}
#### for interactive
# stack = []
# state = 0
font = ImageFont.truetype("./Arial.ttf", 32)
def skip_fun(i, t, guest_id):
global_dict[guest_id]['state'] = 0
# global state
# state = 0
def exe_undo(i, t, guest_id):
global_dict[guest_id]['stack'] = []
global_dict[guest_id]['state'] = 0
# global stack
# global state
# state = 0
# stack = []
image = Image.open(f'./gray256.jpg')
# print('stack', stack)
return image
def exe_redo(i, t, guest_id):
# global state
# state = 0
global_dict[guest_id]['state'] = 0
if len(global_dict[guest_id]['stack']) > 0:
global_dict[guest_id]['stack'].pop()
image = Image.open(f'./gray256.jpg')
draw = ImageDraw.Draw(image)
for items in global_dict[guest_id]['stack']:
# print('now', items)
text_position, t = items
if len(text_position) == 2:
x, y = text_position
text_color = (255, 0, 0)
draw.text((x+2, y), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x-r, y-r)
rightDownPoint = (x+r, y+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
elif len(text_position) == 4:
x0, y0, x1, y1 = text_position
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.rectangle((x0,y0,x1,y1), outline=(255, 0, 0) )
print('stack', global_dict[guest_id]['stack'])
return image
def get_pixels(i, t, guest_id, evt: gr.SelectData):
# global state
# register
if guest_id == '-1':
seed = str(int(time.time()))
global_dict[str(seed)] = {
'state': 0,
'stack': []
}
guest_id = str(seed)
else:
seed = guest_id
text_position = evt.index
if global_dict[guest_id]['state'] == 0:
global_dict[guest_id]['stack'].append(
(text_position, t)
)
print(text_position, global_dict[guest_id]['stack'])
global_dict[guest_id]['state'] = 1
else:
(_, t) = global_dict[guest_id]['stack'].pop()
x, y = _
global_dict[guest_id]['stack'].append(
((x,y,text_position[0],text_position[1]), t)
)
global_dict[guest_id]['state'] = 0
image = Image.open(f'./gray256.jpg')
draw = ImageDraw.Draw(image)
for items in global_dict[guest_id]['stack']:
# print('now', items)
text_position, t = items
if len(text_position) == 2:
x, y = text_position
text_color = (255, 0, 0)
draw.text((x+2, y), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x-r, y-r)
rightDownPoint = (x+r, y+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
elif len(text_position) == 4:
x0, y0, x1, y1 = text_position
text_color = (255, 0, 0)
draw.text((x0+2, y0), t, font=font, fill=text_color)
r = 4
leftUpPoint = (x0-r, y0-r)
rightDownPoint = (x0+r, y0+r)
draw.ellipse((leftUpPoint,rightDownPoint), fill='red')
draw.rectangle((x0,y0,x1,y1), outline=(255, 0, 0) )
print('stack', global_dict[guest_id]['stack'])
return image, seed
font_layout = ImageFont.truetype('./Arial.ttf', 16)
def get_layout_image(ocrs):
blank = Image.new('RGB', (256,256), (0,0,0))
draw = ImageDraw.ImageDraw(blank)
for line in ocrs.split('\n'):
line = line.strip()
if len(line) == 0:
break
pred = ' '.join(line.split()[:-1])
box = line.split()[-1]
l, t, r, b = [int(i)*2 for i in box.split(',')] # the size of canvas is 256x256
draw.rectangle([(l, t), (r, b)], outline ="red")
draw.text((l, t), pred, font=font_layout)
return blank
def text_to_image(guest_id, prompt,keywords,positive_prompt,radio,slider_step,slider_guidance,slider_batch,slider_temperature,slider_natural):
print(f'[info] Prompt: {prompt} | Keywords: {keywords} | Radio: {radio} | Steps: {slider_step} | Guidance: {slider_guidance} | Natural: {slider_natural}')
# global stack
# global state
if len(positive_prompt.strip()) != 0:
prompt += positive_prompt
with torch.no_grad():
time1 = time.time()
user_prompt = prompt
if slider_natural:
user_prompt = f'{user_prompt}'
composed_prompt = user_prompt
prompt = tokenizer.encode(user_prompt)
layout_image = None
else:
if guest_id not in global_dict or len(global_dict[guest_id]['stack']) == 0:
if len(keywords.strip()) == 0:
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}'
else:
keywords = keywords.split('/')
keywords = [i.strip() for i in keywords]
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. In addition, we also provide all keywords at random order for reference. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {prompt}. Keywords: {str(keywords)}'
msg = template
conv = get_conversation_template(m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to('cuda') for k, v in inputs.items()}
output_ids = m1_model.generate(
**inputs,
do_sample=True,
temperature=slider_temperature,
repetition_penalty=1.0,
max_new_tokens=512,
)
if m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = m1_tokenizer.decode(
output_ids, skip_special_tokens=True, spaces_between_special_tokens=False
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
layout_image = get_layout_image(outputs)
ocrs = outputs.split('\n')
time2 = time.time()
print(time2-time1)
# user_prompt = prompt
current_ocr = ocrs
ocr_ids = []
print('user_prompt', user_prompt)
print('current_ocr', current_ocr)
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or '###' in ocr or '.com' in ocr:
continue
items = ocr.split()
pred = ' '.join(items[:-1])
box = items[-1]
l,t,r,b = box.split(',')
l,t,r,b = int(l), int(t), int(r), int(b)
ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b)])
char_list = list(pred)
char_list = [f'[{i}]' for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(tokenizer.eos_token_id)
caption_ids = tokenizer(
user_prompt, truncation=True, return_tensors="pt"
).input_ids[0].tolist()
try:
ocr_ids = tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
user_prompt = tokenizer.decode(prompt)
composed_prompt = tokenizer.decode(prompt)
else:
user_prompt += ' <|endoftext|><|startoftext|>'
layout_image = None
for items in global_dict[guest_id]['stack']:
position, text = items
if len(position) == 2:
x, y = position
x = x // 4
y = y // 4
text_str = ' '.join([f'[{c}]' for c in list(text)])
user_prompt += f' l{x} t{y} {text_str} <|endoftext|>'
elif len(position) == 4:
x0, y0, x1, y1 = position
x0 = x0 // 4
y0 = y0 // 4
x1 = x1 // 4
y1 = y1 // 4
text_str = ' '.join([f'[{c}]' for c in list(text)])
user_prompt += f' l{x0} t{y0} r{x1} b{y1} {text_str} <|endoftext|>'
# composed_prompt = user_prompt
prompt = tokenizer.encode(user_prompt)
composed_prompt = tokenizer.decode(prompt)
prompt = prompt[:77]
while len(prompt) < 77:
prompt.append(tokenizer.pad_token_id)
if radio == 'TextDiffuser-2':
prompts_cond = prompt
prompts_nocond = [tokenizer.pad_token_id]*77
prompts_cond = [prompts_cond] * slider_batch
prompts_nocond = [prompts_nocond] * slider_batch
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = DDPMScheduler.from_pretrained('runwayml/stable-diffusion-v1-5', subfolder="scheduler")
scheduler.set_timesteps(slider_step)
noise = torch.randn((slider_batch, 4, 64, 64)).to("cuda").half()
input = noise
encoder_hidden_states_cond = text_encoder(prompts_cond)[0].half()
encoder_hidden_states_nocond = text_encoder(prompts_nocond)[0].half()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = unet(sample=input, timestep=t, encoder_hidden_states=encoder_hidden_states_cond[:slider_batch]).sample # b, 4, 64, 64
noise_pred_uncond = unet(sample=input, timestep=t, encoder_hidden_states=encoder_hidden_states_nocond[:slider_batch]).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + slider_guidance * (noise_pred_cond - noise_pred_uncond) # b, 4, 64, 64
input = scheduler.step(noisy_residual, t, input).prev_sample
del noise_pred_cond
del noise_pred_uncond
torch.cuda.empty_cache()
# decode
input = 1 / vae.config.scaling_factor * input
images = vae.decode(input, return_dict=False)[0]
width, height = 512, 512
results = []
new_image = Image.new('RGB', (2*width, 2*height))
for index, image in enumerate(images.cpu().float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8")).convert('RGB')
results.append(image)
row = index // 2
col = index % 2
new_image.paste(image, (col*width, row*height))
# os.system('nvidia-smi')
torch.cuda.empty_cache()
# os.system('nvidia-smi')
return tuple(results), composed_prompt, layout_image
elif radio == 'TextDiffuser-2-LCM':
generator = torch.Generator(device=pipe.device).manual_seed(random.randint(0,1000))
image = pipe(
prompt=user_prompt,
generator=generator,
# negative_prompt=negative_prompt,
num_inference_steps=slider_step,
guidance_scale=1,
# num_images_per_prompt=slider_batch,
).images
# os.system('nvidia-smi')
torch.cuda.empty_cache()
# os.system('nvidia-smi')
return tuple(image), composed_prompt, layout_image
with gr.Blocks() as demo:
# guest_id = random.randint(0,100000000)
# register
gr.HTML(
"""
<div style="text-align: center; max-width: 1600px; margin: 20px auto;">
<h2 style="font-weight: 900; font-size: 2.3rem; margin: 0rem">
TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering
</h2>
<h2 style="font-weight: 460; font-size: 1.1rem; margin: 0rem">
<a href="https://jingyechen.github.io/">Jingye Chen</a>, <a href="https://hypjudy.github.io/website/">Yupan Huang</a>, <a href="https://scholar.google.com/citations?user=0LTZGhUAAAAJ&hl=en">Tengchao Lv</a>, <a href="https://www.microsoft.com/en-us/research/people/lecu/">Lei Cui</a>, <a href="https://cqf.io/">Qifeng Chen</a>, <a href="https://thegenerality.com/">Furu Wei</a>
</h2>
<h2 style="font-weight: 460; font-size: 1.1rem; margin: 0rem">
HKUST, Sun Yat-sen University, Microsoft Research
</h2>
<h3 style="font-weight: 450; font-size: 1rem; margin: 0rem">
[<a href="https://arxiv.org/abs/2311.16465" style="color:blue;">arXiv</a>]
[<a href="https://github.com/microsoft/unilm/tree/master/textdiffuser-2" style="color:blue;">Code</a>]
[<a href="https://jingyechen.github.io/textdiffuser2/" style="color:blue;">Project Page</a>]
[<a href="https://discord.gg/q7eHPupu" style="color:purple;">Discord</a>]
</h3>
<h2 style="text-align: left; font-weight: 450; font-size: 1rem; margin-top: 0.5rem; margin-bottom: 0.5rem">
We propose <b>TextDiffuser-2</b>, aiming at unleashing the power of language models for text rendering. Specifically, we <b>tame a language model into a layout planner</b> to transform user prompt into a layout using the caption-OCR pairs. The language model demonstrates flexibility and automation by inferring keywords from user prompts or incorporating user-specified keywords to determine their positions. Secondly, we <b>leverage the language model in the diffusion model as the layout encoder</b> to represent the position and content of text at the line level. This approach enables diffusion models to generate text images with broader diversity.
</h2>
<h2 style="text-align: left; font-weight: 450; font-size: 1rem; margin-top: 0.5rem; margin-bottom: 0.5rem">
👀 <b>Tips for using this demo</b>: <b>(1)</b> Please carefully read the disclaimer in the below. Current verison can only support English. <b>(2)</b> The specification of keywords is optional. If provided, the language model will do its best to plan layouts using the given keywords. <b>(3)</b> If a template is given, the layout planner (M1) is not used. <b>(4)</b> Three operations, including redo, undo, and skip are provided. When using skip, only the left-top point of a keyword will be recorded, resulting in more diversity but sometimes decreasing the accuracy. <b>(5)</b> The layout planner can produce different layouts. You can increase the temperature to enhance the diversity. ✨ <b>(6)</b> We also provide the experimental demo combining <b>TextDiffuser-2</b> and <b>LCM</b>. The inference is fast using less sampling steps, although the precision in text rendering might decrease.
</h2>
<img src="https://raw.githubusercontent.com/JingyeChen/jingyechen.github.io/master/textdiffuser2/static/images/architecture_blank.jpg" alt="textdiffuser-2">
</div>
""")
with gr.Tab("Text-to-Image"):
with gr.Row():
with gr.Column(scale=1):
prompt = gr.Textbox(label="Prompt. You can let language model automatically identify keywords, or provide them below", placeholder="A beautiful city skyline stamp of Shanghai")
keywords = gr.Textbox(label="(Optional) Keywords. Should be seperated by / (e.g., keyword1/keyword2/...)", placeholder="keyword1/keyword2")
positive_prompt = gr.Textbox(label="(Optional) Positive prompt", value=", digital art, very detailed, fantasy, high definition, cinematic light, dnd, trending on artstation")
# many encounter concurrent problem
with gr.Accordion("(Optional) Template - Click to paint", open=False):
with gr.Row():
with gr.Column(scale=1):
i = gr.Image(label="Canvas", type='filepath', value=f'./gray256.jpg', height=256, width=256)
with gr.Column(scale=1):
t = gr.Textbox(label="Keyword", value='input_keyword')
redo = gr.Button(value='Redo - Cancel the last keyword')
undo = gr.Button(value='Undo - Clear the canvas')
skip_button = gr.Button(value='Skip - Operate the next keyword')
radio = gr.Radio(["TextDiffuser-2", "TextDiffuser-2-LCM"], label="Choice of models", value="TextDiffuser-2")
slider_natural = gr.Checkbox(label="Natural image generation", value=False, info="The text position and content info will not be incorporated.")
slider_step = gr.Slider(minimum=1, maximum=50, value=20, step=1, label="Sampling step", info="The sampling step for TextDiffuser-2. You may decease the step to 4 when using LCM.")
slider_guidance = gr.Slider(minimum=1, maximum=13, value=7.5, step=0.5, label="Scale of classifier-free guidance", info="The scale of cfg and is set to 7.5 in default. When using LCM, cfg is set to 1.")
slider_batch = gr.Slider(minimum=1, maximum=6, value=4, step=1, label="Batch size", info="The number of images to be sampled.")
slider_temperature = gr.Slider(minimum=0.1, maximum=2, value=1.4, step=0.1, label="Temperature", info="Control the diversity of layout planner. Higher value indicates more diversity.")
# slider_seed = gr.Slider(minimum=1, maximum=10000, label="Seed", randomize=True)
button = gr.Button("Generate")
guest_id_box = gr.Textbox(label="guest_id", value=f"-1")
i.select(get_pixels,[i,t,guest_id_box],[i,guest_id_box])
redo.click(exe_redo, [i,t,guest_id_box],[i])
undo.click(exe_undo, [i,t,guest_id_box],[i])
skip_button.click(skip_fun, [i,t,guest_id_box])
with gr.Column(scale=1):
output = gr.Gallery(label='Generated image')
with gr.Accordion("Intermediate results", open=False):
gr.Markdown("Composed prompt")
composed_prompt = gr.Textbox(label='')
gr.Markdown("Layout visualization")
layout = gr.Image(height=256, width=256)
button.click(text_to_image, inputs=[guest_id_box, prompt,keywords,positive_prompt, radio,slider_step,slider_guidance,slider_batch,slider_temperature,slider_natural], outputs=[output, composed_prompt, layout])
gr.Markdown("## Prompt Examples")
gr.Examples(
[
["A beautiful city skyline stamp of Shanghai", "", False],
["The words 'KFC VIVO50' are inscribed upon the wall in a neon light effect", "KFC/VIVO50", False],
["A logo of superman", "", False],
["A pencil sketch of a tree with the title nothing to tree here", "", False],
["handwritten signature of peter", "", False],
["Delicate greeting card of happy birthday to xyz", "", False],
["Book cover of good morning baby ", "", False],
["The handwritten words Hello World displayed on a wall in a neon light effect", "", False],
["Logo of winter in artistic font, made by snowflake", "", False],
["A book cover named summer vibe", "", False],
["Newspaper with the title Love Story", "", False],
["A logo for the company EcoGrow, where the letters look like plants", "EcoGrow", False],
["A poster titled 'Quails of North America', showing different kinds of quails.", "Quails/of/North/America", False],
["A detailed portrait of a fox guardian with a shield with Kung Fu written on it, by victo ngai and justin gerard, digital art, realistic painting", "kung/fu", False],
["A stamp of breath of the wild", "breath/of/the/wild", False],
["Poster of the incoming movie Transformers", "Transformers", False],
["Some apples are on a table", "", True],
["a hotdog with mustard and other toppings on it", "", True],
["a bathroom that has a slanted ceiling and a large bath tub", "", True],
["a man holding a tennis racquet on a tennis court", "", True],
["hamburger with bacon, lettuce, tomato and cheese| promotional image| hyperquality| products shot| full - color| extreme render| mouthwatering", "", True],
],
[
prompt,
keywords,
slider_natural
],
examples_per_page=25
)
gr.HTML(
"""
<div style="text-align: justify; max-width: 1100px; margin: 20px auto;">
<h3 style="font-weight: 450; font-size: 0.8rem; margin: 0rem">
<b>Version</b>: 1.0
</h3>
<h3 style="font-weight: 450; font-size: 0.8rem; margin: 0rem">
<b>Contact</b>:
For help or issues using TextDiffuser-2, please email Jingye Chen <a href="mailto:qwerty.chen@connect.ust.hk">(qwerty.chen@connect.ust.hk)</a>, Yupan Huang <a href="mailto:huangyp28@mail2.sysu.edu.cn">(huangyp28@mail2.sysu.edu.cn)</a> or submit a GitHub issue. For other communications related to TextDiffuser-2, please contact Lei Cui <a href="mailto:lecu@microsoft.com">(lecu@microsoft.com)</a> or Furu Wei <a href="mailto:fuwei@microsoft.com">(fuwei@microsoft.com)</a>.
</h3>
<h3 style="font-weight: 450; font-size: 0.8rem; margin: 0rem">
<b>Disclaimer</b>:
Please note that the demo is intended for academic and research purposes <b>ONLY</b>. Any use of the demo for generating inappropriate content is strictly prohibited. The responsibility for any misuse or inappropriate use of the demo lies solely with the users who generated such content, and this demo shall not be held liable for any such use.
</h3>
</div>
"""
)
demo.launch()
@@ -0,0 +1,2 @@
Book cover of summer vibe, high quality, high resolution
Summer Vibe 20,20,100,40,20
@@ -0,0 +1,643 @@
# ------------------------------------------
# TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering
# Paper Link: https://arxiv.org/abs/2311.16465
# Code Link: https://github.com/microsoft/unilm/tree/master/textdiffuser-2
# Copyright (c) Microsoft Corporation.
# ------------------------------------------
import os
import cv2
import random
import logging
import argparse
import numpy as np
import time
from pathlib import Path
from tqdm.auto import tqdm
from typing import Optional
from packaging import version
from PIL import Image
from huggingface_hub import HfFolder, Repository, create_repo, whoami
import string
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + ' ' # len(aphabet) = 95
'''alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
'''
import datasets
from datasets import disable_caching
import torch
import torch.utils.checkpoint
import torch.nn.functional as F
from torchvision import transforms
import accelerate
from accelerate import Accelerator
from accelerate.logging import get_logger
from accelerate.utils import ProjectConfiguration, set_seed
import diffusers
from diffusers import AutoencoderKL, DDPMScheduler, StableDiffusionPipeline, UNet2DConditionModel
from diffusers.optimization import get_scheduler
from diffusers.training_utils import EMAModel
from diffusers.utils import check_min_version, deprecate
from diffusers.utils.import_utils import is_xformers_available
import transformers
from transformers import CLIPTextModel, CLIPTokenizer
disable_caching()
check_min_version("0.15.0.dev0")
logger = get_logger(__name__, log_level="INFO")
def parse_args():
parser = argparse.ArgumentParser(description="Simple example of a training script.")
parser.add_argument(
"--pretrained_model_name_or_path",
type=str,
default='runwayml/stable-diffusion-v1-5', # no need to modify this
help="Path to pretrained model or model identifier from huggingface.co/models. Please do not modify this.",
)
parser.add_argument(
"--revision",
type=str,
default=None,
required=False,
help="Revision of pretrained model identifier from huggingface.co/models.",
)
parser.add_argument(
"--output_dir",
type=str,
default="output",
help="The output directory where the model predictions and checkpoints will be written.",
)
parser.add_argument(
"--cache_dir",
type=str,
default=None,
help="The directory where the downloaded models and datasets will be stored.",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="A seed for reproducible training."
)
parser.add_argument(
"--resolution",
type=int,
default=512,
help=(
"The resolution for input images, all the images in the train/validation dataset will be resized to this"
" resolution"
),
)
parser.add_argument(
"--drop_caption",
action="store_true",
help="Whether to drop captions during training following https://arxiv.org/abs/2207.12598.."
)
parser.add_argument(
"--dataloader_num_workers",
type=int,
default=0,
help="Number of subprocesses to use for data loading. 0 means that the data will be loaded in the main process."
)
parser.add_argument(
"--push_to_hub",
action="store_true",
help="Whether or not to push the model to the Hub."
)
parser.add_argument(
"--hub_token",
type=str,
default=None,
help="The token to use to push to the Model Hub."
)
parser.add_argument(
"--hub_model_id",
type=str,
default=None,
help="The name of the repository to keep in sync with the local `output_dir`.",
)
parser.add_argument(
"--logging_dir",
type=str,
default="logs",
help=(
"[TensorBoard](https://www.tensorflow.org/tensorboard) log directory. Will default to"
" *output_dir/runs/**CURRENT_DATETIME_HOSTNAME***."
),
)
parser.add_argument(
"--mixed_precision",
type=str,
default='fp16',
choices=["no", "fp16", "bf16"],
help=(
"Whether to use mixed precision. Choose between fp16 and bf16 (bfloat16). Bf16 requires PyTorch >="
" 1.10.and an Nvidia Ampere GPU. Default to the value of accelerate config of the current system or the"
" flag passed with the `accelerate.launch` command. Use this argument to override the accelerate config."
),
)
parser.add_argument(
"--report_to",
type=str,
default="tensorboard",
help=(
'The integration to report the results and logs to. Supported platforms are `"tensorboard"`'
' (default), `"wandb"` and `"comet_ml"`. Use `"all"` to report to all integrations.'
),
)
parser.add_argument(
"--local_rank",
type=int,
default=-1,
help="For distributed training: local_rank"
)
parser.add_argument(
"--checkpointing_steps",
type=int,
default=500,
help=(
"Save a checkpoint of the training state every X updates. These checkpoints are only suitable for resuming"
" training using `--resume_from_checkpoint`."
),
)
parser.add_argument(
"--checkpoints_total_limit",
type=int,
default=5,
help=(
"Max number of checkpoints to store. Passed as `total_limit` to the `Accelerator` `ProjectConfiguration`."
" See Accelerator::save_state https://huggingface.co/docs/accelerate/package_reference/accelerator#accelerate.Accelerator.save_state"
" for more docs"
),
)
parser.add_argument(
"--resume_from_checkpoint",
type=str,
default=None, # should be specified during inference
help=(
"Whether training should be resumed from a previous checkpoint. Use a path saved by"
' `--checkpointing_steps`, or `"latest"` to automatically select the last available checkpoint.'
),
)
parser.add_argument(
"--enable_xformers_memory_efficient_attention",
action="store_true",
help="Whether or not to use xformers."
)
#### newly added parameters
parser.add_argument(
"--granularity",
type=int,
default=128,
help="The granularity of coordinates, ranging from 1~512."
)
parser.add_argument(
"--coord_mode",
type=str,
default='lt',
choices=['lt', 'center', 'ltrb'],
help="The way to represent coordinates."
)
parser.add_argument(
"--max_length",
default=77,
type=int,
help="Maximum length of the composed prompt."
)
parser.add_argument(
"--cfg",
default=7,
type=float,
help="classifier free guidance."
)
parser.add_argument(
"--sample_steps",
default=50,
type=int,
help="steps for sampling for diffusion models."
)
parser.add_argument(
"--input_format",
required=True,
type=str,
help="specify the input format",
choices=['prompt', 'prompts_txt_file', 'prompt_layout_txt_file']
)
parser.add_argument(
"--input_prompt",
type=str,
)
parser.add_argument(
"--input_file",
type=str,
)
parser.add_argument(
"--prompts_txt_file",
type=str,
)
parser.add_argument(
"--m1_model_path",
type=str,
help="the checkpoint of layout planner"
)
parser.add_argument(
"--vis_num",
type=int,
default=16,
help=("The number of images to be visualized."),
)
args = parser.parse_args()
print(args)
env_local_rank = int(os.environ.get("LOCAL_RANK", -1))
if env_local_rank != -1 and env_local_rank != args.local_rank:
args.local_rank = env_local_rank
return args
def get_full_repo_name(model_id: str, organization: Optional[str] = None, token: Optional[str] = None):
if token is None:
token = HfFolder.get_token()
if organization is None:
username = whoami(token)["name"]
return f"{username}/{model_id}"
else:
return f"{organization}/{model_id}"
DATASET_NAME_MAPPING = {
# "lambdalabs/pokemon-blip-captions": ("image", "text"),
"MARIO-10M": ("image", "text"),
}
def main():
args = parse_args()
logging_dir = Path(args.output_dir, args.logging_dir)
accelerator_project_config = ProjectConfiguration(project_dir=args.output_dir, logging_dir=logging_dir)
accelerator = Accelerator(
gradient_accumulation_steps=4,
mixed_precision=args.mixed_precision,
log_with=args.report_to,
project_config=accelerator_project_config,
)
if args.report_to == "wandb":
if not is_wandb_available():
raise ImportError("Make sure to install wandb if you want to use it for logging during training.")
import wandb
# Make one log on every process with the configuration for debugging.
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
level=logging.INFO,
)
logger.info(accelerator.state, main_process_only=False)
if accelerator.is_local_main_process:
datasets.utils.logging.set_verbosity_warning()
transformers.utils.logging.set_verbosity_warning()
diffusers.utils.logging.set_verbosity_info()
else:
datasets.utils.logging.set_verbosity_error()
transformers.utils.logging.set_verbosity_error()
diffusers.utils.logging.set_verbosity_error()
# Handle the repository creation
if accelerator.is_main_process:
if args.output_dir is not None:
os.makedirs(args.output_dir, exist_ok=True)
if args.push_to_hub:
repo_id = create_repo(
repo_id=args.hub_model_id or Path(args.output_dir).name, exist_ok=True, token=args.hub_token
).repo_id
# Load scheduler, tokenizer and models.
tokenizer = CLIPTokenizer.from_pretrained(
args.pretrained_model_name_or_path, subfolder="tokenizer"
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print('***************')
print(len(tokenizer))
for i in range(520):
tokenizer.add_tokens(['l' + str(i) ]) # left
tokenizer.add_tokens(['t' + str(i) ]) # top
tokenizer.add_tokens(['r' + str(i) ]) # width
tokenizer.add_tokens(['b' + str(i) ]) # height
#### add rotation extension by appending angle tokens
for i in range(-180,181):
tokenizer.add_tokens(['a' + str(i) ]) # rotation
for c in alphabet:
tokenizer.add_tokens([f'[{c}]'])
print(len(tokenizer))
print('***************')
if args.max_length == 77:
text_encoder = CLIPTextModel.from_pretrained(
args.resume_from_checkpoint, subfolder="text_encoder", ignore_mismatched_sizes=True
)
else:
#### enlarge the context length of text encoder. empirically, enlarging the context length can proceed longer sequence. However, we observe that it will be hard to render general objects
text_encoder = CLIPTextModel.from_pretrained(
args.resume_from_checkpoint, subfolder="text_encoder", max_position_embeddings=args.max_length, ignore_mismatched_sizes=True
)
text_encoder.resize_token_embeddings(len(tokenizer))
vae = AutoencoderKL.from_pretrained(args.pretrained_model_name_or_path, subfolder="vae")
unet = UNet2DConditionModel.from_pretrained(
args.resume_from_checkpoint, subfolder="unet"
)
# freeze parameters of models to save more memory
# unet.requires_grad_(False)
vae.requires_grad_(False)
text_encoder.requires_grad_(False)
# `accelerate` 0.16.0 will have better support for customized saving
if version.parse(accelerate.__version__) >= version.parse("0.16.0"):
# create custom saving & loading hooks so that `accelerator.save_state(...)` serializes in a nice format
def save_model_hook(models, weights, output_dir):
if args.use_ema:
ema_unet.save_pretrained(os.path.join(output_dir, "unet_ema"))
for i, model in enumerate(models):
# model.save_pretrained(os.path.join(output_dir, "unet"))
if i == 0:
model.save_pretrained(os.path.join(output_dir, f"unet"))
elif i == 1:
model.save_pretrained(os.path.join(output_dir, f"text_encoder"))
# make sure to pop weight so that corresponding model is not saved again
weights.pop()
def load_model_hook(models, input_dir):
for i in range(len(models)):
# pop models so that they are not loaded again
model = models.pop()
if i == 1:
load_model = UNet2DConditionModel.from_pretrained(input_dir, subfolder="unet")
model.register_to_config(**load_model.config)
elif i == 0:
load_model = CLIPTextModel.from_pretrained(input_dir, subfolder="text_encoder")
# model.register_to_config(**load_model.config)
# # load diffusers style into model
# load_model = UNet2DConditionModel.from_pretrained(input_dir, subfolder="unet")
# model.register_to_config(**load_model.config)
model.load_state_dict(load_model.state_dict())
del load_model
accelerator.register_save_state_pre_hook(save_model_hook)
accelerator.register_load_state_pre_hook(load_model_hook)
# For mixed precision training we cast all non-trainable weigths (vae, non-lora text_encoder and non-lora unet) to half-precision
# as these weights are only used for inference, keeping weights in full precision is not required.
weight_dtype = torch.float32
if accelerator.mixed_precision == "fp16":
weight_dtype = torch.float16
elif accelerator.mixed_precision == "bf16":
weight_dtype = torch.bfloat16
# Move unet, vae and text_encoder to device and cast to weight_dtype
unet.to(accelerator.device, dtype=weight_dtype)
vae.to(accelerator.device, dtype=weight_dtype)
text_encoder.to(accelerator.device, dtype=weight_dtype)
if args.enable_xformers_memory_efficient_attention:
if is_xformers_available():
import xformers
xformers_version = version.parse(xformers.__version__)
if xformers_version == version.parse("0.0.16"):
logger.warn(
"xFormers 0.0.16 cannot be used for training in some GPUs. If you observe problems during training, please update xFormers to at least 0.0.17. See https://huggingface.co/docs/diffusers/main/en/optimization/xformers for more details."
)
unet.enable_xformers_memory_efficient_attention()
else:
raise ValueError("xformers is not available. Make sure it is installed correctly")
# # Enable TF32 for faster training on Ampere GPUs,
# # cf https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
# if True:
# torch.backends.cuda.matmul.allow_tf32 = True
# # We need to initialize the trackers we use, and also store our configuration.
# # The trackers initializes automatically on the main process.
# if accelerator.is_main_process:
# accelerator.init_trackers("text2image-fine-tune", config=vars(args))
# Potentially load in the weights and states from a previous save
if args.resume_from_checkpoint:
if args.resume_from_checkpoint != "latest":
path = os.path.basename(args.resume_from_checkpoint)
else:
# Get the most recent checkpoint
dirs = os.listdir(args.output_dir)
dirs = [d for d in dirs if d.startswith("checkpoint")]
dirs = sorted(dirs, key=lambda x: int(x.split("-")[1]))
path = dirs[-1] if len(dirs) > 0 else None
if path is None:
accelerator.print(
f"Checkpoint '{args.resume_from_checkpoint}' does not exist. Starting a new training run."
)
args.resume_from_checkpoint = None
else:
accelerator.print(f"Resuming from checkpoint {path}")
# accelerator.load_state(os.path.join(args.output_dir, path))
accelerator.load_state(args.resume_from_checkpoint)
if accelerator.is_main_process and os.path.exists(f'{args.output_dir}'):
print('detect existing output_dir, removing the contained jpg/txt files ...')
os.system(f'rm {args.output_dir}/*.jpg')
os.system(f'rm {args.output_dir}/*.txt')
# user_prompt = "Book cover of summer vibe, high quality, high resolution"
# ocrs = [
# 'Summer Vibe 20,20,100,40'
# ]
if args.input_format == 'prompt_layout_txt_file':
lines = open(args.input_file).readlines()
user_prompts = [lines[0].strip()]
ocrs = [lines[1:]]
elif args.input_format == 'prompt' or args.input_format == 'prompts_txt_file':
#### prepare m1 (layout planner)
from fastchat.model import load_model, get_conversation_template
m1_model, m1_tokenizer = load_model(
args.m1_model_path,
'cuda',
1,
None,
False,
False,
revision="main",
debug=False,
)
# prompt = 'a text image of hello world'
prompts = []
if args.input_format == 'prompt':
prompts = [args.input_prompt]
elif args.input_format == 'prompts_txt_file':
prompts = open(args.prompts_txt_file).readlines()
print(f'there are {len(prompts)} samples for generation')
ocrs = []
user_prompts = []
for prompt in prompts:
user_prompt = prompt
user_prompts.append(user_prompt)
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}'
msg = template
conv = get_conversation_template(args.m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to('cuda') for k, v in inputs.items()}
output_ids = m1_model.generate(
**inputs,
do_sample=True,
temperature=0.7,
repetition_penalty=1.0,
max_new_tokens=512,
)
if m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = m1_tokenizer.decode(
output_ids, skip_special_tokens=True, spaces_between_special_tokens=False
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
# ocrs = outputs.split('\n')
ocrs.append(outputs.split('\n'))
with torch.no_grad():
size = len(ocrs)
print(f'the number of samples: {size}')
time_seed = int(time.time())
random.seed(time_seed)
torch.manual_seed(time_seed)
torch.cuda.manual_seed_all(time_seed)
for sample_index in range(size):
user_prompt = user_prompts[sample_index]
current_ocr = ocrs[sample_index]
ocr_ids = []
print('user_prompt', user_prompt)
print('current_ocr', current_ocr)
current_ocr = []
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or '###' in ocr or '.com' in ocr:
continue
items = ocr.split()
pred = ' '.join(items[:-1])
box = items[-1]
l,t,r,b,a = box.split(',')
l,t,r,b,a = int(l), int(t), int(r), int(b), int(a)
ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b), 'a'+str(a)])
char_list = list(pred)
char_list = [f'[{i}]' for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(tokenizer.eos_token_id)
caption_ids = tokenizer(
user_prompt, truncation=True, return_tensors="pt"
).input_ids[0].tolist()
try:
ocr_ids = tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
prompt = prompt[:args.max_length]
while len(prompt) < args.max_length:
prompt.append(tokenizer.pad_token_id)
prompts_cond = prompt
prompts_nocond = [tokenizer.pad_token_id]*args.max_length
prompts_cond = [prompts_cond] * args.vis_num
prompts_nocond = [prompts_nocond] * args.vis_num
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = DDPMScheduler.from_pretrained(args.pretrained_model_name_or_path, subfolder="scheduler")
scheduler.set_timesteps(args.sample_steps)
noise = torch.randn((args.vis_num, 4, 64, 64)).to("cuda")
input = noise
encoder_hidden_states_cond = text_encoder(prompts_cond)[0]
encoder_hidden_states_nocond = text_encoder(prompts_nocond)[0]
texts = prompts_cond
f = open(f'{args.output_dir}/prompt_{sample_index}_{args.local_rank}.txt', 'w+')
for text in texts:
sentence = tokenizer.decode(text)
f.write(sentence + '\n')
f.close()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_cond[:args.vis_num]).sample # b, 4, 64, 64
noise_pred_uncond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_nocond[:args.vis_num]).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + args.cfg * (noise_pred_cond - noise_pred_uncond) # b, 4, 64, 64
input = scheduler.step(noisy_residual, t, input).prev_sample
# input = prev_noisy_sample
# decode
input = 1 / vae.config.scaling_factor * input
images = vae.decode(input.half(), return_dict=False)[0]
width, height = 512, 512
new_image = Image.new('RGB', (4*width, 4*height))
for index, image in enumerate(images.float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8")).convert('RGB')
row = index // 4
col = index % 4
new_image.paste(image, (col*width, row*height))
new_image.save(f'{args.output_dir}/pred_img_{sample_index}_{args.local_rank}.jpg')
if __name__ == "__main__":
main()
@@ -0,0 +1,14 @@
accelerate launch inference_textdiffuser2_t2i_full_angle.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--mixed_precision="fp16" \
--output_dir="inference_results" \
--enable_xformers_memory_efficient_attention \
--resume_from_checkpoint="put your ckpt here" \
--granularity=128 \
--max_length=77 \
--coord_mode="ltrb" \
--cfg=7.5 \
--sample_steps=20 \
--seed=43555 \
--input_format='prompt_layout_txt_file' \
--input_file='angle_template_file.txt'
@@ -0,0 +1,651 @@
# ------------------------------------------
# TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering
# Paper Link: https://arxiv.org/abs/2311.16465
# Code Link: https://github.com/microsoft/unilm/tree/master/textdiffuser-2
# Copyright (c) Microsoft Corporation.
# ------------------------------------------
import os
import cv2
import random
import logging
import argparse
import numpy as np
import time
from pathlib import Path
from tqdm.auto import tqdm
from typing import Optional
from packaging import version
from PIL import Image
from huggingface_hub import HfFolder, Repository, create_repo, whoami
import string
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + ' ' # len(aphabet) = 95
'''alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
'''
import datasets
from datasets import disable_caching
import torch
import torch.utils.checkpoint
import torch.nn.functional as F
from torchvision import transforms
import accelerate
from accelerate import Accelerator
from accelerate.logging import get_logger
from accelerate.utils import ProjectConfiguration, set_seed
import diffusers
from diffusers import AutoencoderKL, DDPMScheduler, StableDiffusionPipeline, UNet2DConditionModel
from diffusers.optimization import get_scheduler
from diffusers.training_utils import EMAModel
from diffusers.utils import check_min_version, deprecate
from diffusers.utils.import_utils import is_xformers_available
import transformers
from transformers import CLIPTextModel, CLIPTokenizer
disable_caching()
check_min_version("0.15.0.dev0")
logger = get_logger(__name__, log_level="INFO")
def parse_args():
parser = argparse.ArgumentParser(description="Simple example of a training script.")
parser.add_argument(
"--pretrained_model_name_or_path",
type=str,
default='runwayml/stable-diffusion-v1-5', # no need to modify this
help="Path to pretrained model or model identifier from huggingface.co/models. Please do not modify this.",
)
parser.add_argument(
"--revision",
type=str,
default=None,
required=False,
help="Revision of pretrained model identifier from huggingface.co/models.",
)
parser.add_argument(
"--output_dir",
type=str,
default="output",
help="The output directory where the model predictions and checkpoints will be written.",
)
parser.add_argument(
"--cache_dir",
type=str,
default=None,
help="The directory where the downloaded models and datasets will be stored.",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="A seed for reproducible training."
)
parser.add_argument(
"--resolution",
type=int,
default=512,
help=(
"The resolution for input images, all the images in the train/validation dataset will be resized to this"
" resolution"
),
)
parser.add_argument(
"--drop_caption",
action="store_true",
help="Whether to drop captions during training following https://arxiv.org/abs/2207.12598.."
)
parser.add_argument(
"--dataloader_num_workers",
type=int,
default=0,
help="Number of subprocesses to use for data loading. 0 means that the data will be loaded in the main process."
)
parser.add_argument(
"--push_to_hub",
action="store_true",
help="Whether or not to push the model to the Hub."
)
parser.add_argument(
"--hub_token",
type=str,
default=None,
help="The token to use to push to the Model Hub."
)
parser.add_argument(
"--hub_model_id",
type=str,
default=None,
help="The name of the repository to keep in sync with the local `output_dir`.",
)
parser.add_argument(
"--logging_dir",
type=str,
default="logs",
help=(
"[TensorBoard](https://www.tensorflow.org/tensorboard) log directory. Will default to"
" *output_dir/runs/**CURRENT_DATETIME_HOSTNAME***."
),
)
parser.add_argument(
"--mixed_precision",
type=str,
default='fp16',
choices=["no", "fp16", "bf16"],
help=(
"Whether to use mixed precision. Choose between fp16 and bf16 (bfloat16). Bf16 requires PyTorch >="
" 1.10.and an Nvidia Ampere GPU. Default to the value of accelerate config of the current system or the"
" flag passed with the `accelerate.launch` command. Use this argument to override the accelerate config."
),
)
parser.add_argument(
"--report_to",
type=str,
default="tensorboard",
help=(
'The integration to report the results and logs to. Supported platforms are `"tensorboard"`'
' (default), `"wandb"` and `"comet_ml"`. Use `"all"` to report to all integrations.'
),
)
parser.add_argument(
"--local_rank",
type=int,
default=-1,
help="For distributed training: local_rank"
)
parser.add_argument(
"--checkpointing_steps",
type=int,
default=500,
help=(
"Save a checkpoint of the training state every X updates. These checkpoints are only suitable for resuming"
" training using `--resume_from_checkpoint`."
),
)
parser.add_argument(
"--checkpoints_total_limit",
type=int,
default=5,
help=(
"Max number of checkpoints to store. Passed as `total_limit` to the `Accelerator` `ProjectConfiguration`."
" See Accelerator::save_state https://huggingface.co/docs/accelerate/package_reference/accelerator#accelerate.Accelerator.save_state"
" for more docs"
),
)
parser.add_argument(
"--resume_from_checkpoint",
type=str,
default=None, # should be specified during inference
help=(
"Whether training should be resumed from a previous checkpoint. Use a path saved by"
' `--checkpointing_steps`, or `"latest"` to automatically select the last available checkpoint.'
),
)
parser.add_argument(
"--enable_xformers_memory_efficient_attention",
action="store_true",
help="Whether or not to use xformers."
)
#### newly added parameters
parser.add_argument(
"--granularity",
type=int,
default=128,
help="The granularity of coordinates, ranging from 1~512."
)
parser.add_argument(
"--coord_mode",
type=str,
default='lt',
choices=['lt', 'center', 'ltrb'],
help="The way to represent coordinates."
)
parser.add_argument(
"--max_length",
default=77,
type=int,
help="Maximum length of the composed prompt."
)
parser.add_argument(
"--cfg",
default=7,
type=float,
help="classifier free guidance."
)
parser.add_argument(
"--sample_steps",
default=50,
type=int,
help="steps for sampling for diffusion models."
)
parser.add_argument(
"--input_format",
required=True,
type=str,
help="specify the input format",
choices=['prompt', 'prompts_txt_file', 'prompt_layout_txt_file']
)
parser.add_argument(
"--input_prompt",
type=str,
)
parser.add_argument(
"--input_file",
type=str,
)
parser.add_argument(
"--prompts_txt_file",
type=str,
)
parser.add_argument(
"--m1_model_path",
type=str,
help="the checkpoint of layout planner"
)
parser.add_argument(
"--vis_num",
type=int,
default=16,
help=("The number of images to be visualized."),
)
args = parser.parse_args()
print(args)
env_local_rank = int(os.environ.get("LOCAL_RANK", -1))
if env_local_rank != -1 and env_local_rank != args.local_rank:
args.local_rank = env_local_rank
return args
def get_full_repo_name(model_id: str, organization: Optional[str] = None, token: Optional[str] = None):
if token is None:
token = HfFolder.get_token()
if organization is None:
username = whoami(token)["name"]
return f"{username}/{model_id}"
else:
return f"{organization}/{model_id}"
DATASET_NAME_MAPPING = {
# "lambdalabs/pokemon-blip-captions": ("image", "text"),
"MARIO-10M": ("image", "text"),
}
def main():
args = parse_args()
logging_dir = Path(args.output_dir, args.logging_dir)
accelerator_project_config = ProjectConfiguration(project_dir=args.output_dir, logging_dir=logging_dir)
accelerator = Accelerator(
gradient_accumulation_steps=4,
mixed_precision=args.mixed_precision,
log_with=args.report_to,
project_config=accelerator_project_config,
)
if args.report_to == "wandb":
if not is_wandb_available():
raise ImportError("Make sure to install wandb if you want to use it for logging during training.")
import wandb
# Make one log on every process with the configuration for debugging.
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
level=logging.INFO,
)
logger.info(accelerator.state, main_process_only=False)
if accelerator.is_local_main_process:
datasets.utils.logging.set_verbosity_warning()
transformers.utils.logging.set_verbosity_warning()
diffusers.utils.logging.set_verbosity_info()
else:
datasets.utils.logging.set_verbosity_error()
transformers.utils.logging.set_verbosity_error()
diffusers.utils.logging.set_verbosity_error()
# Handle the repository creation
if accelerator.is_main_process:
if args.output_dir is not None:
os.makedirs(args.output_dir, exist_ok=True)
if args.push_to_hub:
repo_id = create_repo(
repo_id=args.hub_model_id or Path(args.output_dir).name, exist_ok=True, token=args.hub_token
).repo_id
# Load scheduler, tokenizer and models.
tokenizer = CLIPTokenizer.from_pretrained(
args.pretrained_model_name_or_path, subfolder="tokenizer"
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print('***************')
print(len(tokenizer))
for i in range(520):
tokenizer.add_tokens(['l' + str(i) ]) # left
tokenizer.add_tokens(['t' + str(i) ]) # top
tokenizer.add_tokens(['r' + str(i) ]) # width
tokenizer.add_tokens(['b' + str(i) ]) # height
for c in alphabet:
tokenizer.add_tokens([f'[{c}]'])
print(len(tokenizer))
print('***************')
if args.max_length == 77:
text_encoder = CLIPTextModel.from_pretrained(
args.resume_from_checkpoint, subfolder="text_encoder", ignore_mismatched_sizes=True
)
else:
#### enlarge the context length of text encoder. empirically, enlarging the context length can proceed longer sequence. However, we observe that it will be hard to render general objects
text_encoder = CLIPTextModel.from_pretrained(
args.resume_from_checkpoint, subfolder="text_encoder", max_position_embeddings=args.max_length, ignore_mismatched_sizes=True
)
text_encoder.resize_token_embeddings(len(tokenizer))
vae = AutoencoderKL.from_pretrained(args.pretrained_model_name_or_path, subfolder="vae")
unet = UNet2DConditionModel.from_pretrained(
args.resume_from_checkpoint, subfolder="unet"
)
# freeze parameters of models to save more memory
# unet.requires_grad_(False)
vae.requires_grad_(False)
text_encoder.requires_grad_(False)
# `accelerate` 0.16.0 will have better support for customized saving
if version.parse(accelerate.__version__) >= version.parse("0.16.0"):
# create custom saving & loading hooks so that `accelerator.save_state(...)` serializes in a nice format
def save_model_hook(models, weights, output_dir):
if args.use_ema:
ema_unet.save_pretrained(os.path.join(output_dir, "unet_ema"))
for i, model in enumerate(models):
# model.save_pretrained(os.path.join(output_dir, "unet"))
if i == 0:
model.save_pretrained(os.path.join(output_dir, f"unet"))
elif i == 1:
model.save_pretrained(os.path.join(output_dir, f"text_encoder"))
# make sure to pop weight so that corresponding model is not saved again
weights.pop()
def load_model_hook(models, input_dir):
for i in range(len(models)):
# pop models so that they are not loaded again
model = models.pop()
if i == 1:
load_model = UNet2DConditionModel.from_pretrained(input_dir, subfolder="unet")
model.register_to_config(**load_model.config)
elif i == 0:
load_model = CLIPTextModel.from_pretrained(input_dir, subfolder="text_encoder")
# model.register_to_config(**load_model.config)
# # load diffusers style into model
# load_model = UNet2DConditionModel.from_pretrained(input_dir, subfolder="unet")
# model.register_to_config(**load_model.config)
model.load_state_dict(load_model.state_dict())
del load_model
accelerator.register_save_state_pre_hook(save_model_hook)
accelerator.register_load_state_pre_hook(load_model_hook)
# For mixed precision training we cast all non-trainable weigths (vae, non-lora text_encoder and non-lora unet) to half-precision
# as these weights are only used for inference, keeping weights in full precision is not required.
weight_dtype = torch.float32
if accelerator.mixed_precision == "fp16":
weight_dtype = torch.float16
elif accelerator.mixed_precision == "bf16":
weight_dtype = torch.bfloat16
# Move unet, vae and text_encoder to device and cast to weight_dtype
unet.to(accelerator.device, dtype=weight_dtype)
vae.to(accelerator.device, dtype=weight_dtype)
text_encoder.to(accelerator.device, dtype=weight_dtype)
if args.enable_xformers_memory_efficient_attention:
if is_xformers_available():
import xformers
xformers_version = version.parse(xformers.__version__)
if xformers_version == version.parse("0.0.16"):
logger.warn(
"xFormers 0.0.16 cannot be used for training in some GPUs. If you observe problems during training, please update xFormers to at least 0.0.17. See https://huggingface.co/docs/diffusers/main/en/optimization/xformers for more details."
)
unet.enable_xformers_memory_efficient_attention()
else:
raise ValueError("xformers is not available. Make sure it is installed correctly")
# # Enable TF32 for faster training on Ampere GPUs,
# # cf https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
# if True:
# torch.backends.cuda.matmul.allow_tf32 = True
# # We need to initialize the trackers we use, and also store our configuration.
# # The trackers initializes automatically on the main process.
# if accelerator.is_main_process:
# accelerator.init_trackers("text2image-fine-tune", config=vars(args))
# Potentially load in the weights and states from a previous save
if args.resume_from_checkpoint:
if args.resume_from_checkpoint != "latest":
path = os.path.basename(args.resume_from_checkpoint)
else:
# Get the most recent checkpoint
dirs = os.listdir(args.output_dir)
dirs = [d for d in dirs if d.startswith("checkpoint")]
dirs = sorted(dirs, key=lambda x: int(x.split("-")[1]))
path = dirs[-1] if len(dirs) > 0 else None
if path is None:
accelerator.print(
f"Checkpoint '{args.resume_from_checkpoint}' does not exist. Starting a new training run."
)
args.resume_from_checkpoint = None
else:
accelerator.print(f"Resuming from checkpoint {path}")
# accelerator.load_state(os.path.join(args.output_dir, path))
accelerator.load_state(args.resume_from_checkpoint)
if accelerator.is_main_process and os.path.exists(f'{args.output_dir}'):
print('detect existing output_dir, removing the contained jpg/txt files ...')
os.system(f'rm {args.output_dir}/*.jpg')
os.system(f'rm {args.output_dir}/*.txt')
# user_prompt = "Book cover of summer vibe, high quality, high resolution"
# ocrs = [
# 'Summer Vibe 20,20,100,40'
# ]
if args.input_format == 'prompt_layout_txt_file':
lines = open(args.input_file).readlines()
user_prompts = [lines[0].strip()]
ocrs = [lines[1:]]
elif args.input_format == 'prompt' or args.input_format == 'prompts_txt_file':
#### prepare m1 (layout planner)
from fastchat.model import load_model, get_conversation_template
m1_model, m1_tokenizer = load_model(
args.m1_model_path,
'cuda',
1,
None,
False,
False,
revision="main",
debug=False,
)
# prompt = 'a text image of hello world'
prompts = []
if args.input_format == 'prompt':
prompts = [args.input_prompt]
elif args.input_format == 'prompts_txt_file':
prompts = open(args.prompts_txt_file).readlines()
print(f'there are {len(prompts)} samples for generation')
ocrs = []
user_prompts = []
for prompt in prompts:
user_prompt = prompt
user_prompts.append(user_prompt)
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}'
msg = template
conv = get_conversation_template(args.m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to('cuda') for k, v in inputs.items()}
output_ids = m1_model.generate(
**inputs,
do_sample=True,
temperature=0.7,
repetition_penalty=1.0,
max_new_tokens=512,
)
if m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = m1_tokenizer.decode(
output_ids, skip_special_tokens=True, spaces_between_special_tokens=False
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
# ocrs = outputs.split('\n')
ocrs.append(outputs.split('\n'))
with torch.no_grad():
size = len(ocrs)
print(f'the number of samples: {size}')
time_seed = int(time.time())
random.seed(time_seed)
torch.manual_seed(time_seed)
torch.cuda.manual_seed_all(time_seed)
for sample_index in range(size):
user_prompt = user_prompts[sample_index]
current_ocr = ocrs[sample_index]
ocr_ids = []
print('user_prompt', user_prompt)
print('current_ocr', current_ocr)
current_ocr = []
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or '###' in ocr or '.com' in ocr:
continue
items = ocr.split()
pred = ' '.join(items[:-1])
box = items[-1]
# l,t,r,b,a = box.split(',')
# l,t,r,b,a = int(l), int(t), int(r), int(b), int(a)
# ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b), 'a'+str(a)])
box = box.split(',')
box = [int(i) for i in box]
x1, y1, x2, y2, x3, y3, x4, y4 = box
# ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b), 'a'+str(a)])
ocr_ids.extend(
[
'l'+str(x1), 't'+str(y1),
'l'+str(x2), 't'+str(y2),
'l'+str(x3), 't'+str(y3),
'l'+str(x4), 't'+str(y4),
]
)
char_list = list(pred)
char_list = [f'[{i}]' for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(tokenizer.eos_token_id)
caption_ids = tokenizer(
user_prompt, truncation=True, return_tensors="pt"
).input_ids[0].tolist()
try:
ocr_ids = tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
prompt = prompt[:args.max_length]
while len(prompt) < args.max_length:
prompt.append(tokenizer.pad_token_id)
prompts_cond = prompt
prompts_nocond = [tokenizer.pad_token_id]*args.max_length
prompts_cond = [prompts_cond] * args.vis_num
prompts_nocond = [prompts_nocond] * args.vis_num
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = DDPMScheduler.from_pretrained(args.pretrained_model_name_or_path, subfolder="scheduler")
scheduler.set_timesteps(args.sample_steps)
noise = torch.randn((args.vis_num, 4, 64, 64)).to("cuda")
input = noise
encoder_hidden_states_cond = text_encoder(prompts_cond)[0]
encoder_hidden_states_nocond = text_encoder(prompts_nocond)[0]
texts = prompts_cond
f = open(f'{args.output_dir}/prompt_{sample_index}_{args.local_rank}.txt', 'w+')
for text in texts:
sentence = tokenizer.decode(text)
f.write(sentence + '\n')
f.close()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_cond[:args.vis_num]).sample # b, 4, 64, 64
noise_pred_uncond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_nocond[:args.vis_num]).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + args.cfg * (noise_pred_cond - noise_pred_uncond) # b, 4, 64, 64
input = scheduler.step(noisy_residual, t, input).prev_sample
# input = prev_noisy_sample
# decode
input = 1 / vae.config.scaling_factor * input
images = vae.decode(input.half(), return_dict=False)[0]
width, height = 512, 512
new_image = Image.new('RGB', (4*width, 4*height))
for index, image in enumerate(images.float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8")).convert('RGB')
row = index // 4
col = index % 4
new_image.paste(image, (col*width, row*height))
new_image.save(f'{args.output_dir}/pred_img_{sample_index}_{args.local_rank}.jpg')
if __name__ == "__main__":
main()
@@ -0,0 +1,14 @@
accelerate launch inference_textdiffuser2_t2i_full_quadrilateral.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--mixed_precision="fp16" \
--output_dir="inference_results" \
--enable_xformers_memory_efficient_attention \
--resume_from_checkpoint="put your ckpt here" \
--granularity=128 \
--max_length=77 \
--coord_mode="ltrb" \
--cfg=7.5 \
--sample_steps=20 \
--seed=43555 \
--input_format='prompt_layout_txt_file' \
--input_file='angle_template_file.txt'
@@ -0,0 +1,2 @@
hat with the word money
MONEY 30,40,80,70,90,60,30,60
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
accelerate launch train_textdiffuser2_t2i_full_angle.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_batch_size=18 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-5 \
--max_grad_norm=1 \
--lr_scheduler="constant" \
--lr_warmup_steps=0 \
--output_dir="diffusion_experiment_result" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--index_file_path='/path/to/train_dataset_index.txt' \
--dataset_path='/path/to/laion-ocr-select/' \
--granularity=128 \
--coord_mode="ltrb" \
--max_length=77 \
--resume_from_checkpoint="latest"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
accelerate launch train_textdiffuser2_t2i_full_quadrilateral.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_batch_size=18 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-5 \
--max_grad_norm=1 \
--lr_scheduler="constant" \
--lr_warmup_steps=0 \
--output_dir="diffusion_experiment_result" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--index_file_path='/path/to/train_dataset_index.txt' \
--dataset_path='/path/to/laion-ocr-select/' \
--granularity=128 \
--coord_mode="ltrb" \
--max_length=77 \
--resume_from_checkpoint="latest"
@@ -0,0 +1,638 @@
# ------------------------------------------
# TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering
# Paper Link: https://arxiv.org/abs/2311.16465
# Code Link: https://github.com/microsoft/unilm/tree/master/textdiffuser-2
# Copyright (c) Microsoft Corporation.
# ------------------------------------------
import os
import cv2
import random
import logging
import argparse
import numpy as np
import time
from pathlib import Path
from tqdm.auto import tqdm
from typing import Optional
from packaging import version
from PIL import Image
from huggingface_hub import HfFolder, Repository, create_repo, whoami
import string
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + ' ' # len(aphabet) = 95
'''alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
'''
import datasets
from datasets import disable_caching
import torch
import torch.utils.checkpoint
import torch.nn.functional as F
from torchvision import transforms
import accelerate
from accelerate import Accelerator
from accelerate.logging import get_logger
from accelerate.utils import ProjectConfiguration, set_seed
import diffusers
from diffusers import AutoencoderKL, DDPMScheduler, StableDiffusionPipeline, UNet2DConditionModel
from diffusers.optimization import get_scheduler
from diffusers.training_utils import EMAModel
from diffusers.utils import check_min_version, deprecate
from diffusers.utils.import_utils import is_xformers_available
import transformers
from transformers import CLIPTextModel, CLIPTokenizer
disable_caching()
check_min_version("0.15.0.dev0")
logger = get_logger(__name__, log_level="INFO")
def parse_args():
parser = argparse.ArgumentParser(description="Simple example of a training script.")
parser.add_argument(
"--pretrained_model_name_or_path",
type=str,
default='runwayml/stable-diffusion-v1-5', # no need to modify this
help="Path to pretrained model or model identifier from huggingface.co/models. Please do not modify this.",
)
parser.add_argument(
"--revision",
type=str,
default=None,
required=False,
help="Revision of pretrained model identifier from huggingface.co/models.",
)
parser.add_argument(
"--output_dir",
type=str,
default="output",
help="The output directory where the model predictions and checkpoints will be written.",
)
parser.add_argument(
"--cache_dir",
type=str,
default=None,
help="The directory where the downloaded models and datasets will be stored.",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="A seed for reproducible training."
)
parser.add_argument(
"--resolution",
type=int,
default=512,
help=(
"The resolution for input images, all the images in the train/validation dataset will be resized to this"
" resolution"
),
)
parser.add_argument(
"--drop_caption",
action="store_true",
help="Whether to drop captions during training following https://arxiv.org/abs/2207.12598.."
)
parser.add_argument(
"--dataloader_num_workers",
type=int,
default=0,
help="Number of subprocesses to use for data loading. 0 means that the data will be loaded in the main process."
)
parser.add_argument(
"--push_to_hub",
action="store_true",
help="Whether or not to push the model to the Hub."
)
parser.add_argument(
"--hub_token",
type=str,
default=None,
help="The token to use to push to the Model Hub."
)
parser.add_argument(
"--hub_model_id",
type=str,
default=None,
help="The name of the repository to keep in sync with the local `output_dir`.",
)
parser.add_argument(
"--logging_dir",
type=str,
default="logs",
help=(
"[TensorBoard](https://www.tensorflow.org/tensorboard) log directory. Will default to"
" *output_dir/runs/**CURRENT_DATETIME_HOSTNAME***."
),
)
parser.add_argument(
"--mixed_precision",
type=str,
default='fp16',
choices=["no", "fp16", "bf16"],
help=(
"Whether to use mixed precision. Choose between fp16 and bf16 (bfloat16). Bf16 requires PyTorch >="
" 1.10.and an Nvidia Ampere GPU. Default to the value of accelerate config of the current system or the"
" flag passed with the `accelerate.launch` command. Use this argument to override the accelerate config."
),
)
parser.add_argument(
"--report_to",
type=str,
default="tensorboard",
help=(
'The integration to report the results and logs to. Supported platforms are `"tensorboard"`'
' (default), `"wandb"` and `"comet_ml"`. Use `"all"` to report to all integrations.'
),
)
parser.add_argument(
"--local_rank",
type=int,
default=-1,
help="For distributed training: local_rank"
)
parser.add_argument(
"--checkpointing_steps",
type=int,
default=500,
help=(
"Save a checkpoint of the training state every X updates. These checkpoints are only suitable for resuming"
" training using `--resume_from_checkpoint`."
),
)
parser.add_argument(
"--checkpoints_total_limit",
type=int,
default=5,
help=(
"Max number of checkpoints to store. Passed as `total_limit` to the `Accelerator` `ProjectConfiguration`."
" See Accelerator::save_state https://huggingface.co/docs/accelerate/package_reference/accelerator#accelerate.Accelerator.save_state"
" for more docs"
),
)
parser.add_argument(
"--resume_from_checkpoint",
type=str,
default=None, # should be specified during inference
help=(
"Whether training should be resumed from a previous checkpoint. Use a path saved by"
' `--checkpointing_steps`, or `"latest"` to automatically select the last available checkpoint.'
),
)
parser.add_argument(
"--enable_xformers_memory_efficient_attention",
action="store_true",
help="Whether or not to use xformers."
)
#### newly added parameters
parser.add_argument(
"--granularity",
type=int,
default=128,
help="The granularity of coordinates, ranging from 1~512."
)
parser.add_argument(
"--coord_mode",
type=str,
default='lt',
choices=['lt', 'center', 'ltrb'],
help="The way to represent coordinates."
)
parser.add_argument(
"--max_length",
default=77,
type=int,
help="Maximum length of the composed prompt."
)
parser.add_argument(
"--cfg",
default=7,
type=float,
help="classifier free guidance."
)
parser.add_argument(
"--sample_steps",
default=50,
type=int,
help="steps for sampling for diffusion models."
)
parser.add_argument(
"--input_format",
required=True,
type=str,
help="specify the input format",
choices=['prompt', 'prompts_txt_file', 'prompt_layout_txt_file']
)
parser.add_argument(
"--input_prompt",
type=str,
)
parser.add_argument(
"--input_file",
type=str,
)
parser.add_argument(
"--prompts_txt_file",
type=str,
)
parser.add_argument(
"--m1_model_path",
type=str,
help="the checkpoint of layout planner"
)
parser.add_argument(
"--vis_num",
type=int,
default=16,
help=("The number of images to be visualized."),
)
args = parser.parse_args()
print(args)
env_local_rank = int(os.environ.get("LOCAL_RANK", -1))
if env_local_rank != -1 and env_local_rank != args.local_rank:
args.local_rank = env_local_rank
return args
def get_full_repo_name(model_id: str, organization: Optional[str] = None, token: Optional[str] = None):
if token is None:
token = HfFolder.get_token()
if organization is None:
username = whoami(token)["name"]
return f"{username}/{model_id}"
else:
return f"{organization}/{model_id}"
DATASET_NAME_MAPPING = {
# "lambdalabs/pokemon-blip-captions": ("image", "text"),
"MARIO-10M": ("image", "text"),
}
def main():
args = parse_args()
logging_dir = Path(args.output_dir, args.logging_dir)
accelerator_project_config = ProjectConfiguration(project_dir=args.output_dir, logging_dir=logging_dir)
accelerator = Accelerator(
gradient_accumulation_steps=4,
mixed_precision=args.mixed_precision,
log_with=args.report_to,
project_config=accelerator_project_config,
)
if args.report_to == "wandb":
if not is_wandb_available():
raise ImportError("Make sure to install wandb if you want to use it for logging during training.")
import wandb
# Make one log on every process with the configuration for debugging.
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
level=logging.INFO,
)
logger.info(accelerator.state, main_process_only=False)
if accelerator.is_local_main_process:
datasets.utils.logging.set_verbosity_warning()
transformers.utils.logging.set_verbosity_warning()
diffusers.utils.logging.set_verbosity_info()
else:
datasets.utils.logging.set_verbosity_error()
transformers.utils.logging.set_verbosity_error()
diffusers.utils.logging.set_verbosity_error()
# Handle the repository creation
if accelerator.is_main_process:
if args.output_dir is not None:
os.makedirs(args.output_dir, exist_ok=True)
if args.push_to_hub:
repo_id = create_repo(
repo_id=args.hub_model_id or Path(args.output_dir).name, exist_ok=True, token=args.hub_token
).repo_id
# Load scheduler, tokenizer and models.
tokenizer = CLIPTokenizer.from_pretrained(
args.pretrained_model_name_or_path, subfolder="tokenizer"
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print('***************')
print(len(tokenizer))
for i in range(520):
tokenizer.add_tokens(['l' + str(i) ]) # left
tokenizer.add_tokens(['t' + str(i) ]) # top
tokenizer.add_tokens(['r' + str(i) ]) # width
tokenizer.add_tokens(['b' + str(i) ]) # height
for c in alphabet:
tokenizer.add_tokens([f'[{c}]'])
print(len(tokenizer))
print('***************')
if args.max_length == 77:
text_encoder = CLIPTextModel.from_pretrained(
args.resume_from_checkpoint, subfolder="text_encoder", ignore_mismatched_sizes=True
)
else:
#### enlarge the context length of text encoder. empirically, enlarging the context length can proceed longer sequence. However, we observe that it will be hard to render general objects
text_encoder = CLIPTextModel.from_pretrained(
args.resume_from_checkpoint, subfolder="text_encoder", max_position_embeddings=args.max_length, ignore_mismatched_sizes=True
)
text_encoder.resize_token_embeddings(len(tokenizer))
vae = AutoencoderKL.from_pretrained(args.pretrained_model_name_or_path, subfolder="vae")
unet = UNet2DConditionModel.from_pretrained(
args.resume_from_checkpoint, subfolder="unet"
)
# freeze parameters of models to save more memory
# unet.requires_grad_(False)
vae.requires_grad_(False)
text_encoder.requires_grad_(False)
# `accelerate` 0.16.0 will have better support for customized saving
if version.parse(accelerate.__version__) >= version.parse("0.16.0"):
# create custom saving & loading hooks so that `accelerator.save_state(...)` serializes in a nice format
def save_model_hook(models, weights, output_dir):
if args.use_ema:
ema_unet.save_pretrained(os.path.join(output_dir, "unet_ema"))
for i, model in enumerate(models):
# model.save_pretrained(os.path.join(output_dir, "unet"))
if i == 0:
model.save_pretrained(os.path.join(output_dir, f"unet"))
elif i == 1:
model.save_pretrained(os.path.join(output_dir, f"text_encoder"))
# make sure to pop weight so that corresponding model is not saved again
weights.pop()
def load_model_hook(models, input_dir):
for i in range(len(models)):
# pop models so that they are not loaded again
model = models.pop()
if i == 1:
load_model = UNet2DConditionModel.from_pretrained(input_dir, subfolder="unet")
model.register_to_config(**load_model.config)
elif i == 0:
load_model = CLIPTextModel.from_pretrained(input_dir, subfolder="text_encoder")
# model.register_to_config(**load_model.config)
# # load diffusers style into model
# load_model = UNet2DConditionModel.from_pretrained(input_dir, subfolder="unet")
# model.register_to_config(**load_model.config)
model.load_state_dict(load_model.state_dict())
del load_model
accelerator.register_save_state_pre_hook(save_model_hook)
accelerator.register_load_state_pre_hook(load_model_hook)
# For mixed precision training we cast all non-trainable weigths (vae, non-lora text_encoder and non-lora unet) to half-precision
# as these weights are only used for inference, keeping weights in full precision is not required.
weight_dtype = torch.float32
if accelerator.mixed_precision == "fp16":
weight_dtype = torch.float16
elif accelerator.mixed_precision == "bf16":
weight_dtype = torch.bfloat16
# Move unet, vae and text_encoder to device and cast to weight_dtype
unet.to(accelerator.device, dtype=weight_dtype)
vae.to(accelerator.device, dtype=weight_dtype)
text_encoder.to(accelerator.device, dtype=weight_dtype)
if args.enable_xformers_memory_efficient_attention:
if is_xformers_available():
import xformers
xformers_version = version.parse(xformers.__version__)
if xformers_version == version.parse("0.0.16"):
logger.warn(
"xFormers 0.0.16 cannot be used for training in some GPUs. If you observe problems during training, please update xFormers to at least 0.0.17. See https://huggingface.co/docs/diffusers/main/en/optimization/xformers for more details."
)
unet.enable_xformers_memory_efficient_attention()
else:
raise ValueError("xformers is not available. Make sure it is installed correctly")
# # Enable TF32 for faster training on Ampere GPUs,
# # cf https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
# if True:
# torch.backends.cuda.matmul.allow_tf32 = True
# # We need to initialize the trackers we use, and also store our configuration.
# # The trackers initializes automatically on the main process.
# if accelerator.is_main_process:
# accelerator.init_trackers("text2image-fine-tune", config=vars(args))
# Potentially load in the weights and states from a previous save
if args.resume_from_checkpoint:
if args.resume_from_checkpoint != "latest":
path = os.path.basename(args.resume_from_checkpoint)
else:
# Get the most recent checkpoint
dirs = os.listdir(args.output_dir)
dirs = [d for d in dirs if d.startswith("checkpoint")]
dirs = sorted(dirs, key=lambda x: int(x.split("-")[1]))
path = dirs[-1] if len(dirs) > 0 else None
if path is None:
accelerator.print(
f"Checkpoint '{args.resume_from_checkpoint}' does not exist. Starting a new training run."
)
args.resume_from_checkpoint = None
else:
accelerator.print(f"Resuming from checkpoint {path}")
# accelerator.load_state(os.path.join(args.output_dir, path))
accelerator.load_state(args.resume_from_checkpoint)
if accelerator.is_main_process and os.path.exists(f'{args.output_dir}'):
print('detect existing output_dir, removing the contained jpg/txt files ...')
os.system(f'rm {args.output_dir}/*.jpg')
os.system(f'rm {args.output_dir}/*.txt')
# user_prompt = "Book cover of summer vibe, high quality, high resolution"
# ocrs = [
# 'Summer Vibe 20,20,100,40'
# ]
if args.input_format == 'prompt_layout_txt_file':
lines = open(args.input_file).readlines()
user_prompts = [lines[0].strip()]
ocrs = [lines[1:]]
elif args.input_format == 'prompt' or args.input_format == 'prompts_txt_file':
#### prepare m1 (layout planner)
from fastchat.model import load_model, get_conversation_template
m1_model, m1_tokenizer = load_model(
args.m1_model_path,
'cuda',
1,
None,
False,
False,
revision="main",
debug=False,
)
# prompt = 'a text image of hello world'
prompts = []
if args.input_format == 'prompt':
prompts = [args.input_prompt]
elif args.input_format == 'prompts_txt_file':
prompts = open(args.prompts_txt_file).readlines()
print(f'there are {len(prompts)} samples for generation')
ocrs = []
user_prompts = []
for prompt in prompts:
user_prompt = prompt
user_prompts.append(user_prompt)
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}'
msg = template
conv = get_conversation_template(args.m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to('cuda') for k, v in inputs.items()}
output_ids = m1_model.generate(
**inputs,
do_sample=True,
temperature=0.7,
repetition_penalty=1.0,
max_new_tokens=512,
)
if m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = m1_tokenizer.decode(
output_ids, skip_special_tokens=True, spaces_between_special_tokens=False
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
# ocrs = outputs.split('\n')
ocrs.append(outputs.split('\n'))
with torch.no_grad():
size = len(ocrs)
print(f'the number of samples: {size}')
time_seed = int(time.time())
random.seed(time_seed)
torch.manual_seed(time_seed)
torch.cuda.manual_seed_all(time_seed)
for sample_index in range(size):
user_prompt = user_prompts[sample_index]
current_ocr = ocrs[sample_index]
ocr_ids = []
print('user_prompt', user_prompt)
print('current_ocr', current_ocr)
current_ocr = []
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or '###' in ocr or '.com' in ocr:
continue
items = ocr.split()
pred = ' '.join(items[:-1])
box = items[-1]
l,t,r,b = box.split(',')
l,t,r,b = int(l), int(t), int(r), int(b)
ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b)])
char_list = list(pred)
char_list = [f'[{i}]' for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(tokenizer.eos_token_id)
caption_ids = tokenizer(
user_prompt, truncation=True, return_tensors="pt"
).input_ids[0].tolist()
try:
ocr_ids = tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
prompt = prompt[:args.max_length]
while len(prompt) < args.max_length:
prompt.append(tokenizer.pad_token_id)
prompts_cond = prompt
prompts_nocond = [tokenizer.pad_token_id]*args.max_length
prompts_cond = [prompts_cond] * args.vis_num
prompts_nocond = [prompts_nocond] * args.vis_num
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = DDPMScheduler.from_pretrained(args.pretrained_model_name_or_path, subfolder="scheduler")
scheduler.set_timesteps(args.sample_steps)
noise = torch.randn((args.vis_num, 4, 64, 64)).to("cuda")
input = noise
encoder_hidden_states_cond = text_encoder(prompts_cond)[0]
encoder_hidden_states_nocond = text_encoder(prompts_nocond)[0]
texts = prompts_cond
f = open(f'{args.output_dir}/prompt_{sample_index}_{args.local_rank}.txt', 'w+')
for text in texts:
sentence = tokenizer.decode(text)
f.write(sentence + '\n')
f.close()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_cond[:args.vis_num]).sample # b, 4, 64, 64
noise_pred_uncond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_nocond[:args.vis_num]).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + args.cfg * (noise_pred_cond - noise_pred_uncond) # b, 4, 64, 64
input = scheduler.step(noisy_residual, t, input).prev_sample
# input = prev_noisy_sample
# decode
input = 1 / vae.config.scaling_factor * input
images = vae.decode(input.half(), return_dict=False)[0]
width, height = 512, 512
new_image = Image.new('RGB', (4*width, 4*height))
for index, image in enumerate(images.float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8")).convert('RGB')
row = index // 4
col = index % 4
new_image.paste(image, (col*width, row*height))
new_image.save(f'{args.output_dir}/pred_img_{sample_index}_{args.local_rank}.jpg')
if __name__ == "__main__":
main()
@@ -0,0 +1,15 @@
accelerate launch inference_textdiffuser2_t2i_full.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--mixed_precision="fp16" \
--output_dir="inference_results" \
--enable_xformers_memory_efficient_attention \
--resume_from_checkpoint="JingyeChen22/textdiffuser2-full-ft" \
--granularity=128 \
--max_length=77 \
--coord_mode="ltrb" \
--cfg=7.5 \
--sample_steps=20 \
--seed=43555 \
--m1_model_path="JingyeChen22/textdiffuser2_layout_planner" \
--input_format='prompt' \
--input_prompt='a hotdog with mustard and other toppings on it'
@@ -0,0 +1,616 @@
# ------------------------------------------
# TextDiffuser-2: Unleashing the Power of Language Models for Text Rendering
# Paper Link: https://arxiv.org/abs/2311.16465
# Code Link: https://github.com/microsoft/unilm/tree/master/textdiffuser-2
# Copyright (c) Microsoft Corporation.
# ------------------------------------------
import argparse
import logging
import math
import os
import random
import shutil
from pathlib import Path
import glob
import time
import datasets
import numpy as np
import torch
import torch.nn.functional as F
import torch.utils.checkpoint
import transformers
from accelerate import Accelerator
from accelerate.logging import get_logger
from accelerate.utils import ProjectConfiguration, set_seed
from datasets import load_dataset, Dataset
from huggingface_hub import create_repo, upload_folder
from packaging import version
from torchvision import transforms
from tqdm.auto import tqdm
from transformers import CLIPTextModel, CLIPTokenizer
import diffusers
from diffusers import AutoencoderKL, DDPMScheduler, DiffusionPipeline, UNet2DConditionModel
from diffusers.loaders import AttnProcsLayers
from diffusers.models.attention_processor import LoRAAttnProcessor
from diffusers.optimization import get_scheduler
from diffusers.utils import check_min_version, is_wandb_available
from diffusers.utils.import_utils import is_xformers_available
from PIL import Image
import string
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + ' ' # len(aphabet) = 95
'''alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
'''
logger = get_logger(__name__, log_level="INFO")
def save_model_card(repo_id: str, images=None, base_model=str, dataset_name=str, repo_folder=None):
img_str = ""
for i, image in enumerate(images):
image.save(os.path.join(repo_folder, f"image_{i}.png"))
img_str += f"![img_{i}](./image_{i}.png)\n"
yaml = f"""
---
license: creativeml-openrail-m
base_model: {base_model}
tags:
- stable-diffusion
- stable-diffusion-diffusers
- text-to-image
- diffusers
- lora
inference: true
---
"""
model_card = f"""
# LoRA text2image fine-tuning - {repo_id}
These are LoRA adaption weights for {base_model}. The weights were fine-tuned on the {dataset_name} dataset. You can find some example images in the following. \n
{img_str}
"""
with open(os.path.join(repo_folder, "README.md"), "w") as f:
f.write(yaml + model_card)
def parse_args():
parser = argparse.ArgumentParser(description="Simple example of a training script.")
parser.add_argument(
"--pretrained_model_name_or_path",
type=str,
default=None,
required=True,
help="Path to pretrained model or model identifier from huggingface.co/models.",
)
parser.add_argument(
"--dataset_name",
type=str,
default='lambdalabs/pokemon-blip-captions',
help=(
"The name of the Dataset (from the HuggingFace hub) to train on (could be your own, possibly private,"
" dataset). It can also be a path pointing to a local copy of a dataset in your filesystem,"
" or to a folder containing files that 🤗 Datasets can understand."
),
)
parser.add_argument(
"--output_dir",
type=str,
default="sd-model-finetuned-lora",
help="The output directory where the model predictions and checkpoints will be written.",
)
parser.add_argument("--seed", type=int, default=None, help="A seed for reproducible training.")
parser.add_argument(
"--resolution",
type=int,
default=512,
help=(
"The resolution for input images, all the images in the train/validation dataset will be resized to this"
" resolution"
),
)
parser.add_argument(
"--gradient_accumulation_steps",
type=int,
default=1,
help="Number of updates steps to accumulate before performing a backward/update pass.",
)
parser.add_argument(
"--gradient_checkpointing",
action="store_true",
help="Whether or not to use gradient checkpointing to save memory at the expense of slower backward pass.",
)
parser.add_argument(
"--allow_tf32",
action="store_true",
help=(
"Whether or not to allow TF32 on Ampere GPUs. Can be used to speed up training. For more information, see"
" https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices"
),
)
parser.add_argument("--push_to_hub", action="store_true", help="Whether or not to push the model to the Hub.")
parser.add_argument("--hub_token", type=str, default=None, help="The token to use to push to the Model Hub.")
parser.add_argument(
"--hub_model_id",
type=str,
default=None,
help="The name of the repository to keep in sync with the local `output_dir`.",
)
parser.add_argument(
"--logging_dir",
type=str,
default="logs",
help=(
"[TensorBoard](https://www.tensorflow.org/tensorboard) log directory. Will default to"
" *output_dir/runs/**CURRENT_DATETIME_HOSTNAME***."
),
)
parser.add_argument(
"--mixed_precision",
type=str,
default=None,
choices=["no", "fp16", "bf16"],
help=(
"Whether to use mixed precision. Choose between fp16 and bf16 (bfloat16). Bf16 requires PyTorch >="
" 1.10.and an Nvidia Ampere GPU. Default to the value of accelerate config of the current system or the"
" flag passed with the `accelerate.launch` command. Use this argument to override the accelerate config."
),
)
parser.add_argument(
"--report_to",
type=str,
default="tensorboard",
help=(
'The integration to report the results and logs to. Supported platforms are `"tensorboard"`'
' (default), `"wandb"` and `"comet_ml"`. Use `"all"` to report to all integrations.'
),
)
parser.add_argument("--local_rank", type=int, default=-1, help="For distributed training: local_rank")
parser.add_argument(
"--resume_from_checkpoint",
type=str,
default=None,
help=(
"Whether training should be resumed from a previous checkpoint. Use a path saved by"
' `--checkpointing_steps`, or `"latest"` to automatically select the last available checkpoint.'
),
)
parser.add_argument(
"--enable_xformers_memory_efficient_attention", action="store_true", help="Whether or not to use xformers."
)
parser.add_argument(
"--rank",
type=int,
default=4,
help=("The dimension of the LoRA update matrices."),
)
parser.add_argument(
"--vis_num",
type=int,
default=16,
help=("The number of images to be visualized."),
)
#### newly added parameters
parser.add_argument(
"--granularity",
type=int,
default=128,
help="The granularity of coordinates, ranging from 1~512."
)
parser.add_argument(
"--coord_mode",
type=str,
default='lt',
choices=['lt', 'center', 'ltrb'],
help="The way to represent coordinates."
)
parser.add_argument(
"--max_length",
default=77,
type=int,
help="Maximum length of the composed prompt."
)
parser.add_argument(
"--cfg",
default=7,
type=float,
help="classifier free guidance."
)
parser.add_argument(
"--sample_steps",
default=50,
type=int,
help="steps for sampling for diffusion models."
)
parser.add_argument(
"--input_format",
required=True,
type=str,
help="specify the input format",
choices=['prompt', 'prompts_txt_file', 'prompt_layout_txt_file']
)
parser.add_argument(
"--input_prompt",
type=str,
)
parser.add_argument(
"--input_file",
type=str,
)
parser.add_argument(
"--prompts_txt_file",
type=str,
)
parser.add_argument(
"--m1_model_path",
type=str,
help="the checkpoint of layout planner"
)
args = parser.parse_args()
env_local_rank = int(os.environ.get("LOCAL_RANK", -1))
if env_local_rank != -1 and env_local_rank != args.local_rank:
args.local_rank = env_local_rank
return args
DATASET_NAME_MAPPING = {
# "lambdalabs/pokemon-blip-captions": ("image", "text"),
"MARIO-10M": ("image", "text"),
}
def main():
args = parse_args()
logging_dir = Path(args.output_dir, args.logging_dir)
accelerator_project_config = ProjectConfiguration(project_dir=args.output_dir, logging_dir=logging_dir)
accelerator = Accelerator(
gradient_accumulation_steps=args.gradient_accumulation_steps,
mixed_precision=args.mixed_precision,
log_with=args.report_to,
project_config=accelerator_project_config,
)
if args.report_to == "wandb":
if not is_wandb_available():
raise ImportError("Make sure to install wandb if you want to use it for logging during training.")
import wandb
# Make one log on every process with the configuration for debugging.
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
level=logging.INFO,
)
logger.info(accelerator.state, main_process_only=False)
if accelerator.is_local_main_process:
datasets.utils.logging.set_verbosity_warning()
transformers.utils.logging.set_verbosity_warning()
diffusers.utils.logging.set_verbosity_info()
else:
datasets.utils.logging.set_verbosity_error()
transformers.utils.logging.set_verbosity_error()
diffusers.utils.logging.set_verbosity_error()
# Handle the repository creation
if accelerator.is_main_process:
if args.output_dir is not None:
os.makedirs(args.output_dir, exist_ok=True)
if args.push_to_hub:
repo_id = create_repo(
repo_id=args.hub_model_id or Path(args.output_dir).name, exist_ok=True, token=args.hub_token
).repo_id
# Load scheduler, tokenizer and models.
tokenizer = CLIPTokenizer.from_pretrained(
args.pretrained_model_name_or_path, subfolder="tokenizer"
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print('***************')
print(len(tokenizer))
for i in range(520):
tokenizer.add_tokens(['l' + str(i) ]) # left
tokenizer.add_tokens(['t' + str(i) ]) # top
tokenizer.add_tokens(['r' + str(i) ]) # width
tokenizer.add_tokens(['b' + str(i) ]) # height
for c in alphabet:
tokenizer.add_tokens([f'[{c}]'])
print(len(tokenizer))
print('***************')
if args.max_length == 77:
text_encoder = CLIPTextModel.from_pretrained(
args.pretrained_model_name_or_path, subfolder="text_encoder", ignore_mismatched_sizes=True
)
else:
#### enlarge the context length of text encoder. empirically, enlarging the context length can proceed longer sequence. However, we observe that it will be hard to render general objects
text_encoder = CLIPTextModel.from_pretrained(
args.pretrained_model_name_or_path, subfolder="text_encoder", max_position_embeddings=args.max_length, ignore_mismatched_sizes=True
)
text_encoder.resize_token_embeddings(len(tokenizer))
vae = AutoencoderKL.from_pretrained(args.pretrained_model_name_or_path, subfolder="vae")
unet = UNet2DConditionModel.from_pretrained(
args.pretrained_model_name_or_path, subfolder="unet"
)
# freeze parameters of models to save more memory
unet.requires_grad_(False)
vae.requires_grad_(False)
text_encoder.requires_grad_(True)
# For mixed precision training we cast all non-trainable weigths (vae, non-lora text_encoder and non-lora unet) to half-precision
# as these weights are only used for inference, keeping weights in full precision is not required.
weight_dtype = torch.float32
if accelerator.mixed_precision == "fp16":
weight_dtype = torch.float16
elif accelerator.mixed_precision == "bf16":
weight_dtype = torch.bfloat16
# Move unet, vae and text_encoder to device and cast to weight_dtype
unet.to(accelerator.device, dtype=weight_dtype)
vae.to(accelerator.device, dtype=weight_dtype)
# text_encoder.to(accelerator.device, dtype=weight_dtype)
# now we will add new LoRA weights to the attention layers
# It's important to realize here how many attention weights will be added and of which sizes
# The sizes of the attention layers consist only of two different variables:
# 1) - the "hidden_size", which is increased according to `unet.config.block_out_channels`.
# 2) - the "cross attention size", which is set to `unet.config.cross_attention_dim`.
# Let's first see how many attention processors we will have to set.
# For Stable Diffusion, it should be equal to:
# - down blocks (2x attention layers) * (2x transformer layers) * (3x down blocks) = 12
# - mid blocks (2x attention layers) * (1x transformer layers) * (1x mid blocks) = 2
# - up blocks (2x attention layers) * (3x transformer layers) * (3x down blocks) = 18
# => 32 layers
# Set correct lora layers
lora_attn_procs = {}
for name in unet.attn_processors.keys():
cross_attention_dim = None if name.endswith("attn1.processor") else unet.config.cross_attention_dim
if name.startswith("mid_block"):
hidden_size = unet.config.block_out_channels[-1]
elif name.startswith("up_blocks"):
block_id = int(name[len("up_blocks.")])
hidden_size = list(reversed(unet.config.block_out_channels))[block_id]
elif name.startswith("down_blocks"):
block_id = int(name[len("down_blocks.")])
hidden_size = unet.config.block_out_channels[block_id]
lora_attn_procs[name] = LoRAAttnProcessor(
hidden_size=hidden_size,
cross_attention_dim=cross_attention_dim,
rank=args.rank,
)
unet.set_attn_processor(lora_attn_procs)
if args.enable_xformers_memory_efficient_attention:
if is_xformers_available():
import xformers
xformers_version = version.parse(xformers.__version__)
if xformers_version == version.parse("0.0.16"):
logger.warn(
"xFormers 0.0.16 cannot be used for training in some GPUs. If you observe problems during training, please update xFormers to at least 0.0.17. See https://huggingface.co/docs/diffusers/main/en/optimization/xformers for more details."
)
unet.enable_xformers_memory_efficient_attention()
else:
raise ValueError("xformers is not available. Make sure it is installed correctly")
lora_layers = AttnProcsLayers(unet.attn_processors)
lora_layers, text_encoder = accelerator.prepare(
lora_layers, text_encoder
)
# Enable TF32 for faster training on Ampere GPUs,
# cf https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
if args.allow_tf32:
torch.backends.cuda.matmul.allow_tf32 = True
# # We need to initialize the trackers we use, and also store our configuration.
# # The trackers initializes automatically on the main process.
# if accelerator.is_main_process:
# accelerator.init_trackers("text2image-fine-tune", config=vars(args))
# Potentially load in the weights and states from a previous save
if args.resume_from_checkpoint:
if args.resume_from_checkpoint != "latest":
path = os.path.basename(args.resume_from_checkpoint)
else:
# Get the most recent checkpoint
dirs = os.listdir(args.output_dir)
dirs = [d for d in dirs if d.startswith("checkpoint")]
dirs = sorted(dirs, key=lambda x: int(x.split("-")[1]))
path = dirs[-1] if len(dirs) > 0 else None
if path is None:
accelerator.print(
f"Checkpoint '{args.resume_from_checkpoint}' does not exist. Starting a new training run."
)
args.resume_from_checkpoint = None
else:
accelerator.print(f"Resuming from checkpoint {path}")
# accelerator.load_state(os.path.join(args.output_dir, path))
accelerator.load_state(args.resume_from_checkpoint)
if accelerator.is_main_process and os.path.exists(f'{args.output_dir}'):
print('detect existing output_dir, removing the contained jpg/txt files ...')
os.system(f'rm {args.output_dir}/*.jpg')
os.system(f'rm {args.output_dir}/*.txt')
# user_prompt = "Book cover of summer vibe, high quality, high resolution"
# ocrs = [
# 'Summer Vibe 20,20,100,40'
# ]
if args.input_format == 'prompt_layout_txt_file':
lines = open(args.input_file).readlines()
user_prompts = [lines[0].strip()]
ocrs = [lines[1:]]
elif args.input_format == 'prompt' or args.input_format == 'prompts_txt_file':
#### prepare m1 (layout planner)
from fastchat.model import load_model, get_conversation_template
m1_model, m1_tokenizer = load_model(
args.m1_model_path,
'cuda',
1,
None,
False,
False,
revision="main",
debug=False,
)
# prompt = 'a text image of hello world'
prompts = []
if args.input_format == 'prompt':
prompts = [args.input_prompt]
elif args.input_format == 'prompts_txt_file':
prompts = open(args.prompts_txt_file).readlines()
print(f'there are {len(prompts)} samples for generation')
ocrs = []
user_prompts = []
for prompt in prompts:
user_prompt = prompt
user_prompts.append(user_prompt)
template = f'Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}'
msg = template
conv = get_conversation_template(args.m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to('cuda') for k, v in inputs.items()}
output_ids = m1_model.generate(
**inputs,
do_sample=True,
temperature=0.7,
repetition_penalty=1.0,
max_new_tokens=512,
)
if m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = m1_tokenizer.decode(
output_ids, skip_special_tokens=True, spaces_between_special_tokens=False
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
# ocrs = outputs.split('\n')
ocrs.append(outputs.split('\n'))
with torch.no_grad():
size = len(ocrs)
print(f'the number of samples: {size}')
time_seed = int(time.time())
random.seed(time_seed)
torch.manual_seed(time_seed)
torch.cuda.manual_seed_all(time_seed)
for sample_index in range(size):
user_prompt = user_prompts[sample_index]
current_ocr = ocrs[sample_index]
ocr_ids = []
print('user_prompt', user_prompt)
print('current_ocr', current_ocr)
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or '###' in ocr or '.com' in ocr:
continue
items = ocr.split()
pred = ' '.join(items[:-1])
box = items[-1]
l,t,r,b = box.split(',')
l,t,r,b = int(l), int(t), int(r), int(b)
ocr_ids.extend(['l'+str(l), 't'+str(t), 'r'+str(r), 'b'+str(b)])
char_list = list(pred)
char_list = [f'[{i}]' for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(tokenizer.eos_token_id)
caption_ids = tokenizer(
user_prompt, truncation=True, return_tensors="pt"
).input_ids[0].tolist()
try:
ocr_ids = tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
prompt = prompt[:args.max_length]
while len(prompt) < args.max_length:
prompt.append(tokenizer.pad_token_id)
prompts_cond = prompt
prompts_nocond = [tokenizer.pad_token_id]*args.max_length
prompts_cond = [prompts_cond] * args.vis_num
prompts_nocond = [prompts_nocond] * args.vis_num
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = DDPMScheduler.from_pretrained(args.pretrained_model_name_or_path, subfolder="scheduler")
scheduler.set_timesteps(args.sample_steps)
noise = torch.randn((args.vis_num, 4, 64, 64)).to("cuda")
input = noise
encoder_hidden_states_cond = text_encoder(prompts_cond)[0]
encoder_hidden_states_nocond = text_encoder(prompts_nocond)[0]
texts = prompts_cond
f = open(f'{args.output_dir}/prompt_{sample_index}_{args.local_rank}.txt', 'w+')
for text in texts:
sentence = tokenizer.decode(text)
f.write(sentence + '\n')
f.close()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_cond[:args.vis_num]).sample # b, 4, 64, 64
noise_pred_uncond = unet(sample=input.half(), timestep=t, encoder_hidden_states=encoder_hidden_states_nocond[:args.vis_num]).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + args.cfg * (noise_pred_cond - noise_pred_uncond) # b, 4, 64, 64
prev_noisy_sample = scheduler.step(noisy_residual, t, input).prev_sample
input = prev_noisy_sample
# decode
input = 1 / vae.config.scaling_factor * input
images = vae.decode(input.half(), return_dict=False)[0]
width, height = 512, 512
new_image = Image.new('RGB', (4*width, 4*height))
for index, image in enumerate(images.float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray((image * 255).round().astype("uint8")).convert('RGB')
row = index // 4
col = index % 4
new_image.paste(image, (col*width, row*height))
new_image.save(f'{args.output_dir}/pred_img_{sample_index}_{args.local_rank}.jpg')
if __name__ == "__main__":
main()
@@ -0,0 +1,16 @@
accelerate launch inference_textdiffuser2_t2i_lora.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--output_dir="inference_results" \
--enable_xformers_memory_efficient_attention \
--resume_from_checkpoint="JingyeChen22/textdiffuser2-lora-ft" \
--granularity=128 \
--coord_mode="ltrb" \
--cfg=7.5 \
--sample_steps=50 \
--seed=43555 \
--m1_model_path="/home/jingyechen/FastChat/1204_final" \
--input_format='prompt' \
--input_prompt='a stamp of u.s.a'
+358
View File
@@ -0,0 +1,358 @@
# Prediction interface for Cog ⚙️
# https://github.com/replicate/cog/blob/main/docs/python.md
import copy
import string
import random
from typing import Optional
import torch
from transformers import CLIPTextModel, CLIPTokenizer
from diffusers import (
AutoencoderKL,
DDPMScheduler,
StableDiffusionPipeline,
UNet2DConditionModel,
DiffusionPipeline,
LCMScheduler,
)
from tqdm import tqdm
from PIL import Image
from PIL import Image, ImageDraw, ImageFont
from fastchat.model import load_model, get_conversation_template
from transformers import AutoTokenizer, AutoModelForCausalLM
from cog import BasePredictor, Input, Path, BaseModel
alphabet = (
string.digits
+ string.ascii_lowercase
+ string.ascii_uppercase
+ string.punctuation
+ " "
) # len(aphabet) = 95
"""alphabet
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
"""
font_layout = ImageFont.truetype("./Arial.ttf", 16)
class ModelOutput(BaseModel):
output_images: list[Path]
composed_prompt: str
class Predictor(BasePredictor):
def setup(self) -> None:
"""Load the model into memory to make running multiple predictions efficient"""
cache_dir = "model_cache"
local_files_only = True # set to True if the models are saved in cache_dir
self.m1_model_path = "JingyeChen22/textdiffuser2_layout_planner"
self.m1_tokenizer = AutoTokenizer.from_pretrained(
self.m1_model_path,
use_fast=False,
cache_dir=cache_dir,
local_files_only=local_files_only,
)
self.m1_model = AutoModelForCausalLM.from_pretrained(
self.m1_model_path,
torch_dtype=torch.float16,
low_cpu_mem_usage=True,
cache_dir=cache_dir,
local_files_only=local_files_only,
).cuda()
self.text_encoder = (
CLIPTextModel.from_pretrained(
"JingyeChen22/textdiffuser2-full-ft",
subfolder="text_encoder",
cache_dir=cache_dir,
local_files_only=local_files_only,
)
.cuda()
.half()
)
self.tokenizer = CLIPTokenizer.from_pretrained(
"runwayml/stable-diffusion-v1-5",
subfolder="tokenizer",
cache_dir=cache_dir,
local_files_only=local_files_only,
)
#### additional tokens are introduced, including coordinate tokens and character tokens
print("***************")
print(f"tokenizer size: {len(self.tokenizer)}")
for i in range(520):
self.tokenizer.add_tokens(["l" + str(i)]) # left
self.tokenizer.add_tokens(["t" + str(i)]) # top
self.tokenizer.add_tokens(["r" + str(i)]) # width
self.tokenizer.add_tokens(["b" + str(i)]) # height
for c in alphabet:
self.tokenizer.add_tokens([f"[{c}]"])
print(f"new tokenizer size: {len(self.tokenizer)}")
print("***************")
self.vae = (
AutoencoderKL.from_pretrained(
"runwayml/stable-diffusion-v1-5",
subfolder="vae",
cache_dir=cache_dir,
local_files_only=local_files_only,
)
.half()
.cuda()
)
self.unet = (
UNet2DConditionModel.from_pretrained(
"JingyeChen22/textdiffuser2-full-ft",
subfolder="unet",
cache_dir=cache_dir,
local_files_only=local_files_only,
)
.half()
.cuda()
)
self.text_encoder.resize_token_embeddings(len(self.tokenizer))
self.scheduler = DDPMScheduler.from_pretrained(
"runwayml/stable-diffusion-v1-5",
subfolder="scheduler",
cache_dir=cache_dir,
local_files_only=local_files_only,
)
#### load lcm components
self.pipe = DiffusionPipeline.from_pretrained(
"lambdalabs/sd-pokemon-diffusers",
unet=copy.deepcopy(self.unet),
tokenizer=self.tokenizer,
text_encoder=copy.deepcopy(self.text_encoder),
torch_dtype=torch.float16,
cache_dir=cache_dir,
local_files_only=local_files_only,
)
self.pipe.scheduler = LCMScheduler.from_config(self.pipe.scheduler.config)
self.pipe.load_lora_weights("latent-consistency/lcm-lora-sdv1-5")
self.pipe.to(device="cuda")
def predict(
self,
prompt: str = Input(
description="Input Prompt. You can let language model automatically identify keywords, or provide them below.",
default="A beautiful city skyline stamp of Shanghai",
),
keywords: str = Input(
description="(Optional) Keywords. Should be seperated by / (e.g., keyword1/keyword2/...).",
default=None,
),
positive_prompt: str = Input(
description="(Optional) Positive prompt.",
default=", digital art, very detailed, fantasy, high definition, cinematic light, dnd, trending on artstation",
),
use_lcm: bool = Input(
description="Use Latent Consistent Model.", default=False
),
generate_natural_image: bool = Input(
description="If set to True, the text position and content info will not be incorporated.",
default=False,
),
num_images: int = Input(
description="Number of Output images.", default=1, ge=1, le=4
),
num_inference_steps: int = Input(
description="Number of denoising steps. You may decease the step to 4 when using LCM.",
ge=1,
le=50,
default=20,
),
guidance_scale: float = Input(
description="Scale for classifier-free guidance. The scale is set to 7.5 by default. When using LCM, guidance_scale is set to 1.",
ge=1,
le=20,
default=7.5,
),
temperature: float = Input(
description="Control the diversity of layout planner. Higher value indicates more diversity.",
ge=0.1,
le=2,
default=1.4,
),
) -> ModelOutput:
"""Run a single prediction on the model"""
if positive_prompt is not None and not len(positive_prompt.strip()) == 0:
prompt += positive_prompt
with torch.no_grad():
user_prompt = prompt
if generate_natural_image:
composed_prompt = user_prompt
prompt = self.tokenizer.encode(user_prompt)
else:
if keywords is None or len(keywords.strip()) == 0:
template = f"Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. All keywords are included in the caption. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {user_prompt}"
else:
keywords = keywords.split("/")
keywords = [i.strip() for i in keywords]
template = f"Given a prompt that will be used to generate an image, plan the layout of visual text for the image. The size of the image is 128x128. Therefore, all properties of the positions should not exceed 128, including the coordinates of top, left, right, and bottom. In addition, we also provide all keywords at random order for reference. You dont need to specify the details of font styles. At each line, the format should be keyword left, top, right, bottom. So let us begin. Prompt: {prompt}. Keywords: {str(keywords)}"
msg = template
conv = get_conversation_template(self.m1_model_path)
conv.append_message(conv.roles[0], msg)
conv.append_message(conv.roles[1], None)
prompt = conv.get_prompt()
inputs = self.m1_tokenizer([prompt], return_token_type_ids=False)
inputs = {k: torch.tensor(v).to("cuda") for k, v in inputs.items()}
output_ids = self.m1_model.generate(
**inputs,
do_sample=True,
temperature=temperature,
repetition_penalty=1.0,
max_new_tokens=512,
)
if self.m1_model.config.is_encoder_decoder:
output_ids = output_ids[0]
else:
output_ids = output_ids[0][len(inputs["input_ids"][0]) :]
outputs = self.m1_tokenizer.decode(
output_ids,
skip_special_tokens=True,
spaces_between_special_tokens=False,
)
print(f"[{conv.roles[0]}]\n{msg}")
print(f"[{conv.roles[1]}]\n{outputs}")
ocrs = outputs.split("\n")
current_ocr = ocrs
ocr_ids = []
print("user_prompt", user_prompt)
print("current_ocr", current_ocr)
for ocr in current_ocr:
ocr = ocr.strip()
if len(ocr) == 0 or "###" in ocr or ".com" in ocr:
continue
items = ocr.split()
pred = " ".join(items[:-1])
box = items[-1]
l, t, r, b = box.split(",")
l, t, r, b = int(l), int(t), int(r), int(b)
ocr_ids.extend(
["l" + str(l), "t" + str(t), "r" + str(r), "b" + str(b)]
)
char_list = list(pred)
char_list = [f"[{i}]" for i in char_list]
ocr_ids.extend(char_list)
ocr_ids.append(self.tokenizer.eos_token_id)
caption_ids = (
self.tokenizer(user_prompt, truncation=True, return_tensors="pt")
.input_ids[0]
.tolist()
)
try:
ocr_ids = self.tokenizer.encode(ocr_ids)
prompt = caption_ids + ocr_ids
except:
prompt = caption_ids
user_prompt = self.tokenizer.decode(prompt)
composed_prompt = self.tokenizer.decode(prompt)
prompt = prompt[:77]
while len(prompt) < 77:
prompt.append(self.tokenizer.pad_token_id)
if not use_lcm:
prompts_cond = prompt
prompts_nocond = [self.tokenizer.pad_token_id] * 77
prompts_cond = [prompts_cond] * num_images
prompts_nocond = [prompts_nocond] * num_images
prompts_cond = torch.Tensor(prompts_cond).long().cuda()
prompts_nocond = torch.Tensor(prompts_nocond).long().cuda()
scheduler = self.scheduler
scheduler.set_timesteps(num_inference_steps)
noise = torch.randn((num_images, 4, 64, 64)).to("cuda").half()
input = noise
encoder_hidden_states_cond = self.text_encoder(prompts_cond)[0].half()
encoder_hidden_states_nocond = self.text_encoder(prompts_nocond)[
0
].half()
for t in tqdm(scheduler.timesteps):
with torch.no_grad(): # classifier free guidance
noise_pred_cond = self.unet(
sample=input,
timestep=t,
encoder_hidden_states=encoder_hidden_states_cond[
:num_images
],
).sample # b, 4, 64, 64
noise_pred_uncond = self.unet(
sample=input,
timestep=t,
encoder_hidden_states=encoder_hidden_states_nocond[
:num_images
],
).sample # b, 4, 64, 64
noisy_residual = noise_pred_uncond + guidance_scale * (
noise_pred_cond - noise_pred_uncond
) # b, 4, 64, 64
input = scheduler.step(noisy_residual, t, input).prev_sample
del noise_pred_cond
del noise_pred_uncond
torch.cuda.empty_cache()
# decode
input = 1 / self.vae.config.scaling_factor * input
images = self.vae.decode(input, return_dict=False)[0]
width, height = 512, 512
results = []
new_image = Image.new("RGB", (2 * width, 2 * height))
for index, image in enumerate(images.cpu().float()):
image = (image / 2 + 0.5).clamp(0, 1).unsqueeze(0)
image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
image = Image.fromarray(
(image * 255).round().astype("uint8")
).convert("RGB")
results.append(image)
row = index // 2
col = index % 2
new_image.paste(image, (col * width, row * height))
else:
generator = torch.Generator(device=self.pipe.device).manual_seed(
random.randint(0, 1000)
)
results = self.pipe(
prompt=user_prompt,
generator=generator,
num_inference_steps=num_inference_steps,
guidance_scale=1,
num_images_per_prompt=num_images,
).images
torch.cuda.empty_cache()
output_paths = []
for i, sample in enumerate(results):
output_path = f"/tmp/out-{i}.png"
sample.save(output_path)
output_paths.append(Path(output_path))
return ModelOutput(
output_images=output_paths,
composed_prompt=composed_prompt,
)
+6
View File
@@ -0,0 +1,6 @@
diffusers==0.24.0
accelerate==0.22.0
datasets==2.11.0
transformers==4.28.1
fschat==0.2.26
+24
View File
@@ -0,0 +1,24 @@
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 torchrun --nproc_per_node=8 --master_port=20003 fastchat/train/train_mem.py \
--model_name_or_path lmsys/vicuna-7b-v1.5 \
--data_path data/layout_planner_data_5k.json \
--bf16 True \
--output_dir experiment_result \
--num_train_epochs 6 \
--per_device_train_batch_size 2 \
--per_device_eval_batch_size 2 \
--gradient_accumulation_steps 16 \
--evaluation_strategy "no" \
--save_strategy "steps" \
--save_steps 500 \
--save_total_limit 5 \
--learning_rate 2e-5 \
--weight_decay 0. \
--warmup_ratio 0.03 \
--lr_scheduler_type "cosine" \
--logging_steps 1 \
--fsdp "full_shard auto_wrap" \
--fsdp_transformer_layer_cls_to_wrap 'LlamaDecoderLayer' \
--tf32 True \
--model_max_length 2048 \
--gradient_checkpointing True \
--lazy_preprocess True
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
accelerate launch train_textdiffuser2_inpainting_full.py \
--train_batch_size=16 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-5 \
--max_grad_norm=1 \
--lr_scheduler="constant" \
--lr_warmup_steps=0 \
--output_dir="inpainting_experiment_name" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--resume_from_checkpoint="latest" \
--dataset_path=/path/to/laion-ocr-select \
--train_dataset_index_file=/path/to/train_dataset_index.txt \
--vis_num=16
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
accelerate launch train_textdiffuser2_t2i_full.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_batch_size=18 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-5 \
--max_grad_norm=1 \
--lr_scheduler="constant" \
--lr_warmup_steps=0 \
--output_dir="diffusion_experiment_result" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--index_file_path='/path/to/train_dataset_index.txt' \
--dataset_path='/path/to/laion-ocr-select/' \
--granularity=128 \
--coord_mode="ltrb" \
--max_length=77 \
--resume_from_checkpoint="latest"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
accelerate launch train_textdiffuser2_t2i_lora.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_batch_size=18 \
--gradient_accumulation_steps=4 \
--gradient_checkpointing \
--mixed_precision="fp16" \
--num_train_epochs=6 \
--learning_rate=1e-4 \
--text_encoder_learning_rate=1e-5 \
--lr_scheduler="constant" \
--output_dir="diffusion_experiment_result" \
--enable_xformers_memory_efficient_attention \
--dataloader_num_workers=8 \
--index_file_path='/path/to/train_dataset_index.txt' \
--dataset_path='/path/to/laion-ocr-select/' \
--granularity=128 \
--coord_mode="ltrb" \
--max_length=77 \
--resume_from_checkpoint="latest"