Nexus-GenV2 模型部署教程Nexus-Gen 是一个统一的模型,它结合了大语言模型的语言推理能力和扩散模型的图像合成能力。提出了一种统一的图像嵌入空间来建模图像理解、生成和编辑任务。为了在多个任务上进行联合优化,整理了一个包含 2630 万个样本的大规模数据集,并使用多阶段策略训练 Nexus-Gen,包括自回归模型的多任务预训练以及生成和编辑解码器的条件适应。

Nexus-Gen 的定性结果:

限制:请注意,Nexus-Gen 是在有限的文本到图像数据上训练的,可能对文本提示不够鲁棒。
| 环境 | 版本 |
|---|---|
| Python | >= 3.10 |
| controlnet-aux | == 0.0.7 |
| PyTorch | >= 2.0.0 |
| transformers | == 4.49.0 |
步骤 1:更新系统
更新您的系统软件包:
sudo apt update
sudo apt upgrade -y
步骤 2:下载 Miniconda 安装脚本
访问 Miniconda 的官方网站或使用以下命令直接下载最新版本的安装脚本(以 Python 3 为例):
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
步骤 3:验证安装脚本的完整性(可忽略)
下载 SHA256 校验和文件并验证安装包的完整性:(比较输出的校验和与.sha256 文件中的值是否一致,确保文件未被篡改。)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh.sha256
sha256sum Miniconda3-latest-Linux-x86_64.sh
步骤 4:运行安装脚本
为安装脚本添加执行权限:
chmod +x Miniconda3-latest-Linux-x86_64.sh
运行安装脚本:
./Miniconda3-latest-Linux-x86_64.sh
步骤 5:按照提示完成安装
在安装过程中,您需要:
阅读许可协议 :按 Enter 键逐页阅读,或者按 Q 退出阅读。
接受许可协议 :输入 yes 并按 Enter。
选择安装路径 :默认路径为 "/home/您的用户名/miniconda3",直接按 Enter 即可,或输入自定义路径。
是否初始化 Miniconda :输入 yes 将 Miniconda 添加到您的 PATH 环境变量中。
步骤 6:激活 Miniconda 环境
安装完成后,使环境变量生效:
source ~/.bashrc
步骤 7:验证安装是否成功
检查 conda 版本:
conda --version
创建新 conda 环境(环境名为 NexusGen ,可自主取名),后续 python 库安装和 py 文件运行都在这个 conda 环境下进行
conda create -n NexusGen python=3.10 -y
conda activate NexusGen
项目地址:https://github.com/modelscope/Nexus-Gen.git
git clone https://github.com/modelscope/Nexus-Gen.git
会在使用以上命令的当前目录下自动创建文件夹Nexus-Gen。
之前导入的git库内部有 requirements.txt,但是不全面,经过整合需要以下配置(内容可另存requirements.txt):
安装命令:pip install -r requirements.txt
注意:如果下载太慢,可以进行国内源替换(临时),基本所有python库单独或者 txt 集合下载都可以添加 源。
pip install -r requirements.txt -i <清华源 or 阿里源 等国内镜像源加速 python 库的下载>
e.g. pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
以下是修改后的 requirements.txt
torch>=2.0.0
torchvision
cupy-cuda12x
transformers
controlnet-aux==0.0.7
imageio
imageio[ffmpeg]
safetensors
einops
sentencepiece
protobuf
modelscope
ftfy
pynvml
pandas
accelerate
qwen_vl_utils
flash-attn (这个库需要在安装 torch 之后才能安装)
transformers==4.49.0
gradio
之前 https://github.com/modelscope/Nexus-Gen.git 克隆的文件夹内部有 download_models.py 文件,可以直接运行,运行之后,会在该文件同目录下自动创建 models 文件夹。然后再生成 Nexus-GenV2 和 FLUX 文件夹。
python download_models.py
download_models.py 文件内容:
from modelscope import snapshot_download
snapshot_download('DiffSynth-Studio/Nexus-GenV2', local_dir='models/Nexus-GenV2')
flux_path = snapshot_download('black-forest-labs/FLUX.1-dev',
allow_file_pattern=[
"text_encoder/model.safetensors",
"text_encoder_2/*",
"ae.safetensors",
],
local_dir='models/FLUX/FLUX.1-dev')
注意:之前下载的git仓库里面的 app.py 源码仅支持单卡运行,测试环境采用的是三张 4090 24G 显卡,所以 app.py 已经接受修改。
如果单卡显存足够大,可以忽略针对git克隆后文件夹内 app.py,editing_decoder.py,modules.py 修改。(editing_decoder.py 和 modules.py 在 "Nexus-Gen/modeling/decoder/" 目录下)
运行demo,出现 "Running on local URL" 字样就可以浏览器打开了
python app.py
以下是文件修改后启动项目的 demo UI:
图像编辑
图像生成

