千问图像生成16Bit(Qwen-Turbo-BF16)开源大模型部署教程:从PyTorch到Flask

想体验4步就能生成高清大图的AI绘画吗?还在为FP16精度生成“黑图”而烦恼?今天,我们就来手把手部署一个专为RTX 4090等现代显卡优化的高性能图像生成系统——千问图像生成16Bit(Qwen-Turbo-BF16)。

这个系统基于强大的Qwen-Image-2512模型,并集成了Wuli-Art Turbo LoRA,通过BFloat16(BF16)全链路推理,不仅解决了传统FP16的“黑图”和“溢出”问题,还能在保持16位精度高性能的同时,获得媲美32位精度的色彩表现。最吸引人的是,它只需要4步迭代就能输出1024x1024的高质量图像,真正实现了“秒级出图”。

无论你是AI绘画爱好者,还是希望将AI图像生成能力集成到自己应用中的开发者,这篇教程都将带你从零开始,完成从环境搭建到Web界面部署的全过程。

1. 系统核心亮点与准备工作

在开始动手之前,我们先快速了解一下这个系统的几个核心优势,这能帮你更好地理解后续的部署步骤。

1.1 为什么选择这个系统?

这个系统有几个让你无法拒绝的理由:

第一是速度极快。 传统的Stable Diffusion模型通常需要20-50步采样才能生成清晰的图像,而这个系统集成了Wuli-Art V3.0 Turbo LoRA,只需要4步迭代就能输出高质量的1024px图像。这意味着生成一张图的时间从几分钟缩短到了几十秒,体验上的提升是巨大的。

第二是稳定性强。 如果你用过其他16位精度的图像生成模型,可能遇到过生成的图片全黑或者颜色异常的问题。这是因为FP16精度范围有限,在复杂的计算过程中容易“溢出”。这个系统采用了BFloat16(BF16)数据类型,专门针对RTX 4000系列显卡优化,不仅节省显存,更重要的是大幅提升了数值稳定性,再复杂的提示词也不怕出“黑图”。

第三是界面美观易用。 系统采用了现代化的玻璃拟态设计,半透明的毛玻璃质感和动态流光背景看起来非常酷。交互布局参考了ChatGPT和Midjourney的习惯,输入框在底部,操作起来很顺手。更重要的是,它会自动保存当前会话生成的所有图片缩略图,你可以快速回溯查看历史作品。

1.2 部署前需要准备什么?

在开始部署之前,你需要确保满足以下条件:

硬件要求:

  • 显卡:推荐RTX 4090(24GB显存),RTX 4080、RTX 3090等大显存显卡也可以
  • 内存:至少32GB系统内存
  • 存储:至少50GB可用磁盘空间(用于存放模型文件)

软件环境:

  • 操作系统:Ubuntu 20.04/22.04或Windows 11(WSL2)
  • Python:3.8-3.10版本
  • CUDA:11.7或11.8(与你的PyTorch版本匹配)

如果你使用的是云服务器,确保选择带有RTX 4090等高性能显卡的实例。如果是本地部署,请提前更新显卡驱动到最新版本。

2. 环境搭建与依赖安装

现在我们来一步步搭建运行环境。整个过程分为三个主要步骤:安装基础依赖、配置Python环境、下载模型文件。

2.1 第一步:创建并激活Python虚拟环境

首先,我们创建一个独立的Python环境,避免与系统其他Python包产生冲突。

打开终端,执行以下命令:

# 创建项目目录
mkdir qwen-turbo-bf16 && cd qwen-turbo-bf16

# 创建Python虚拟环境(使用venv)
python3 -m venv venv

# 激活虚拟环境
# Linux/macOS
source venv/bin/activate
# Windows
venv\Scripts\activate

激活后,你的命令行提示符前面应该会出现(venv)字样,表示已经进入了虚拟环境。

2.2 第二步:安装PyTorch与核心依赖

接下来安装PyTorch和其他必要的Python包。这里需要特别注意PyTorch版本与CUDA版本的匹配。

# 安装PyTorch(根据你的CUDA版本选择)
# CUDA 11.8
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 或者CUDA 11.7
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu117

# 安装Diffusers和Transformers(图像生成核心库)
pip install diffusers transformers accelerate

# 安装Flask(Web界面框架)
pip install flask

# 安装其他辅助库
pip install pillow requests tqdm

安装完成后,可以通过以下命令验证PyTorch是否正确识别了你的GPU:

import torch
print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA可用: {torch.cuda.is_available()}")
print(f"GPU数量: {torch.cuda.device_count()}")
print(f"当前GPU: {torch.cuda.get_device_name(0)}")

如果一切正常,你应该能看到你的GPU型号被正确识别。

2.3 第三步:下载模型文件

模型文件比较大,我们需要从Hugging Face下载。系统需要两个主要模型:底座模型和Turbo LoRA。

from huggingface_hub import snapshot_download
import os

# 创建模型缓存目录
os.makedirs("models", exist_ok=True)

