feat: init

This commit is contained in:
raykindle
2025-04-30 17:10:09 +08:00
parent ef729d7d26
commit de9dca93bb
22 changed files with 3053 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# Python cache files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
dist/
build/
*.egg-info/
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Models and data
models/
outputs/
# IDE specific files
.idea/
.vscode/
*.swp
*.swo
# OS specific files
.DS_Store
Thumbs.db
refer_src/
+135
View File
@@ -0,0 +1,135 @@
# ComfyUI_Step1X-Edit
[English](README.md) | [中文文档](README_CN.md)
This custom node integrates the [Step1X-Edit](https://github.com/stepfun-ai/Step1X-Edit) image editing model into [ComfyUI](https://github.com/comfyanonymous/ComfyUI). Step1X-Edit is a state-of-the-art image editing model that processes a reference image and user's editing instruction to generate a new image.
## Features
- Load and use Step1X-Edit models in ComfyUI
- Edit images with natural language instructions
- Control the editing process with parameters like steps and guidance scale
## Examples
Here are some examples of what you can achieve with ComfyUI_Step1X-Edit:
| Example 1 | Example 2 |
|:-----------:|:------------:|
| ![Example Image1](examples/0000.jpg) | ![Example Image2](examples/0001.jpg) |
- Example 1: The corresponding prompt was: "给这个女生的脖子上戴一个带有红宝石的吊坠。" ("Add pendant with a ruby around this girl's neck.")
- Example 2: The corresponding prompt was: "让她哭。" ("Let her cry.")
You can find the example workflow in the [examples directory](examples/step1x_edit_example.json). Load it directly into ComfyUI to see how it works.
## Installation
1. Clone this repository into your ComfyUI's `custom_nodes` directory:
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/raykindle/ComfyUI_Step1X-Edit.git
```
2. Install the required dependencies:
Step 1: Install ComfyUI_Step1X-Edit
```bash
cd ComfyUI_Step1X-Edit
pip install -r requirements.txt
```
Step 2: Install [`flash-attn`](https://github.com/Dao-AILab/flash-attention), here we provide a script to help find the pre-built wheel suitable for your system.
```bash
python utils/get_flash_attn.py
```
The script will generate a wheel name like `flash_attn-2.7.2.post1+cu12torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl`, which could be found in [the release page of flash-attn](https://github.com/Dao-AILab/flash-attention/releases).
Then you can download the corresponding pre-built wheel and install it following the instructions in [`flash-attn`](https://github.com/Dao-AILab/flash-attention).
3. Download the Step1X-Edit-FP8 model:
- Step1X-Edit diffusion model: Download `step1x-edit-i1258-FP8.safetensors` from [HuggingFace](https://huggingface.co/meimeilook/Step1X-Edit-FP8/tree/main) and place it in ComfyUI's `models/diffusion_models` directory
- Step1X-Edit VAE: Download `vae.safetensors` from [HuggingFace](https://huggingface.co/meimeilook/Step1X-Edit-FP8/tree/main) and place it in ComfyUI's `models/vae` directory
- Qwen2.5-VL model: Download [Qwen2.5-VL-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct/tree/main) and place it in ComfyUI's `models/text_encoders/Qwen2.5-VL-7B-Instruct` directory
## Usage
1. Start ComfyUI and create a new workflow.
2. Add the "Step1X-Edit Model Loader" node to your workflow.
3. Configure the model parameters:
- Select `step1x-edit-i1258-FP8.safetensors` as the diffusion model
- Select `vae.safetensors` as the VAE
- Set "Qwen2.5-VL-7B-Instruct" as the text encoder
- Set additional parameters (dtype, quantized, offload) as needed
4. Connect an "Step1X-Edit Generate" node to the model node.
5. Provide an input image and an editing prompt.
6. Run the workflow to generate edited images.
## Example Workflow
Here's a simple example workflow:
1. Load Image → Step1X-Edit Generate
2. Step1X-Edit Model Loader → Step1X-Edit Generate
3. Step1X-Edit Generate → Preview Image
## Parameters
### Step1X-Edit Model Loader
- `diffusion_model`: The Step1X-Edit diffusion model file (select from the diffusion_models dropdown)
- `vae`: The Step1X-Edit VAE file (select from the vae dropdown)
- `text_encoder`: The path to the Qwen2.5-VL model directory name (e.g., "Qwen2.5-VL-7B-Instruct")
- `dtype`: Model precision (bfloat16, float16, or float32)
- `quantized`: Whether to use FP8 quantized weights (true recommended)
- `offload`: Whether to offload models to CPU when not in use
### Step1X-Edit Generate
- `model`: The Step1X-Edit model bundle
- `image`: The input image to edit
- `prompt`: Text instructions describing the desired edit
- `negative_prompt`: Text describing what to avoid
- `steps`: Number of denoising steps (more steps = better quality but slower)
- `cfg_scale`: Guidance scale (how closely to follow the prompt)
- `image_size`: Size of the output image (512 recommended)
- `seed`: Random seed for reproducibility
## Memory Requirements
The Step1X-Edit model requires significant GPU memory:
| Model Version | Peak GPU Memory (512px / 768px / 1024px) |
|:-------------:|:----------------------------------------:|
| Standard | 42.5GB / 46.5GB / 49.8GB |
| FP8 Quantized | 31GB / 31.5GB / 34GB |
| With Offload | 25.9GB / 27.3GB / 29.1GB |
| FP8 + Offload | 18GB / 18GB / 18GB |
For lower memory usage, enable the `quantized` and/or `offload` options in the Model Loader node.
## Troubleshooting
- If you encounter CUDA out of memory errors, try:
- Enabling the `offload` option
- Using the FP8 quantized model
- Reducing the image size
- Closing other GPU-intensive applications
- If you get errors about missing files:
- Make sure your model paths are correct
- The diffusion model should be in `models/diffusion_models`
- The VAE should be in `models/vae`
- The text encoder should be in `models/text_encoders/Qwen2.5-VL-7B-Instruct`
- If you get import errors, ensure all dependencies are installed correctly
## License
This custom node is licensed under the Apache License 2.0, consistent with the original Step1X-Edit repository.
## Acknowledgements
- The Step1X-Edit team for creating the original model
- ComfyUI for the extensible UI framework
+135
View File
@@ -0,0 +1,135 @@
# ComfyUI_Step1X-Edit
[English](README.md) | [中文文档](README_CN.md)
此自定义节点将 [Step1X-Edit](https://github.com/stepfun-ai/Step1X-Edit) 图像编辑模型集成到 [ComfyUI](https://github.com/comfyanonymous/ComfyUI) 中。Step1X-Edit 是一个先进的图像编辑模型,它接收参考图像和用户的编辑指令,生成新的图像。
## 功能特点
- 在 ComfyUI 中加载和使用 Step1X-Edit 模型
- 使用自然语言指令编辑图像
- 通过步数和引导系数等参数控制编辑过程
## 示例展示
以下是使用 ComfyUI_Step1X-Edit 可以实现的效果示例:
| 示例 1 | 示例 2 |
|:-----------:|:------------:|
| ![示例图像1](examples/0000.jpg) | ![示例图像2](examples/0001.jpg) |
- 示例 1:对应的提示词是:"给这个女生的脖子上戴一个带有红宝石的吊坠。" ("Add pendant with a ruby around this girl's neck.")
- 示例 2:对应的提示词是:"让她哭。" ("Let her cry.")
您可以在[示例目录](examples/step1x_edit_example.json)中找到示例工作流。直接将其加载到 ComfyUI 中即可查看其工作原理。
## 安装方法
1. 将此仓库克隆到 ComfyUI 的 `custom_nodes` 目录中:
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/raykindle/ComfyUI_Step1X-Edit.git
```
2. 安装所需依赖:
步骤 1: 安装 ComfyUI_Step1X-Edit
```bash
cd ComfyUI_Step1X-Edit
pip install -r requirements.txt
```
步骤 2: 安装 [`flash-attn`](https://github.com/Dao-AILab/flash-attention),我们提供了一个脚本,帮助您找到适合您系统的预编译轮子。
```bash
python utils/get_flash_attn.py
```
该脚本将生成一个轮子名称,例如:`flash_attn-2.7.2.post1+cu12torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl`,该名称可在 [flash-attn 的发布页面](https://github.com/Dao-AILab/flash-attention/releases) 中找到。
然后,您可以下载相应的预编译轮子,并按照 [`flash-attn`](https://github.com/Dao-AILab/flash-attention) 中的说明进行安装。
3. 下载 Step1X-Edit-FP8 模型:
- Step1X-Edit 扩散模型:从 [HuggingFace](https://huggingface.co/meimeilook/Step1X-Edit-FP8/tree/main) 下载 `step1x-edit-i1258-FP8.safetensors` 并放置在 ComfyUI 的 `models/diffusion_models` 目录中
- Step1X-Edit VAE:从 [HuggingFace](https://huggingface.co/meimeilook/Step1X-Edit-FP8/tree/main) 下载 `vae.safetensors` 并放置在 ComfyUI 的 `models/vae` 目录中
- Qwen2.5-VL 模型:下载 [Qwen2.5-VL-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct/tree/main) 并放置在 ComfyUI 的 `models/text_encoders/Qwen2.5-VL-7B-Instruct` 目录中
## 使用方法
1. 启动 ComfyUI 并创建新的工作流。
2. 添加 "Step1X-Edit Model Loader"(Step1X-Edit 模型加载器)节点到工作流中。
3. 配置模型参数:
- 选择 `step1x-edit-i1258-FP8.safetensors` 作为扩散模型
- 选择 `vae.safetensors` 作为 VAE
- 设置 "Qwen2.5-VL-7B-Instruct" 作为文本编码器
- 根据需要设置其他参数(dtype、quantized、offload)
4. 连接 "Step1X-Edit Generate"(Step1X-Edit 生成)节点到模型节点。
5. 提供输入图像和编辑提示。
6. 运行工作流生成编辑后的图像。
## 工作流示例
以下是一个简单的工作流示例:
1. Load Image(加载图像)→ Step1X-Edit Generate(Step1X-Edit 生成)
2. Step1X-Edit Model Loader(Step1X-Edit 模型加载器)→ Step1X-Edit Generate(Step1X-Edit 生成)
3. Step1X-Edit Generate(Step1X-Edit 生成)→ Preview Image(预览图像)
## 参数说明
### Step1X-Edit Model Loader(Step1X-Edit 模型加载器)
- `diffusion_model`:Step1X-Edit 扩散模型文件(从 diffusion_models 下拉菜单中选择)
- `vae`:Step1X-Edit VAE 文件(从 vae 下拉菜单中选择)
- `text_encoder`:Qwen2.5-VL 模型目录名称(例如 "Qwen2.5-VL-7B-Instruct")
- `dtype`:模型精度(bfloat16、float16 或 float32)
- `quantized`:是否使用 FP8 量化权重(推荐 开启)
- `offload`:在不使用时是否将模型卸载到 CPU
### Step1X-Edit Generate(Step1X-Edit 生成)
- `model_bundle`:Step1X-Edit 模型包
- `input_image`:要编辑的输入图像
- `prompt`:描述所需编辑的文本指令
- `negative_prompt`:描述要避免的内容的文本
- `num_steps`:去噪步数(更多步数 = 更好的质量但更慢)
- `cfg_guidance`:引导系数(控制对提示的遵循程度)
- `size_level`:输出图像大小(推荐 512)
- `seed`:随机种子,用于可重现性
## 内存需求
Step1X-Edit 模型需要相当大的 GPU 内存:
| 模型版本 | 峰值 GPU 内存(512px / 768px / 1024px) |
|:-------------:|:----------------------------------------:|
| 标准版 | 42.5GB / 46.5GB / 49.8GB |
| FP8 量化版 | 31GB / 31.5GB / 34GB |
| 使用 Offload | 25.9GB / 27.3GB / 29.1GB |
| FP8 + Offload | 18GB / 18GB / 18GB |
为了降低内存使用,请在模型加载器节点中启用 `quantized` 和/或 `offload` 选项。
## 故障排除
- 如果遇到 CUDA 内存不足错误,请尝试:
- 启用 `offload` 选项
- 使用 FP8 量化模型
- 减小图像大小
- 关闭其他占用 GPU 的应用程序
- 如果遇到文件缺失错误:
- 确保模型路径正确
- 扩散模型应位于 `models/diffusion_models`
- VAE 应位于 `models/vae`
- 文本编码器应位于 `models/text_encoders/Qwen2.5-VL-7B-Instruct`
- 如果遇到导入错误,请确保所有依赖项都正确安装
## 许可证
此自定义节点采用 Apache License 2.0 许可,与原始 Step1X-Edit 仓库一致。
## 致谢
- 感谢 Step1X-Edit 团队创建原始模型
- 感谢 ComfyUI 提供可扩展的 UI 框架
+12
View File
@@ -0,0 +1,12 @@
from .nodes.step1x_edit_nodes import Step1XEditModelLoader, Step1XEditGenerateNode
NODE_CLASS_MAPPINGS = {
"Step1XEditModelLoader": Step1XEditModelLoader,
"Step1XEditGenerate": Step1XEditGenerateNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Step1XEditModelLoader": "Step1X-Edit Model Loader",
"Step1XEditGenerate": "Step1X-Edit Generate"
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

+196
View File
@@ -0,0 +1,196 @@
{
"last_node_id": 10,
"last_link_id": 17,
"nodes": [
{
"id": 2,
"type": "LoadImage",
"pos": [
745.9241333007812,
676.44091796875
],
"size": [
210,
299.4796447753906
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
15
],
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"0001.png",
"image"
]
},
{
"id": 4,
"type": "PreviewImage",
"pos": [
961.9817504882812,
358.0769958496094
],
"size": [
397.47607421875,
618.7960815429688
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 17
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 9,
"type": "Step1XEditGenerate",
"pos": [
745.9320068359375,
358.9280700683594
],
"size": [
210,
282
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "model_bundle",
"type": "STEP1X_MODEL_BUNDLE",
"link": 16
},
{
"name": "input_image",
"type": "IMAGE",
"link": 15
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
17
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Step1XEditGenerate"
},
"widgets_values": [
"让她哭。",
"",
10,
6,
318,
"randomize",
768
]
},
{
"id": 10,
"type": "Step1XEditModelLoader",
"pos": [
744.1864624023438,
127.32762908935547
],
"size": [
617.0538330078125,
198.91787719726562
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "STEP1X_MODEL_BUNDLE",
"type": "STEP1X_MODEL_BUNDLE",
"links": [
16
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Step1XEditModelLoader"
},
"widgets_values": [
"step1x-edit-i1258-FP8.safetensors",
"vae.safetensors",
"Qwen2.5-VL-7B-Instruct",
"bfloat16",
true,
false
]
}
],
"links": [
[
15,
2,
0,
9,
1,
"IMAGE"
],
[
16,
10,
0,
9,
0,
"STEP1X_MODEL_BUNDLE"
],
[
17,
9,
0,
4,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.7283845674106205,
"offset": [
-159.39467322432967,
-30.630752842927603
]
}
},
"version": 0.4
}
View File
+133
View File
@@ -0,0 +1,133 @@
import math
import torch
import torch.nn.functional as F
try:
import flash_attn
from flash_attn.flash_attn_interface import (
_flash_attn_forward,
flash_attn_func,
flash_attn_varlen_func,
)
except ImportError:
flash_attn = None
flash_attn_varlen_func = None
_flash_attn_forward = None
flash_attn_func = None
MEMORY_LAYOUT = {
# flash模式:
# 预处理: 输入 [batch_size, seq_len, num_heads, head_dim]
# 后处理: 保持形状不变
"flash": (
lambda x: x, # 保持形状
lambda x: x, # 保持形状
),
# torch/vanilla模式:
# 预处理: 交换序列和注意力头的维度 [B,S,A,D] -> [B,A,S,D]
# 后处理: 交换回原始维度 [B,A,S,D] -> [B,S,A,D]
"torch": (
lambda x: x.transpose(1, 2), # (B,S,A,D) -> (B,A,S,D)
lambda x: x.transpose(1, 2), # (B,A,S,D) -> (B,S,A,D)
),
"vanilla": (
lambda x: x.transpose(1, 2),
lambda x: x.transpose(1, 2),
),
}
def attention(
q,
k,
v,
mode="flash",
drop_rate=0,
attn_mask=None,
causal=False,
):
"""
执行QKV自注意力计算
Args:
q (torch.Tensor): 查询张量,形状 [batch_size, seq_len, num_heads, head_dim]
k (torch.Tensor): 键张量,形状 [batch_size, seq_len_kv, num_heads, head_dim]
v (torch.Tensor): 值张量,形状 [batch_size, seq_len_kv, num_heads, head_dim]
mode (str): 注意力模式,可选 'flash', 'torch', 'vanilla'
drop_rate (float): 注意力矩阵的dropout概率
attn_mask (torch.Tensor): 注意力掩码,形状根据模式不同而变化
causal (bool): 是否使用因果注意力(仅关注前面位置)
Returns:
torch.Tensor: 注意力输出,形状 [batch_size, seq_len, num_heads * head_dim]
"""
# 获取预处理和后处理函数
pre_attn_layout, post_attn_layout = MEMORY_LAYOUT[mode]
# 应用预处理变换
q = pre_attn_layout(q) # 形状根据模式变化
k = pre_attn_layout(k)
v = pre_attn_layout(v)
if mode == "torch":
# 使用PyTorch原生的scaled_dot_product_attention
if attn_mask is not None and attn_mask.dtype != torch.bool:
attn_mask = attn_mask.to(q.dtype)
x = F.scaled_dot_product_attention(
q, k, v, attn_mask=attn_mask, dropout_p=drop_rate, is_causal=causal
)
elif mode == "flash":
assert flash_attn_func is not None, "flash_attn_func未定义"
assert attn_mask is None, "不支持的注意力掩码"
x: torch.Tensor = flash_attn_func(
q, k, v, dropout_p=drop_rate, causal=causal, softmax_scale=None
) # type: ignore
elif mode == "vanilla":
# 手动实现注意力机制
scale_factor = 1 / math.sqrt(q.size(-1)) # 缩放因子 1/sqrt(d_k)
b, a, s, _ = q.shape # 获取形状参数
s1 = k.size(2) # 键值序列长度
# 初始化注意力偏置
attn_bias = torch.zeros(b, a, s, s1, dtype=q.dtype, device=q.device)
# 处理因果掩码
if causal:
assert attn_mask is None, "因果掩码和注意力掩码不能同时使用"
# 生成下三角因果掩码
temp_mask = torch.ones(b, a, s, s, dtype=torch.bool, device=q.device).tril(
diagonal=0
)
attn_bias.masked_fill_(temp_mask.logical_not(), float("-inf"))
attn_bias = attn_bias.to(q.dtype)
# 处理自定义注意力掩码
if attn_mask is not None:
if attn_mask.dtype == torch.bool:
attn_bias.masked_fill_(attn_mask.logical_not(), float("-inf"))
else:
attn_bias += attn_mask # 允许类似ALiBi的位置偏置
# 计算注意力矩阵
attn = (q @ k.transpose(-2, -1)) * scale_factor # [B,A,S,S1]
attn += attn_bias
# softmax和dropout
attn = attn.softmax(dim=-1)
attn = torch.dropout(attn, p=drop_rate, train=True)
# 计算输出
x = attn @ v # [B,A,S,D]
else:
raise NotImplementedError(f"不支持的注意力模式: {mode}")
# 应用后处理变换
x = post_attn_layout(x) # 恢复原始维度顺序
# 合并注意力头维度
b, s, a, d = x.shape
out = x.reshape(b, s, -1) # [B,S,A*D]
return out
+326
View File
@@ -0,0 +1,326 @@
# Modified from Flux
#
# Copyright 2024 Black Forest Labs
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
import torch
from einops import rearrange
from torch import Tensor, nn
def swish(x: Tensor) -> Tensor:
return x * torch.sigmoid(x)
class AttnBlock(nn.Module):
def __init__(self, in_channels: int):
super().__init__()
self.in_channels = in_channels
self.norm = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
self.q = nn.Conv2d(in_channels, in_channels, kernel_size=1)
self.k = nn.Conv2d(in_channels, in_channels, kernel_size=1)
self.v = nn.Conv2d(in_channels, in_channels, kernel_size=1)
self.proj_out = nn.Conv2d(in_channels, in_channels, kernel_size=1)
def attention(self, h_: Tensor) -> Tensor:
h_ = self.norm(h_)
q = self.q(h_)
k = self.k(h_)
v = self.v(h_)
b, c, h, w = q.shape
q = rearrange(q, "b c h w -> b 1 (h w) c").contiguous()
k = rearrange(k, "b c h w -> b 1 (h w) c").contiguous()
v = rearrange(v, "b c h w -> b 1 (h w) c").contiguous()
h_ = nn.functional.scaled_dot_product_attention(q, k, v)
return rearrange(h_, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b)
def forward(self, x: Tensor) -> Tensor:
return x + self.proj_out(self.attention(x))
class ResnetBlock(nn.Module):
def __init__(self, in_channels: int, out_channels: int):
super().__init__()
self.in_channels = in_channels
out_channels = in_channels if out_channels is None else out_channels
self.out_channels = out_channels
self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
if self.in_channels != self.out_channels:
self.nin_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
def forward(self, x):
h = x
h = self.norm1(h)
h = swish(h)
h = self.conv1(h)
h = self.norm2(h)
h = swish(h)
h = self.conv2(h)
if self.in_channels != self.out_channels:
x = self.nin_shortcut(x)
return x + h
class Downsample(nn.Module):
def __init__(self, in_channels: int):
super().__init__()
# no asymmetric padding in torch conv, must do it ourselves
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0)
def forward(self, x: Tensor):
pad = (0, 1, 0, 1)
x = nn.functional.pad(x, pad, mode="constant", value=0)
x = self.conv(x)
return x
class Upsample(nn.Module):
def __init__(self, in_channels: int):
super().__init__()
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
def forward(self, x: Tensor):
x = nn.functional.interpolate(x, scale_factor=2.0, mode="nearest")
x = self.conv(x)
return x
class Encoder(nn.Module):
def __init__(
self,
resolution: int,
in_channels: int,
ch: int,
ch_mult: list[int],
num_res_blocks: int,
z_channels: int,
):
super().__init__()
self.ch = ch
self.num_resolutions = len(ch_mult)
self.num_res_blocks = num_res_blocks
self.resolution = resolution
self.in_channels = in_channels
# downsampling
self.conv_in = nn.Conv2d(in_channels, self.ch, kernel_size=3, stride=1, padding=1)
curr_res = resolution
in_ch_mult = (1, *tuple(ch_mult))
self.in_ch_mult = in_ch_mult
self.down = nn.ModuleList()
block_in = self.ch
for i_level in range(self.num_resolutions):
block = nn.ModuleList()
attn = nn.ModuleList()
block_in = ch * in_ch_mult[i_level]
block_out = ch * ch_mult[i_level]
for _ in range(self.num_res_blocks):
block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
block_in = block_out
down = nn.Module()
down.block = block
down.attn = attn
if i_level != self.num_resolutions - 1:
down.downsample = Downsample(block_in)
curr_res = curr_res // 2
self.down.append(down)
# middle
self.mid = nn.Module()
self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
self.mid.attn_1 = AttnBlock(block_in)
self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
# end
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
self.conv_out = nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1)
def forward(self, x: Tensor) -> Tensor:
# downsampling
hs = [self.conv_in(x)]
for i_level in range(self.num_resolutions):
for i_block in range(self.num_res_blocks):
h = self.down[i_level].block[i_block](hs[-1])
if len(self.down[i_level].attn) > 0:
h = self.down[i_level].attn[i_block](h)
hs.append(h)
if i_level != self.num_resolutions - 1:
hs.append(self.down[i_level].downsample(hs[-1]))
# middle
h = hs[-1]
h = self.mid.block_1(h)
h = self.mid.attn_1(h)
h = self.mid.block_2(h)
# end
h = self.norm_out(h)
h = swish(h)
h = self.conv_out(h)
return h
class Decoder(nn.Module):
def __init__(
self,
ch: int,
out_ch: int,
ch_mult: list[int],
num_res_blocks: int,
in_channels: int,
resolution: int,
z_channels: int,
):
super().__init__()
self.ch = ch
self.num_resolutions = len(ch_mult)
self.num_res_blocks = num_res_blocks
self.resolution = resolution
self.in_channels = in_channels
self.ffactor = 2 ** (self.num_resolutions - 1)
# compute in_ch_mult, block_in and curr_res at lowest res
block_in = ch * ch_mult[self.num_resolutions - 1]
curr_res = resolution // 2 ** (self.num_resolutions - 1)
self.z_shape = (1, z_channels, curr_res, curr_res)
# z to block_in
self.conv_in = nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1)
# middle
self.mid = nn.Module()
self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
self.mid.attn_1 = AttnBlock(block_in)
self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
# upsampling
self.up = nn.ModuleList()
for i_level in reversed(range(self.num_resolutions)):
block = nn.ModuleList()
attn = nn.ModuleList()
block_out = ch * ch_mult[i_level]
for _ in range(self.num_res_blocks + 1):
block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
block_in = block_out
up = nn.Module()
up.block = block
up.attn = attn
if i_level != 0:
up.upsample = Upsample(block_in)
curr_res = curr_res * 2
self.up.insert(0, up) # prepend to get consistent order
# end
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1)
def forward(self, z: Tensor) -> Tensor:
# z to block_in
h = self.conv_in(z)
# middle
h = self.mid.block_1(h)
h = self.mid.attn_1(h)
h = self.mid.block_2(h)
# upsampling
for i_level in reversed(range(self.num_resolutions)):
for i_block in range(self.num_res_blocks + 1):
h = self.up[i_level].block[i_block](h)
if len(self.up[i_level].attn) > 0:
h = self.up[i_level].attn[i_block](h)
if i_level != 0:
h = self.up[i_level].upsample(h)
# end
h = self.norm_out(h)
h = swish(h)
h = self.conv_out(h)
return h
class DiagonalGaussian(nn.Module):
def __init__(self, sample: bool = True, chunk_dim: int = 1):
super().__init__()
self.sample = sample
self.chunk_dim = chunk_dim
def forward(self, z: Tensor) -> Tensor:
mean, logvar = torch.chunk(z, 2, dim=self.chunk_dim)
if self.sample:
std = torch.exp(0.5 * logvar)
return mean + std * torch.randn_like(mean)
else:
return mean
class AutoEncoder(nn.Module):
def __init__(
self,
resolution: int,
in_channels: int,
ch: int,
out_ch: int,
ch_mult: list[int],
num_res_blocks: int,
z_channels: int,
scale_factor: float,
shift_factor: float,
):
super().__init__()
self.encoder = Encoder(
resolution=resolution,
in_channels=in_channels,
ch=ch,
ch_mult=ch_mult,
num_res_blocks=num_res_blocks,
z_channels=z_channels,
)
self.decoder = Decoder(
resolution=resolution,
in_channels=in_channels,
ch=ch,
out_ch=out_ch,
ch_mult=ch_mult,
num_res_blocks=num_res_blocks,
z_channels=z_channels,
)
self.reg = DiagonalGaussian()
self.scale_factor = scale_factor
self.shift_factor = shift_factor
def encode(self, x: Tensor) -> Tensor:
z = self.reg(self.encoder(x))
z = self.scale_factor * (z - self.shift_factor)
return z
def decode(self, z: Tensor) -> Tensor:
z = z / self.scale_factor + self.shift_factor
return self.decoder(z)
def forward(self, x: Tensor) -> Tensor:
return self.decode(self.encode(x))
+217
View File
@@ -0,0 +1,217 @@
import torch
from qwen_vl_utils import process_vision_info
from transformers import (
AutoProcessor,
Qwen2VLForConditionalGeneration,
Qwen2_5_VLForConditionalGeneration,
)
from torchvision.transforms import ToPILImage
to_pil = ToPILImage()
Qwen25VL_7b_PREFIX = '''Given a user prompt, generate an "Enhanced prompt" that provides detailed visual descriptions suitable for image generation. Evaluate the level of detail in the user prompt:
- If the prompt is simple, focus on adding specifics about colors, shapes, sizes, textures, and spatial relationships to create vivid and concrete scenes.
- If the prompt is already detailed, refine and enhance the existing details slightly without overcomplicating.\n
Here are examples of how to transform or refine prompts:
- User Prompt: A cat sleeping -> Enhanced: A small, fluffy white cat curled up in a round shape, sleeping peacefully on a warm sunny windowsill, surrounded by pots of blooming red flowers.
- User Prompt: A busy city street -> Enhanced: A bustling city street scene at dusk, featuring glowing street lamps, a diverse crowd of people in colorful clothing, and a double-decker bus passing by towering glass skyscrapers.\n
Please generate only the enhanced description for the prompt below and avoid including any additional commentary or evaluations:
User Prompt:'''
def split_string(s):
# 将中文引号替换为英文引号
s = s.replace("“", '"').replace("”", '"') # use english quotes
result = []
# 标记是否在引号内
in_quotes = False
temp = ""
# 遍历字符串中的每个字符及其索引
for idx, char in enumerate(s):
# 如果字符是引号且索引大于 155
if char == '"' and idx > 155:
# 将引号添加到临时字符串
temp += char
# 如果不在引号内
if not in_quotes:
# 将临时字符串添加到结果列表
result.append(temp)
# 清空临时字符串
temp = ""
# 切换引号状态
in_quotes = not in_quotes
continue
# 如果在引号内
if in_quotes:
# 如果字符是空格
if char.isspace():
pass # have space token
# 将字符用中文引号包裹后添加到结果列表
result.append("“" + char + "”")
else:
# 将字符添加到临时字符串
temp += char
# 如果临时字符串不为空
if temp:
# 将临时字符串添加到结果列表
result.append(temp)
return result
class Qwen25VL_7b_Embedder(torch.nn.Module):
def __init__(self, model_path, max_length=640, dtype=torch.bfloat16, device="cuda"):
super(Qwen25VL_7b_Embedder, self).__init__()
self.max_length = max_length
self.dtype = dtype
self.device = device
print(f"Qwen25VL model run on current device: {torch.cuda.current_device()}")
self.model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
model_path,
torch_dtype=dtype,
attn_implementation="flash_attention_2"
).to(torch.cuda.current_device())
self.model.requires_grad_(False)
self.processor = AutoProcessor.from_pretrained(
model_path, min_pixels=256 * 28 * 28, max_pixels=324 * 28 * 28, use_fast=True
)
self.prefix = Qwen25VL_7b_PREFIX
def forward(self, caption, ref_images):
text_list = caption
embs = torch.zeros(
len(text_list),
self.max_length,
self.model.config.hidden_size,
dtype=torch.bfloat16,
device=torch.cuda.current_device(),
)
hidden_states = torch.zeros(
len(text_list),
self.max_length,
self.model.config.hidden_size,
dtype=torch.bfloat16,
device=torch.cuda.current_device(),
)
masks = torch.zeros(
len(text_list),
self.max_length,
dtype=torch.long,
device=torch.cuda.current_device(),
)
input_ids_list = []
attention_mask_list = []
emb_list = []
def split_string(s):
s = s.replace("“", '"').replace("”", '"').replace("'", '''"''') # use english quotes
result = []
in_quotes = False
temp = ""
for idx,char in enumerate(s):
if char == '"' and idx>155:
temp += char
if not in_quotes:
result.append(temp)
temp = ""
in_quotes = not in_quotes
continue
if in_quotes:
if char.isspace():
pass # have space token
result.append("“" + char + "”")
else:
temp += char
if temp:
result.append(temp)
return result
for idx, (txt, imgs) in enumerate(zip(text_list, ref_images)):
messages = [{"role": "user", "content": []}]
messages[0]["content"].append({"type": "text", "text": f"{self.prefix}"})
messages[0]["content"].append({"type": "image", "image": to_pil(imgs)})
# 再添加 text
messages[0]["content"].append({"type": "text", "text": f"{txt}"})
# Preparation for inference
text = self.processor.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True, add_vision_id=True
)
image_inputs, video_inputs = process_vision_info(messages)
inputs = self.processor(
text=[text],
images=image_inputs,
padding=True,
return_tensors="pt",
)
old_inputs_ids = inputs.input_ids
text_split_list = split_string(text)
token_list = []
for text_each in text_split_list:
txt_inputs = self.processor(
text=text_each,
images=None,
videos=None,
padding=True,
return_tensors="pt",
)
token_each = txt_inputs.input_ids
if token_each[0][0] == 2073 and token_each[0][-1] == 854:
token_each = token_each[:, 1:-1]
token_list.append(token_each)
else:
token_list.append(token_each)
new_txt_ids = torch.cat(token_list, dim=1).to("cuda")
new_txt_ids = new_txt_ids.to(old_inputs_ids.device)
idx1 = (old_inputs_ids == 151653).nonzero(as_tuple=True)[1][0]
idx2 = (new_txt_ids == 151653).nonzero(as_tuple=True)[1][0]
inputs.input_ids = (
torch.cat([old_inputs_ids[0, :idx1], new_txt_ids[0, idx2:]], dim=0)
.unsqueeze(0)
.to("cuda")
)
inputs.attention_mask = (inputs.input_ids > 0).long().to("cuda")
outputs = self.model(
input_ids=inputs.input_ids,
attention_mask=inputs.attention_mask,
pixel_values=inputs.pixel_values.to("cuda"),
image_grid_thw=inputs.image_grid_thw.to("cuda"),
output_hidden_states=True,
)
emb = outputs["hidden_states"][-1]
embs[idx, : min(self.max_length, emb.shape[1] - 217)] = emb[0, 217:][
: self.max_length
]
masks[idx, : min(self.max_length, emb.shape[1] - 217)] = torch.ones(
(min(self.max_length, emb.shape[1] - 217)),
dtype=torch.long,
device=torch.cuda.current_device(),
)
return embs, masks
+486
View File
@@ -0,0 +1,486 @@
from typing import Optional
import torch
import torch.nn
from einops import rearrange
from torch import nn
from .layers import MLP, TextProjection, TimestepEmbedder, apply_gate, attention
class RMSNorm(nn.Module):
def __init__(
self,
dim: int,
elementwise_affine=True,
eps: float = 1e-6,
device=None,
dtype=None,
):
"""
Initialize the RMSNorm normalization layer.
Args:
dim (int): The dimension of the input tensor.
eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-6.
Attributes:
eps (float): A small value added to the denominator for numerical stability.
weight (nn.Parameter): Learnable scaling parameter.
"""
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
self.eps = eps
if elementwise_affine:
self.weight = nn.Parameter(torch.ones(dim, **factory_kwargs))
def _norm(self, x):
"""
Apply the RMSNorm normalization to the input tensor.
Args:
x (torch.Tensor): The input tensor.
Returns:
torch.Tensor: The normalized tensor.
"""
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
def forward(self, x):
"""
Forward pass through the RMSNorm layer.
Args:
x (torch.Tensor): The input tensor.
Returns:
torch.Tensor: The output tensor after applying RMSNorm.
"""
output = self._norm(x.float()).type_as(x)
if hasattr(self, "weight"):
output = output * self.weight
return output
def get_norm_layer(norm_layer):
"""
Get the normalization layer.
Args:
norm_layer (str): The type of normalization layer.
Returns:
norm_layer (nn.Module): The normalization layer.
"""
if norm_layer == "layer":
return nn.LayerNorm
elif norm_layer == "rms":
return RMSNorm
else:
raise NotImplementedError(f"Norm layer {norm_layer} is not implemented")
def get_activation_layer(act_type):
"""get activation layer
Args:
act_type (str): the activation type
Returns:
torch.nn.functional: the activation layer
"""
if act_type == "gelu":
return lambda: nn.GELU()
elif act_type == "gelu_tanh":
return lambda: nn.GELU(approximate="tanh")
elif act_type == "relu":
return nn.ReLU
elif act_type == "silu":
return nn.SiLU
else:
raise ValueError(f"Unknown activation type: {act_type}")
class IndividualTokenRefinerBlock(torch.nn.Module):
def __init__(
self,
hidden_size,
heads_num,
mlp_width_ratio: str = 4.0,
mlp_drop_rate: float = 0.0,
act_type: str = "silu",
qk_norm: bool = False,
qk_norm_type: str = "layer",
qkv_bias: bool = True,
need_CA: bool = False,
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
):
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
self.need_CA = need_CA
self.heads_num = heads_num
head_dim = hidden_size // heads_num
mlp_hidden_dim = int(hidden_size * mlp_width_ratio)
self.norm1 = nn.LayerNorm(
hidden_size, elementwise_affine=True, eps=1e-6, **factory_kwargs
)
self.self_attn_qkv = nn.Linear(
hidden_size, hidden_size * 3, bias=qkv_bias, **factory_kwargs
)
qk_norm_layer = get_norm_layer(qk_norm_type)
self.self_attn_q_norm = (
qk_norm_layer(head_dim, elementwise_affine=True, eps=1e-6, **factory_kwargs)
if qk_norm
else nn.Identity()
)
self.self_attn_k_norm = (
qk_norm_layer(head_dim, elementwise_affine=True, eps=1e-6, **factory_kwargs)
if qk_norm
else nn.Identity()
)
self.self_attn_proj = nn.Linear(
hidden_size, hidden_size, bias=qkv_bias, **factory_kwargs
)
self.norm2 = nn.LayerNorm(
hidden_size, elementwise_affine=True, eps=1e-6, **factory_kwargs
)
act_layer = get_activation_layer(act_type)
self.mlp = MLP(
in_channels=hidden_size,
hidden_channels=mlp_hidden_dim,
act_layer=act_layer,
drop=mlp_drop_rate,
**factory_kwargs,
)
self.adaLN_modulation = nn.Sequential(
act_layer(),
nn.Linear(hidden_size, 2 * hidden_size, bias=True, **factory_kwargs),
)
if self.need_CA:
self.cross_attnblock=CrossAttnBlock(hidden_size=hidden_size,
heads_num=heads_num,
mlp_width_ratio=mlp_width_ratio,
mlp_drop_rate=mlp_drop_rate,
act_type=act_type,
qk_norm=qk_norm,
qk_norm_type=qk_norm_type,
qkv_bias=qkv_bias,
**factory_kwargs,)
# Zero-initialize the modulation
nn.init.zeros_(self.adaLN_modulation[1].weight)
nn.init.zeros_(self.adaLN_modulation[1].bias)
def forward(
self,
x: torch.Tensor,
c: torch.Tensor, # timestep_aware_representations + context_aware_representations
attn_mask: torch.Tensor = None,
y: torch.Tensor = None,
):
gate_msa, gate_mlp = self.adaLN_modulation(c).chunk(2, dim=1)
norm_x = self.norm1(x)
qkv = self.self_attn_qkv(norm_x)
q, k, v = rearrange(qkv, "B L (K H D) -> K B L H D", K=3, H=self.heads_num)
# Apply QK-Norm if needed
q = self.self_attn_q_norm(q).to(v)
k = self.self_attn_k_norm(k).to(v)
# Self-Attention
attn = attention(q, k, v, mode="torch", attn_mask=attn_mask)
x = x + apply_gate(self.self_attn_proj(attn), gate_msa)
if self.need_CA:
x = self.cross_attnblock(x, c, attn_mask, y)
# FFN Layer
x = x + apply_gate(self.mlp(self.norm2(x)), gate_mlp)
return x
class CrossAttnBlock(torch.nn.Module):
def __init__(
self,
hidden_size,
heads_num,
mlp_width_ratio: str = 4.0,
mlp_drop_rate: float = 0.0,
act_type: str = "silu",
qk_norm: bool = False,
qk_norm_type: str = "layer",
qkv_bias: bool = True,
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
):
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
self.heads_num = heads_num
head_dim = hidden_size // heads_num
self.norm1 = nn.LayerNorm(
hidden_size, elementwise_affine=True, eps=1e-6, **factory_kwargs
)
self.norm1_2 = nn.LayerNorm(
hidden_size, elementwise_affine=True, eps=1e-6, **factory_kwargs
)
self.self_attn_q = nn.Linear(
hidden_size, hidden_size, bias=qkv_bias, **factory_kwargs
)
self.self_attn_kv = nn.Linear(
hidden_size, hidden_size*2, bias=qkv_bias, **factory_kwargs
)
qk_norm_layer = get_norm_layer(qk_norm_type)
self.self_attn_q_norm = (
qk_norm_layer(head_dim, elementwise_affine=True, eps=1e-6, **factory_kwargs)
if qk_norm
else nn.Identity()
)
self.self_attn_k_norm = (
qk_norm_layer(head_dim, elementwise_affine=True, eps=1e-6, **factory_kwargs)
if qk_norm
else nn.Identity()
)
self.self_attn_proj = nn.Linear(
hidden_size, hidden_size, bias=qkv_bias, **factory_kwargs
)
self.norm2 = nn.LayerNorm(
hidden_size, elementwise_affine=True, eps=1e-6, **factory_kwargs
)
act_layer = get_activation_layer(act_type)
self.adaLN_modulation = nn.Sequential(
act_layer(),
nn.Linear(hidden_size, 2 * hidden_size, bias=True, **factory_kwargs),
)
# Zero-initialize the modulation
nn.init.zeros_(self.adaLN_modulation[1].weight)
nn.init.zeros_(self.adaLN_modulation[1].bias)
def forward(
self,
x: torch.Tensor,
c: torch.Tensor, # timestep_aware_representations + context_aware_representations
attn_mask: torch.Tensor = None,
y: torch.Tensor=None,
):
gate_msa, gate_mlp = self.adaLN_modulation(c).chunk(2, dim=1)
norm_x = self.norm1(x)
norm_y = self.norm1_2(y)
q = self.self_attn_q(norm_x)
q = rearrange(q, "B L (H D) -> B L H D", H=self.heads_num)
kv = self.self_attn_kv(norm_y)
k, v = rearrange(kv, "B L (K H D) -> K B L H D", K=2, H=self.heads_num)
# Apply QK-Norm if needed
q = self.self_attn_q_norm(q).to(v)
k = self.self_attn_k_norm(k).to(v)
# Self-Attention
attn = attention(q, k, v, mode="torch", attn_mask=attn_mask)
x = x + apply_gate(self.self_attn_proj(attn), gate_msa)
return x
class IndividualTokenRefiner(torch.nn.Module):
def __init__(
self,
hidden_size,
heads_num,
depth,
mlp_width_ratio: float = 4.0,
mlp_drop_rate: float = 0.0,
act_type: str = "silu",
qk_norm: bool = False,
qk_norm_type: str = "layer",
qkv_bias: bool = True,
need_CA:bool=False,
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
):
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
self.need_CA = need_CA
self.blocks = nn.ModuleList(
[
IndividualTokenRefinerBlock(
hidden_size=hidden_size,
heads_num=heads_num,
mlp_width_ratio=mlp_width_ratio,
mlp_drop_rate=mlp_drop_rate,
act_type=act_type,
qk_norm=qk_norm,
qk_norm_type=qk_norm_type,
qkv_bias=qkv_bias,
need_CA=self.need_CA,
**factory_kwargs,
)
for _ in range(depth)
]
)
def forward(
self,
x: torch.Tensor,
c: torch.LongTensor,
mask: Optional[torch.Tensor] = None,
y:torch.Tensor=None,
):
self_attn_mask = None
if mask is not None:
batch_size = mask.shape[0]
seq_len = mask.shape[1]
mask = mask.to(x.device)
# batch_size x 1 x seq_len x seq_len
self_attn_mask_1 = mask.view(batch_size, 1, 1, seq_len).repeat(
1, 1, seq_len, 1
)
# batch_size x 1 x seq_len x seq_len
self_attn_mask_2 = self_attn_mask_1.transpose(2, 3)
# batch_size x 1 x seq_len x seq_len, 1 for broadcasting of heads_num
self_attn_mask = (self_attn_mask_1 & self_attn_mask_2).bool()
# avoids self-attention weight being NaN for padding tokens
self_attn_mask[:, :, :, 0] = True
for block in self.blocks:
x = block(x, c, self_attn_mask,y)
return x
class SingleTokenRefiner(torch.nn.Module):
"""
A single token refiner block for llm text embedding refine.
"""
def __init__(
self,
in_channels,
hidden_size,
heads_num,
depth,
mlp_width_ratio: float = 4.0,
mlp_drop_rate: float = 0.0,
act_type: str = "silu",
qk_norm: bool = False,
qk_norm_type: str = "layer",
qkv_bias: bool = True,
need_CA:bool=False,
attn_mode: str = "torch",
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
):
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
self.attn_mode = attn_mode
self.need_CA = need_CA
assert self.attn_mode == "torch", "Only support 'torch' mode for token refiner."
self.input_embedder = nn.Linear(
in_channels, hidden_size, bias=True, **factory_kwargs
)
if self.need_CA:
self.input_embedder_CA = nn.Linear(
in_channels, hidden_size, bias=True, **factory_kwargs
)
act_layer = get_activation_layer(act_type)
# Build timestep embedding layer
self.t_embedder = TimestepEmbedder(hidden_size, act_layer, **factory_kwargs)
# Build context embedding layer
self.c_embedder = TextProjection(
in_channels, hidden_size, act_layer, **factory_kwargs
)
self.individual_token_refiner = IndividualTokenRefiner(
hidden_size=hidden_size,
heads_num=heads_num,
depth=depth,
mlp_width_ratio=mlp_width_ratio,
mlp_drop_rate=mlp_drop_rate,
act_type=act_type,
qk_norm=qk_norm,
qk_norm_type=qk_norm_type,
qkv_bias=qkv_bias,
need_CA=need_CA,
**factory_kwargs,
)
def forward(
self,
x: torch.Tensor,
t: torch.LongTensor,
mask: Optional[torch.LongTensor] = None,
y: torch.LongTensor=None,
):
timestep_aware_representations = self.t_embedder(t)
if mask is None:
context_aware_representations = x.mean(dim=1)
else:
mask_float = mask.unsqueeze(-1) # [b, s1, 1]
context_aware_representations = (x * mask_float).sum(
dim=1
) / mask_float.sum(dim=1)
context_aware_representations = self.c_embedder(context_aware_representations)
c = timestep_aware_representations + context_aware_representations
x = self.input_embedder(x)
if self.need_CA:
y = self.input_embedder_CA(y)
x = self.individual_token_refiner(x, c, mask, y)
else:
x = self.individual_token_refiner(x, c, mask)
return x
class Qwen2Connector(torch.nn.Module):
def __init__(
self,
# biclip_dim=1024,
in_channels=3584,
hidden_size=4096,
heads_num=32,
depth=2,
need_CA=False,
device=None,
dtype=torch.bfloat16,
):
super().__init__()
factory_kwargs = {"device": device, "dtype":dtype}
self.S =SingleTokenRefiner(in_channels=in_channels,hidden_size=hidden_size,heads_num=heads_num,depth=depth,need_CA=need_CA,**factory_kwargs)
self.global_proj_out=nn.Linear(in_channels,768)
self.scale_factor = nn.Parameter(torch.zeros(1))
with torch.no_grad():
self.scale_factor.data += -(1 - 0.09)
def forward(self, x,t,mask):
mask_float = mask.unsqueeze(-1) # [b, s1, 1]
x_mean = (x * mask_float).sum(
dim=1
) / mask_float.sum(dim=1) * (1 + self.scale_factor.to(x.dtype))
global_out=self.global_proj_out(x_mean)
encoder_hidden_states = self.S(x,t,mask)
return encoder_hidden_states,global_out
+639
View File
@@ -0,0 +1,639 @@
# Modified from Flux
#
# Copyright 2024 Black Forest Labs
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
import math # noqa: I001
from dataclasses import dataclass
from functools import partial
import torch
import torch.nn.functional as F
from einops import rearrange
from liger_kernel.ops.rms_norm import LigerRMSNormFunction
from torch import Tensor, nn
try:
import flash_attn
from flash_attn.flash_attn_interface import (
_flash_attn_forward,
flash_attn_varlen_func,
)
except ImportError:
flash_attn = None
flash_attn_varlen_func = None
_flash_attn_forward = None
MEMORY_LAYOUT = {
"flash": (
lambda x: x.view(x.shape[0] * x.shape[1], *x.shape[2:]),
lambda x: x,
),
"torch": (
lambda x: x.transpose(1, 2),
lambda x: x.transpose(1, 2),
),
"vanilla": (
lambda x: x.transpose(1, 2),
lambda x: x.transpose(1, 2),
),
}
def attention(
q,
k,
v,
mode="flash",
drop_rate=0,
attn_mask=None,
causal=False,
cu_seqlens_q=None,
cu_seqlens_kv=None,
max_seqlen_q=None,
max_seqlen_kv=None,
batch_size=1,
):
"""
Perform QKV self attention.
Args:
q (torch.Tensor): Query tensor with shape [b, s, a, d], where a is the number of heads.
k (torch.Tensor): Key tensor with shape [b, s1, a, d]
v (torch.Tensor): Value tensor with shape [b, s1, a, d]
mode (str): Attention mode. Choose from 'self_flash', 'cross_flash', 'torch', and 'vanilla'.
drop_rate (float): Dropout rate in attention map. (default: 0)
attn_mask (torch.Tensor): Attention mask with shape [b, s1] (cross_attn), or [b, a, s, s1] (torch or vanilla).
(default: None)
causal (bool): Whether to use causal attention. (default: False)
cu_seqlens_q (torch.Tensor): dtype torch.int32. The cumulative sequence lengths of the sequences in the batch,
used to index into q.
cu_seqlens_kv (torch.Tensor): dtype torch.int32. The cumulative sequence lengths of the sequences in the batch,
used to index into kv.
max_seqlen_q (int): The maximum sequence length in the batch of q.
max_seqlen_kv (int): The maximum sequence length in the batch of k and v.
Returns:
torch.Tensor: Output tensor after self attention with shape [b, s, ad]
"""
pre_attn_layout, post_attn_layout = MEMORY_LAYOUT[mode]
q = pre_attn_layout(q)
k = pre_attn_layout(k)
v = pre_attn_layout(v)
if mode == "torch":
if attn_mask is not None and attn_mask.dtype != torch.bool:
attn_mask = attn_mask.to(q.dtype)
x = F.scaled_dot_product_attention(
q, k, v, attn_mask=attn_mask, dropout_p=drop_rate, is_causal=causal
)
elif mode == "flash":
assert flash_attn_varlen_func is not None
x: torch.Tensor = flash_attn_varlen_func(
q,
k,
v,
cu_seqlens_q,
cu_seqlens_kv,
max_seqlen_q,
max_seqlen_kv,
) # type: ignore
# x with shape [(bxs), a, d]
x = x.view(batch_size, max_seqlen_q, x.shape[-2], x.shape[-1]) # type: ignore # reshape x to [b, s, a, d]
elif mode == "vanilla":
scale_factor = 1 / math.sqrt(q.size(-1))
b, a, s, _ = q.shape
s1 = k.size(2)
attn_bias = torch.zeros(b, a, s, s1, dtype=q.dtype, device=q.device)
if causal:
# Only applied to self attention
assert attn_mask is None, (
"Causal mask and attn_mask cannot be used together"
)
temp_mask = torch.ones(b, a, s, s, dtype=torch.bool, device=q.device).tril(
diagonal=0
)
attn_bias.masked_fill_(temp_mask.logical_not(), float("-inf"))
attn_bias.to(q.dtype)
if attn_mask is not None:
if attn_mask.dtype == torch.bool:
attn_bias.masked_fill_(attn_mask.logical_not(), float("-inf"))
else:
attn_bias += attn_mask
# TODO: Maybe force q and k to be float32 to avoid numerical overflow
attn = (q @ k.transpose(-2, -1)) * scale_factor
attn += attn_bias
attn = attn.softmax(dim=-1)
attn = torch.dropout(attn, p=drop_rate, train=True)
x = attn @ v
else:
raise NotImplementedError(f"Unsupported attention mode: {mode}")
x = post_attn_layout(x)
b, s, a, d = x.shape
out = x.reshape(b, s, -1)
return out
def apply_gate(x, gate=None, tanh=False):
"""AI is creating summary for apply_gate
Args:
x (torch.Tensor): input tensor.
gate (torch.Tensor, optional): gate tensor. Defaults to None.
tanh (bool, optional): whether to use tanh function. Defaults to False.
Returns:
torch.Tensor: the output tensor after apply gate.
"""
if gate is None:
return x
if tanh:
return x * gate.unsqueeze(1).tanh()
else:
return x * gate.unsqueeze(1)
class MLP(nn.Module):
"""MLP as used in Vision Transformer, MLP-Mixer and related networks"""
def __init__(
self,
in_channels,
hidden_channels=None,
out_features=None,
act_layer=nn.GELU,
norm_layer=None,
bias=True,
drop=0.0,
use_conv=False,
device=None,
dtype=None,
):
super().__init__()
out_features = out_features or in_channels
hidden_channels = hidden_channels or in_channels
bias = (bias, bias)
drop_probs = (drop, drop)
linear_layer = partial(nn.Conv2d, kernel_size=1) if use_conv else nn.Linear
self.fc1 = linear_layer(
in_channels, hidden_channels, bias=bias[0], device=device, dtype=dtype
)
self.act = act_layer()
self.drop1 = nn.Dropout(drop_probs[0])
self.norm = (
norm_layer(hidden_channels, device=device, dtype=dtype)
if norm_layer is not None
else nn.Identity()
)
self.fc2 = linear_layer(
hidden_channels, out_features, bias=bias[1], device=device, dtype=dtype
)
self.drop2 = nn.Dropout(drop_probs[1])
def forward(self, x):
x = self.fc1(x)
x = self.act(x)
x = self.drop1(x)
x = self.norm(x)
x = self.fc2(x)
x = self.drop2(x)
return x
class TextProjection(nn.Module):
"""
Projects text embeddings. Also handles dropout for classifier-free guidance.
Adapted from https://github.com/PixArt-alpha/PixArt-alpha/blob/master/diffusion/model/nets/PixArt_blocks.py
"""
def __init__(self, in_channels, hidden_size, act_layer, dtype=None, device=None):
factory_kwargs = {"dtype": dtype, "device": device}
super().__init__()
self.linear_1 = nn.Linear(
in_features=in_channels,
out_features=hidden_size,
bias=True,
**factory_kwargs,
)
self.act_1 = act_layer()
self.linear_2 = nn.Linear(
in_features=hidden_size,
out_features=hidden_size,
bias=True,
**factory_kwargs,
)
def forward(self, caption):
hidden_states = self.linear_1(caption)
hidden_states = self.act_1(hidden_states)
hidden_states = self.linear_2(hidden_states)
return hidden_states
class TimestepEmbedder(nn.Module):
"""
Embeds scalar timesteps into vector representations.
"""
def __init__(
self,
hidden_size,
act_layer,
frequency_embedding_size=256,
max_period=10000,
out_size=None,
dtype=None,
device=None,
):
factory_kwargs = {"dtype": dtype, "device": device}
super().__init__()
self.frequency_embedding_size = frequency_embedding_size
self.max_period = max_period
if out_size is None:
out_size = hidden_size
self.mlp = nn.Sequential(
nn.Linear(
frequency_embedding_size, hidden_size, bias=True, **factory_kwargs
),
act_layer(),
nn.Linear(hidden_size, out_size, bias=True, **factory_kwargs),
)
nn.init.normal_(self.mlp[0].weight, std=0.02) # type: ignore
nn.init.normal_(self.mlp[2].weight, std=0.02) # type: ignore
@staticmethod
def timestep_embedding(t, dim, max_period=10000):
"""
Create sinusoidal timestep embeddings.
Args:
t (torch.Tensor): a 1-D Tensor of N indices, one per batch element. These may be fractional.
dim (int): the dimension of the output.
max_period (int): controls the minimum frequency of the embeddings.
Returns:
embedding (torch.Tensor): An (N, D) Tensor of positional embeddings.
.. ref_link: 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):
t_freq = self.timestep_embedding(
t, self.frequency_embedding_size, self.max_period
).type(self.mlp[0].weight.dtype) # type: ignore
t_emb = self.mlp(t_freq)
return t_emb
class EmbedND(nn.Module):
def __init__(self, dim: int, theta: int, axes_dim: list[int]):
super().__init__()
self.dim = dim
self.theta = theta
self.axes_dim = axes_dim
def forward(self, ids: Tensor) -> Tensor:
n_axes = ids.shape[-1]
emb = torch.cat(
[rope(ids[..., i], self.axes_dim[i], self.theta) for i in range(n_axes)],
dim=-3,
)
return emb.unsqueeze(1)
class MLPEmbedder(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int):
super().__init__()
self.in_layer = nn.Linear(in_dim, hidden_dim, bias=True)
self.silu = nn.SiLU()
self.out_layer = nn.Linear(hidden_dim, hidden_dim, bias=True)
def forward(self, x: Tensor) -> Tensor:
return self.out_layer(self.silu(self.in_layer(x)))
def rope(pos, dim: int, theta: int):
assert dim % 2 == 0
scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim
omega = 1.0 / (theta**scale)
out = torch.einsum("...n,d->...nd", pos, omega)
out = torch.stack(
[torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1
)
out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2)
return out.float()
def attention_after_rope(q, k, v, pe):
q, k = apply_rope(q, k, pe)
from .attention import attention
x = attention(q, k, v, mode="flash")
return x
@torch.compile(mode="max-autotune-no-cudagraphs", dynamic=True)
def apply_rope(xq, xk, freqs_cis):
# 将 num_heads 和 seq_len 的维度交换回原函数的处理顺序
xq = xq.transpose(1, 2) # [batch, num_heads, seq_len, head_dim]
xk = xk.transpose(1, 2)
# 将 head_dim 拆分为复数部分(实部和虚部)
xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2)
xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
# 应用旋转位置编码(复数乘法)
xq_out = freqs_cis[..., 0] * xq_[..., 0] + freqs_cis[..., 1] * xq_[..., 1]
xk_out = freqs_cis[..., 0] * xk_[..., 0] + freqs_cis[..., 1] * xk_[..., 1]
# 恢复张量形状并转置回目标维度顺序
xq_out = xq_out.reshape(*xq.shape).type_as(xq).transpose(1, 2)
xk_out = xk_out.reshape(*xk.shape).type_as(xk).transpose(1, 2)
return xq_out, xk_out
@torch.compile(mode="max-autotune-no-cudagraphs", dynamic=True)
def scale_add_residual(
x: torch.Tensor, scale: torch.Tensor, residual: torch.Tensor
) -> torch.Tensor:
return x * scale + residual
@torch.compile(mode="max-autotune-no-cudagraphs", dynamic=True)
def layernorm_and_scale_shift(
x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor
) -> torch.Tensor:
return torch.nn.functional.layer_norm(x, (x.size(-1),)) * (scale + 1) + shift
class SelfAttention(nn.Module):
def __init__(self, dim: int, num_heads: int = 8, qkv_bias: bool = False):
super().__init__()
self.num_heads = num_heads
head_dim = dim // num_heads
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
self.norm = QKNorm(head_dim)
self.proj = nn.Linear(dim, dim)
def forward(self, x: Tensor, pe: Tensor) -> Tensor:
qkv = self.qkv(x)
q, k, v = rearrange(qkv, "B L (K H D) -> K B L H D", K=3, H=self.num_heads)
q, k = self.norm(q, k, v)
x = attention_after_rope(q, k, v, pe=pe)
x = self.proj(x)
return x
@dataclass
class ModulationOut:
shift: Tensor
scale: Tensor
gate: Tensor
class RMSNorm(torch.nn.Module):
def __init__(self, dim: int):
super().__init__()
self.scale = nn.Parameter(torch.ones(dim))
@staticmethod
def rms_norm_fast(x, weight, eps):
return LigerRMSNormFunction.apply(
x,
weight,
eps,
0.0,
"gemma",
True,
)
@staticmethod
def rms_norm(x, weight, eps):
x_dtype = x.dtype
x = x.float()
rrms = torch.rsqrt(torch.mean(x**2, dim=-1, keepdim=True) + eps)
return (x * rrms).to(dtype=x_dtype) * weight
def forward(self, x: Tensor):
return self.rms_norm_fast(x, self.scale.to(x.dtype), 1e-6)
class QKNorm(torch.nn.Module):
def __init__(self, dim: int):
super().__init__()
self.query_norm = RMSNorm(dim)
self.key_norm = RMSNorm(dim)
def forward(self, q: Tensor, k: Tensor, v: Tensor) -> tuple[Tensor, Tensor]:
q = self.query_norm(q)
k = self.key_norm(k)
return q.to(v), k.to(v)
class Modulation(nn.Module):
def __init__(self, dim: int, double: bool):
super().__init__()
self.is_double = double
self.multiplier = 6 if double else 3
self.lin = nn.Linear(dim, self.multiplier * dim, bias=True)
def forward(self, vec: Tensor) -> tuple[ModulationOut, ModulationOut | None]:
out = self.lin(nn.functional.silu(vec))[:, None, :].chunk(
self.multiplier, dim=-1
)
return (
ModulationOut(*out[:3]),
ModulationOut(*out[3:]) if self.is_double else None,
)
class DoubleStreamBlock(nn.Module):
def __init__(
self, hidden_size: int, num_heads: int, mlp_ratio: float, qkv_bias: bool = False
):
super().__init__()
mlp_hidden_dim = int(hidden_size * mlp_ratio)
self.num_heads = num_heads
self.hidden_size = hidden_size
self.img_mod = Modulation(hidden_size, double=True)
self.img_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.img_attn = SelfAttention(
dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias
)
self.img_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.img_mlp = nn.Sequential(
nn.Linear(hidden_size, mlp_hidden_dim, bias=True),
nn.GELU(approximate="tanh"),
nn.Linear(mlp_hidden_dim, hidden_size, bias=True),
)
self.txt_mod = Modulation(hidden_size, double=True)
self.txt_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.txt_attn = SelfAttention(
dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias
)
self.txt_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.txt_mlp = nn.Sequential(
nn.Linear(hidden_size, mlp_hidden_dim, bias=True),
nn.GELU(approximate="tanh"),
nn.Linear(mlp_hidden_dim, hidden_size, bias=True),
)
def forward(
self, img: Tensor, txt: Tensor, vec: Tensor, pe: Tensor
) -> tuple[Tensor, Tensor]:
img_mod1, img_mod2 = self.img_mod(vec)
txt_mod1, txt_mod2 = self.txt_mod(vec)
# prepare image for attention
img_modulated = self.img_norm1(img)
img_modulated = (1 + img_mod1.scale) * img_modulated + img_mod1.shift
img_qkv = self.img_attn.qkv(img_modulated)
img_q, img_k, img_v = rearrange(
img_qkv, "B L (K H D) -> K B L H D", K=3, H=self.num_heads
)
img_q, img_k = self.img_attn.norm(img_q, img_k, img_v)
# prepare txt for attention
txt_modulated = self.txt_norm1(txt)
txt_modulated = (1 + txt_mod1.scale) * txt_modulated + txt_mod1.shift
txt_qkv = self.txt_attn.qkv(txt_modulated)
txt_q, txt_k, txt_v = rearrange(
txt_qkv, "B L (K H D) -> K B L H D", K=3, H=self.num_heads
)
txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k, txt_v)
# run actual attention
q = torch.cat((txt_q, img_q), dim=1)
k = torch.cat((txt_k, img_k), dim=1)
v = torch.cat((txt_v, img_v), dim=1)
attn = attention_after_rope(q, k, v, pe=pe)
txt_attn, img_attn = attn[:, : txt.shape[1]], attn[:, txt.shape[1] :]
# calculate the img bloks
img = img + img_mod1.gate * self.img_attn.proj(img_attn)
img_mlp = self.img_mlp(
(1 + img_mod2.scale) * self.img_norm2(img) + img_mod2.shift
)
img = scale_add_residual(img_mlp, img_mod2.gate, img)
# calculate the txt bloks
txt = txt + txt_mod1.gate * self.txt_attn.proj(txt_attn)
txt_mlp = self.txt_mlp(
(1 + txt_mod2.scale) * self.txt_norm2(txt) + txt_mod2.shift
)
txt = scale_add_residual(txt_mlp, txt_mod2.gate, txt)
return img, txt
class SingleStreamBlock(nn.Module):
"""
A DiT block with parallel linear layers as described in
https://arxiv.org/abs/2302.05442 and adapted modulation interface.
"""
def __init__(
self,
hidden_size: int,
num_heads: int,
mlp_ratio: float = 4.0,
qk_scale: float | None = None,
):
super().__init__()
self.hidden_dim = hidden_size
self.num_heads = num_heads
head_dim = hidden_size // num_heads
self.scale = qk_scale or head_dim**-0.5
self.mlp_hidden_dim = int(hidden_size * mlp_ratio)
# qkv and mlp_in
self.linear1 = nn.Linear(hidden_size, hidden_size * 3 + self.mlp_hidden_dim)
# proj and mlp_out
self.linear2 = nn.Linear(hidden_size + self.mlp_hidden_dim, hidden_size)
self.norm = QKNorm(head_dim)
self.hidden_size = hidden_size
self.pre_norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
self.mlp_act = nn.GELU(approximate="tanh")
self.modulation = Modulation(hidden_size, double=False)
def forward(self, x: Tensor, vec: Tensor, pe: Tensor) -> Tensor:
mod, _ = self.modulation(vec)
x_mod = (1 + mod.scale) * self.pre_norm(x) + mod.shift
qkv, mlp = torch.split(
self.linear1(x_mod), [3 * self.hidden_size, self.mlp_hidden_dim], dim=-1
)
q, k, v = rearrange(qkv, "B L (K H D) -> K B L H D", K=3, H=self.num_heads)
q, k = self.norm(q, k, v)
# compute attention
attn = attention_after_rope(q, k, v, pe=pe)
# compute activation in mlp stream, cat again and run second linear layer
output = self.linear2(torch.cat((attn, self.mlp_act(mlp)), 2))
return scale_add_residual(output, mod.gate, x)
class LastLayer(nn.Module):
def __init__(self, hidden_size: int, patch_size: int, out_channels: int):
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: Tensor, vec: Tensor) -> Tensor:
shift, scale = self.adaLN_modulation(vec).chunk(2, dim=1)
x = (1 + scale[:, None, :]) * self.norm_final(x) + shift[:, None, :]
x = self.linear(x)
return x
+143
View File
@@ -0,0 +1,143 @@
import math
from dataclasses import dataclass
import numpy as np
import torch
from torch import Tensor, nn
from .connector_edit import Qwen2Connector
from .layers import DoubleStreamBlock, EmbedND, LastLayer, MLPEmbedder, SingleStreamBlock
@dataclass
class Step1XParams:
in_channels: int
out_channels: int
vec_in_dim: int
context_in_dim: int
hidden_size: int
mlp_ratio: float
num_heads: int
depth: int
depth_single_blocks: int
axes_dim: list[int]
theta: int
qkv_bias: bool
class Step1XEdit(nn.Module):
"""
Transformer model for flow matching on sequences.
"""
def __init__(self, params: Step1XParams):
super().__init__()
self.params = params
self.in_channels = params.in_channels
self.out_channels = params.out_channels
if params.hidden_size % params.num_heads != 0:
raise ValueError(
f"Hidden size {params.hidden_size} must be divisible by num_heads {params.num_heads}"
)
pe_dim = params.hidden_size // params.num_heads
if sum(params.axes_dim) != pe_dim:
raise ValueError(
f"Got {params.axes_dim} but expected positional dim {pe_dim}"
)
self.hidden_size = params.hidden_size
self.num_heads = params.num_heads
self.pe_embedder = EmbedND(
dim=pe_dim, theta=params.theta, axes_dim=params.axes_dim
)
self.img_in = nn.Linear(self.in_channels, self.hidden_size, bias=True)
self.time_in = MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size)
self.vector_in = MLPEmbedder(params.vec_in_dim, self.hidden_size)
self.txt_in = nn.Linear(params.context_in_dim, self.hidden_size)
self.double_blocks = nn.ModuleList(
[
DoubleStreamBlock(
self.hidden_size,
self.num_heads,
mlp_ratio=params.mlp_ratio,
qkv_bias=params.qkv_bias,
)
for _ in range(params.depth)
]
)
self.single_blocks = nn.ModuleList(
[
SingleStreamBlock(
self.hidden_size, self.num_heads, mlp_ratio=params.mlp_ratio
)
for _ in range(params.depth_single_blocks)
]
)
self.final_layer = LastLayer(self.hidden_size, 1, self.out_channels)
self.connector = Qwen2Connector()
@staticmethod
def timestep_embedding(
t: Tensor, dim, max_period=10000, time_factor: float = 1000.0
):
"""
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.
"""
t = time_factor * t
half = dim // 2
freqs = torch.exp(
-math.log(max_period)
* torch.arange(start=0, end=half, dtype=torch.float32)
/ half
).to(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
)
if torch.is_floating_point(t):
embedding = embedding.to(t)
return embedding
def forward(
self,
img: Tensor,
img_ids: Tensor,
txt: Tensor,
txt_ids: Tensor,
timesteps: Tensor,
y: Tensor,
) -> Tensor:
if img.ndim != 3 or txt.ndim != 3:
raise ValueError("Input img and txt tensors must have 3 dimensions.")
img = self.img_in(img)
vec = self.time_in(self.timestep_embedding(timesteps, 256))
vec = vec + self.vector_in(y)
txt = self.txt_in(txt)
ids = torch.cat((txt_ids, img_ids), dim=1)
pe = self.pe_embedder(ids)
for block in self.double_blocks:
img, txt = block(img=img, txt=txt, vec=vec, pe=pe)
img = torch.cat((txt, img), 1)
for block in self.single_blocks:
img = block(img, vec=vec, pe=pe)
img = img[:, txt.shape[1] :, ...]
img = self.final_layer(img, vec) # (N, T, patch_size ** 2 * out_channels)
return img
+1
View File
@@ -0,0 +1 @@
# Step1X-Edit nodes package
+338
View File
@@ -0,0 +1,338 @@
import os
import torch
import numpy as np
import itertools
import math
from einops import rearrange, repeat
from torchvision.transforms import functional as F
from ..utils.model_loader import Step1XEditModelBundle
from ..utils.sampling import get_schedule
import folder_paths
class Step1XEditModelLoader:
"""Node for loading the Step1X-Edit model."""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"diffusion_model": (folder_paths.get_filename_list("diffusion_models"), {"default": "step1x-edit-i1258-FP8.safetensors"}),
"vae": (folder_paths.get_filename_list("vae"), {"default": "vae.safetensors"}),
"text_encoder": (os.listdir(folder_paths.get_folder_paths("text_encoders")[0]), {"default": "Qwen2.5-VL-7B-Instruct"}),
"dtype": (["bfloat16", "float16", "float32"], {"default": "bfloat16"}),
"quantized": ("BOOLEAN", {"default": True}),
"offload": ("BOOLEAN", {"default": False})
}
}
RETURN_TYPES = ("STEP1X_MODEL_BUNDLE",)
FUNCTION = "load_model"
CATEGORY = "Step1X-Edit"
def load_model(self, diffusion_model, vae, text_encoder, dtype, quantized, offload):
"""Load the Step1X-Edit model components."""
dit_path = folder_paths.get_full_path("diffusion_models", diffusion_model)
ae_path = folder_paths.get_full_path("vae", vae)
qwen2vl_model_path = os.path.join(folder_paths.get_folder_paths("text_encoders")[0], text_encoder)
# Convert dtype string to torch dtype
if dtype == "bfloat16":
torch_dtype = torch.bfloat16
elif dtype == "float16":
torch_dtype = torch.float16
else:
torch_dtype = torch.float32
# Determine device
device = "cuda" if torch.cuda.is_available() else "cpu"
# Create model bundle
model_bundle = Step1XEditModelBundle(device=device, dtype=torch_dtype)
model_bundle.set_quantized(quantized)
model_bundle.set_offload(offload)
# Load models
success = model_bundle.load_models(
dit_path=dit_path,
ae_path=ae_path,
qwen2vl_model_path=qwen2vl_model_path,
max_length=640
)
if not success:
raise RuntimeError("Failed to load Step1X-Edit models. Please check paths and ensure all dependencies are installed.")
return (model_bundle,)
class Step1XEditGenerateNode:
"""Node for generating images using Step1X-Edit."""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model_bundle": ("STEP1X_MODEL_BUNDLE",),
"input_image": ("IMAGE",),
"prompt": ("STRING", {"multiline": True}),
"negative_prompt": ("STRING", {"multiline": True, "default": ""}),
"num_steps": ("INT", {"default": 28, "min": 1, "max": 100}),
"cfg_guidance": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 20.0, "step": 0.1}),
"seed": ("INT", {"default": 42}),
"size_level": ("INT", {"default": 512, "min": 256, "max": 1024, "step": 64})
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate"
CATEGORY = "Step1X-Edit"
def process_diff_norm(self, diff_norm, k):
pow_result = torch.pow(diff_norm, k)
result = torch.where(
diff_norm > 1.0,
pow_result,
torch.where(diff_norm < 1.0, torch.ones_like(diff_norm), diff_norm),
)
return result
def unpack(self, x, height, width):
return rearrange(
x,
"b (h w) (c ph pw) -> b c (h ph) (w pw)",
h=math.ceil(height / 16),
w=math.ceil(width / 16),
ph=2,
pw=2,
)
def prepare(self, prompt, img, ref_image, ref_image_raw, model_bundle, device):
bs, _, h, w = img.shape
bs, _, ref_h, ref_w = ref_image.shape
assert h == ref_h and w == ref_w
if bs == 1 and not isinstance(prompt, str):
bs = len(prompt)
elif bs >= 1 and isinstance(prompt, str):
prompt = [prompt] * bs
img = rearrange(img, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=2, pw=2)
ref_img = rearrange(ref_image, "b c (ref_h ph) (ref_w pw) -> b (ref_h ref_w) (c ph pw)", ph=2, pw=2)
if img.shape[0] == 1 and bs > 1:
img = repeat(img, "1 ... -> bs ...", bs=bs)
ref_img = repeat(ref_img, "1 ... -> bs ...", bs=bs)
img_ids = torch.zeros(h // 2, w // 2, 3)
img_ids[..., 1] = img_ids[..., 1] + torch.arange(h // 2)[:, None]
img_ids[..., 2] = img_ids[..., 2] + torch.arange(w // 2)[None, :]
img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)
ref_img_ids = torch.zeros(ref_h // 2, ref_w // 2, 3)
ref_img_ids[..., 1] = ref_img_ids[..., 1] + torch.arange(ref_h // 2)[:, None]
ref_img_ids[..., 2] = ref_img_ids[..., 2] + torch.arange(ref_w // 2)[None, :]
ref_img_ids = repeat(ref_img_ids, "ref_h ref_w c -> b (ref_h ref_w) c", b=bs)
if isinstance(prompt, str):
prompt = [prompt]
llm_encoder = model_bundle.llm_encoder
if model_bundle.offload:
llm_encoder = llm_encoder.to(device)
txt, mask = llm_encoder(prompt, ref_image_raw)
if model_bundle.offload:
llm_encoder = llm_encoder.cpu()
self.cudagc()
txt_ids = torch.zeros(bs, txt.shape[1], 3)
img = torch.cat([img, ref_img.to(device=img.device, dtype=img.dtype)], dim=-2)
img_ids = torch.cat([img_ids, ref_img_ids], dim=-2)
return {
"img": img,
"mask": mask,
"img_ids": img_ids.to(img.device),
"llm_embedding": txt.to(img.device),
"txt_ids": txt_ids.to(img.device),
}
def cudagc(self):
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
def denoise(self, model_bundle, img, img_ids, llm_embedding, txt_ids, timesteps, cfg_guidance=4.5, mask=None, timesteps_truncate=1.0):
device = img.device
dit = model_bundle.dit
if model_bundle.offload:
dit = dit.to(device)
for t_curr, t_prev in itertools.pairwise(timesteps):
if img.shape[0] == 1 and cfg_guidance != -1:
img = torch.cat([img, img], dim=0)
t_vec = torch.full(
(img.shape[0],), t_curr, dtype=img.dtype, device=img.device
)
txt, vec = dit.connector(llm_embedding, t_vec, mask)
pred = dit(
img=img,
img_ids=img_ids,
txt=txt,
txt_ids=txt_ids,
y=vec,
timesteps=t_vec,
)
if cfg_guidance != -1:
cond, uncond = (
pred[0 : pred.shape[0] // 2, :],
pred[pred.shape[0] // 2 :, :],
)
if t_curr > timesteps_truncate:
diff = cond - uncond
diff_norm = torch.norm(diff, dim=(2), keepdim=True)
pred = uncond + cfg_guidance * (
cond - uncond
) / self.process_diff_norm(diff_norm, k=0.4)
else:
pred = uncond + cfg_guidance * (cond - uncond)
tem_img = img[0 : img.shape[0] // 2, :] + (t_prev - t_curr) * pred
img_input_length = img.shape[1] // 2
img = torch.cat(
[
tem_img[:, :img_input_length],
img[ : img.shape[0] // 2, img_input_length:],
], dim=1
)
if model_bundle.offload:
dit = dit.cpu()
self.cudagc()
return img[:, :img.shape[1] // 2]
def input_process_image(self, img_tensor, size_level=512):
# Convert tensor to PIL for resizing
img = F.to_pil_image(img_tensor.squeeze(0))
# Calculate dimensions
w, h = img.size
r = w / h
if w > h:
w_new = math.ceil(math.sqrt(size_level * size_level * r))
h_new = math.ceil(w_new / r)
else:
h_new = math.ceil(math.sqrt(size_level * size_level / r))
w_new = math.ceil(h_new * r)
h_new = math.ceil(h_new) // 16 * 16
w_new = math.ceil(w_new) // 16 * 16
img_resized = img.resize((w_new, h_new))
return img_resized, img.size
@torch.inference_mode()
def generate(self, model_bundle, input_image, prompt, negative_prompt, num_steps, cfg_guidance, seed, size_level):
# ComfyUI passes images in BHWC format [batch, height, width, channels] with values in range [0, 1]
# Convert to BCHW format for processing
device = "cuda" if torch.cuda.is_available() else "cpu"
# Convert from ComfyUI format [B, H, W, C] to [B, C, H, W]
input_tensor = input_image.permute(0, 3, 1, 2)
# Process the input image
batch_size = input_tensor.shape[0]
processed_images = []
for i in range(batch_size):
single_img = input_tensor[i:i+1]
# Process image to get proper dimensions
ref_img_pil, original_size = self.input_process_image(single_img, size_level)
width, height = ref_img_pil.width, ref_img_pil.height
# Convert PIL back to tensor in proper format
ref_img_tensor = F.to_tensor(ref_img_pil).unsqueeze(0).to(device) * 2 - 1
# Get autoencoder from model bundle
ae = model_bundle.ae
if model_bundle.offload:
ae = ae.to(device)
# Encode the image
ref_images = ae.encode(ref_img_tensor)
if model_bundle.offload:
ae = ae.cpu()
self.cudagc()
# Setup seed
seed_value = int(seed)
if seed_value < 0:
seed_value = torch.Generator(device="cpu").seed()
# Create latent noise
x = torch.randn(
1, # num_samples
16,
height // 8,
width // 8,
device=device,
dtype=torch.bfloat16 if model_bundle.dtype == torch.bfloat16 else torch.float32,
generator=torch.Generator(device=device).manual_seed(seed_value),
)
# Get timesteps
timesteps = get_schedule(
num_steps, x.shape[-1] * x.shape[-2] // 4, shift=True
)
# Cat for classifier-free guidance
x = torch.cat([x, x], dim=0)
ref_images = torch.cat([ref_images, ref_images], dim=0)
ref_img_tensor = torch.cat([ref_img_tensor, ref_img_tensor], dim=0)
# Prepare inputs
inputs = self.prepare([prompt, negative_prompt], x, ref_image=ref_images, ref_image_raw=ref_img_tensor, model_bundle=model_bundle, device=device)
# Denoise
with torch.autocast(device_type=device, dtype=torch.bfloat16):
x = self.denoise(
model_bundle=model_bundle,
**inputs,
cfg_guidance=cfg_guidance,
timesteps=timesteps,
timesteps_truncate=1.0,
)
# Unpack and decode
x = self.unpack(x.float(), height, width)
if model_bundle.offload:
ae = model_bundle.ae.to(device)
x = ae.decode(x)
if model_bundle.offload:
ae = ae.cpu()
self.cudagc()
x = x.clamp(-1, 1)
x = x.mul(0.5).add(0.5)
# Convert back to PIL for resizing to original dimensions
for img in x.float():
pil_img = F.to_pil_image(img)
pil_img = pil_img.resize(original_size)
# Convert back to tensor in ComfyUI format [H, W, C]
img_tensor = torch.tensor(np.array(pil_img)).float() / 255.0
processed_images.append(img_tensor)
# Stack all processed images
output_tensor = torch.stack(processed_images)
return (output_tensor,)
+7
View File
@@ -0,0 +1,7 @@
torch>=2.3.1
liger_kernel>=0.5.4
einops>=0.8.1
transformers>=4.49.0
qwen_vl_utils>=0.0.10
safetensors>=0.4.5
pillow>=11.1.0
+1
View File
@@ -0,0 +1 @@
# Step1X-Edit utilities package
+59
View File
@@ -0,0 +1,59 @@
import platform
import sys
import torch
def get_cuda_version():
if torch.cuda.is_available():
cuda_version = torch.version.cuda
return f"cu{cuda_version.replace('.', '')[:2]}" # 例如:cu121
return "cpu"
def get_torch_version():
return f"torch{torch.__version__.split('+')[0]}"[:-2] # 例如:torch2.2
def get_python_version():
version = sys.version_info
return f"cp{version.major}{version.minor}" # 例如:cp310
def get_abi_flag():
return "abiTRUE" if torch._C._GLIBCXX_USE_CXX11_ABI else "abiFALSE"
def get_platform():
system = platform.system().lower()
machine = platform.machine().lower()
if system == "linux" and machine == "x86_64":
return "linux_x86_64"
elif system == "windows" and machine == "amd64":
return "win_amd64"
elif system == "darwin" and machine == "x86_64":
return "macosx_x86_64"
else:
raise ValueError(f"Unsupported platform: {system}_{machine}")
def generate_flash_attn_filename(flash_attn_version="2.7.2.post1"):
cuda_version = get_cuda_version()
torch_version = get_torch_version()
python_version = get_python_version()
abi_flag = get_abi_flag()
platform_tag = get_platform()
filename = (
f"flash_attn-{flash_attn_version}+{cuda_version}{torch_version}cxx11{abi_flag}-"
f"{python_version}-{python_version}-{platform_tag}.whl"
)
return filename
if __name__ == "__main__":
try:
filename = generate_flash_attn_filename()
print("Generated filename:", filename)
except Exception as e:
print("Error generating filename:", e)
+120
View File
@@ -0,0 +1,120 @@
import torch
from .step1x_edit_utils import load_state_dict
# Try to import Step1X-Edit modules
try:
from ..modules.autoencoder import AutoEncoder
from ..modules.conditioner import Qwen25VL_7b_Embedder as Qwen2VLEmbedder
from ..modules.model_edit import Step1XParams, Step1XEdit
except ImportError:
print("Warning: Could not import Step1X-Edit modules. Will attempt to load them dynamically.")
# Define placeholders for the modules
AutoEncoder = None
Qwen2VLEmbedder = None
Step1XParams = None
Step1XEdit = None
class Step1XEditModelBundle:
"""Bundle containing all components of the Step1X-Edit model."""
def __init__(self, ae=None, dit=None, llm_encoder=None, device="cuda", dtype=torch.bfloat16):
self.ae = ae
self.dit = dit
self.llm_encoder = llm_encoder
self.device = device
self.dtype = dtype
self.quantized = False
self.offload = False
def load_models(self, dit_path, ae_path, qwen2vl_model_path, max_length=256):
print(f"Loading models from dit_path: {dit_path}, \n ae_path: {ae_path}, \n qwen2vl_model_path: {qwen2vl_model_path}")
"""Load all model components."""
if Step1XParams is None or AutoEncoder is None or Qwen2VLEmbedder is None:
print("Error: Step1X-Edit modules not found. Please make sure they are properly installed.")
return False
# Initialize LLM encoder
self.llm_encoder = Qwen2VLEmbedder(
qwen2vl_model_path,
device=self.device,
max_length=max_length,
dtype=self.dtype,
)
# Initialize autoencoder
with torch.device("meta"):
self.ae = AutoEncoder(
resolution=256,
in_channels=3,
ch=128,
out_ch=3,
ch_mult=[1, 2, 4, 4],
num_res_blocks=2,
z_channels=16,
scale_factor=0.3611,
shift_factor=0.1159,
)
# Initialize DIT model
step1x_params = Step1XParams(
in_channels=64,
out_channels=64,
vec_in_dim=768,
context_in_dim=4096,
hidden_size=3072,
mlp_ratio=4.0,
num_heads=24,
depth=19,
depth_single_blocks=38,
axes_dim=[16, 56, 56],
theta=10_000,
qkv_bias=True,
)
self.dit = Step1XEdit(step1x_params)
# Load weights
self.ae = load_state_dict(self.ae, ae_path, 'cpu')
self.dit = load_state_dict(self.dit, dit_path, 'cpu')
# Set model precision
self.ae = self.ae.to(dtype=torch.float32)
if not self.quantized:
self.dit = self.dit.to(dtype=self.dtype)
if not self.offload:
self.dit = self.dit.to(device=self.device)
self.ae = self.ae.to(device=self.device)
return True
def set_quantized(self, quantized):
"""Set whether to use quantized weights."""
self.quantized = quantized
def set_offload(self, offload):
"""Set whether to offload models to CPU."""
self.offload = offload
def to_device(self, device):
"""Move models to specified device."""
self.device = device
if not self.offload:
self.dit = self.dit.to(device=self.device)
self.ae = self.ae.to(device=self.device)
def get_models(self):
"""Return all model components."""
return {
"ae": self.ae,
"dit": self.dit,
"llm_encoder": self.llm_encoder,
"device": self.device,
"dtype": self.dtype,
"quantized": self.quantized,
"offload": self.offload
}
+47
View File
@@ -0,0 +1,47 @@
import math
from collections.abc import Callable
import torch
from torch import Tensor
def get_noise(num_samples: int, height: int, width: int, device: torch.device, dtype: torch.dtype, seed: int):
return torch.randn(
num_samples,
16,
# allow for packing
2 * math.ceil(height / 16),
2 * math.ceil(width / 16),
device=device,
dtype=dtype,
generator=torch.Generator(device=device).manual_seed(seed),
)
def time_shift(mu: float, sigma: float, t: Tensor):
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
def get_lin_function(x1: float = 256, y1: float = 0.5, x2: float = 4096, y2: float = 1.15) -> Callable[[float], float]:
m = (y2 - y1) / (x2 - x1)
b = y1 - m * x1
return lambda x: m * x + b
def get_schedule(
num_steps: int,
image_seq_len: int,
base_shift: float = 0.5,
max_shift: float = 1.15,
shift: bool = True,
) -> list[float]:
# extra step for zero
timesteps = torch.linspace(1, 0, num_steps + 1)
# shifting the schedule to favor high timesteps for higher signal images
if shift:
# estimate mu based on linear estimation between two points
mu = get_lin_function(y1=base_shift, y2=max_shift)(image_seq_len)
timesteps = time_shift(mu, 1.0, timesteps)
return timesteps.tolist()
+24
View File
@@ -0,0 +1,24 @@
import os
import torch
from safetensors.torch import load_file
def load_state_dict(model, ckpt_path, device="cuda", strict=False, assign=True):
"""Load state dict from checkpoint file."""
if os.path.splitext(ckpt_path)[1] == ".safetensors":
state_dict = load_file(ckpt_path, device)
else:
state_dict = torch.load(ckpt_path, map_location="cpu")
missing, unexpected = model.load_state_dict(
state_dict, strict=strict, assign=assign
)
if len(missing) > 0 and len(unexpected) > 0:
print(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))
print("\n" + "-" * 79 + "\n")
print(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))
elif len(missing) > 0:
print(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))
elif len(unexpected) > 0:
print(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))
return model