yheye43's picture
Create app.py
b33af53 verified
Raw
History Blame
5.18 kB
import torch
import time
import uuid
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional, Union, Dict, Any
import uvicorn
app = FastAPI()
# CORS設定
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# INT4量子化設定
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
# モデルとトークナイザーの初期化
model_name = "Qwen/Qwen3-235B-A22B"
model_id = "qwen3-235b-a22b-int4" # OpenAI API互換のモデルID
print(f"Loading model: {model_name}")
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=quantization_config,
device_map="auto",
torch_dtype=torch.float16,
trust_remote_code=True
)
print("Model loaded successfully")
# OpenAI API互換のデータモデル
class ChatMessage(BaseModel):
role: str
content: str
class ChatCompletionRequest(BaseModel):
model: str
messages: List[ChatMessage]
temperature: Optional[float] = 0.7
top_p: Optional[float] = 0.9
max_tokens: Optional[int] = 512
stream: Optional[bool] = False
class ChatCompletionChoice(BaseModel):
index: int
message: ChatMessage
finish_reason: str
class ChatCompletionUsage(BaseModel):
prompt_tokens: int
completion_tokens: int
total_tokens: int
class ChatCompletionResponse(BaseModel):
id: str
object: str = "chat.completion"
created: int
model: str
choices: List[ChatCompletionChoice]
usage: ChatCompletionUsage
class ModelInfo(BaseModel):
id: str
object: str = "model"
created: int
owned_by: str = "huggingface"
class ModelsResponse(BaseModel):
object: str = "list"
data: List[ModelInfo]
# チャット形式のプロンプト構築
def build_chat_prompt(messages: List[ChatMessage]) -> str:
"""Qwen形式のチャットプロンプトを構築"""
prompt = ""
for msg in messages:
if msg.role == "system":
prompt += f"System: {msg.content}\n\n"
elif msg.role == "user":
prompt += f"Human: {msg.content}\n\n"
elif msg.role == "assistant":
prompt += f"Assistant: {msg.content}\n\n"
# 最後にAssistantのプロンプトを追加
if messages[-1].role != "assistant":
prompt += "Assistant: "
return prompt
@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def chat_completions(request: ChatCompletionRequest):
"""OpenAI互換のチャット補完エンドポイント"""
try:
# プロンプト構築
prompt = build_chat_prompt(request.messages)
# トークナイズ
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
input_length = inputs["input_ids"].shape[1]
# 生成
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=request.max_tokens,
temperature=request.temperature,
top_p=request.top_p,
do_sample=True,
pad_token_id=tokenizer.eos_token_id
)
# デコード
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
# プロンプトを除去
response_text = generated_text[len(prompt):].strip()
# トークン数計算
output_length = outputs[0].shape[0] - input_length
# レスポンス構築
response = ChatCompletionResponse(
id=f"chatcmpl-{uuid.uuid4().hex[:8]}",
created=int(time.time()),
model=request.model,
choices=[
ChatCompletionChoice(
index=0,
message=ChatMessage(role="assistant", content=response_text),
finish_reason="stop"
)
],
usage=ChatCompletionUsage(
prompt_tokens=input_length,
completion_tokens=output_length,
total_tokens=input_length + output_length
)
)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/v1/models", response_model=ModelsResponse)
async def list_models():
"""利用可能なモデルのリストを返す"""
return ModelsResponse(
data=[
ModelInfo(
id=model_id,
created=int(time.time())
)
]
)
@app.get("/health")
async def health():
return {
"status": "healthy",
"model": model_name,
"model_id": model_id,
"quantization": "INT4"
}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)