# 下载Qwen-Image-2512底座模型
print("正在下载Qwen-Image-2512底座模型...")
snapshot_download(
    repo_id="Qwen/Qwen-Image-2512",
    local_dir="models/Qwen-Image-2512",
    local_dir_use_symlinks=False
)

# 下载Wuli-Art Turbo LoRA
print("正在下载Wuli-Art Turbo LoRA...")
snapshot_download(
    repo_id="Wuli-Art/Qwen-Image-2512-Turbo-LoRA",
    local_dir="models/Qwen-Turbo-LoRA",
    local_dir_use_symlinks=False
)

print("模型下载完成!")

下载过程可能需要一些时间,Qwen-Image-2512大约15-20GB,Turbo LoRA大约200-300MB。请确保有足够的磁盘空间和稳定的网络连接。

如果下载速度较慢,可以考虑使用镜像源或者提前下载好模型文件,然后直接放到对应的目录中。

3. 核心代码解析与配置

环境准备好后,我们来编写系统的核心代码。主要分为三个部分:图像生成引擎、Web服务器、前端界面。

3.1 图像生成引擎(image_generator.py)

这是系统的核心,负责加载模型并生成图像。

import torch
from diffusers import StableDiffusionPipeline
from PIL import Image
import time

class QwenTurboBF16Generator:
    def __init__(self, model_path="models/Qwen-Image-2512", lora_path="models/Qwen-Turbo-LoRA"):
        """初始化图像生成器"""
        print("正在加载模型...")
        start_time = time.time()
        
        # 加载基础管道
        self.pipe = StableDiffusionPipeline.from_pretrained(
            model_path,
            torch_dtype=torch.bfloat16,  # 使用BF16精度
            safety_checker=None,  # 禁用安全检查器以提升速度
            requires_safety_checker=False
        )
        
        # 加载Turbo LoRA
        self.pipe.load_lora_weights(lora_path)
        
        # 启用顺序CPU卸载(节省显存)
        self.pipe.enable_sequential_cpu_offload()
        
        # 启用VAE分块解码(支持大尺寸生成)
        self.pipe.vae.enable_tiling()
        
        # 将管道移动到GPU
        self.pipe.to("cuda")
        
        load_time = time.time() - start_time
        print(f"模型加载完成,耗时: {load_time:.2f}秒")
    
    def generate_image(self, prompt, negative_prompt="", steps=4, guidance_scale=1.8, width=1024, height=1024):
        """生成图像
        
        参数:
            prompt: 正面提示词
            negative_prompt: 负面提示词
            steps: 采样步数(默认4步)
            guidance_scale: 指导尺度(默认1.8)
            width: 图像宽度(默认1024)
            height: 图像高度(默认1024)
        """
        print(f"开始生成图像: {prompt[:50]}...")
        
        # 设置生成参数
        generator = torch.Generator(device="cuda").manual_seed(int(time.time()))
        
        # 生成图像
        with torch.autocast("cuda"):
            image = self.pipe(
                prompt=prompt,
                negative_prompt=negative_prompt,
                num_inference_steps=steps,
                guidance_scale=guidance_scale,
                width=width,
                height=height,
                generator=generator
            ).images[0]
        
        return image
    
    def generate_multiple(self, prompts, **kwargs):
        """批量生成图像"""
        images = []
        for i, prompt in enumerate(prompts):
            print(f"生成第 {i+1}/{len(prompts)} 张图像...")
            image = self.generate_image(prompt, **kwargs)
            images.append(image)
        return images

# 测试函数
def test_generation():
    """测试图像生成"""
    generator = QwenTurboBF16Generator()
    
    # 测试提示词
    test_prompt = "A beautiful sunset over mountains, digital art, masterpiece, 8k resolution"
    
    # 生成图像
    image = generator.generate_image(test_prompt)
    
    # 保存图像
    image.save("test_output.jpg")
    print("测试图像已保存为 test_output.jpg")
    
    return image

if __name__ == "__main__":
    test_generation()

这个类的关键点:

  1. 使用torch.bfloat16数据类型,这是BF16精度的关键
  2. 加载LoRA权重来启用4步Turbo生成
  3. 启用顺序CPU卸载,让大模型能在24GB显存上流畅运行
  4. 启用VAE分块解码,支持生成大尺寸图像

3.2 Web服务器(app.py)

接下来我们创建Flask Web服务器,提供API接口和前端页面。

from flask import Flask, render_template, request, jsonify, send_file
from image_generator import QwenTurboBF16Generator
from PIL import Image
import io
import os
import uuid
from datetime import datetime

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB限制

# 初始化图像生成器
generator = None
try:
    generator = QwenTurboBF16Generator()
    print("✅ 图像生成器初始化成功")
except Exception as e:
    print(f"❌ 图像生成器初始化失败: {e}")

# 创建输出目录
output_dir = "static/generated"
os.makedirs(output_dir, exist_ok=True)

# 存储生成历史
generation_history = []

@app.route('/')
def index():
    """渲染主页面"""
    return render_template('index.html', history=generation_history[-10:])  # 显示最近10条记录

