first commit

This commit is contained in:
chflame163
2024-11-06 11:26:50 +08:00
parent b93fbf65cf
commit 3e942afb64
21 changed files with 2163 additions and 1 deletions
+6
View File
@@ -0,0 +1,6 @@
_test_*.*
__pycache__
.venv
.idea
*.pth
*.ini
+65 -1
View File
@@ -1,2 +1,66 @@
# ComfyUI_OmniGen_Wrapper
ComfyUI custom node of [OmniGen] project.
![image](image/omnigen_wrapper_example.jpg)
This node is an unofficial wrapper of the [OmniGen](https://github.com/VectorSpaceLab/OmniGen), running in ComfyUI.
The quantization code is referenced from [Manni1000/OmniGen](https://github.com/Manni1000/OmniGen).
[中文说明点这里](./README_CN.MD)
### Install
Open a terminal window in the ```ComfyUI/custom_nodes``` folder and enter the following command:
```
git clone https://github.com/chflame163/ComfyUI_OmniGen_Wrapper.git
```
### Install dependencies
Run the following command in the Python environment of ComfyUI:
```
python -s -m pip install -r ComfyUI/custom_nodes/ComfyUI_OmniGen_Wrapper/requirements.txt
```
### Download models
When running the plugin for the first time, the model will be automatically downloaded. If the automatic download fails, you can manually download it. Choose one of the following two download methods:
From Huggingface:
* Download all files from [Shitao/OmniGen-v1](https://huggingface.co/Shitao/OmniGen-v1/tree/main) and copy to ```ComfyUI/models/OmniGen/Shitao/OmniGen-v1``` folder;
* Download ```diffusion_pytorch_model.safetensors``` and ```config.json``` from [stabilityai/sdxl-vae](https://huggingface.co/stabilityai/sdxl-vae/tree/main), copy the two files to ```ComfyUI/models/OmniGen/Shitao/OmniGen-v1/vae``` folder.
Or Download all files from BaiduNetdisk and copy to ```ComfyUI/models/OmniGen/Shitao/OmniGen-v1``` folder.
* [BaiduNetdisk](https://pan.baidu.com/s/1uivyo_voaZ668nT3aMLw8Q?pwd=ma06)
### How to use
Start ComfyUI, right click on screen to activate the menu, find ```Add Node``` - ```😺dzNodes``` - ```OmniGen Wrapper```, the node is here.
![image](image/add_node.jpg)
Alternatively, enter OmniGen Wrapper in the node search bar to find the node.
![image](image/search_node.jpg)
### Node Options
![image](image/omnigen_wrapper_node.jpg)
* image_1: Optional input image_1. If input, this image must be described in the prompt and referred to as ```{imgae_1}```.
* image_2: Optional input image_2. If input, this image must be described in the prompt and referred to as ```{imgae_2}```.
* image_3: Optional input image_3. If input, this image must be described in the prompt and referred to as ```{imgae_3}```.
* dtype: Model accuracy, default is the default model accuracy, optional int8. The default precision occupies approximately 12GB of video memory, while int8 occupies approximately 7GB of video memory.
* prompt: The prompt or prompts to guide the image generation. If have image input, use the placeholder ```{imgae_1}```, ```{imgae_2}```, ```{imgae_3}``` to refer to it.
* width: The height in pixels of the generated image. The number must be a multiple of 16.
* height: The width in pixels of the generated image. The number must be a multiple of 16.
* guidance_scale: A higher value will make the generated results of the model more biased towards the condition, but may sacrifice the diversity and degrees of freedom of the image.
* image_guidance_scale: The guidance scale of image.
* steps: The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference.
* separate_cfg_infer: Perform inference on images with different guidance separately; this can save memory when generating images of large size at the expense of slower inference.
* use_kv_cache: Enable kv cache to speed up the inference
* seed: A random seed for generating output.
* control_after_generate: Seed value change option every time it runs.
* cache_model: When set to True, the model is cached and does not need to be loaded again during the next run.
### Star
[![Star History Chart](https://api.star-history.com/svg?repos=chflame163/ComfyUI_OmniGen_Wrapper&type=Date)](https://star-history.com/#chflame163/ComfyUI_OmniGen_Wrapper&Date)
### statement
This project follows the MIT license, Some of its functional code comes from other open-source projects. Thanks to the original author. If used for commercial purposes, please refer to the original project license to authorization agreement.
+60
View File
@@ -0,0 +1,60 @@
# ComfyUI_OmniGen_Wrapper
![image](image/omnigen_wrapper_example.jpg)
本节点是 [OmniGen](https://github.com/VectorSpaceLab/OmniGen) 项目的非官方封装,在ComfyUI中运行。
量化代码参考自 [Manni1000/OmniGen](https://github.com/Manni1000/OmniGen)。
### 安装插件
在ComfyUI/custom_nodes文件夹打开终端窗口,输入以下命令:
```
git clone https://github.com/chflame163/ComfyUI_OmniGen_Wrapper.git
```
### 安装依赖
请在ComfyUI的Python 环境里运行以下命令:
```
python -s -m pip install -r ComfyUI/custom_nodes/ComfyUI_OmniGen_Wrapper/requirements.txt
```
### 下载模型
首次运行插件时将自动下载模型。也可以手动下载,在下面两个下载途径选择其一:
从Huggingface下载:
* 从 [Shitao/OmniGen-v1](https://huggingface.co/Shitao/OmniGen-v1/tree/main) 下载全部模型文件,并复制到```ComfyUI/models/OmniGen/Shitao/OmniGen-v1```文件夹;
* 从 [stabilityai/sdxl-vae](https://huggingface.co/stabilityai/sdxl-vae/tree/main) 下载 diffusion_pytorch_model.safetensors 和 config.json 两个文件,并复制到```ComfyUI/models/OmniGen/Shitao/OmniGen-v1/vae```文件夹。
或者从百度网盘下载全部模型文件并复制到```ComfyUI/models/OmniGen/Shitao/OmniGen-v1```文件夹:
* [百度网盘](https://pan.baidu.com/s/1uivyo_voaZ668nT3aMLw8Q?pwd=ma06)
### 使用节点
启动ComfyUI,点击右键菜单-```Add Node``` - ```😺dzNodes``` - ```OmniGen Wrapper```,找到节点。
![image](image/add_node.jpg)
或者在节点搜索栏中输入 OmniGen Wrapper 找到节点。
![image](image/search_node.jpg)
### 节点参数说明
![image](image/omnigen_wrapper_node.jpg)
* image_1: 可选输入图片1。如果输入,须在prompt中描述此图,用```{imgae_1}```指代。
* image_2: 可选输入图片2。如果输入,须在prompt中描述此图,用```{imgae_2}```指代。
* image_3: 可选输入图片3。如果输入,须在prompt中描述此图,用```{imgae_3}```指代。
* dtype: 模型精度,default为模型默认精度, 可选int8。默认精度大约占用12GB显存,int8大约占用7GB显存。
* prompt: 生成图片的提示词。如果有图片输入,请用```{imgae_1}```、```{imgae_2}```、```{imgae_3}```指代。
* width: 生成图片的宽度,必须为16的倍数。
* height: 生成图片的高度,必须为16的倍数。
* guidance_scale: 引导比例。较高的值会使模型的生成结果更倾向于条件,但可能损失图像的多样性和自由度。
* image_guidance_scale: 图片引导比例。
* steps: 图片生成推理步数。
* separate_cfg_infer: 在不同引导下分别对图像进行推理;这可以在生成大尺寸图像时节省内存,但会使推理速度变慢。
* use_kv_cache: 使用kv缓存以加快推理速度。
* seed: 随机种子。推理使用不同的种子产生不同结果。
* control_after_generate: 每次运行时种子值变化选项。
* cache_model: 设置为True时缓存模型,下次运行无需再次加载模型。
### Star 记录
[![Star History Chart](https://api.star-history.com/svg?repos=chflame163/ComfyUI_OmniGen_Wrapper&type=Date)](https://star-history.com/#chflame163/ComfyUI_OmniGen_Wrapper&Date)
### 声明
本节点遵照MIT开源协议,有部分功能代码和模型来自其他开源项目,感谢原作者。如果作为商业用途,请查阅原项目授权协议使用。
+36
View File
@@ -0,0 +1,36 @@
import importlib.util
import glob
import os
import sys
import __main__
import shutil
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
python = sys.executable
def get_ext_dir(subpath=None, mkdir=False):
dir = os.path.dirname(__file__)
if subpath is not None:
dir = os.path.join(dir, subpath)
dir = os.path.abspath(dir)
if mkdir and not os.path.exists(dir):
os.makedirs(dir)
return dir
py = get_ext_dir("py")
files = os.listdir(py)
for file in files:
if not file.endswith(".py"):
continue
name = os.path.splitext(file)[0]
imported_module = importlib.import_module(".py.{}".format(name), __name__)
try:
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS}
except:
pass
print(f"# 😺 ComfyUI_OmniGen_Wrapper: -> " + '\033[1;32m' + f"Success loaded {len(NODE_CLASS_MAPPINGS)} nodes." + '\033[m')
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+4
View File
@@ -0,0 +1,4 @@
from .model import OmniGen
from .processor import OmniGenProcessor
from .scheduler import OmniGenScheduler
from .pipeline import OmniGenPipeline
+457
View File
@@ -0,0 +1,457 @@
# The code is revised from DiT
import os
import torch
import torch.nn as nn
import numpy as np
import math
from typing import Dict
import torch.nn.functional as F
from diffusers.loaders import PeftAdapterMixin
from timm.models.vision_transformer import PatchEmbed, Attention, Mlp
from huggingface_hub import snapshot_download
from safetensors.torch import load_file
from OmniGen.transformer import Phi3Config, Phi3Transformer
def modulate(x, shift, scale):
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
class TimestepEmbedder(nn.Module):
"""
Embeds scalar timesteps into vector representations.
"""
def __init__(self, hidden_size, frequency_embedding_size=256):
super().__init__()
self.mlp = nn.Sequential(
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
nn.SiLU(),
nn.Linear(hidden_size, hidden_size, bias=True),
)
self.frequency_embedding_size = frequency_embedding_size
@staticmethod
def timestep_embedding(t, dim, max_period=10000):
"""
Create sinusoidal timestep embeddings.
:param t: a 1-D Tensor of N indices, one per batch element.
These may be fractional.
:param dim: the dimension of the output.
:param max_period: controls the minimum frequency of the embeddings.
:return: an (N, D) Tensor of positional embeddings.
"""
# https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py
half = dim // 2
freqs = torch.exp(
-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
).to(device=t.device)
args = t[:, None].float() * freqs[None]
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
if dim % 2:
embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
return embedding
def forward(self, t, dtype=torch.float32):
t_freq = self.timestep_embedding(t, self.frequency_embedding_size).to(dtype)
t_emb = self.mlp(t_freq)
return t_emb
class FinalLayer(nn.Module):
"""
The final layer of DiT.
"""
def __init__(self, hidden_size, patch_size, out_channels):
super().__init__()
self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)
self.adaLN_modulation = nn.Sequential(
nn.SiLU(),
nn.Linear(hidden_size, 2 * hidden_size, bias=True)
)
def forward(self, x, c):
shift, scale = self.adaLN_modulation(c).chunk(2, dim=1)
x = modulate(self.norm_final(x), shift, scale)
x = self.linear(x)
return x
def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0, interpolation_scale=1.0, base_size=1):
"""
grid_size: int of the grid height and width return: pos_embed: [grid_size*grid_size, embed_dim] or
[1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token)
"""
if isinstance(grid_size, int):
grid_size = (grid_size, grid_size)
grid_h = np.arange(grid_size[0], dtype=np.float32) / (grid_size[0] / base_size) / interpolation_scale
grid_w = np.arange(grid_size[1], dtype=np.float32) / (grid_size[1] / base_size) / interpolation_scale
grid = np.meshgrid(grid_w, grid_h) # here w goes first
grid = np.stack(grid, axis=0)
grid = grid.reshape([2, 1, grid_size[1], grid_size[0]])
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
if cls_token and extra_tokens > 0:
pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0)
return pos_embed
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
assert embed_dim % 2 == 0
# use half of dimensions to encode grid_h
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
return emb
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
"""
embed_dim: output dimension for each position
pos: a list of positions to be encoded: size (M,)
out: (M, D)
"""
assert embed_dim % 2 == 0
omega = np.arange(embed_dim // 2, dtype=np.float64)
omega /= embed_dim / 2.
omega = 1. / 10000**omega # (D/2,)
pos = pos.reshape(-1) # (M,)
out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
emb_sin = np.sin(out) # (M, D/2)
emb_cos = np.cos(out) # (M, D/2)
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
return emb
class PatchEmbedMR(nn.Module):
""" 2D Image to Patch Embedding
"""
def __init__(
self,
patch_size: int = 2,
in_chans: int = 4,
embed_dim: int = 768,
bias: bool = True,
):
super().__init__()
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias)
def forward(self, x):
x = self.proj(x)
x = x.flatten(2).transpose(1, 2) # NCHW -> NLC
return x
class Int8Quantized(nn.Module):
def __init__(self, tensor, scale_factor=None):
super().__init__()
if scale_factor is None:
max_val = torch.max(torch.abs(tensor))
scale_factor = max_val / 127.0
# Store quantized weights and scale factor
self.register_buffer('quantized_weight', torch.round(tensor / scale_factor).to(torch.int8))
self.register_buffer('scale_factor', torch.tensor(scale_factor))
def forward(self, dtype=None):
# Dequantize and convert to specified dtype
weight = self.quantized_weight.float() * self.scale_factor
if dtype is not None:
weight = weight.to(dtype)
return weight
class QuantizedLinear(nn.Module):
def __init__(self, weight, bias=None):
super().__init__()
self.weight_quantized = Int8Quantized(weight)
if bias is not None:
self.register_buffer('bias', bias)
else:
self.bias = None
def forward(self, x):
# Dequantize weight to match input dtype
weight = self.weight_quantized(dtype=x.dtype)
return F.linear(x, weight, self.bias)
class OmniGen(nn.Module, PeftAdapterMixin):
"""
Diffusion model with a Transformer backbone.
"""
def __init__(
self,
transformer_config=Phi3Config,
patch_size=2,
in_channels=4,
pe_interpolation: float = 1.0,
pos_embed_max_size: int = 192,
):
super().__init__()
self.in_channels = in_channels
self.out_channels = in_channels
self.patch_size = patch_size
self.pos_embed_max_size = pos_embed_max_size
hidden_size = transformer_config.hidden_size
self.x_embedder = PatchEmbedMR(patch_size, in_channels, hidden_size, bias=True)
self.input_x_embedder = PatchEmbedMR(patch_size, in_channels, hidden_size, bias=True)
self.time_token = TimestepEmbedder(hidden_size)
self.t_embedder = TimestepEmbedder(hidden_size)
self.pe_interpolation = pe_interpolation
pos_embed = get_2d_sincos_pos_embed(hidden_size, pos_embed_max_size, interpolation_scale=self.pe_interpolation, base_size=64)
self.register_buffer("pos_embed", torch.from_numpy(pos_embed).float().unsqueeze(0), persistent=True)
self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels)
self.initialize_weights()
self.llm = Phi3Transformer(config=transformer_config)
self.llm.config.use_cache = False
def _quantize_module(self, module):
"""
Quantize a module to 8-bit precision
"""
for name, child in module.named_children():
if isinstance(child, nn.Linear):
setattr(module, name, QuantizedLinear(child.weight.data, child.bias.data if child.bias is not None else None))
elif isinstance(child, nn.LayerNorm):
# Skip quantization for LayerNorm
continue
else:
self._quantize_module(child)
@classmethod
def from_pretrained(cls, model_name, quantize=False):
if not os.path.exists(model_name):
cache_folder = os.getenv('HF_HUB_CACHE')
model_name = snapshot_download(repo_id=model_name,
cache_dir=cache_folder,
ignore_patterns=['flax_model.msgpack', 'rust_model.ot', 'tf_model.h5'])
config = Phi3Config.from_pretrained(model_name)
model = cls(config)
if os.path.exists(os.path.join(model_name, 'model.safetensors')):
print("Loading safetensors")
ckpt = load_file(os.path.join(model_name, 'model.safetensors'))
else:
ckpt = torch.load(os.path.join(model_name, 'model.pt'), map_location='cpu')
model.load_state_dict(ckpt)
# Only quantize if explicitly requested
if quantize:
print("Quantizing weights to 8-bit...")
model._quantize_module(model.llm)
return model
def initialize_weights(self):
assert not hasattr(self, "llama")
# Initialize transformer layers:
def _basic_init(module):
if isinstance(module, nn.Linear):
torch.nn.init.xavier_uniform_(module.weight)
if module.bias is not None:
nn.init.constant_(module.bias, 0)
self.apply(_basic_init)
# Initialize patch_embed like nn.Linear (instead of nn.Conv2d):
w = self.x_embedder.proj.weight.data
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
nn.init.constant_(self.x_embedder.proj.bias, 0)
w = self.input_x_embedder.proj.weight.data
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
nn.init.constant_(self.x_embedder.proj.bias, 0)
# Initialize timestep embedding MLP:
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
nn.init.normal_(self.time_token.mlp[0].weight, std=0.02)
nn.init.normal_(self.time_token.mlp[2].weight, std=0.02)
# Zero-out output layers:
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
nn.init.constant_(self.final_layer.linear.weight, 0)
nn.init.constant_(self.final_layer.linear.bias, 0)
def unpatchify(self, x, h, w):
"""
x: (N, T, patch_size**2 * C)
imgs: (N, H, W, C)
"""
c = self.out_channels
x = x.reshape(shape=(x.shape[0], h//self.patch_size, w//self.patch_size, self.patch_size, self.patch_size, c))
x = torch.einsum('nhwpqc->nchpwq', x)
imgs = x.reshape(shape=(x.shape[0], c, h, w))
return imgs
def cropped_pos_embed(self, height, width):
"""Crops positional embeddings for SD3 compatibility."""
if self.pos_embed_max_size is None:
raise ValueError("`pos_embed_max_size` must be set for cropping.")
height = height // self.patch_size
width = width // self.patch_size
if height > self.pos_embed_max_size:
raise ValueError(
f"Height ({height}) cannot be greater than `pos_embed_max_size`: {self.pos_embed_max_size}."
)
if width > self.pos_embed_max_size:
raise ValueError(
f"Width ({width}) cannot be greater than `pos_embed_max_size`: {self.pos_embed_max_size}."
)
top = (self.pos_embed_max_size - height) // 2
left = (self.pos_embed_max_size - width) // 2
spatial_pos_embed = self.pos_embed.reshape(1, self.pos_embed_max_size, self.pos_embed_max_size, -1)
spatial_pos_embed = spatial_pos_embed[:, top : top + height, left : left + width, :]
# print(top, top + height, left, left + width, spatial_pos_embed.size())
spatial_pos_embed = spatial_pos_embed.reshape(1, -1, spatial_pos_embed.shape[-1])
return spatial_pos_embed
def patch_multiple_resolutions(self, latents, padding_latent=None, is_input_images:bool=False):
if isinstance(latents, list):
return_list = False
if padding_latent is None:
padding_latent = [None] * len(latents)
return_list = True
patched_latents, num_tokens, shapes = [], [], []
for latent, padding in zip(latents, padding_latent):
height, width = latent.shape[-2:]
if is_input_images:
latent = self.input_x_embedder(latent)
else:
latent = self.x_embedder(latent)
pos_embed = self.cropped_pos_embed(height, width)
latent = latent + pos_embed
if padding is not None:
latent = torch.cat([latent, padding], dim=-2)
patched_latents.append(latent)
num_tokens.append(pos_embed.size(1))
shapes.append([height, width])
if not return_list:
latents = torch.cat(patched_latents, dim=0)
else:
latents = patched_latents
else:
height, width = latents.shape[-2:]
if is_input_images:
latents = self.input_x_embedder(latents)
else:
latents = self.x_embedder(latents)
pos_embed = self.cropped_pos_embed(height, width)
latents = latents + pos_embed
num_tokens = latents.size(1)
shapes = [height, width]
return latents, num_tokens, shapes
def forward(self, x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, padding_latent=None, past_key_values=None, return_past_key_values=True, offload_model:bool=False):
"""
"""
input_is_list = isinstance(x, list)
x, num_tokens, shapes = self.patch_multiple_resolutions(x, padding_latent)
time_token = self.time_token(timestep, dtype=x[0].dtype).unsqueeze(1)
if input_img_latents is not None:
input_latents, _, _ = self.patch_multiple_resolutions(input_img_latents, is_input_images=True)
if input_ids is not None:
condition_embeds = self.llm.embed_tokens(input_ids).clone()
input_img_inx = 0
for b_inx in input_image_sizes.keys():
for start_inx, end_inx in input_image_sizes[b_inx]:
condition_embeds[b_inx, start_inx: end_inx] = input_latents[input_img_inx]
input_img_inx += 1
if input_img_latents is not None:
assert input_img_inx == len(input_latents)
input_emb = torch.cat([condition_embeds, time_token, x], dim=1)
else:
input_emb = torch.cat([time_token, x], dim=1)
output = self.llm(inputs_embeds=input_emb, attention_mask=attention_mask, position_ids=position_ids, past_key_values=past_key_values, offload_model=offload_model)
output, past_key_values = output.last_hidden_state, output.past_key_values
if input_is_list:
image_embedding = output[:, -max(num_tokens):]
time_emb = self.t_embedder(timestep, dtype=x.dtype)
x = self.final_layer(image_embedding, time_emb)
latents = []
for i in range(x.size(0)):
latent = x[i:i+1, :num_tokens[i]]
latent = self.unpatchify(latent, shapes[i][0], shapes[i][1])
latents.append(latent)
else:
image_embedding = output[:, -num_tokens:]
time_emb = self.t_embedder(timestep, dtype=x.dtype)
x = self.final_layer(image_embedding, time_emb)
latents = self.unpatchify(x, shapes[0], shapes[1])
if return_past_key_values:
return latents, past_key_values
return latents
@torch.no_grad()
def forward_with_cfg(self, x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, cfg_scale, use_img_cfg, img_cfg_scale, past_key_values, use_kv_cache, offload_model):
self.llm.config.use_cache = use_kv_cache
model_out, past_key_values = self.forward(x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, past_key_values=past_key_values, return_past_key_values=True, offload_model=offload_model)
if use_img_cfg:
cond, uncond, img_cond = torch.split(model_out, len(model_out) // 3, dim=0)
cond = uncond + img_cfg_scale * (img_cond - uncond) + cfg_scale * (cond - img_cond)
model_out = [cond, cond, cond]
else:
cond, uncond = torch.split(model_out, len(model_out) // 2, dim=0)
cond = uncond + cfg_scale * (cond - uncond)
model_out = [cond, cond]
return torch.cat(model_out, dim=0), past_key_values
@torch.no_grad()
def forward_with_separate_cfg(self, x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, cfg_scale, use_img_cfg, img_cfg_scale, past_key_values, use_kv_cache, offload_model):
self.llm.config.use_cache = use_kv_cache
if past_key_values is None:
past_key_values = [None] * len(attention_mask)
x = torch.split(x, len(x) // len(attention_mask), dim=0)
timestep = timestep.to(x[0].dtype)
timestep = torch.split(timestep, len(timestep) // len(input_ids), dim=0)
model_out, pask_key_values = [], []
for i in range(len(input_ids)):
temp_out, temp_pask_key_values = self.forward(x[i], timestep[i], input_ids[i], input_img_latents[i], input_image_sizes[i], attention_mask[i], position_ids[i], past_key_values=past_key_values[i], return_past_key_values=True, offload_model=offload_model)
model_out.append(temp_out)
pask_key_values.append(temp_pask_key_values)
if len(model_out) == 3:
cond, uncond, img_cond = model_out
cond = uncond + img_cfg_scale * (img_cond - uncond) + cfg_scale * (cond - img_cond)
model_out = [cond, cond, cond]
elif len(model_out) == 2:
cond, uncond = model_out
cond = uncond + cfg_scale * (cond - uncond)
model_out = [cond, cond]
else:
return model_out[0]
return torch.cat(model_out, dim=0), pask_key_values
+336
View File
@@ -0,0 +1,336 @@
import os
import inspect
from typing import Any, Callable, Dict, List, Optional, Union
import gc
from PIL import Image
import numpy as np
import torch
from huggingface_hub import snapshot_download
from peft import LoraConfig, PeftModel
from diffusers.models import AutoencoderKL
from diffusers.utils import (
USE_PEFT_BACKEND,
is_torch_xla_available,
logging,
replace_example_docstring,
scale_lora_layers,
unscale_lora_layers,
)
from safetensors.torch import load_file
from OmniGen import OmniGen, OmniGenProcessor, OmniGenScheduler
logger = logging.get_logger(__name__)
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> from OmniGen import OmniGenPipeline
>>> pipe = FluxControlNetPipeline.from_pretrained(
... base_model
... )
>>> prompt = "A woman holds a bouquet of flowers and faces the camera"
>>> image = pipe(
... prompt,
... guidance_scale=2.5,
... num_inference_steps=50,
... ).images[0]
>>> image.save("t2i.png")
```
"""
class OmniGenPipeline:
def __init__(
self,
vae: AutoencoderKL,
model: OmniGen,
processor: OmniGenProcessor,
device: Union[str, torch.device] = None,
):
self.vae = vae
self.model = model
self.processor = processor
self.device = device
if device is None:
if torch.cuda.is_available():
self.device = torch.device("cuda")
elif torch.backends.mps.is_available():
self.device = torch.device("mps")
else:
logger.info("Don't detect any available GPUs, using CPU instead, this may take long time to generate image!!!")
self.device = torch.device("cpu")
self.model.to(torch.bfloat16)
self.model.eval()
self.vae.eval()
self.model_cpu_offload = False
@classmethod
def from_pretrained(cls, model_name, vae_path: str=None, Quantization: bool=False):
if not os.path.exists(model_name) or (not os.path.exists(os.path.join(model_name, 'model.safetensors')) and model_name == "Shitao/OmniGen-v1"):
logger.info("Model not found, downloading...")
cache_folder = os.getenv('HF_HUB_CACHE')
model_name = snapshot_download(repo_id=model_name,
cache_dir=cache_folder,
ignore_patterns=['flax_model.msgpack', 'rust_model.ot', 'tf_model.h5', 'model.pt'])
logger.info(f"Downloaded model to {model_name}")
print(f"Loading OmniGen Model")
model = OmniGen.from_pretrained(model_name, quantize=Quantization)
print(f"Loading OmniGen Processor")
processor = OmniGenProcessor.from_pretrained(model_name)
print(f"Loading OmniGen VAE")
if os.path.exists(os.path.join(model_name, "vae")):
vae = AutoencoderKL.from_pretrained(os.path.join(model_name, "vae"))
elif vae_path is not None:
vae = AutoencoderKL.from_pretrained(vae_path)
else:
logger.info(f"No VAE found in {model_name}, downloading stabilityai/sdxl-vae from HF")
vae = AutoencoderKL.from_pretrained("stabilityai/sdxl-vae")
return cls(vae, model, processor)
def merge_lora(self, lora_path: str):
model = PeftModel.from_pretrained(self.model, lora_path)
model.merge_and_unload()
self.model = model
def to(self, device: Union[str, torch.device]):
if isinstance(device, str):
device = torch.device(device)
self.model.to(device)
self.vae.to(device)
self.device = device
def vae_encode(self, x, dtype):
if self.vae.config.shift_factor is not None:
x = self.vae.encode(x).latent_dist.sample()
x = (x - self.vae.config.shift_factor) * self.vae.config.scaling_factor
else:
x = self.vae.encode(x).latent_dist.sample().mul_(self.vae.config.scaling_factor)
x = x.to(dtype)
return x
def move_to_device(self, data):
if isinstance(data, list):
return [x.to(self.device) for x in data]
return data.to(self.device)
def enable_model_cpu_offload(self):
self.model_cpu_offload = True
self.model.to("cpu")
self.vae.to("cpu")
torch.cuda.empty_cache() # Clear VRAM
gc.collect() # Run garbage collection to free system RAM
def disable_model_cpu_offload(self):
self.model_cpu_offload = False
self.model.to(self.device)
self.vae.to(self.device)
@torch.no_grad()
@replace_example_docstring(EXAMPLE_DOC_STRING)
def __call__(
self,
prompt: Union[str, List[str]],
input_images: Union[List[str], List[List[str]]] = None,
height: int = 1024,
width: int = 1024,
num_inference_steps: int = 50,
guidance_scale: float = 3,
use_img_guidance: bool = True,
img_guidance_scale: float = 1.6,
max_input_image_size: int = 1024,
separate_cfg_infer: bool = True,
offload_model: bool = False,
use_kv_cache: bool = True,
offload_kv_cache: bool = True,
use_input_image_size_as_output: bool = False,
dtype: torch.dtype = torch.bfloat16,
seed: int = None,
Quantization: bool = False,
):
r"""
Function invoked when calling the pipeline for generation.
Args:
prompt (`str` or `List[str]`):
The prompt or prompts to guide the image generation.
input_images (`List[str]` or `List[List[str]]`, *optional*):
The list of input images. We will replace the "<|image_i|>" in prompt with the 1-th image in list.
height (`int`, *optional*, defaults to 1024):
The height in pixels of the generated image. The number must be a multiple of 16.
width (`int`, *optional*, defaults to 1024):
The width in pixels of the generated image. The number must be a multiple of 16.
num_inference_steps (`int`, *optional*, defaults to 50):
The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference.
guidance_scale (`float`, *optional*, defaults to 4.0):
Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
`guidance_scale` is defined as `w` of equation 2. of [Imagen
Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
usually at the expense of lower image quality.
use_img_guidance (`bool`, *optional*, defaults to True):
Defined as equation 3 in [Instrucpix2pix](https://arxiv.org/pdf/2211.09800).
img_guidance_scale (`float`, *optional*, defaults to 1.6):
Defined as equation 3 in [Instrucpix2pix](https://arxiv.org/pdf/2211.09800).
max_input_image_size (`int`, *optional*, defaults to 1024): the maximum size of input image, which will be used to crop the input image to the maximum size
separate_cfg_infer (`bool`, *optional*, defaults to False):
Perform inference on images with different guidance separately; this can save memory when generating images of large size at the expense of slower inference.
use_kv_cache (`bool`, *optional*, defaults to True): enable kv cache to speed up the inference
offload_kv_cache (`bool`, *optional*, defaults to True): offload the cached key and value to cpu, which can save memory but slow down the generation silightly
offload_model (`bool`, *optional*, defaults to False): offload the model to cpu, which can save memory but slow down the generation
use_input_image_size_as_output (bool, defaults to False): whether to use the input image size as the output image size, which can be used for single-image input, e.g., image editing task
seed (`int`, *optional*):
A random seed for generating output.
dtype (`torch.dtype`, *optional*, defaults to `torch.bfloat16`):
data type for the model
Examples:
Returns:
A list with the generated images.
"""
# check inputs:
if use_input_image_size_as_output:
assert isinstance(prompt, str) and len(input_images) == 1, "if you want to make sure the output image have the same size as the input image, please only input one image instead of multiple input images"
else:
assert height%16 == 0 and width%16 == 0, "The height and width must be a multiple of 16."
if input_images is None:
use_img_guidance = False
if isinstance(prompt, str):
prompt = [prompt]
input_images = [input_images] if input_images is not None else None
# set model and processor
if max_input_image_size != self.processor.max_image_size:
self.processor = OmniGenProcessor(self.processor.text_tokenizer, max_image_size=max_input_image_size)
if offload_model:
self.enable_model_cpu_offload()
else:
self.disable_model_cpu_offload()
input_data = self.processor(prompt, input_images, height=height, width=width, use_img_cfg=use_img_guidance, separate_cfg_input=separate_cfg_infer, use_input_image_size_as_output=use_input_image_size_as_output)
num_prompt = len(prompt)
num_cfg = 2 if use_img_guidance else 1
if use_input_image_size_as_output:
if separate_cfg_infer:
height, width = input_data['input_pixel_values'][0][0].shape[-2:]
else:
height, width = input_data['input_pixel_values'][0].shape[-2:]
latent_size_h, latent_size_w = height//8, width//8
if seed is not None:
generator = torch.Generator(device=self.device).manual_seed(seed)
else:
generator = None
latents = torch.randn(num_prompt, 4, latent_size_h, latent_size_w, device=self.device, generator=generator)
latents = torch.cat([latents]*(1+num_cfg), 0).to(dtype)
self.vae.to(self.device, dtype=torch.bfloat16)
if input_images is not None and self.model_cpu_offload: self.vae.to(self.device)
input_img_latents = []
if separate_cfg_infer:
for temp_pixel_values in input_data['input_pixel_values']:
temp_input_latents = []
for img in temp_pixel_values:
img = self.vae_encode(img.to(self.device, dtype=torch.bfloat16), dtype)
temp_input_latents.append(img)
input_img_latents.append(temp_input_latents)
else:
for img in input_data['input_pixel_values']:
img = self.vae_encode(img.to(self.device), dtype)
input_img_latents.append(img)
if input_images is not None and self.model_cpu_offload:
self.vae.to('cpu')
torch.cuda.empty_cache() # Clear VRAM
gc.collect() # Run garbage collection to free system RAM
model_kwargs = dict(input_ids=self.move_to_device(input_data['input_ids']),
input_img_latents=input_img_latents,
input_image_sizes=input_data['input_image_sizes'],
attention_mask=self.move_to_device(input_data["attention_mask"]),
position_ids=self.move_to_device(input_data["position_ids"]),
cfg_scale=guidance_scale,
img_cfg_scale=img_guidance_scale,
use_img_cfg=use_img_guidance,
use_kv_cache=use_kv_cache,
offload_model=offload_model,
)
#unlode vae to cpu
self.vae.to('cpu')
torch.cuda.empty_cache() # Clear VRAM
gc.collect() # Run garbage collection to free system RAM
if separate_cfg_infer:
func = self.model.forward_with_separate_cfg
else:
func = self.model.forward_with_cfg
# self.model.to(dtype)
# move main model to gpu
self.model.to(self.device, dtype=dtype)
if self.model_cpu_offload:
for name, param in self.model.named_parameters():
if 'layers' in name and 'layers.0' not in name:
param.data = param.data.cpu()
else:
param.data = param.data.to(self.device)
for buffer_name, buffer in self.model.named_buffers():
setattr(self.model, buffer_name, buffer.to(self.device))
# else:
# self.model.to(self.device)
scheduler = OmniGenScheduler(num_steps=num_inference_steps)
samples = scheduler(latents, func, model_kwargs, use_kv_cache=use_kv_cache, offload_kv_cache=offload_kv_cache)
samples = samples.chunk((1+num_cfg), dim=0)[0]
if self.model_cpu_offload:
self.model.to('cpu')
torch.cuda.empty_cache()
gc.collect()
self.vae.to(self.device)
samples = samples.to(torch.float32)
if self.vae.config.shift_factor is not None:
samples = samples / self.vae.config.scaling_factor + self.vae.config.shift_factor
else:
samples = samples / self.vae.config.scaling_factor
# Move samples to GPU and ensure they are in bfloat16 (for the VAE)
samples = samples.to(self.device, dtype=torch.bfloat16)
# Load VAE into VRAM (GPU) in bfloat16
self.vae.to(self.device, dtype=torch.bfloat16)
# Decode the samples using the VAE
samples = self.vae.decode(samples).sample
if self.model_cpu_offload:
# unlode main model to cpu
self.model.to('cpu')
self.vae.to('cpu')
torch.cuda.empty_cache()
gc.collect()
output_samples = (samples * 0.5 + 0.5).clamp(0, 1)*255
output_samples = output_samples.permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy()
output_images = []
for i, sample in enumerate(output_samples):
output_images.append(Image.fromarray(sample))
torch.cuda.empty_cache() # Clear VRAM
gc.collect() # Run garbage collection to free system RAM
return output_images
+338
View File
@@ -0,0 +1,338 @@
import os
import re
from typing import Dict, List
import json
import torch
import numpy as np
import random
from PIL import Image
from torchvision import transforms
from transformers import AutoTokenizer
from huggingface_hub import snapshot_download
from OmniGen.utils import (
create_logger,
update_ema,
requires_grad,
center_crop_arr,
crop_arr,
)
class OmniGenProcessor:
def __init__(self,
text_tokenizer,
max_image_size: int=1024):
self.text_tokenizer = text_tokenizer
self.max_image_size = max_image_size
self.image_transform = transforms.Compose([
transforms.Lambda(lambda pil_image: crop_arr(pil_image, max_image_size)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True)
])
self.collator = OmniGenCollator()
self.separate_collator = OmniGenSeparateCollator()
@classmethod
def from_pretrained(cls, model_name):
if not os.path.exists(model_name):
cache_folder = os.getenv('HF_HUB_CACHE')
model_name = snapshot_download(repo_id=model_name,
cache_dir=cache_folder,
allow_patterns="*.json")
text_tokenizer = AutoTokenizer.from_pretrained(model_name)
return cls(text_tokenizer)
def process_image(self, image):
image = Image.open(image).convert('RGB')
return self.image_transform(image)
def process_multi_modal_prompt(self, text, input_images):
text = self.add_prefix_instruction(text)
if input_images is None or len(input_images) == 0:
model_inputs = self.text_tokenizer(text)
return {"input_ids": model_inputs.input_ids, "pixel_values": None, "image_sizes": None}
pattern = r"<\|image_\d+\|>"
prompt_chunks = [self.text_tokenizer(chunk).input_ids for chunk in re.split(pattern, text)]
for i in range(1, len(prompt_chunks)):
if prompt_chunks[i][0] == 1:
prompt_chunks[i] = prompt_chunks[i][1:]
image_tags = re.findall(pattern, text)
image_ids = [int(s.split("|")[1].split("_")[-1]) for s in image_tags]
unique_image_ids = sorted(list(set(image_ids)))
assert unique_image_ids == list(range(1, len(unique_image_ids)+1)), f"image_ids must start from 1, and must be continuous int, e.g. [1, 2, 3], cannot be {unique_image_ids}"
# total images must be the same as the number of image tags
assert len(unique_image_ids) == len(input_images), f"total images must be the same as the number of image tags, got {len(unique_image_ids)} image tags and {len(input_images)} images"
input_images = [input_images[x-1] for x in image_ids]
all_input_ids = []
img_inx = []
idx = 0
for i in range(len(prompt_chunks)):
all_input_ids.extend(prompt_chunks[i])
if i != len(prompt_chunks) -1:
start_inx = len(all_input_ids)
size = input_images[i].size(-2) * input_images[i].size(-1) // 16 // 16
img_inx.append([start_inx, start_inx+size])
all_input_ids.extend([0]*size)
return {"input_ids": all_input_ids, "pixel_values": input_images, "image_sizes": img_inx}
def add_prefix_instruction(self, prompt):
user_prompt = '<|user|>\n'
generation_prompt = 'Generate an image according to the following instructions\n'
assistant_prompt = '<|assistant|>\n<|diffusion|>'
prompt_suffix = "<|end|>\n"
prompt = f"{user_prompt}{generation_prompt}{prompt}{prompt_suffix}{assistant_prompt}"
return prompt
def __call__(self,
instructions: List[str],
input_images: List[List[str]] = None,
height: int = 1024,
width: int = 1024,
negative_prompt: str = "low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers.",
use_img_cfg: bool = True,
separate_cfg_input: bool = False,
use_input_image_size_as_output: bool=False,
) -> Dict:
if input_images is None:
use_img_cfg = False
if isinstance(instructions, str):
instructions = [instructions]
input_images = [input_images]
input_data = []
for i in range(len(instructions)):
cur_instruction = instructions[i]
cur_input_images = None if input_images is None else input_images[i]
if cur_input_images is not None and len(cur_input_images) > 0:
cur_input_images = [self.process_image(x) for x in cur_input_images]
else:
cur_input_images = None
assert "<img><|image_1|></img>" not in cur_instruction
mllm_input = self.process_multi_modal_prompt(cur_instruction, cur_input_images)
neg_mllm_input, img_cfg_mllm_input = None, None
neg_mllm_input = self.process_multi_modal_prompt(negative_prompt, None)
if use_img_cfg:
if cur_input_images is not None and len(cur_input_images) >= 1:
img_cfg_prompt = [f"<img><|image_{i+1}|></img>" for i in range(len(cur_input_images))]
img_cfg_mllm_input = self.process_multi_modal_prompt(" ".join(img_cfg_prompt), cur_input_images)
else:
img_cfg_mllm_input = neg_mllm_input
if use_input_image_size_as_output:
input_data.append((mllm_input, neg_mllm_input, img_cfg_mllm_input, [mllm_input['pixel_values'][0].size(-2), mllm_input['pixel_values'][0].size(-1)]))
else:
input_data.append((mllm_input, neg_mllm_input, img_cfg_mllm_input, [height, width]))
if separate_cfg_input:
return self.separate_collator(input_data)
return self.collator(input_data)
class OmniGenCollator:
def __init__(self, pad_token_id=2, hidden_size=3072):
self.pad_token_id = pad_token_id
self.hidden_size = hidden_size
def create_position(self, attention_mask, num_tokens_for_output_images):
position_ids = []
text_length = attention_mask.size(-1)
img_length = max(num_tokens_for_output_images)
for mask in attention_mask:
temp_l = torch.sum(mask)
temp_position = [0]*(text_length-temp_l) + [i for i in range(temp_l+img_length+1)] # we add a time embedding into the sequence, so add one more token
position_ids.append(temp_position)
return torch.LongTensor(position_ids)
def create_mask(self, attention_mask, num_tokens_for_output_images):
extended_mask = []
padding_images = []
text_length = attention_mask.size(-1)
img_length = max(num_tokens_for_output_images)
seq_len = text_length + img_length + 1 # we add a time embedding into the sequence, so add one more token
inx = 0
for mask in attention_mask:
temp_l = torch.sum(mask)
pad_l = text_length - temp_l
temp_mask = torch.tril(torch.ones(size=(temp_l+1, temp_l+1)))
image_mask = torch.zeros(size=(temp_l+1, img_length))
temp_mask = torch.cat([temp_mask, image_mask], dim=-1)
image_mask = torch.ones(size=(img_length, temp_l+img_length+1))
temp_mask = torch.cat([temp_mask, image_mask], dim=0)
if pad_l > 0:
pad_mask = torch.zeros(size=(temp_l+1+img_length, pad_l))
temp_mask = torch.cat([pad_mask, temp_mask], dim=-1)
pad_mask = torch.ones(size=(pad_l, seq_len))
temp_mask = torch.cat([pad_mask, temp_mask], dim=0)
true_img_length = num_tokens_for_output_images[inx]
pad_img_length = img_length - true_img_length
if pad_img_length > 0:
temp_mask[:, -pad_img_length:] = 0
temp_padding_imgs = torch.zeros(size=(1, pad_img_length, self.hidden_size))
else:
temp_padding_imgs = None
extended_mask.append(temp_mask.unsqueeze(0))
padding_images.append(temp_padding_imgs)
inx += 1
return torch.cat(extended_mask, dim=0), padding_images
def adjust_attention_for_input_images(self, attention_mask, image_sizes):
for b_inx in image_sizes.keys():
for start_inx, end_inx in image_sizes[b_inx]:
attention_mask[b_inx][start_inx:end_inx, start_inx:end_inx] = 1
return attention_mask
def pad_input_ids(self, input_ids, image_sizes):
max_l = max([len(x) for x in input_ids])
padded_ids = []
attention_mask = []
new_image_sizes = []
for i in range(len(input_ids)):
temp_ids = input_ids[i]
temp_l = len(temp_ids)
pad_l = max_l - temp_l
if pad_l == 0:
attention_mask.append([1]*max_l)
padded_ids.append(temp_ids)
else:
attention_mask.append([0]*pad_l+[1]*temp_l)
padded_ids.append([self.pad_token_id]*pad_l+temp_ids)
if i in image_sizes:
new_inx = []
for old_inx in image_sizes[i]:
new_inx.append([x+pad_l for x in old_inx])
image_sizes[i] = new_inx
return torch.LongTensor(padded_ids), torch.LongTensor(attention_mask), image_sizes
def process_mllm_input(self, mllm_inputs, target_img_size):
num_tokens_for_output_images = []
for img_size in target_img_size:
num_tokens_for_output_images.append(img_size[0]*img_size[1]//16//16)
pixel_values, image_sizes = [], {}
b_inx = 0
for x in mllm_inputs:
if x['pixel_values'] is not None:
pixel_values.extend(x['pixel_values'])
for size in x['image_sizes']:
if b_inx not in image_sizes:
image_sizes[b_inx] = [size]
else:
image_sizes[b_inx].append(size)
b_inx += 1
pixel_values = [x.unsqueeze(0) for x in pixel_values]
input_ids = [x['input_ids'] for x in mllm_inputs]
padded_input_ids, attention_mask, image_sizes = self.pad_input_ids(input_ids, image_sizes)
position_ids = self.create_position(attention_mask, num_tokens_for_output_images)
attention_mask, padding_images = self.create_mask(attention_mask, num_tokens_for_output_images)
attention_mask = self.adjust_attention_for_input_images(attention_mask, image_sizes)
return padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes
def __call__(self, features):
mllm_inputs = [f[0] for f in features]
cfg_mllm_inputs = [f[1] for f in features]
img_cfg_mllm_input = [f[2] for f in features]
target_img_size = [f[3] for f in features]
if img_cfg_mllm_input[0] is not None:
mllm_inputs = mllm_inputs + cfg_mllm_inputs + img_cfg_mllm_input
target_img_size = target_img_size + target_img_size + target_img_size
else:
mllm_inputs = mllm_inputs + cfg_mllm_inputs
target_img_size = target_img_size + target_img_size
all_padded_input_ids, all_position_ids, all_attention_mask, all_padding_images, all_pixel_values, all_image_sizes = self.process_mllm_input(mllm_inputs, target_img_size)
data = {"input_ids": all_padded_input_ids,
"attention_mask": all_attention_mask,
"position_ids": all_position_ids,
"input_pixel_values": all_pixel_values,
"input_image_sizes": all_image_sizes,
"padding_images": all_padding_images,
}
return data
class OmniGenSeparateCollator(OmniGenCollator):
def __call__(self, features):
mllm_inputs = [f[0] for f in features]
cfg_mllm_inputs = [f[1] for f in features]
img_cfg_mllm_input = [f[2] for f in features]
target_img_size = [f[3] for f in features]
all_padded_input_ids, all_attention_mask, all_position_ids, all_pixel_values, all_image_sizes, all_padding_images = [], [], [], [], [], []
padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes = self.process_mllm_input(mllm_inputs, target_img_size)
all_padded_input_ids.append(padded_input_ids)
all_attention_mask.append(attention_mask)
all_position_ids.append(position_ids)
all_pixel_values.append(pixel_values)
all_image_sizes.append(image_sizes)
all_padding_images.append(padding_images)
if cfg_mllm_inputs[0] is not None:
padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes = self.process_mllm_input(cfg_mllm_inputs, target_img_size)
all_padded_input_ids.append(padded_input_ids)
all_attention_mask.append(attention_mask)
all_position_ids.append(position_ids)
all_pixel_values.append(pixel_values)
all_image_sizes.append(image_sizes)
all_padding_images.append(padding_images)
if img_cfg_mllm_input[0] is not None:
padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes = self.process_mllm_input(img_cfg_mllm_input, target_img_size)
all_padded_input_ids.append(padded_input_ids)
all_attention_mask.append(attention_mask)
all_position_ids.append(position_ids)
all_pixel_values.append(pixel_values)
all_image_sizes.append(image_sizes)
all_padding_images.append(padding_images)
data = {"input_ids": all_padded_input_ids,
"attention_mask": all_attention_mask,
"position_ids": all_position_ids,
"input_pixel_values": all_pixel_values,
"input_image_sizes": all_image_sizes,
"padding_images": all_padding_images,
}
return data
+183
View File
@@ -0,0 +1,183 @@
from tqdm import tqdm
from typing import Optional, Dict, Any, Tuple, List
import gc
import torch
from transformers.cache_utils import Cache, DynamicCache, OffloadedCache
class OmniGenCache(DynamicCache):
def __init__(self,
num_tokens_for_img: int, offload_kv_cache: bool=False) -> None:
if not torch.cuda.is_available():
print("No avaliable GPU, offload_kv_cache wiil be set to False, which will result in large memory usage and time cost when input multiple images!!!")
offload_kv_cache = False
raise RuntimeError("OffloadedCache can only be used with a GPU")
super().__init__()
self.original_device = []
self.prefetch_stream = torch.cuda.Stream()
self.num_tokens_for_img = num_tokens_for_img
self.offload_kv_cache = offload_kv_cache
def prefetch_layer(self, layer_idx: int):
"Starts prefetching the next layer cache"
if layer_idx < len(self):
with torch.cuda.stream(self.prefetch_stream):
# Prefetch next layer tensors to GPU
device = self.original_device[layer_idx]
self.key_cache[layer_idx] = self.key_cache[layer_idx].to(device, non_blocking=True)
self.value_cache[layer_idx] = self.value_cache[layer_idx].to(device, non_blocking=True)
def evict_previous_layer(self, layer_idx: int):
"Moves the previous layer cache to the CPU"
if len(self) > 2:
# We do it on the default stream so it occurs after all earlier computations on these tensors are done
if layer_idx == 0:
prev_layer_idx = -1
else:
prev_layer_idx = (layer_idx - 1) % len(self)
self.key_cache[prev_layer_idx] = self.key_cache[prev_layer_idx].to("cpu", non_blocking=True)
self.value_cache[prev_layer_idx] = self.value_cache[prev_layer_idx].to("cpu", non_blocking=True)
def __getitem__(self, layer_idx: int) -> List[Tuple[torch.Tensor]]:
"Gets the cache for this layer to the device. Prefetches the next and evicts the previous layer."
if layer_idx < len(self):
if self.offload_kv_cache:
# Evict the previous layer if necessary
torch.cuda.current_stream().synchronize()
self.evict_previous_layer(layer_idx)
# Load current layer cache to its original device if not already there
original_device = self.original_device[layer_idx]
# self.prefetch_stream.synchronize(original_device)
torch.cuda.synchronize(self.prefetch_stream)
key_tensor = self.key_cache[layer_idx]
value_tensor = self.value_cache[layer_idx]
# Prefetch the next layer
self.prefetch_layer((layer_idx + 1) % len(self))
else:
key_tensor = self.key_cache[layer_idx]
value_tensor = self.value_cache[layer_idx]
return (key_tensor, value_tensor)
else:
raise KeyError(f"Cache only has {len(self)} layers, attempted to access layer with index {layer_idx}")
def update(
self,
key_states: torch.Tensor,
value_states: torch.Tensor,
layer_idx: int,
cache_kwargs: Optional[Dict[str, Any]] = None,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Updates the cache with the new `key_states` and `value_states` for the layer `layer_idx`.
Parameters:
key_states (`torch.Tensor`):
The new key states to cache.
value_states (`torch.Tensor`):
The new value states to cache.
layer_idx (`int`):
The index of the layer to cache the states for.
cache_kwargs (`Dict[str, Any]`, `optional`):
Additional arguments for the cache subclass. No additional arguments are used in `OffloadedCache`.
Return:
A tuple containing the updated key and value states.
"""
# Update the cache
if len(self.key_cache) < layer_idx:
raise ValueError("OffloadedCache does not support model usage where layers are skipped. Use DynamicCache.")
elif len(self.key_cache) == layer_idx:
# only cache the states for condition tokens
key_states = key_states[..., :-(self.num_tokens_for_img+1), :]
value_states = value_states[..., :-(self.num_tokens_for_img+1), :]
# Update the number of seen tokens
if layer_idx == 0:
self._seen_tokens += key_states.shape[-2]
self.key_cache.append(key_states)
self.value_cache.append(value_states)
self.original_device.append(key_states.device)
if self.offload_kv_cache:
self.evict_previous_layer(layer_idx)
return self.key_cache[layer_idx], self.value_cache[layer_idx]
else:
# only cache the states for condition tokens
key_tensor, value_tensor = self[layer_idx]
k = torch.cat([key_tensor, key_states], dim=-2)
v = torch.cat([value_tensor, value_states], dim=-2)
return k, v
class OmniGenScheduler:
def __init__(self, num_steps: int=50, time_shifting_factor: int=1):
self.num_steps = num_steps
self.time_shift = time_shifting_factor
t = torch.linspace(0, 1, num_steps+1)
t = t / (t + time_shifting_factor - time_shifting_factor * t)
self.sigma = t
def crop_kv_cache(self, past_key_values, num_tokens_for_img):
# return
crop_past_key_values = ()
for layer_idx in range(len(past_key_values)):
key_states, value_states = past_key_values[layer_idx][:2]
crop_past_key_values += ((key_states[..., :-(num_tokens_for_img+1), :], value_states[..., :-(num_tokens_for_img+1), :], ),)
# return crop_past_key_values
return DynamicCache.from_legacy_cache(crop_past_key_values)
def crop_position_ids_for_cache(self, position_ids, num_tokens_for_img):
if isinstance(position_ids, list):
for i in range(len(position_ids)):
position_ids[i] = position_ids[i][:, -(num_tokens_for_img+1):]
else:
position_ids = position_ids[:, -(num_tokens_for_img+1):]
return position_ids
def crop_attention_mask_for_cache(self, attention_mask, num_tokens_for_img):
if isinstance(attention_mask, list):
return [x[..., -(num_tokens_for_img+1):, :] for x in attention_mask]
return attention_mask[..., -(num_tokens_for_img+1):, :]
def crop_cache(self, cache, num_tokens_for_img):
for i in range(len(cache.key_cache)):
cache.key_cache[i] = cache.key_cache[i][..., :-(num_tokens_for_img+1), :]
cache.value_cache[i] = cache.value_cache[i][..., :-(num_tokens_for_img+1), :]
return cache
def __call__(self, z, func, model_kwargs, use_kv_cache: bool=True, offload_kv_cache: bool=True):
num_tokens_for_img = z.size(-1)*z.size(-2) // 4
if isinstance(model_kwargs['input_ids'], list):
cache = [OmniGenCache(num_tokens_for_img, offload_kv_cache) for _ in range(len(model_kwargs['input_ids']))] if use_kv_cache else None
else:
cache = OmniGenCache(num_tokens_for_img, offload_kv_cache) if use_kv_cache else None
results = {}
for i in tqdm(range(self.num_steps)):
timesteps = torch.zeros(size=(len(z), )).to(z.device) + self.sigma[i]
pred, cache = func(z, timesteps, past_key_values=cache, **model_kwargs)
sigma_next = self.sigma[i+1]
sigma = self.sigma[i]
z = z + (sigma_next - sigma) * pred
if i == 0 and use_kv_cache:
num_tokens_for_img = z.size(-1)*z.size(-2) // 4
if isinstance(cache, list):
model_kwargs['input_ids'] = [None] * len(cache)
else:
model_kwargs['input_ids'] = None
model_kwargs['position_ids'] = self.crop_position_ids_for_cache(model_kwargs['position_ids'], num_tokens_for_img)
model_kwargs['attention_mask'] = self.crop_attention_mask_for_cache(model_kwargs['attention_mask'], num_tokens_for_img)
del cache
torch.cuda.empty_cache()
gc.collect()
return z
+2
View File
@@ -0,0 +1,2 @@
from .data import DatasetFromJson, TrainDataCollator
from .loss import training_losses
+116
View File
@@ -0,0 +1,116 @@
import os
import datasets
from datasets import load_dataset, ClassLabel, concatenate_datasets
import torch
import numpy as np
import random
from PIL import Image
import json
import copy
# import torchvision.transforms as T
from torchvision import transforms
import pickle
import re
from OmniGen import OmniGenProcessor
from OmniGen.processor import OmniGenCollator
class DatasetFromJson(torch.utils.data.Dataset):
def __init__(
self,
json_file: str,
image_path: str,
processer: OmniGenProcessor,
image_transform,
max_input_length_limit: int = 18000,
condition_dropout_prob: float = 0.1,
keep_raw_resolution: bool = True,
):
self.image_transform = image_transform
self.processer = processer
self.condition_dropout_prob = condition_dropout_prob
self.max_input_length_limit = max_input_length_limit
self.keep_raw_resolution = keep_raw_resolution
self.data = load_dataset('json', data_files=json_file)['train']
self.image_path = image_path
def process_image(self, image_file):
if self.image_path is not None:
image_file = os.path.join(self.image_path, image_file)
image = Image.open(image_file).convert('RGB')
return self.image_transform(image)
def get_example(self, index):
example = self.data[index]
instruction, input_images, output_image = example['instruction'], example['input_images'], example['output_image']
if random.random() < self.condition_dropout_prob:
instruction = '<cfg>'
input_images = None
if input_images is not None:
input_images = [self.process_image(x) for x in input_images]
mllm_input = self.processer.process_multi_modal_prompt(instruction, input_images)
output_image = self.process_image(output_image)
return (mllm_input, output_image)
def __getitem__(self, index):
return self.get_example(index)
for _ in range(8):
try:
mllm_input, output_image = self.get_example(index)
if len(mllm_input['input_ids']) > self.max_input_length_limit:
raise RuntimeError(f"cur number of tokens={len(mllm_input['input_ids'])}, larger than max_input_length_limit={self.max_input_length_limit}")
return mllm_input, output_image
except Exception as e:
print("error when loading data: ", e)
print(self.data[index])
index = random.randint(0, len(self.data)-1)
raise RuntimeError("Too many bad data.")
def __len__(self):
return len(self.data)
class TrainDataCollator(OmniGenCollator):
def __init__(self, pad_token_id: int, hidden_size: int, keep_raw_resolution: bool):
self.pad_token_id = pad_token_id
self.hidden_size = hidden_size
self.keep_raw_resolution = keep_raw_resolution
def __call__(self, features):
mllm_inputs = [f[0] for f in features]
output_images = [f[1].unsqueeze(0) for f in features]
target_img_size = [[x.size(-2), x.size(-1)] for x in output_images]
all_padded_input_ids, all_position_ids, all_attention_mask, all_padding_images, all_pixel_values, all_image_sizes = self.process_mllm_input(mllm_inputs, target_img_size)
if not self.keep_raw_resolution:
output_image = torch.cat(output_image, dim=0)
if len(pixel_values) > 0:
all_pixel_values = torch.cat(all_pixel_values, dim=0)
else:
all_pixel_values = None
data = {"input_ids": all_padded_input_ids,
"attention_mask": all_attention_mask,
"position_ids": all_position_ids,
"input_pixel_values": all_pixel_values,
"input_image_sizes": all_image_sizes,
"padding_images": all_padding_images,
"output_images": output_images,
}
return data
+68
View File
@@ -0,0 +1,68 @@
import torch
def sample_x0(x1):
"""Sampling x0 & t based on shape of x1 (if needed)
Args:
x1 - data point; [batch, *dim]
"""
if isinstance(x1, (list, tuple)):
x0 = [torch.randn_like(img_start) for img_start in x1]
else:
x0 = torch.randn_like(x1)
return x0
def sample_timestep(x1):
u = torch.normal(mean=0.0, std=1.0, size=(len(x1),))
t = 1 / (1 + torch.exp(-u))
t = t.to(x1[0])
return t
def training_losses(model, x1, model_kwargs=None, snr_type='uniform'):
"""Loss for training torche score model
Args:
- model: backbone model; could be score, noise, or velocity
- x1: datapoint
- model_kwargs: additional arguments for torche model
"""
if model_kwargs == None:
model_kwargs = {}
B = len(x1)
x0 = sample_x0(x1)
t = sample_timestep(x1)
if isinstance(x1, (list, tuple)):
xt = [t[i] * x1[i] + (1 - t[i]) * x0[i] for i in range(B)]
ut = [x1[i] - x0[i] for i in range(B)]
else:
dims = [1] * (len(x1.size()) - 1)
t_ = t.view(t.size(0), *dims)
xt = t_ * x1 + (1 - t_) * x0
ut = x1 - x0
model_output = model(xt, t, **model_kwargs)
terms = {}
if isinstance(x1, (list, tuple)):
assert len(model_output) == len(ut) == len(x1)
for i in range(B):
terms["loss"] = torch.stack(
[((ut[i] - model_output[i]) ** 2).mean() for i in range(B)],
dim=0,
)
else:
terms["loss"] = mean_flat(((model_output - ut) ** 2))
return terms
def mean_flat(x):
"""
Take torche mean over all non-batch dimensions.
"""
return torch.mean(x, dim=list(range(1, len(x.size()))))
+194
View File
@@ -0,0 +1,194 @@
import math
import warnings
from typing import List, Optional, Tuple, Union
import torch
import torch.utils.checkpoint
from torch import nn
from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
from huggingface_hub import snapshot_download
from transformers.modeling_outputs import (
BaseModelOutputWithPast,
CausalLMOutputWithPast,
SequenceClassifierOutputWithPast,
TokenClassifierOutput,
)
from transformers.modeling_utils import PreTrainedModel
from transformers import Phi3Config, Phi3Model
from transformers.cache_utils import Cache, DynamicCache, StaticCache
from transformers.utils import logging
logger = logging.get_logger(__name__)
class Phi3Transformer(Phi3Model):
"""
Transformer decoder consisting of *config.num_hidden_layers* layers. Each layer is a [`Phi3DecoderLayer`]
We only modified the attention mask
Args:
config: Phi3Config
"""
def prefetch_layer(self, layer_idx: int, device: torch.device):
"Starts prefetching the next layer cache"
with torch.cuda.stream(self.prefetch_stream):
# Prefetch next layer tensors to GPU
for name, param in self.layers[layer_idx].named_parameters():
param.data = param.data.to(device, non_blocking=True)
def evict_previous_layer(self, layer_idx: int):
"Moves the previous layer cache to the CPU"
prev_layer_idx = layer_idx - 1
for name, param in self.layers[prev_layer_idx].named_parameters():
param.data = param.data.to("cpu", non_blocking=True)
def get_offlaod_layer(self, layer_idx: int, device: torch.device):
# init stream
if not hasattr(self, "prefetch_stream"):
self.prefetch_stream = torch.cuda.Stream()
# delete previous layer
torch.cuda.current_stream().synchronize()
self.evict_previous_layer(layer_idx)
# make sure the current layer is ready
torch.cuda.synchronize(self.prefetch_stream)
# load next layer
self.prefetch_layer((layer_idx + 1) % len(self.layers), device)
def forward(
self,
input_ids: torch.LongTensor = None,
attention_mask: Optional[torch.Tensor] = None,
position_ids: Optional[torch.LongTensor] = None,
past_key_values: Optional[List[torch.FloatTensor]] = None,
inputs_embeds: Optional[torch.FloatTensor] = None,
use_cache: Optional[bool] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
cache_position: Optional[torch.LongTensor] = None,
offload_model: Optional[bool] = False,
) -> Union[Tuple, BaseModelOutputWithPast]:
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
output_hidden_states = (
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
)
use_cache = use_cache if use_cache is not None else self.config.use_cache
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
if (input_ids is None) ^ (inputs_embeds is not None):
raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
if self.gradient_checkpointing and self.training:
if use_cache:
logger.warning_once(
"`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`..."
)
use_cache = False
# kept for BC (non `Cache` `past_key_values` inputs)
return_legacy_cache = False
if use_cache and not isinstance(past_key_values, Cache):
return_legacy_cache = True
if past_key_values is None:
past_key_values = DynamicCache()
else:
past_key_values = DynamicCache.from_legacy_cache(past_key_values)
logger.warning_once(
"We detected that you are passing `past_key_values` as a tuple of tuples. This is deprecated and "
"will be removed in v4.47. Please convert your cache or use an appropriate `Cache` class "
"(https://huggingface.co/docs/transformers/kv_cache#legacy-cache-format)"
)
# if inputs_embeds is None:
# inputs_embeds = self.embed_tokens(input_ids)
# if cache_position is None:
# past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
# cache_position = torch.arange(
# past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
# )
# if position_ids is None:
# position_ids = cache_position.unsqueeze(0)
if attention_mask is not None and attention_mask.dim() == 3:
dtype = inputs_embeds.dtype
min_dtype = torch.finfo(dtype).min
attention_mask = (1 - attention_mask) * min_dtype
attention_mask = attention_mask.unsqueeze(1).to(inputs_embeds.dtype)
else:
raise Exception("attention_mask parameter was unavailable or invalid")
# causal_mask = self._update_causal_mask(
# attention_mask, inputs_embeds, cache_position, past_key_values, output_attentions
# )
hidden_states = inputs_embeds
# decoder layers
all_hidden_states = () if output_hidden_states else None
all_self_attns = () if output_attentions else None
next_decoder_cache = None
layer_idx = -1
for decoder_layer in self.layers:
layer_idx += 1
if output_hidden_states:
all_hidden_states += (hidden_states,)
if self.gradient_checkpointing and self.training:
layer_outputs = self._gradient_checkpointing_func(
decoder_layer.__call__,
hidden_states,
attention_mask,
position_ids,
past_key_values,
output_attentions,
use_cache,
cache_position,
)
else:
if offload_model and not self.training:
self.get_offlaod_layer(layer_idx, device=inputs_embeds.device)
layer_outputs = decoder_layer(
hidden_states,
attention_mask=attention_mask,
position_ids=position_ids,
past_key_value=past_key_values,
output_attentions=output_attentions,
use_cache=use_cache,
cache_position=cache_position,
)
hidden_states = layer_outputs[0]
if use_cache:
next_decoder_cache = layer_outputs[2 if output_attentions else 1]
if output_attentions:
all_self_attns += (layer_outputs[1],)
hidden_states = self.norm(hidden_states)
# add hidden states from the last decoder layer
if output_hidden_states:
print('************')
all_hidden_states += (hidden_states,)
next_cache = next_decoder_cache if use_cache else None
if return_legacy_cache:
next_cache = next_cache.to_legacy_cache()
if not return_dict:
return tuple(v for v in [hidden_states, next_cache, all_hidden_states, all_self_attns] if v is not None)
return BaseModelOutputWithPast(
last_hidden_state=hidden_states,
past_key_values=next_cache,
hidden_states=all_hidden_states,
attentions=all_self_attns,
)
+110
View File
@@ -0,0 +1,110 @@
import logging
from PIL import Image
import torch
import numpy as np
def create_logger(logging_dir):
"""
Create a logger that writes to a log file and stdout.
"""
logging.basicConfig(
level=logging.INFO,
format='[\033[34m%(asctime)s\033[0m] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[logging.StreamHandler(), logging.FileHandler(f"{logging_dir}/log.txt")]
)
logger = logging.getLogger(__name__)
return logger
@torch.no_grad()
def update_ema(ema_model, model, decay=0.9999):
"""
Step the EMA model towards the current model.
"""
ema_params = dict(ema_model.named_parameters())
for name, param in model.named_parameters():
# TODO: Consider applying only to params that require_grad to avoid small numerical changes of pos_embed
ema_params[name].mul_(decay).add_(param.data, alpha=1 - decay)
def requires_grad(model, flag=True):
"""
Set requires_grad flag for all parameters in a model.
"""
for p in model.parameters():
p.requires_grad = flag
def center_crop_arr(pil_image, image_size):
"""
Center cropping implementation from ADM.
https://github.com/openai/guided-diffusion/blob/8fb3ad9197f16bbc40620447b2742e13458d2831/guided_diffusion/image_datasets.py#L126
"""
while min(*pil_image.size) >= 2 * image_size:
pil_image = pil_image.resize(
tuple(x // 2 for x in pil_image.size), resample=Image.BOX
)
scale = image_size / min(*pil_image.size)
pil_image = pil_image.resize(
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC
)
arr = np.array(pil_image)
crop_y = (arr.shape[0] - image_size) // 2
crop_x = (arr.shape[1] - image_size) // 2
return Image.fromarray(arr[crop_y: crop_y + image_size, crop_x: crop_x + image_size])
def crop_arr(pil_image, max_image_size):
while min(*pil_image.size) >= 2 * max_image_size:
pil_image = pil_image.resize(
tuple(x // 2 for x in pil_image.size), resample=Image.BOX
)
if max(*pil_image.size) > max_image_size:
scale = max_image_size / max(*pil_image.size)
pil_image = pil_image.resize(
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC
)
if min(*pil_image.size) < 16:
scale = 16 / min(*pil_image.size)
pil_image = pil_image.resize(
tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC
)
arr = np.array(pil_image)
crop_y1 = (arr.shape[0] % 16) // 2
crop_y2 = arr.shape[0] % 16 - crop_y1
crop_x1 = (arr.shape[1] % 16) // 2
crop_x2 = arr.shape[1] % 16 - crop_x1
arr = arr[crop_y1:arr.shape[0]-crop_y2, crop_x1:arr.shape[1]-crop_x2]
return Image.fromarray(arr)
def vae_encode(vae, x, weight_dtype):
if x is not None:
if vae.config.shift_factor is not None:
x = vae.encode(x).latent_dist.sample()
x = (x - vae.config.shift_factor) * vae.config.scaling_factor
else:
x = vae.encode(x).latent_dist.sample().mul_(vae.config.scaling_factor)
x = x.to(weight_dtype)
return x
def vae_encode_list(vae, x, weight_dtype):
latents = []
for img in x:
img = vae_encode(vae, img, weight_dtype)
latents.append(img)
return latents
+164
View File
@@ -0,0 +1,164 @@
import os
import sys
sys.path.append(os.path.dirname(__file__))
import torch
import numpy as np
import random
import shutil
import tempfile
from PIL import Image
from huggingface_hub import snapshot_download
import folder_paths
from .OmniGen import OmniGenPipeline
model_path = os.path.join(folder_paths.models_dir, "OmniGen", "Shitao", "OmniGen-v1")
def tensor2pil(t_image: torch.Tensor) -> Image:
return Image.fromarray(np.clip(255.0 * t_image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
def generate_random_name(prefix:str, suffix:str, length:int) -> str:
name = ''.join(random.choice("abcdefghijklmnopqrstupvxyz1234567890") for x in range(length))
return prefix + name + suffix
def save_tmp_image(image:Image, temp_dir:str) -> str:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False, dir=temp_dir) as f:
image.save(f.name)
return f.name
class OmniGen_Model:
def __init__(self, quantization):
self.quantization = quantization
self.pipe = OmniGenPipeline.from_pretrained(
model_path,
Quantization=quantization
)
class DZ_OmniGenV1:
def __init__(self):
self.NODE_NAME = "OmniGen Wrapper"
self.model = None
@classmethod
def INPUT_TYPES(s):
dtype_list = ["default", "int8"]
return {
"required": {
"dtype": (dtype_list,),
"prompt": ("STRING", {
"default": "input image as {image_1}, e.g.", "multiline":True
}),
"width": ("INT", {
"default": 1024, "min": 16, "max": 2048, "step": 16
}),
"height": ("INT", {
"default": 1024, "min": 8, "max": 2048, "step": 16
}),
"guidance_scale": ("FLOAT", {
"default": 2.5, "min": 1.0, "max": 5.0, "step": 0.1
}),
"img_guidance_scale": ("FLOAT", {
"default": 1.6, "min": 1.0, "max": 2.0, "step": 0.1
}),
"steps": ("INT", {
"default": 25, "min": 1, "max": 100, "step": 1
}),
"separate_cfg_infer": ("BOOLEAN", {
"default": True,
"tooltip": "Can save memory when generating images of large size at the expense of slower inference"
}),
"use_kv_cache": ("BOOLEAN", {
"default": False,
"tooltip": "Enable kv cache to speed up the inference"
}),
"seed": ("INT", {
"default": 0, "min": 0, "max": 1e18, "step": 1
}),
"cache_model": ("BOOLEAN", {
"default": False, "tooltip": "Cache model in VRM to save loading time"
}),
},
"optional": {
"image_1": ("IMAGE",),
"image_2": ("IMAGE",),
"image_3": ("IMAGE",)
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run_omnigen"
CATEGORY = '😺dzNodes/OmniGen Wrapper'
def run_omnigen(self, dtype, prompt, width, height, guidance_scale, img_guidance_scale,
steps, separate_cfg_infer, use_kv_cache, seed, cache_model,
image_1=None, image_2=None, image_3=None
):
if not os.path.exists(os.path.join(model_path, "model.safetensors")):
snapshot_download("Shitao/OmniGen-v1",local_dir=model_path)
quantization = True if dtype == "int8" else False
if self.model is None or self.model.quantization != quantization:
self.model = OmniGen_Model(quantization)
temp_dir = os.path.join(folder_paths.get_temp_directory(), generate_random_name('_ominigen_', '_temp', 16))
if os.path.isdir(temp_dir):
shutil.rmtree(temp_dir)
try:
os.makedirs(temp_dir)
except Exception as e:
print(f"Error: {self.NODE_NAME} skipped, because {e}", message_type='error')
return (None,)
input_images = []
if image_1 is not None:
input_images.append(save_tmp_image(tensor2pil(image_1), temp_dir))
prompt = prompt.replace("{image_1}", "<img><|image_1|></img>")
if image_2 is not None:
input_images.append(save_tmp_image(tensor2pil(image_2), temp_dir))
prompt = prompt.replace("{image_2}", "<img><|image_2|></img>")
if image_3 is not None:
input_images.append(save_tmp_image(tensor2pil(image_2), temp_dir))
prompt = prompt.replace("{image_3}", "<img><|image_3|></img>")
if len(input_images) == 0:
input_images = None
# Generate image
output = self.model.pipe(
prompt=prompt,
input_images=input_images,
height=height,
width=width,
guidance_scale=guidance_scale,
img_guidance_scale=img_guidance_scale,
num_inference_steps=steps,
separate_cfg_infer=separate_cfg_infer, # set False can speed up the inference process
use_kv_cache=use_kv_cache,
seed=seed,
)
ret_image = np.array(output[0]) / 255.0
ret_image = torch.from_numpy(ret_image)
if not cache_model:
self.model = None
import gc
# Cleanup
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
shutil.rmtree(temp_dir)
return (ret_image.unsqueeze(0),)
NODE_CLASS_MAPPINGS = {
"dzOmniGenWrapper": DZ_OmniGenV1
}
NODE_DISPLAY_NAME_MAPPINGS = {
"dzOmniGenWrapper": "😺dz: OmniGen Wrapper"
}
+15
View File
@@ -0,0 +1,15 @@
[project]
name = "ComfyUI_OmniGen_Wrapper"
description = "ComfyUI custom node of OmniGen project."
version = "1.0.0"
license = "MIT"
dependencies = ["transformers", "diffusers", "numpy", "datasets", accelerate", "timm", "peft", "safetensors", "huggingface_hub"]
[project.urls]
Repository = "https://github.com/chflame163/ComfyUI_OmniGen_Wrapper"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "chflame163"
DisplayName = "ComfyUI_OmniGen_Wrapper"
Icon = ""
+9
View File
@@ -0,0 +1,9 @@
transformers>=4.45.0
diffusers>=0.29.0
numpy<2
datasets
accelerate
timm
peft
safetensors
huggingface_hub