图像理解

针对该 demo 使用 3 张 4090 24G 显存的显卡 进行 图片生成、图片理解、图片编辑 三项功能。源文件也做了相应修改,以下作为修改参考。
原git上下载的 app.py 需要替换为以下内容。
import gradio as gr
import torch
from PIL import Image
import os
import random
import gc
import subprocess
import time
import psutil
from transformers import AutoConfig
from qwen_vl_utils import process_vision_info, smart_resize
from modeling.decoder.generation_decoder import NexusGenGenerationDecoder
from modeling.decoder.editing_decoder import NexusGenEditingDecoder
from modeling.ar.modeling_qwen2_5_vl import Qwen2_5_VLForConditionalGeneration
from modeling.ar.processing_qwen2_5_vl import Qwen2_5_VLProcessor
import numpy as np
# <--- 新增: DynamicCache兼容性修复 ---
def patch_dynamic_cache_compatibility():
"""修复DynamicCache兼容性问题"""
try:
from transformers.cache_utils import DynamicCache
if not hasattr(DynamicCache, 'is_compileable'):
DynamicCache.is_compileable = lambda self: False
print("✅ DynamicCache兼容性补丁已应用")
except Exception as e:
print(f"⚠️ DynamicCache补丁应用失败: {e}")
# 立即应用兼容性补丁
patch_dynamic_cache_compatibility()
# --- 兼容性修复结束 ---
# <--- 新增: 应用启动时的初始化清理 ---
def initialize_clean_gpu_environment():
"""应用启动时清理所有GPU残留"""
print("=" * 60)
print("🚀 Nexus-Gen 应用启动 - 初始化GPU环境")
print("=" * 60)
# 1. 显示启动前的GPU状态
print("📊 启动前GPU状态:")
if torch.cuda.is_available():
for i in range(torch.cuda.device_count()):
try:
allocated = torch.cuda.memory_allocated(i) / 1024**3
reserved = torch.cuda.memory_reserved(i) / 1024**3
print(f" GPU {i}: 已分配 {allocated:.2f}GB, 已保留 {reserved:.2f}GB")
except:
print(f" GPU {i}: 无法获取状态")
# 2. 安全清理残留进程(排除当前进程)
print("🔄 清理残留进程...")
try:
current_pid = os.getpid()
# 查找并终止其他Python进程,但排除当前进程
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
try:
if proc.info['pid'] != current_pid and proc.info['name'] and 'python' in proc.info['name'].lower():
cmdline = ' '.join(proc.info['cmdline']) if proc.info['cmdline'] else ''
# 只终止包含nexus或flux的进程,避免误杀其他Python程序
if any(keyword in cmdline.lower() for keyword in ['nexus', 'flux', 'diffsynth']):
print(f" 终止进程: PID {proc.info['pid']} - {cmdline[:50]}...")
proc.terminate()
proc.wait(timeout=3)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired):
continue
time.sleep(1) # 等待进程完全终止
print(" ✅ 残留进程清理完成")
except ImportError:
print(" ⚠️ psutil未安装,跳过进程清理")
except Exception as e:
print(f" ⚠️ 进程清理警告: {e}")
# 3. 强制清理所有GPU显存
print("🧹 强制清理GPU显存...")
if torch.cuda.is_available():
try:
# 清理PyTorch缓存
for i in range(torch.cuda.device_count()):
with torch.cuda.device(i):
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
# 强制垃圾回收
gc.collect()
time.sleep(1) # 等待清理完成
print(" ✅ GPU显存清理完成")
except Exception as e:
print(f" ⚠️ GPU清理警告: {e}")
# 4. 显示清理后的GPU状态
print("📊 清理后GPU状态:")
if torch.cuda.is_available():
for i in range(torch.cuda.device_count()):
try:
allocated = torch.cuda.memory_allocated(i) / 1024**3
reserved = torch.cuda.memory_reserved(i) / 1024**3
print(f" GPU {i}: 已分配 {allocated:.2f}GB, 已保留 {reserved:.2f}GB")
except:
print(f" GPU {i}: 无法获取状态")
print("✨ GPU环境初始化完成,开始加载模型...")
print("=" * 60)
# 立即执行初始化清理
initialize_clean_gpu_environment()
# --- 初始化清理结束 ---
def bound_image(image, max_pixels=262640):
resized_height, resized_width = smart_resize(
image.height,
image.width,
max_pixels=max_pixels,
)
return image.resize((resized_width, resized_height))
# <--- 新增: 显存管理函数 ---
def clear_gpu_memory():
"""清理所有GPU显存"""
if torch.cuda.is_available():
for i in range(torch.cuda.device_count()):
with torch.cuda.device(i):
torch.cuda.empty_cache()
gc.collect()
def print_gpu_memory():
"""打印GPU显存使用情况"""
if torch.cuda.is_available():
for i in range(torch.cuda.device_count()):
allocated = torch.cuda.memory_allocated(i) / 1024**3
reserved = torch.cuda.memory_reserved(i) / 1024**3
print(f"GPU {i}: 已分配 {allocated:.2f}GB, 已保留 {reserved:.2f}GB")
# --- 显存管理函数结束 ---
# Initialize model and processor
model_path = 'models/Nexus-GenV2'
model_config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
# <--- 修改: 真正的组件分片策略 ---
print("🎯 组件分片策略:")
print(" 📍 cuda:0: 图像理解专用 (主模型)")
print(" 📍 cuda:1: 延迟加载生成&编辑解码器")
print(" 📍 cuda:2: 延迟加载生成&编辑解码器")
print("=" * 60)
# 主模型只加载到cuda:0,专门用于图像理解
understanding_device = "cuda:0"
# --- 组件分片策略结束 ---
# <--- 修改: 主模型只加载到cuda:0 ---
print("📦 加载主模型 (Qwen2.5-VL) 到 cuda:0 专用于图像理解...")
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
model_path,
config=model_config,
trust_remote_code=True,
torch_dtype="auto",
device_map=understanding_device, # 只加载到cuda:0
)
processor = Qwen2_5_VLProcessor.from_pretrained(model_path, trust_remote_code=True)
model.eval()
print(f"✅ 主模型已加载到 {understanding_device}")
print_gpu_memory()
# --- 主模型加载结束 ---
# Initialize Flux Decoder paths
flux_path = "models"
generation_decoder_path = "models/Nexus-GenV2/generation_decoder.bin"
editing_decoder_path = "models/Nexus-GenV2/edit_decoder.bin"
# <--- 修改: 真正的延迟加载和组件分片 ---
print("📦 设置延迟加载策略 - 避免初始化时显存溢出...")
# 全局解码器变量
generation_decoder = None
editing_decoder = None
current_task = None # 跟踪当前任务类型
def clear_all_decoders():
"""清理所有解码器"""
global generation_decoder, editing_decoder
if generation_decoder is not None:
del generation_decoder
generation_decoder = None
print(" 🗑️ 图像生成解码器已释放")
if editing_decoder is not None:
del editing_decoder
editing_decoder = None
print(" 🗑️ 图像编辑解码器已释放")
# 清理cuda:1和cuda:2的显存
for device_id in [1, 2]:
if torch.cuda.is_available() and device_id < torch.cuda.device_count():
with torch.cuda.device(device_id):
torch.cuda.empty_cache()
gc.collect()
print(" ✅ 所有解码器已清理")
def get_generation_decoder():
"""延迟初始化图像生成解码器"""
global generation_decoder, current_task
# 如果当前不是生成任务,先清理其他解码器
if current_task != "generation":
clear_all_decoders()
current_task = "generation"
if generation_decoder is None:
print("📦 初始化图像生成解码器 (cuda:1)...")
try:
generation_decoder = NexusGenGenerationDecoder(
generation_decoder_path,
flux_path,
device="cuda:1", # 只使用cuda:1
enable_cpu_offload=True # 启用CPU offload节省显存
)
print("✅ 图像生成解码器已加载到 cuda:1")
print_gpu_memory()
except Exception as e:
print(f"❌ 图像生成解码器加载失败: {e}")
# 如果cuda:1显存不足,尝试使用CPU offload
try:
generation_decoder = NexusGenGenerationDecoder(
generation_decoder_path,
flux_path,
device="cpu", # 降级到CPU
enable_cpu_offload=True
)
print("⚠️ 图像生成解码器已降级到CPU")
except Exception as e2:
print(f"❌ CPU降级也失败: {e2}")
raise e2
return generation_decoder
def get_editing_decoder():
"""延迟初始化图像编辑解码器"""
global editing_decoder, current_task
# 如果当前不是编辑任务,先清理其他解码器
if current_task != "editing":
clear_all_decoders()
current_task = "editing"
if editing_decoder is None:
print("📦 初始化图像编辑解码器 (cuda:2)...")
try:
editing_decoder = NexusGenEditingDecoder(
editing_decoder_path,
flux_path,
model_path,
device="cuda:2", # 只使用cuda:2
enable_cpu_offload=True # 启用CPU offload节省显存
)
print("✅ 图像编辑解码器已加载到 cuda:2")
print_gpu_memory()
except Exception as e:
print(f"❌ 图像编辑解码器加载失败: {e}")
# 如果cuda:2显存不足,尝试使用CPU offload
try:
editing_decoder = NexusGenEditingDecoder(
editing_decoder_path,
flux_path,
model_path,
device="cpu", # 降级到CPU
enable_cpu_offload=True
)
print("⚠️ 图像编辑解码器已降级到CPU")
except Exception as e2:
print(f"❌ CPU降级也失败: {e2}")
raise e2
return editing_decoder
print("✅ 延迟加载策略设置完成")
# --- 延迟加载策略结束 ---
# Define system prompt
SYSTEM_PROMPT = "You are a helpful assistant."
def image_understanding(image, question):
"""图像理解功能 - 专用cuda:0"""
print("=== 开始图像理解任务 (专用cuda:0) ===")
# 确保其他任务的解码器被清理
global current_task
if current_task != "understanding":
clear_all_decoders()
current_task = "understanding"
print_gpu_memory()
if image is not None:
# Convert numpy array to PIL Image
if isinstance(image, np.ndarray):
image = Image.fromarray(image)
messages = [
{
"role": "system",
"content": SYSTEM_PROMPT
},
{
"role": "user",
"content": [
{
"type": "image",
"image": image,
},
{"type": "text", "text": question if question else "Please give a brief description of the image."},
],
}
]
else:
# Text-only Q&A mode
messages = [
{
"role": "system",
"content": SYSTEM_PROMPT
},
{
"role": "user",
"content": [
{"type": "text", "text": question},
],
}
]
# Preparation for inference
text = processor.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
if image is not None:
image_inputs, _ = process_vision_info(messages)
image_inputs = [bound_image(image) for image in image_inputs]
inputs = processor(
text=[text],
images=image_inputs,
padding=True,
return_tensors="pt",
)
else:
inputs = processor(
text=[text],
padding=True,
return_tensors="pt",
)
inputs = inputs.to(understanding_device)
# <--- 兼容性修复 ---
with torch.no_grad():
# 设置模型为非编译模式,避免DynamicCache问题
if hasattr(model, '_dynamo_compile'):
model._dynamo_compile = False
generated_ids = model.generate(
**inputs,
max_new_tokens=1024,
do_sample=True, # 禁用采样以提高稳定性 (废弃)
use_cache=True,
pad_token_id=processor.tokenizer.eos_token_id
)
# --- 兼容性修复结束 ---
generated_ids_trimmed = [
out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output_text = processor.batch_decode(
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)
print("=== 图像理解任务完成 ===")
print_gpu_memory()
return output_text[0]
def image_generation(prompt):
"""图像生成功能 - 使用cuda:1"""
print("=== 开始图像生成任务 (cuda:1) ===")
print_gpu_memory()
generation_instruction = 'Generate an image according to the following description: {}'
prompt = generation_instruction.format(prompt)
messages = [{"role": "user", "content": [{"type": "text", "text": prompt}]}]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = processor(text=[text], padding=True, return_tensors="pt")
inputs = inputs.to(understanding_device) # 先在cuda:0上处理
generation_image_grid_thw = torch.tensor([[1, 18, 18]]).to(understanding_device)
# <--- 兼容性修复 ---
with torch.no_grad():
if hasattr(model, '_dynamo_compile'):
model._dynamo_compile = False
outputs = model.generate(
**inputs,
max_new_tokens=1024,
return_dict_in_generate=True,
generation_image_grid_thw=generation_image_grid_thw,
do_sample=True,
use_cache=True,
pad_token_id=processor.tokenizer.eos_token_id
)
# --- 兼容性修复结束 ---
if not hasattr(outputs, 'output_image_embeddings'):
raise ValueError("Failed to generate image embeddings")
else:
output_image_embeddings = outputs.output_image_embeddings
# 获取生成解码器并生成图像
decoder = get_generation_decoder()
seed = random.randint(0, 10000)
image = decoder.decode_image_embeds(output_image_embeddings, cfg_scale=3.0, seed=seed)
print("=== 图像生成任务完成 ===")
print_gpu_memory()
return image
def get_image_embedding(vision_encoder, processor, image, target_size=(504, 504)):
image = image.resize(target_size, Image.BILINEAR)
inputs = processor.image_processor(images=[image], videos=None, return_tensors='pt', do_resize=False)
device = vision_encoder.device
pixel_values = inputs["pixel_values"].to(device)
image_grid_thw = inputs["image_grid_thw"].to(device)
pixel_values = pixel_values.type(vision_encoder.dtype)
with torch.no_grad():
image_embeds = vision_encoder(pixel_values, grid_thw=image_grid_thw)
return image_embeds
def image_editing(image, instruction):
"""图像编辑功能 - 使用cuda:2"""
print("=== 开始图像编辑任务 (cuda:2) ===")
print_gpu_memory()
if '<image>' not in instruction:
instruction = '<image> ' + instruction
instruction = instruction.replace('<image>', '<|vision_start|><|image_pad|><|vision_end|>')
messages = [{"role": "user", "content": [{"type": "text", "text": instruction}]}]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# Convert numpy array to PIL Image if needed
input_image = Image.fromarray(image) if not isinstance(image, Image.Image) else image
bounded_image = bound_image(input_image)
inputs = processor(
text=[text],
images=[bounded_image],
padding=True,
return_tensors="pt",
)
inputs = inputs.to(understanding_device) # 先在cuda:0上处理
generation_image_grid_thw = torch.tensor([[1, 18, 18]]).to(understanding_device)
# <--- 兼容性修复 ---
with torch.no_grad():
if hasattr(model, '_dynamo_compile'):
model._dynamo_compile = False
outputs = model.generate(
**inputs,
max_new_tokens=1024,
return_dict_in_generate=True,
generation_image_grid_thw=generation_image_grid_thw,
do_sample=True,
use_cache=True,
pad_token_id=processor.tokenizer.eos_token_id
)
# --- 兼容性修复结束 ---
if not hasattr(outputs, 'output_image_embeddings'):
raise ValueError("Failed to generate image embeddings")
else:
output_image_embeddings = outputs.output_image_embeddings
# 获取参考图像嵌入
ref_embeddings = get_image_embedding(model.visual, processor, input_image, target_size=(504, 504))
# 获取编辑解码器并编辑图像
decoder = get_editing_decoder()
edited_image = decoder.decode_image_embeds(output_image_embeddings, ref_embed=ref_embeddings, cfg_scale=1.0)
print("=== 图像编辑任务完成 ===")
print_gpu_memory()
return edited_image
def edit_with_instruction(image, instruction):
return image_editing(image, instruction)
def understand_with_image(image, question):
return image_understanding(image, question)
# Create Gradio interface
with gr.Blocks(title="Nexus-Gen Demo") as demo:
gr.Markdown("# Nexus-Gen Demo")
with gr.Tab("Image Generation"):
with gr.Row():
with gr.Column():
prompt_input = gr.Textbox(label="Input Prompt", lines=3, placeholder="Describe the image you want to generate")
generate_btn = gr.Button("Generate") # , variant="primary"
with gr.Column():
output_image = gr.Image(label="Generated Image") # , type="pil"
def generate_with_option(prompt):
return image_generation(prompt)
generate_btn.click(
fn=generate_with_option,
inputs=[prompt_input], # , option_dropdown
outputs=[output_image] # output_text
)
gr.Examples(
examples=[
"A cut dog sitting on a bench in a park, wearing a red collar.",
"A woman in a blue dress standing on a beach at sunset.",
"一只可爱的猫。"
],
inputs=[prompt_input],
outputs=[output_image],
fn=generate_with_option,
cache_examples=False,
)
with gr.Tab("Image Editing"):
with gr.Row():
with gr.Column():
input_image = gr.Image(label="Upload Image to Edit") # , type="numpy"
edit_instruction = gr.Textbox(label="Editing Instruction", lines=2, placeholder="Describe how to edit the image...")
edit_btn = gr.Button("Edit Image") # , variant="primary"
with gr.Column():
edited_image = gr.Image(label="Edited Image") # , type="pil"
edit_btn.click(
fn=edit_with_instruction,
inputs=[input_image, edit_instruction],
outputs=[edited_image]
)
gr.Examples(
examples=[
["assets/examples/cat.png", "Add a pair of sunglasses for the cat."],
["assets/examples/cat.png", "给猫加一副太阳镜。"],
],
inputs=[input_image, edit_instruction],
outputs=edited_image,
fn=edit_with_instruction,
cache_examples=False,
)
with gr.Tab("Multimodal Q&A"):
with gr.Row():
with gr.Column():
qa_image = gr.Image(label="Upload Image (Optional)")# type="numpy"
qa_question = gr.Textbox(label="Input Question", lines=2, placeholder="You can:\n1. Upload an image and ask questions about it\n2. Ask text-only questions\n3. Upload an image without a question for automatic description")
qa_btn = gr.Button("Generate Response") # , variant="primary"
with gr.Column():
qa_answer = gr.Textbox(label="Answer", lines=10)
qa_btn.click(
fn=understand_with_image,
inputs=[qa_image, qa_question],
outputs=[qa_answer]
)
# 例子
gr.Examples(
examples=[
# Visual Q&A examples
["assets/examples/cat.png", "What color is the cat?"],
# Text Q&A examples
[None, "What are the main differences between electric and traditional fuel vehicles?"],
# Image description example
["assets/examples/cat.png", "...."],
],
inputs=[qa_image, qa_question],
outputs=[qa_answer],
fn=understand_with_image,
cache_examples=False,
)
if __name__ == "__main__":
print_gpu_memory()
print("🌐 启动Web界面...")
print("=" * 60)
demo.launch(server_name="0.0.0.0", server_port=8080) # , share=True
同理,加载模型和后续推理计算采用不同cuda,避免显存占用完报出异常。
点击此处,立即体验Nexus-GenV2!