@app.route('/generate', methods=['POST'])
def generate_image():
    """生成图像API"""
    if generator is None:
        return jsonify({"error": "图像生成器未初始化"}), 500
    
    try:
        # 获取请求参数
        data = request.json
        prompt = data.get('prompt', '')
        negative_prompt = data.get('negative_prompt', '')
        steps = int(data.get('steps', 4))
        guidance_scale = float(data.get('guidance_scale', 1.8))
        width = int(data.get('width', 1024))
        height = int(data.get('height', 1024))
        
        if not prompt:
            return jsonify({"error": "提示词不能为空"}), 400
        
        # 生成图像
        print(f"收到生成请求: {prompt}")
        image = generator.generate_image(
            prompt=prompt,
            negative_prompt=negative_prompt,
            steps=steps,
            guidance_scale=guidance_scale,
            width=width,
            height=height
        )
        
        # 保存图像
        filename = f"{uuid.uuid4().hex[:8]}.jpg"
        filepath = os.path.join(output_dir, filename)
        image.save(filepath, quality=95)
        
        # 记录生成历史
        history_entry = {
            "id": len(generation_history) + 1,
            "prompt": prompt,
            "negative_prompt": negative_prompt,
            "filename": filename,
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "steps": steps,
            "guidance_scale": guidance_scale,
            "size": f"{width}x{height}"
        }
        generation_history.append(history_entry)
        
        # 返回结果
        return jsonify({
            "success": True,
            "filename": filename,
            "url": f"/generated/{filename}",
            "history": history_entry
        })
        
    except Exception as e:
        print(f"生成图像时出错: {e}")
        return jsonify({"error": str(e)}), 500

@app.route('/generated/<filename>')
def get_generated_image(filename):
    """获取生成的图像"""
    filepath = os.path.join(output_dir, filename)
    if os.path.exists(filepath):
        return send_file(filepath, mimetype='image/jpeg')
    return jsonify({"error": "图像不存在"}), 404

@app.route('/history')
def get_history():
    """获取生成历史"""
    return jsonify(generation_history[-20:])  # 返回最近20条记录

@app.route('/batch_generate', methods=['POST'])
def batch_generate():
    """批量生成图像"""
    if generator is None:
        return jsonify({"error": "图像生成器未初始化"}), 500
    
    try:
        data = request.json
        prompts = data.get('prompts', [])
        
        if not prompts:
            return jsonify({"error": "提示词列表不能为空"}), 400
        
        results = []
        for prompt in prompts:
            image = generator.generate_image(prompt)
            filename = f"{uuid.uuid4().hex[:8]}.jpg"
            filepath = os.path.join(output_dir, filename)
            image.save(filepath, quality=95)
            
            results.append({
                "prompt": prompt,
                "filename": filename,
                "url": f"/generated/{filename}"
            })
        
        return jsonify({"success": True, "results": results})
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

这个Web服务器提供了几个关键功能:

  1. 主页展示和图像生成界面
  2. 单张图像生成API
  3. 批量图像生成API
  4. 生成历史记录和查看功能

3.3 前端界面(templates/index.html)

