TAI Research
Initial commit: Lumina_Dev_Legacy (archived)
29691f6
Raw
History Blame Contribute Delete
21.8 kB
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import JSONResponse, FileResponse, StreamingResponse
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
import torch
import io
from PIL import Image
import base64
import json
import time
from datetime import datetime
import asyncio
from concurrent.futures import ThreadPoolExecutor
import uuid
import os
from .sampler import TextToImagePipeline, SamplerFactory
from ..data.text_encoder import CLIPTextEncoderWrapper
# 请求/响应模型
class TextToImageRequest(BaseModel):
prompt: str
negative_prompt: Optional[str] = ""
width: int = 512
height: int = 512
num_steps: int = 50
guidance_scale: float = 7.5
num_images: int = 1
seed: Optional[int] = None
sampler: str = "ddim"
class ImageResponse(BaseModel):
images: List[str] # base64编码的图像
metadata: Dict[str, Any]
request_id: str
generation_time: float
class BatchRequest(BaseModel):
requests: List[TextToImageRequest]
priority: int = 0
class StatusResponse(BaseModel):
status: str
queue_length: int
active_tasks: int
gpu_memory_usage: float
uptime: float
# API应用
class LuminaAPI:
"""Lumina API服务器"""
def __init__(
self,
model,
diffusion,
text_encoder,
vae_decoder=None,
host: str = "0.0.0.0",
port: int = 8000,
max_queue_size: int = 100,
max_workers: int = 2
):
self.model = model
self.diffusion = diffusion
self.text_encoder = text_encoder
self.vae_decoder = vae_decoder
self.host = host
self.port = port
# 创建FastAPI应用
self.app = FastAPI(
title="Lumina Image Generation API",
description="轻量级图像生成模型API",
version="1.0.0"
)
# 任务队列
self.task_queue = asyncio.Queue(maxsize=max_queue_size)
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self.active_tasks = 0
# 请求历史
self.request_history = []
self.max_history = 1000
# 统计信息
self.start_time = time.time()
self.total_requests = 0
self.total_images = 0
# 初始化管道
self.pipeline = TextToImagePipeline(
model=model,
diffusion=diffusion,
text_encoder=text_encoder,
vae_decoder=vae_decoder,
sampler_type="ddim"
)
# 设置路由
self._setup_routes()
def _setup_routes(self):
"""设置API路由"""
@self.app.get("/")
async def root():
return {
"message": "Lumina Image Generation API",
"version": "1.0.0",
"docs": "/docs",
"endpoints": [
"/generate",
"/batch_generate",
"/status",
"/health"
]
}
@self.app.get("/health")
async def health_check():
"""健康检查"""
gpu_available = torch.cuda.is_available()
gpu_memory = torch.cuda.memory_allocated() / 1024**3 if gpu_available else 0
return {
"status": "healthy",
"gpu_available": gpu_available,
"gpu_memory_gb": gpu_memory,
"model_loaded": self.model is not None,
"text_encoder_loaded": self.text_encoder is not None
}
@self.app.get("/status")
async def get_status():
"""获取服务状态"""
uptime = time.time() - self.start_time
# GPU内存使用
if torch.cuda.is_available():
gpu_memory = torch.cuda.memory_allocated() / 1024**3
else:
gpu_memory = 0
return StatusResponse(
status="running",
queue_length=self.task_queue.qsize(),
active_tasks=self.active_tasks,
gpu_memory_usage=gpu_memory,
uptime=uptime
)
@self.app.post("/generate", response_model=ImageResponse)
async def generate_image(request: TextToImageRequest):
"""生成单个图像"""
request_id = str(uuid.uuid4())
# 记录请求
self.request_history.append({
"request_id": request_id,
"prompt": request.prompt,
"timestamp": datetime.now().isoformat()
})
# 限制历史记录大小
if len(self.request_history) > self.max_history:
self.request_history = self.request_history[-self.max_history:]
# 生成图像
start_time = time.time()
try:
# 在线程池中运行生成任务
loop = asyncio.get_event_loop()
images = await loop.run_in_executor(
self.executor,
self._generate_sync,
request
)
generation_time = time.time() - start_time
# 转换为base64
image_b64_list = []
for img_tensor in images:
if isinstance(img_tensor, torch.Tensor):
# 转换为PIL图像
if img_tensor.dim() == 4:
img_tensor = img_tensor.squeeze(0)
# 归一化到[0, 255]
img_tensor = torch.clamp(img_tensor * 255, 0, 255).byte()
# 转换为numpy数组
if img_tensor.shape[0] == 3: # CHW格式
img_array = img_tensor.permute(1, 2, 0).cpu().numpy()
else:
img_array = img_tensor.cpu().numpy()
# 转换为PIL图像
img = Image.fromarray(img_array)
# 转换为base64
buffered = io.BytesIO()
img.save(buffered, format="PNG")
img_b64 = base64.b64encode(buffered.getvalue()).decode()
image_b64_list.append(img_b64)
else:
image_b64_list.append("")
# 更新统计信息
self.total_requests += 1
self.total_images += len(images)
return ImageResponse(
images=image_b64_list,
metadata={
"prompt": request.prompt,
"negative_prompt": request.negative_prompt,
"width": request.width,
"height": request.height,
"num_steps": request.num_steps,
"guidance_scale": request.guidance_scale,
"seed": request.seed,
"sampler": request.sampler
},
request_id=request_id,
generation_time=generation_time
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/batch_generate")
async def batch_generate(batch_request: BatchRequest):
"""批量生成图像"""
request_ids = [str(uuid.uuid4()) for _ in batch_request.requests]
results = []
# 为每个请求生成任务
tasks = []
for req, req_id in zip(batch_request.requests, request_ids):
task = asyncio.create_task(self._process_single_request(req, req_id))
tasks.append(task)
# 等待所有任务完成
results = await asyncio.gather(*tasks, return_exceptions=True)
# 处理结果
successful_results = []
failed_results = []
for result in results:
if isinstance(result, Exception):
failed_results.append({"error": str(result)})
else:
successful_results.append(result)
return {
"successful": successful_results,
"failed": failed_results,
"total_requests": len(batch_request.requests),
"successful_count": len(successful_results),
"failed_count": len(failed_results)
}
@self.app.get("/history")
async def get_history(limit: int = 50):
"""获取请求历史"""
return self.request_history[-limit:]
@self.app.post("/txt2img") # Stable Diffusion兼容端点
async def txt2img(
prompt: str = Form(...),
negative_prompt: str = Form(""),
width: int = Form(512),
height: int = Form(512),
steps: int = Form(50),
cfg_scale: float = Form(7.5),
seed: int = Form(-1)
):
"""兼容Stable Diffusion WebUI的端点"""
request = TextToImageRequest(
prompt=prompt,
negative_prompt=negative_prompt,
width=width,
height=height,
num_steps=steps,
guidance_scale=cfg_scale,
seed=seed if seed != -1 else None
)
response = await generate_image(request)
# 返回第一个图像
if response.images:
return StreamingResponse(
io.BytesIO(base64.b64decode(response.images[0])),
media_type="image/png"
)
else:
raise HTTPException(status_code=500, detail="生成失败")
@self.app.get("/stats")
async def get_stats():
"""获取统计信息"""
uptime = time.time() - self.start_time
return {
"total_requests": self.total_requests,
"total_images": self.total_images,
"requests_per_minute": self.total_requests / (uptime / 60) if uptime > 0 else 0,
"avg_generation_time": None, # 可以添加计时逻辑
"uptime_seconds": uptime,
"queue_size": self.task_queue.qsize(),
"active_workers": self.active_tasks
}
def _generate_sync(self, request: TextToImageRequest) -> List[torch.Tensor]:
"""同步生成图像(在单独的线程中运行)"""
# 更新管道采样器
self.pipeline.sampler = SamplerFactory.create_sampler(
request.sampler,
self.model,
self.diffusion,
request.num_steps
)
# 生成图像
images = self.pipeline(
prompt=request.prompt,
negative_prompt=request.negative_prompt,
height=request.height,
width=request.width,
num_inference_steps=request.num_steps,
guidance_scale=request.guidance_scale,
num_images=request.num_images,
seed=request.seed,
progress_bar=False
)
return images
async def _process_single_request(self, request: TextToImageRequest, request_id: str) -> Dict:
"""处理单个请求"""
try:
# 在队列中添加任务
await self.task_queue.put((request, request_id))
# 更新活动任务计数
self.active_tasks += 1
# 处理任务
loop = asyncio.get_event_loop()
images = await loop.run_in_executor(
self.executor,
self._generate_sync,
request
)
# 转换图像
image_b64_list = []
for img_tensor in images:
# 简化的转换逻辑
img_b64 = "placeholder" # 实际应该转换为base64
image_b64_list.append(img_b64)
# 更新活动任务计数
self.active_tasks -= 1
return {
"request_id": request_id,
"images": image_b64_list,
"success": True
}
except Exception as e:
self.active_tasks -= 1
return {
"request_id": request_id,
"error": str(e),
"success": False
}
def run(self):
"""运行API服务器"""
import uvicorn
print(f"启动Lumina API服务器在 http://{self.host}:{self.port}")
print(f"API文档: http://{self.host}:{self.port}/docs")
uvicorn.run(
self.app,
host=self.host,
port=self.port,
log_level="info"
)
class SimpleWebUI:
"""简单的Web UI(使用Gradio)"""
def __init__(self, pipeline: TextToImagePipeline):
self.pipeline = pipeline
def create_interface(self):
"""创建Gradio界面"""
try:
import gradio as gr
except ImportError:
print("Gradio未安装,无法创建Web UI")
return None
def generate_image_ui(
prompt,
negative_prompt,
width,
height,
num_steps,
guidance_scale,
seed,
sampler
):
"""UI生成函数"""
# 设置种子
if seed and seed > 0:
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed(seed)
# 生成图像
images = self.pipeline(
prompt=prompt,
negative_prompt=negative_prompt,
height=height,
width=width,
num_inference_steps=num_steps,
guidance_scale=guidance_scale,
num_images=1,
seed=seed if seed > 0 else None,
progress_bar=False
)
# 转换为PIL图像
if images:
img_tensor = images[0]
if isinstance(img_tensor, torch.Tensor):
if img_tensor.dim() == 4:
img_tensor = img_tensor.squeeze(0)
# 归一化到[0, 255]
img_tensor = torch.clamp(img_tensor * 255, 0, 255).byte()
# 转换为numpy数组
if img_tensor.shape[0] == 3: # CHW格式
img_array = img_tensor.permute(1, 2, 0).cpu().numpy()
else:
img_array = img_tensor.cpu().numpy()
# 转换为PIL图像
from PIL import Image
img = Image.fromarray(img_array)
return img
return None
# 创建界面
with gr.Blocks(title="Lumina Image Generator") as interface:
gr.Markdown("# 🎨 Lumina - 轻量级图像生成")
gr.Markdown("基于扩散模型的文本到图像生成系统")
with gr.Row():
with gr.Column():
prompt = gr.Textbox(
label="提示词",
placeholder="输入描述图像的文本...",
lines=3
)
negative_prompt = gr.Textbox(
label="负面提示词",
placeholder="不想在图像中出现的内容...",
lines=2
)
with gr.Row():
width = gr.Slider(
minimum=256,
maximum=1024,
value=512,
step=64,
label="宽度"
)
height = gr.Slider(
minimum=256,
maximum=1024,
value=512,
step=64,
label="高度"
)
with gr.Row():
num_steps = gr.Slider(
minimum=1,
maximum=100,
value=30,
step=1,
label="采样步数"
)
guidance_scale = gr.Slider(
minimum=1.0,
maximum=20.0,
value=7.5,
step=0.5,
label="引导强度"
)
with gr.Row():
seed = gr.Number(
value=-1,
label="随机种子 (-1为随机)"
)
sampler = gr.Dropdown(
choices=["ddim", "dpm", "lcm"],
value="ddim",
label="采样器"
)
generate_btn = gr.Button("生成图像", variant="primary")
with gr.Column():
output_image = gr.Image(
label="生成的图像",
type="pil"
)
# 示例
gr.Markdown("### 示例提示词")
examples = gr.Examples(
examples=[
["A beautiful sunset over mountains, digital art", "", 512, 512, 30, 7.5, -1],
["A cute cat playing with a ball of yarn", "blurry, deformed", 512, 512, 25, 8.0, -1],
["An astronaut riding a horse on Mars", "cartoon, anime", 512, 512, 40, 7.0, -1]
],
inputs=[prompt, negative_prompt, width, height, num_steps, guidance_scale, seed]
)
# 事件处理
generate_btn.click(
fn=generate_image_ui,
inputs=[prompt, negative_prompt, width, height, num_steps, guidance_scale, seed, sampler],
outputs=output_image
)
return interface
def launch(self, share: bool = False, server_name: str = "0.0.0.0", server_port: int = 7860):
"""启动Web UI"""
interface = self.create_interface()
if interface:
interface.launch(
share=share,
server_name=server_name,
server_port=server_port
)
def create_api_server(config: dict, model, diffusion, text_encoder, vae_decoder=None):
"""创建API服务器"""
# 确定主机和端口
host = config.get('host', '0.0.0.0')
port = config.get('port', 8000)
# 创建API服务器
api_server = LuminaAPI(
model=model,
diffusion=diffusion,
text_encoder=text_encoder,
vae_decoder=vae_decoder,
host=host,
port=port,
max_queue_size=config.get('max_queue_size', 100),
max_workers=config.get('max_workers', 2)
)
return api_server
def test_api():
"""测试API"""
import torch.nn as nn
# 创建模拟组件
class MockModel(nn.Module):
def forward(self, x, t, context):
return torch.randn_like(x)
class MockDiffusion:
pass
class MockTextEncoder:
def encode(self, texts):
return torch.randn(len(texts), 77, 768)
model = MockModel()
diffusion = MockDiffusion()
text_encoder = MockTextEncoder()
# 创建API服务器
api = LuminaAPI(
model=model,
diffusion=diffusion,
text_encoder=text_encoder
)
print("API服务器创建成功")
print("端点:")
print(" POST /generate - 生成图像")
print(" GET /health - 健康检查")
print(" GET /status - 状态信息")
return api
if __name__ == '__main__':
# 测试API
api = test_api()
# 注意:实际运行需要调用 api.run()