yheye43 commited on
Commit
b33af53
·
verified ·
1 Parent(s): 8d7e765

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -0
app.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import time
3
+ import uuid
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
5
+ from fastapi import FastAPI, HTTPException
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+ from typing import List, Optional, Union, Dict, Any
9
+ import uvicorn
10
+
11
+ app = FastAPI()
12
+
13
+ # CORS設定
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["*"],
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+
22
+ # INT4量子化設定
23
+ quantization_config = BitsAndBytesConfig(
24
+ load_in_4bit=True,
25
+ bnb_4bit_compute_dtype=torch.float16,
26
+ bnb_4bit_use_double_quant=True,
27
+ bnb_4bit_quant_type="nf4"
28
+ )
29
+
30
+ # モデルとトークナイザーの初期化
31
+ model_name = "Qwen/Qwen3-235B-A22B"
32
+ model_id = "qwen3-235b-a22b-int4" # OpenAI API互換のモデルID
33
+
34
+ print(f"Loading model: {model_name}")
35
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
36
+ model = AutoModelForCausalLM.from_pretrained(
37
+ model_name,
38
+ quantization_config=quantization_config,
39
+ device_map="auto",
40
+ torch_dtype=torch.float16,
41
+ trust_remote_code=True
42
+ )
43
+ print("Model loaded successfully")
44
+
45
+ # OpenAI API互換のデータモデル
46
+ class ChatMessage(BaseModel):
47
+ role: str
48
+ content: str
49
+
50
+ class ChatCompletionRequest(BaseModel):
51
+ model: str
52
+ messages: List[ChatMessage]
53
+ temperature: Optional[float] = 0.7
54
+ top_p: Optional[float] = 0.9
55
+ max_tokens: Optional[int] = 512
56
+ stream: Optional[bool] = False
57
+
58
+ class ChatCompletionChoice(BaseModel):
59
+ index: int
60
+ message: ChatMessage
61
+ finish_reason: str
62
+
63
+ class ChatCompletionUsage(BaseModel):
64
+ prompt_tokens: int
65
+ completion_tokens: int
66
+ total_tokens: int
67
+
68
+ class ChatCompletionResponse(BaseModel):
69
+ id: str
70
+ object: str = "chat.completion"
71
+ created: int
72
+ model: str
73
+ choices: List[ChatCompletionChoice]
74
+ usage: ChatCompletionUsage
75
+
76
+ class ModelInfo(BaseModel):
77
+ id: str
78
+ object: str = "model"
79
+ created: int
80
+ owned_by: str = "huggingface"
81
+
82
+ class ModelsResponse(BaseModel):
83
+ object: str = "list"
84
+ data: List[ModelInfo]
85
+
86
+ # チャット形式のプロンプト構築
87
+ def build_chat_prompt(messages: List[ChatMessage]) -> str:
88
+ """Qwen形式のチャットプロンプトを構築"""
89
+ prompt = ""
90
+ for msg in messages:
91
+ if msg.role == "system":
92
+ prompt += f"System: {msg.content}\n\n"
93
+ elif msg.role == "user":
94
+ prompt += f"Human: {msg.content}\n\n"
95
+ elif msg.role == "assistant":
96
+ prompt += f"Assistant: {msg.content}\n\n"
97
+
98
+ # 最後にAssistantのプロンプトを追加
99
+ if messages[-1].role != "assistant":
100
+ prompt += "Assistant: "
101
+
102
+ return prompt
103
+
104
+ @app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
105
+ async def chat_completions(request: ChatCompletionRequest):
106
+ """OpenAI互換のチャット補完エンドポイント"""
107
+ try:
108
+ # プロンプト構築
109
+ prompt = build_chat_prompt(request.messages)
110
+
111
+ # トークナイズ
112
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
113
+ input_length = inputs["input_ids"].shape[1]
114
+
115
+ # 生成
116
+ with torch.no_grad():
117
+ outputs = model.generate(
118
+ **inputs,
119
+ max_new_tokens=request.max_tokens,
120
+ temperature=request.temperature,
121
+ top_p=request.top_p,
122
+ do_sample=True,
123
+ pad_token_id=tokenizer.eos_token_id
124
+ )
125
+
126
+ # デコード
127
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
128
+ # プロンプトを除去
129
+ response_text = generated_text[len(prompt):].strip()
130
+
131
+ # トークン数計算
132
+ output_length = outputs[0].shape[0] - input_length
133
+
134
+ # レスポンス構築
135
+ response = ChatCompletionResponse(
136
+ id=f"chatcmpl-{uuid.uuid4().hex[:8]}",
137
+ created=int(time.time()),
138
+ model=request.model,
139
+ choices=[
140
+ ChatCompletionChoice(
141
+ index=0,
142
+ message=ChatMessage(role="assistant", content=response_text),
143
+ finish_reason="stop"
144
+ )
145
+ ],
146
+ usage=ChatCompletionUsage(
147
+ prompt_tokens=input_length,
148
+ completion_tokens=output_length,
149
+ total_tokens=input_length + output_length
150
+ )
151
+ )
152
+
153
+ return response
154
+
155
+ except Exception as e:
156
+ raise HTTPException(status_code=500, detail=str(e))
157
+
158
+ @app.get("/v1/models", response_model=ModelsResponse)
159
+ async def list_models():
160
+ """利用可能なモデルのリストを返す"""
161
+ return ModelsResponse(
162
+ data=[
163
+ ModelInfo(
164
+ id=model_id,
165
+ created=int(time.time())
166
+ )
167
+ ]
168
+ )
169
+
170
+ @app.get("/health")
171
+ async def health():
172
+ return {
173
+ "status": "healthy",
174
+ "model": model_name,
175
+ "model_id": model_id,
176
+ "quantization": "INT4"
177
+ }
178
+
179
+ if __name__ == "__main__":
180
+ uvicorn.run(app, host="0.0.0.0", port=7860)