最后,我们创建一个美观的前端界面。这里使用简单的HTML、CSS和JavaScript实现。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>千问图像生成16Bit (Qwen-Turbo-BF16)</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
            color: #fff;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 30px;
        }
        
        .header {
            grid-column: 1 / -1;
            text-align: center;
            margin-bottom: 30px;
            padding: 20px;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            border: 1px solid rgba(255, 255, 255, 0.2);
        }
        
        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
            background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }
        
        .badges {
            display: flex;
            justify-content: center;
            gap: 10px;
            margin-top: 15px;
        }
        
        .badge {
            padding: 5px 15px;
            border-radius: 20px;
            font-size: 0.9em;
            font-weight: bold;
        }
        
        .badge.version { background: #8a2be2; }
        .badge.hardware { background: #28a745; }
        .badge.precision { background: #fd7e14; }
        .badge.framework { background: #007bff; }
        
        .input-panel {
            background: rgba(255, 255, 255, 0.08);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 25px;
            border: 1px solid rgba(255, 255, 255, 0.15);
        }
        
        .output-panel {
            background: rgba(255, 255, 255, 0.08);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 25px;
            border: 1px solid rgba(255, 255, 255, 0.15);
            display: flex;
            flex-direction: column;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #4ecdc4;
        }
        
        textarea, input, select {
            width: 100%;
            padding: 12px;
            background: rgba(255, 255, 255, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 8px;
            color: #fff;
            font-size: 14px;
            resize: vertical;
        }
        
        textarea:focus, input:focus, select:focus {
            outline: none;
            border-color: #4ecdc4;
            box-shadow: 0 0 0 2px rgba(78, 205, 196, 0.2);
        }
        
        .params-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
            margin-top: 20px;
        }
        
        .btn {
            background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
            color: white;
            border: none;
            padding: 14px 28px;
            border-radius: 8px;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            width: 100%;
            margin-top: 10px;
        }
        
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }
        
        .btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
        
        .image-container {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            background: rgba(0, 0, 0, 0.3);
            border-radius: 10px;
            overflow: hidden;
            min-height: 400px;
        }
        
        #generatedImage {
            max-width: 100%;
            max-height: 500px;
            border-radius: 8px;
            display: none;
        }
        
        .loading {
            display: none;
            text-align: center;
            color: #4ecdc4;
        }
        
        .loading-spinner {
            border: 3px solid rgba(255, 255, 255, 0.1);
            border-top: 3px solid #4ecdc4;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
            margin: 0 auto 10px;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        .history {
            grid-column: 1 / -1;
            margin-top: 30px;
            background: rgba(255, 255, 255, 0.08);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 25px;
            border: 1px solid rgba(255, 255, 255, 0.15);
        }
        
        .history h3 {
            margin-bottom: 15px;
            color: #ff6b6b;
        }
        
        .history-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
            gap: 15px;
        }
        
        .history-item {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            overflow: hidden;
            cursor: pointer;
            transition: transform 0.2s;
        }
        
        .history-item:hover {
            transform: translateY(-5px);
        }
        
        .history-img {
            width: 100%;
            height: 150px;
            object-fit: cover;
        }
        
        .history-prompt {
            padding: 10px;
            font-size: 12px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        
        .status {
            padding: 10px;
            border-radius: 8px;
            margin-top: 15px;
            display: none;
        }
        
        .status.success {
            background: rgba(40, 167, 69, 0.2);
            border: 1px solid #28a745;
        }
        
        .status.error {
            background: rgba(220, 53, 69, 0.2);
            border: 1px solid #dc3545;
        }
        
        @media (max-width: 768px) {
            .container {
                grid-template-columns: 1fr;
            }
            .params-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🎨 千问图像生成16Bit (Qwen-Turbo-BF16)</h1>
            <p>基于 Qwen-Image-2512 与 Wuli-Art Turbo LoRA 的高性能图像生成系统</p>
            <div class="badges">
                <span class="badge version">Version 3.0</span>
                <span class="badge hardware">RTX 4090</span>
                <span class="badge precision">BFloat16</span>
                <span class="badge framework">Flask + PyTorch</span>
            </div>
        </div>
        
        <div class="input-panel">
            <h2>🖋️ 提示词输入</h2>
            <div class="form-group">
                <label for="prompt">正面提示词 (必填)</label>
                <textarea id="prompt" rows="4" placeholder="描述你想要生成的图像...">A beautiful sunset over mountains, digital art, masterpiece, 8k resolution</textarea>
            </div>
            
            <div class="form-group">
                <label for="negativePrompt">负面提示词 (可选)</label>
                <textarea id="negativePrompt" rows="2" placeholder="描述你不希望在图像中出现的内容...">blurry, low quality, watermark, text</textarea>
            </div>
            
            <div class="params-grid">
                <div class="form-group">
                    <label for="steps">采样步数</label>
                    <select id="steps">
                        <option value="4" selected>4步 (Turbo模式)</option>
                        <option value="8">8步</option>
                        <option value="12">12步</option>
                        <option value="20">20步</option>
                    </select>
                </div>
                
                <div class="form-group">
                    <label for="guidance">指导尺度</label>
                    <input type="number" id="guidance" value="1.8" step="0.1" min="1.0" max="10.0">
                </div>
                
                <div class="form-group">
                    <label for="width">图像宽度</label>
                    <select id="width">
                        <option value="512">512px</option>
                        <option value="768">768px</option>
                        <option value="1024" selected>1024px</option>
                    </select>
                </div>
                
                <div class="form-group">
                    <label for="height">图像高度</label>
                    <select id="height">
                        <option value="512">512px</option>
                        <option value="768">768px</option>
                        <option value="1024" selected>1024px</option>
                    </select>
                </div>
            </div>
            
            <button class="btn" onclick="generateImage()" id="generateBtn">
                🚀 生成图像 (4步极速)
            </button>
            
            <div class="loading" id="loading">
                <div class="loading-spinner"></div>
                <p>正在生成图像中... 通常需要10-30秒</p>
            </div>
            
            <div class="status" id="status"></div>
        </div>
        
        <div class="output-panel">
            <h2>🖼️ 生成结果</h2>
            <div class="image-container">
                <img id="generatedImage" alt="生成的图像">
                <p id="placeholder">图像将在这里显示</p>
            </div>
            
            <div class="form-group" style="margin-top: 20px;">
                <label>图像信息</label>
                <div id="imageInfo" style="padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px;">
                    等待生成...
                </div>
            </div>
            
            <button class="btn" onclick="downloadImage()" id="downloadBtn" style="display: none;">
                💾 下载图像
            </button>
        </div>
        
        <div class="history">
            <h3>📜 生成历史</h3>
            <div class="history-grid" id="historyGrid">
                <!-- 历史记录将通过JavaScript动态加载 -->
            </div>
        </div>
    </div>
    
    <script>
        let currentImageUrl = '';
        let currentFilename = '';
        
        async function generateImage() {
            const prompt = document.getElementById('prompt').value.trim();
            if (!prompt) {
                showStatus('请输入提示词', 'error');
                return;
            }
            
            // 显示加载状态
            document.getElementById('loading').style.display = 'block';
            document.getElementById('generateBtn').disabled = true;
            document.getElementById('status').style.display = 'none';
            
            const data = {
                prompt: prompt,
                negative_prompt: document.getElementById('negativePrompt').value,
                steps: parseInt(document.getElementById('steps').value),
                guidance_scale: parseFloat(document.getElementById('guidance').value),
                width: parseInt(document.getElementById('width').value),
                height: parseInt(document.getElementById('height').value)
            };
            
            try {
                const response = await fetch('/generate', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(data)
                });
                
                const result = await response.json();
                
                if (result.success) {
                    // 显示生成的图像
                    currentImageUrl = result.url;
                    currentFilename = result.filename;
                    
                    const img = document.getElementById('generatedImage');
                    img.src = result.url + '?t=' + new Date().getTime(); // 添加时间戳避免缓存
                    img.style.display = 'block';
                    document.getElementById('placeholder').style.display = 'none';
                    
                    // 显示图像信息
                    document.getElementById('imageInfo').innerHTML = `
                        <strong>提示词:</strong> ${result.history.prompt}<br>
                        <strong>时间:</strong> ${result.history.timestamp}<br>
                        <strong>参数:</strong> ${result.history.steps}步, 尺度${result.history.guidance_scale}, 尺寸${result.history.size}
                    `;
                    
                    // 显示下载按钮
                    document.getElementById('downloadBtn').style.display = 'block';
                    
                    // 更新历史记录
                    loadHistory();
                    
                    showStatus('图像生成成功!', 'success');
                } else {
                    showStatus('生成失败: ' + (result.error || '未知错误'), 'error');
                }
            } catch (error) {
                showStatus('请求失败: ' + error.message, 'error');
            } finally {
                // 隐藏加载状态
                document.getElementById('loading').style.display = 'none';
                document.getElementById('generateBtn').disabled = false;
            }
        }
        
        function downloadImage() {
            if (!currentImageUrl) return;
            
            const link = document.createElement('a');
            link.href = currentImageUrl;
            link.download = currentFilename || 'generated_image.jpg';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
        
        function showStatus(message, type) {
            const statusEl = document.getElementById('status');
            statusEl.textContent = message;
            statusEl.className = 'status ' + type;
            statusEl.style.display = 'block';
            
            // 3秒后自动隐藏
            setTimeout(() => {
                statusEl.style.display = 'none';
            }, 3000);
        }
        
        async function loadHistory() {
            try {
                const response = await fetch('/history');
                const history = await response.json();
                
                const historyGrid = document.getElementById('historyGrid');
                historyGrid.innerHTML = '';
                
                // 只显示最近9条记录
                const recentHistory = history.slice(-9).reverse();
                
                recentHistory.forEach(item => {
                    const historyItem = document.createElement('div');
                    historyItem.className = 'history-item';
                    historyItem.onclick = () => {
                        document.getElementById('prompt').value = item.prompt;
                        document.getElementById('negativePrompt').value = item.negative_prompt || '';
                    };
                    
                    historyItem.innerHTML = `
                        <img src="/generated/${item.filename}" class="history-img" alt="${item.prompt}">
                        <div class="history-prompt">${item.prompt.substring(0, 30)}${item.prompt.length > 30 ? '...' : ''}</div>
                    `;
                    
                    historyGrid.appendChild(historyItem);
                });
            } catch (error) {
                console.error('加载历史记录失败:', error);
            }
        }
        
        // 页面加载时获取历史记录
        document.addEventListener('DOMContentLoaded', loadHistory);
        
        // 示例提示词
        const examplePrompts = [
            "A futuristic cyberpunk city street at night, heavy rain, neon signs reflecting on wet ground, cinematic lighting",
            "A beautiful Chinese goddess in flowing silk hanfu, standing on a lotus leaf, ethereal atmosphere, golden sunset light",
            "Epic landscape of a floating castle above the clouds, giant waterfalls, dragons flying, cinematic scale",
            "Close-up portrait of an elderly craftsman with deep wrinkles, hyper-realistic skin texture, bokeh background"
        ];
        
        // 随机选择一个示例提示词
        document.getElementById('prompt').value = examplePrompts[Math.floor(Math.random() * examplePrompts.length)];
    </script>
</body>
</html>

这个界面采用了现代化的玻璃拟态设计,主要特点包括:

  1. 渐变背景和毛玻璃效果
  2. 响应式布局,适配不同屏幕尺寸
  3. 实时生成状态显示
  4. 生成历史记录浏览
  5. 一键下载功能

4. 部署与启动脚本

所有代码准备好后,我们需要创建启动脚本和配置文件,让部署更加简单。

4.1 创建启动脚本(start.sh)

为了方便启动服务,我们创建一个Shell脚本:

#!/bin/bash

# 千问图像生成16Bit启动脚本
echo "========================================"
echo "  千问图像生成16Bit (Qwen-Turbo-BF16)"
echo "========================================"
echo ""

# 检查Python环境
if ! command -v python3 &> /dev/null; then
    echo "❌ 未找到Python3,请先安装Python3.8或更高版本"
    exit 1
fi

# 检查CUDA
if ! command -v nvidia-smi &> /dev/null; then
    echo "⚠️  未检测到NVIDIA显卡驱动,将使用CPU模式(性能较差)"
    export CUDA_VISIBLE_DEVICES=""
else
    echo "✅ 检测到NVIDIA显卡"
    nvidia-smi --query-gpu=name,memory.total --format=csv
fi

# 检查虚拟环境
if [ ! -d "venv" ]; then
    echo "📦 创建Python虚拟环境..."
    python3 -m venv venv
fi

# 激活虚拟环境
echo "🔧 激活虚拟环境..."
source venv/bin/activate

# 安装依赖
echo "📥 安装Python依赖..."
pip install -r requirements.txt

# 检查模型文件
echo "🔍 检查模型文件..."
if [ ! -d "models/Qwen-Image-2512" ]; then
    echo "⚠️  未找到Qwen-Image-2512模型,请先下载模型文件"
    echo "运行: python download_models.py"
    read -p "是否现在下载?(y/n): " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        python download_models.py
    else
        echo "请手动下载模型文件后重试"
        exit 1
    fi
fi

if [ ! -d "models/Qwen-Turbo-LoRA" ]; then
    echo "⚠️  未找到Turbo LoRA模型,请先下载模型文件"
    echo "运行: python download_models.py"
    read -p "是否现在下载?(y/n): " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        python download_models.py
    else
        echo "请手动下载模型文件后重试"
        exit 1
    fi
fi

# 创建必要的目录
echo "📁 创建目录结构..."
mkdir -p static/generated
mkdir -p templates

# 启动服务
echo "🚀 启动图像生成服务..."
echo "访问地址: http://localhost:5000"
echo "按 Ctrl+C 停止服务"
echo ""

python app.py

# 服务停止后
deactivate
echo "服务已停止"

4.2 创建依赖文件(requirements.txt)

torch==2.1.0
torchvision==0.16.0
torchaudio==2.1.0
diffusers==0.24.0
transformers==4.36.0
accelerate==0.25.0
flask==3.0.0
pillow==10.1.0
huggingface-hub==0.19.4

4.3 创建模型下载脚本(download_models.py)

#!/usr/bin/env python3
"""
模型下载脚本
用于下载Qwen-Image-2512和Turbo LoRA模型
"""

import os
from huggingface_hub import snapshot_download
import sys

def download_model(repo_id, local_dir, description):
    """下载模型文件"""
    print(f"开始下载: {description}")
    print(f"仓库ID: {repo_id}")
    print(f"保存到: {local_dir}")
    print("-" * 50)
    
    try:
        snapshot_download(
            repo_id=repo_id,
            local_dir=local_dir,
            local_dir_use_symlinks=False,
            resume_download=True
        )
        print(f"✅ {description} 下载完成")
        return True
    except Exception as e:
        print(f"❌ {description} 下载失败: {e}")
        return False

def main():
    """主函数"""
    print("=" * 60)
    print("千问图像生成16Bit - 模型下载工具")
    print("=" * 60)
    print()
    
    # 创建模型目录
    os.makedirs("models", exist_ok=True)
    
    # 下载Qwen-Image-2512
    success1 = download_model(
        repo_id="Qwen/Qwen-Image-2512",
        local_dir="models/Qwen-Image-2512",
        description="Qwen-Image-2512 底座模型"
    )
    
    print()
    
    # 下载Turbo LoRA
    success2 = download_model(
        repo_id="Wuli-Art/Qwen-Image-2512-Turbo-LoRA",
        local_dir="models/Qwen-Turbo-LoRA",
        description="Wuli-Art Turbo LoRA"
    )
    
    print()
    print("=" * 60)
    
    if success1 and success2:
        print("🎉 所有模型下载完成!")
        print("现在可以运行: bash start.sh 启动服务")
    else:
        print("⚠️  部分模型下载失败,请检查网络连接后重试")
        print("或者手动下载模型文件到对应目录")
        sys.exit(1)

if __name__ == "__main__":
    main()

4.4 项目结构整理

最终的项目目录结构应该是这样的:

qwen-turbo-bf16/
├── app.py                    # Flask Web服务器
├── image_generator.py        # 图像生成引擎
├── download_models.py        # 模型下载脚本
├── start.sh                  # 启动脚本
├── requirements.txt          # Python依赖
├── models/                   # 模型文件目录
│   ├── Qwen-Image-2512/     # 底座模型
│   └── Qwen-Turbo-LoRA/     # Turbo LoRA
├── static/
│   └── generated/           # 生成的图像
├── templates/
│   └── index.html           # 前端界面
└── venv/                    # Python虚拟环境

5. 运行与使用指南

一切准备就绪后,让我们启动服务并开始使用。

5.1 启动服务

在项目根目录下,给启动脚本添加执行权限并运行:

# 添加执行权限
chmod +x start.sh

# 启动服务
./start.sh

脚本会自动执行以下步骤:

  1. 检查Python和CUDA环境
  2. 创建并激活虚拟环境
  3. 安装所有依赖包
  4. 检查模型文件(如果不存在会提示下载)
  5. 启动Flask服务器

看到以下输出表示启动成功:

🚀 启动图像生成服务...
访问地址: http://localhost:5000
按 Ctrl+C 停止服务

5.2 使用Web界面

打开浏览器,访问 http://localhost:5000,你会看到美观的生成界面。

基本使用步骤:

  1. 输入提示词:在"正面提示词"框中描述你想要生成的图像
  2. 设置参数(可选):
    • 负面提示词:描述不希望出现的内容
    • 采样步数:推荐使用4步(Turbo模式)
    • 指导尺度:控制生成与提示词的匹配程度,默认1.8
    • 图像尺寸:选择生成图像的大小
  3. 点击生成:点击"🚀 生成图像"按钮
  4. 查看结果:等待10-30秒,图像会显示在右侧
  5. 下载图像:点击"💾 下载图像"保存结果

5.3 提示词技巧

为了获得最佳效果,这里有一些提示词写作技巧:

基础结构:

[主体描述], [风格描述], [质量词], [技术参数]

示例:

  • 写实人像A portrait of a wise old man with wrinkles, detailed skin texture, studio lighting, 8k resolution, photorealistic
  • 动漫风格Anime girl with blue hair, wearing school uniform, cherry blossoms in background, anime style, masterpiece, vibrant colors
  • 风景摄影Mountain landscape at sunrise, misty valley, golden hour lighting, national geographic photo, 8k, ultra detailed

常用质量词:

  • masterpiece, best quality - 提升整体质量
  • 8k, ultra detailed - 增加细节
  • cinematic lighting - 电影感光照
  • sharp focus - 清晰对焦
  • professional photography - 专业摄影风格

5.4 高级功能

批量生成: 系统支持批量生成,你可以通过API一次性生成多张图像:

import requests
import json

# 批量生成API
url = "http://localhost:5000/batch_generate"
prompts = [
    "A beautiful sunset over mountains",
    "A cyberpunk city at night",
    "A fantasy castle in the clouds"
]

response = requests.post(url, json={"prompts": prompts})
results = response.json()

for result in results["results"]:
    print(f"提示词: {result['prompt']}")
    print(f"图像URL: http://localhost:5000{result['url']}")

API调用: 你也可以直接调用生成API,集成到自己的应用中:

import requests

def generate_via_api(prompt, negative_prompt="", steps=4):
    url = "http://localhost:5000/generate"
    data = {
        "prompt": prompt,
        "negative_prompt": negative_prompt,
        "steps": steps,
        "guidance_scale": 1.8,
        "width": 1024,
        "height": 1024
    }
    
    response = requests.post(url, json=data)
    return response.json()

# 使用示例
result = generate_via_api("A beautiful digital art of a forest")
if result["success"]:
    image_url = f"http://localhost:5000{result['url']}"
    print(f"图像生成成功: {image_url}")

6. 常见问题与优化建议

在部署和使用过程中,你可能会遇到一些问题。这里整理了一些常见问题的解决方法。

6.1 常见问题解答

Q: 启动时显示"CUDA不可用"怎么办? A: 检查以下几点:

  1. 确认已安装NVIDIA显卡驱动:nvidia-smi
  2. 确认已安装CUDA工具包:nvcc --version
  3. 确认PyTorch版本与CUDA版本匹配
  4. 如果确实没有GPU,系统会自动回退到CPU模式,但速度会很慢

Q: 模型下载太慢或失败怎么办? A: 可以尝试以下方法:

  1. 使用国内镜像源:修改download_models.py中的下载地址
  2. 手动下载:从Hugging Face网站直接下载模型文件,放到对应目录
  3. 使用代理:设置HTTP_PROXY环境变量

Q: 生成图像时显存不足怎么办? A: 系统已经内置了显存优化,但如果还是不足,可以:

  1. 减小生成图像尺寸(如从1024x1024降到768x768)
  2. 启用更激进的CPU卸载:在代码中启用enable_model_cpu_offload()
  3. 使用更低精度的VAE解码

Q: 生成的图像质量不理想怎么办? A: 尝试以下优化:

  1. 使用更详细的提示词,包含风格、质量、光照等描述
  2. 适当增加采样步数(如从4步增加到8步)
  3. 调整指导尺度(1.5-2.5之间尝试)
  4. 添加负面提示词排除不想要的内容

6.2 性能优化建议

针对RTX 4090的优化:

# 在image_generator.py中添加以下优化
def optimize_for_4090(pipe):
    """针对RTX 4090的优化设置"""
    # 启用TF32精度(RTX 30/40系列支持)
    torch.backends.cuda.matmul.allow_tf32 = True
    torch.backends.cudnn.allow_tf32 = True
    
    # 使用更快的注意力机制
    pipe.enable_xformers_memory_efficient_attention()
    
    # 启用VAE切片解码,进一步节省显存
    pipe.vae.enable_slicing()
    
    # 设置更快的调度器
    from diffusers import DPMSolverMultistepScheduler
    pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
    
    return pipe

批量生成优化: 如果你需要批量生成图像,可以修改代码启用批处理:

# 修改generate_multiple方法,启用批处理
def generate_batch(self, prompts, batch_size=2, **kwargs):
    """批量生成图像(批处理优化版)"""
    images = []
    for i in range(0, len(prompts), batch_size):
        batch_prompts = prompts[i:i+batch_size]
        print(f"生成批次 {i//batch_size + 1}/{(len(prompts)+batch_size-1)//batch_size}")
        
        # 批处理生成
        with torch.autocast("cuda"):
            batch_images = self.pipe(
                prompt=batch_prompts,
                num_inference_steps=kwargs.get('steps', 4),
                guidance_scale=kwargs.get('guidance_scale', 1.8),
                width=kwargs.get('width', 1024),
                height=kwargs.get('height', 1024)
            ).images
        
        images.extend(batch_images)
    
    return images

6.3 监控与日志

添加监控功能,帮助了解系统运行状态:

# 在app.py中添加监控端点
@app.route('/status')
def system_status():
    """系统状态监控"""
    import psutil
    import torch
    
    status = {
        "system": {
            "cpu_percent": psutil.cpu_percent(),
            "memory_percent": psutil.virtual_memory().percent,
            "disk_usage": psutil.disk_usage('/').percent
        },
        "gpu": {
            "available": torch.cuda.is_available(),
            "device_count": torch.cuda.device_count() if torch.cuda.is_available() else 0,
            "current_device": torch.cuda.get_device_name(0) if torch.cuda.is_available() else None,
            "memory_allocated": torch.cuda.memory_allocated(0) / 1024**3 if torch.cuda.is_available() else 0,
            "memory_reserved": torch.cuda.memory_reserved(0) / 1024**3 if torch.cuda.is_available() else 0
        },
        "service": {
            "total_generations": len(generation_history),
            "model_loaded": generator is not None
        }
    }
    
    return jsonify(status)

# 添加定时清理旧图像的功能
import schedule
import time
import threading
from datetime import datetime, timedelta

def cleanup_old_images():
    """清理7天前的生成图像"""
    cutoff_time = datetime.now() - timedelta(days=7)
    
    for filename in os.listdir(output_dir):
        filepath = os.path.join(output_dir, filename)
        if os.path.isfile(filepath):
            file_time = datetime.fromtimestamp(os.path.getmtime(filepath))
            if file_time < cutoff_time:
                os.remove(filepath)
                print(f"清理旧文件: {filename}")

# 在单独线程中运行定时任务
def run_scheduler():
    schedule.every().day.at("03:00").do(cleanup_old_images)
    while True:
        schedule.run_pending()
        time.sleep(60)

scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
scheduler_thread.start()

7. 总结

通过这篇教程,我们完整地部署了一个基于Qwen-Turbo-BF16的高性能图像生成系统。让我们回顾一下关键要点:

系统核心优势:

  1. 极速生成:4步Turbo采样,秒级出图
  2. 稳定可靠:BF16精度彻底解决黑图问题
  3. 显存友好:智能卸载策略,24GB显存轻松运行
  4. 界面美观:现代化设计,操作流畅

部署要点回顾:

  1. 环境准备:确保有RTX 4090等高性能显卡和足够的存储空间
  2. 模型下载:从Hugging Face获取Qwen-Image-2512和Turbo LoRA
  3. 代码配置:按照教程编写三个核心文件
  4. 启动运行:使用提供的脚本一键启动服务

使用技巧:

  1. 提示词要具体,包含主体、风格、质量描述
  2. 从4步采样开始,不满意再增加步数
  3. 指导尺度在1.5-2.5之间调整效果最佳
  4. 利用历史记录功能快速回溯和复用提示词

这个系统不仅适合个人使用,也可以作为企业级AI绘画应用的基础。你可以基于这个框架,添加更多功能,比如:

  • 用户系统和管理后台
  • 风格模板和提示词库
  • 高级编辑和后期处理
  • API服务商业化

最重要的是,通过BF16精度的使用,你获得了接近FP32的生成质量,同时保持了FP16的推理速度,这在需要高质量图像生成的场景中非常有价值。

现在,你可以开始创作属于自己的AI艺术作品了。从简单的风景到复杂的场景,从写实风格到艺术创作,这个系统都能帮你快速实现创意。记住,好的提示词是成功的一半,多尝试不同的描述组合,你会发现AI绘画的无限可能。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