🎯 本文能帮你解决什么?
✅ 把本地AI绘画能力包装成标准REST API
✅ 用ollama自动优化用户输入的中文提示词(告别手动写英文长句)
✅ 基于diffusers加载dreamshaper8-lcm模型,生成速度快、质量高
✅ 配置CORS,让同局域网下的手机、平板、电视都能调用你的服务
📌 主要内容脉络
1️⃣ 为什么我要自己搭这套服务(需求与背景)
2️⃣ 选型思路:FastAPI + ollama + diffusers + dreamshaper8-lcm
3️⃣ 一步步动手实现(附完整代码片段)
4️⃣ 避坑指南 & 进阶优化建议
🔍 第一部分:问题与背景
之前我一直用在线AI绘画平台,但团队内部频繁使用,既担心数据隐私,又心疼费用。后来想:不如用自己的机器跑开源模型,然后做成API给前端调用?
需求很明确:
▪️ 接口要简单:前端传一句“一只猫在太空里弹吉他”,后端自动生成图片。
▪️ 提示词要智能:用户说人话就行,后台帮我优化成适合Stable Diffusion的英文描述。
▪️ 速度要快:等太久体验就崩了,所以需要支持快速生成模型。
▪️ 局域网全设备可用:手机、平板、甚至同事的Mac都能直接访问。
所以技术栈就这么定了:FastAPI(高性能API框架)+ ollama(本地运行大模型优化提示词)+ diffusers(加载dreamshaper8-lcm模型)。LCM(Latent Consistency Model)配合DreamShaper,生成一张512x512的图只需要2-3步迭代,速度起飞!
⚙️ 第二部分:核心原理/步骤
🧠 ollama优化提示词
ollama可以本地运行各种大模型,我用的是qwen2.5:7b(中文理解强)。原理很简单:写一个系统提示,让大模型把用户输入的中文描述扩写成详细的英文提示词。比如用户输入“小猫”,模型会输出“A cute fluffy kitten playing with a ball of yarn, soft lighting, photorealistic, 8k”。
🖼️ diffusers生成图片
HuggingFace的diffusers库配合dreamshaper8-lcm模型(这是微调过的版本,支持LCM采样),再用LCM-LoRA加速,几步就能出图。注意要下载到本地,避免每次重复拉取。
🌐 FastAPI + CORS
FastAPI天然支持异步,性能好。CORS中间件配置允许所有来源(开发时),或者指定局域网IP段,这样任何设备都能跨域请求。
🛠️ 第三部分:实战演示(代码可直接用)
别急着复制,先看我标注的红字警告,都是我自己踩过的坑!
1️⃣ 安装依赖
uv add fastapi uvicorn diffusers transformers accelerate torch ollama pillow python-multipart注意:torch最好根据你的CUDA版本安装,否则可能跑在CPU上慢死。
2️⃣ 编写ollama提示词优化函数
import ollama def optimize_prompt(user_input: str) -> str: system_prompt = """你是一个AI绘画提示词优化专家。将用户输入的中文描述转化为详细、高质量的英文提示词,适合Stable Diffusion使用。包含主体、细节、风格、光线等。只输出英文提示词,不要解释。""" response = ollama.chat(model='qwen2.5:7b', messages=[ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': user_input} ]) return response['message']['content'].strip()⚠️ 第一次运行ollama会拉取模型,确保网络通畅,大概4GB左右,耐心等。
3️⃣ diffusers图像生成函数
import torch from diffusers import AutoPipelineForText2Image from diffusers import LCMScheduler # 加载模型(第一次会自动下载,之后从缓存加载) pipe = AutoPipelineForText2Image.from_pretrained( "Lykon/dreamshaper-8-lcm", torch_dtype=torch.float16, variant="fp16", safety_checker=None # 为了速度可以禁用安全检查 ) pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config) pipe.to("cuda") def generate_image(prompt: str, steps: int = 4) -> bytes: image = pipe( prompt=prompt, num_inference_steps=steps, guidance_scale=1.0, # LCM模型通常guidance scale较低 width=512, height=512 ).images[0] # 直接返回图片字节数据 from io import BytesIO img_byte_arr = BytesIO() image.save(img_byte_arr, format='PNG') return img_byte_arr.getvalue()🔥 踩坑提醒:别忘了设置safety_checker=None,否则可能会卡在审核环节。还有guidance_scale要调低,1.0左右就行,太高反而模糊。
4️⃣ FastAPI应用 + CORS配置
from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from fastapi.responses import Response app = FastAPI() # 允许所有局域网设备访问(开发用) app.add_middleware( CORSMiddleware, allow_origins=["*"], # 上线前建议改为具体IP allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class PromptRequest(BaseModel): prompt: str steps: int = 4 @app.post("/generate") async def generate(request: PromptRequest): try: # 1. 优化提示词 enhanced_prompt = optimize_prompt(request.prompt) print(f"优化后提示词: {enhanced_prompt}") # 日志方便调试 # 2. 生成图片 img_bytes = generate_image(enhanced_prompt, request.steps) return Response(content=img_bytes, media_type="image/png") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)🚨 注意:host必须设为"0.0.0.0",才能监听所有网络接口,让局域网其他设备访问。
5️⃣ 测试你的接口
运行python main.py,然后在同一局域网下的手机或另一台电脑上执行:
curl -X POST "http://你的电脑IP:8000/generate" \ -H "Content-Type: application/json" \ -d '{"prompt": "一只戴眼镜的柴犬程序员在敲代码", "steps": 4}' \ --output test.png如果返回一张图片,恭喜你,成功了!
🧨 第四部分:注意事项与进阶思考
⚠️ 安全警告:上面的代码直接开放了所有来源的CORS,并且没有做任何认证。如果你的局域网有外人,或者你一不小心暴露到公网,就会被滥用。至少加个简单的令牌验证,比如在Header里校验一个预共享的密钥。
其他容易翻车的点:
▪️显存占用:dreamshaper8-lcm加载后大概占用4GB显存,生成时还会临时增加。如果你的卡只有4GB,可能爆显存。可以尝试用CPU模式(慢)或者量化模型。
▪️ollama超时:大模型推理需要时间,建议给ollama调用设置超时(默认是无限的)。如果并发高,可以考虑用异步请求或缓存常用提示词。
▪️并发处理:目前的代码是串行的,多个请求同时进来会排队。可以用FastAPI的BackgroundTasks或者消息队列优化,但小心显存冲突。
▪️路径不要写死:模型下载默认在~/.cache/huggingface,如果磁盘不够,可以设置环境变量HF_HOME指定路径。
我自己踩过最痛的坑是:第一次运行时忘了把模型放到GPU,结果CPU跑了半小时一张图都没出来…… 所以一定要检查pipe.to("cuda")是否生效。