本地部署 Qwen-Image 文生图:完整流程与避坑指南

目录

环境安装:

推理代码:

封装server:


环境安装:

bash 复制代码
pip install git+https://github.com/huggingface/diffusers

pip install accelerate

推理代码:

python 复制代码
import torch
from diffusers import QwenImage21Pipeline

pipe = QwenImage21Pipeline.from_pretrained(
    "/data/feature/0lbg/models/Qwen_Qwen-Image-2.1", 
    torch_dtype=torch.bfloat16
)

# 关键:用 offload 替代 .to("cuda")
pipe.enable_model_cpu_offload()

# 关键:VAE 解码阶段显存不够,必须开 tiling
pipe.vae.enable_tiling()

image = pipe(
    prompt='A neon shop sign that reads "QWEN IMAGE 2.1", rainy night, reflections on wet pavement',
    width=2048, height=2048,
    num_inference_steps=40,
    generator=torch.Generator("cuda").manual_seed(42),
).images[0]

image.save("t2i_example.png")

封装server:

python 复制代码
import io
import base64
import time
import uuid
from contextlib import asynccontextmanager

import torch
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel, Field
from diffusers import QwenImage21Pipeline
import uvicorn

# ---------- 全局 pipeline ----------
pipe = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    global pipe
    print("Loading Qwen-Image-2.1 pipeline ...")
    t0 = time.time()
    pipe = QwenImage21Pipeline.from_pretrained(
        "/data/feature/0lbg/models/Qwen_Qwen-Image-2.1",
        torch_dtype=torch.bfloat16,
    )
    pipe.enable_model_cpu_offload()
    pipe.vae.enable_tiling()
    print(f"Pipeline loaded in {time.time() - t0:.1f}s")
    yield
    # 关闭时释放
    del pipe
    torch.cuda.empty_cache()


app = FastAPI(title="Qwen-Image-2.1 T2I Server", lifespan=lifespan)


# ---------- 请求体 ----------
class T2IRequest(BaseModel):
    prompt: str = Field(..., description="正向提示词")
    negative_prompt: str = Field("", description="负向提示词")
    width: int = Field(2048, ge=256, le=2048)
    height: int = Field(2048, ge=256, le=2048)
    num_inference_steps: int = Field(40, ge=1, le=100)
    true_cfg_scale: float = Field(4.0, ge=0.0, le=20.0)
    seed: int | None = Field(None, description="不传则随机")


# ---------- 接口 ----------
@app.get("/health")
def health():
    return {"status": "ok", "model_loaded": pipe is not None}


@app.post("/generate")
def generate(req: T2IRequest):
    if pipe is None:
        raise HTTPException(status_code=503, detail="Model not loaded yet")

    # seed:不传则随机
    if req.seed is None:
        seed = torch.randint(0, 2**31 - 1, (1,)).item()
    else:
        seed = req.seed
    generator = torch.Generator("cuda").manual_seed(seed)

    try:
        image = pipe(
            prompt=req.prompt,
            negative_prompt=req.negative_prompt or None,
            width=req.width,
            height=req.height,
            num_inference_steps=req.num_inference_steps,
            true_cfg_scale=req.true_cfg_scale,
            generator=generator,
        ).images[0]
    except torch.cuda.OutOfMemoryError as e:
        torch.cuda.empty_cache()
        raise HTTPException(status_code=507, detail=f"CUDA OOM: {e}")

    # 返回 PNG 二进制
    buf = io.BytesIO()
    image.save(buf, format="PNG")
    buf.seek(0)

    return Response(
        content=buf.getvalue(),
        media_type="image/png",
        headers={
            "X-Seed": str(seed),
            "Content-Disposition": f'attachment; filename="{uuid.uuid4().hex}.png"',
        },
    )


@app.post("/generate_b64")
def generate_b64(req: T2IRequest):
    """返回 base64,方便前端直接展示。"""
    if pipe is None:
        raise HTTPException(status_code=503, detail="Model not loaded yet")

    seed = req.seed if req.seed is not None else torch.randint(0, 2**31 - 1, (1,)).item()
    generator = torch.Generator("cuda").manual_seed(seed)

    try:
        image = pipe(
            prompt=req.prompt,
            negative_prompt=req.negative_prompt or None,
            width=req.width,
            height=req.height,
            num_inference_steps=req.num_inference_steps,
            true_cfg_scale=req.true_cfg_scale,
            generator=generator,
        ).images[0]
    except torch.cuda.OutOfMemoryError as e:
        torch.cuda.empty_cache()
        raise HTTPException(status_code=507, detail=f"CUDA OOM: {e}")

    buf = io.BytesIO()
    image.save(buf, format="PNG")
    b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
    return {"seed": seed, "format": "png", "image_base64": b64}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=7999)
相关推荐
东姬AI7 小时前
迪士尼联手Adobe自训“片场专用“AI模型:当内容工业按下生产线开关,角色“开口“这道工序还没自动化
大模型·数字人·多模态·ai视频生成·内容生产
承渊政道1 天前
LangChainAI应用开发框架【LangChain与LangGraph介绍】
langchain·大模型·langgraph·ai应用开发
Flynn Liang1 天前
LangChain4j 1.23 架构全景:Java 大模型应用的 6 层抽象
java·langchain·大模型·rag·ai agent·langchain4j
武雄(小星Ai)1 天前
Ollama报llama-server binary not found排坑实录:服务活着引擎没了
人工智能·windows·大模型·ollama·排坑
小田学Python2 天前
工具调用:让 Agent 从“说”到“做”
大数据·大模型·agent·ai工具·deepseek
小田学Python3 天前
DeepSeek Harness 完整上手指南:安装部署 + 必装插件 + 用一周就回不去的功能
大数据·大模型·agent·ai工具·deepseek
aicat_cn3 天前
json-render 接入 Chat SDK:Agent 在 Slack 和 Teams 里直接回表格和按钮
ai·大模型
神奇的代码在哪里3 天前
Agent 记忆的组成部分:Anthropic The components of agent memory 文章的简要解读
大模型·claude·ai agent·上下文工程·agent记忆
神奇的代码在哪里3 天前
ERNIE-5.5-Flash-Base 开源速览:280B-A14B MoE 基座,微调与推理支持 1M 上下文
大模型·模型部署·文心大模型·moe·ernie5.5
謓泽3 天前
【插件】用写网页的方式做视频?HyperFrames 让我重新理解了“做视频“这件事!
gpt·ai·大模型·音视频·媒体·claude·视频