| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import os |
| import torch |
| from fastapi import FastAPI, HTTPException, Request |
| from fastapi.responses import JSONResponse, HTMLResponse, FileResponse |
| from fastapi.staticfiles import StaticFiles |
| from pydantic import BaseModel, Field |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
| import uvicorn |
| import time |
|
|
| |
| |
| |
| MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct" |
|
|
| print("🚀 Loading model – this may take a minute …") |
| tokenizer = AutoTokenizer.from_pretrained( |
| MODEL_NAME, |
| trust_remote_code=True, |
| use_fast=True |
| ) |
|
|
| model = AutoModelForCausalLM.from_pretrained( |
| MODEL_NAME, |
| torch_dtype=torch.float16, |
| device_map="auto", |
| trust_remote_code=True |
| ) |
|
|
| |
| |
| |
| app = FastAPI( |
| title="Qwen2.5‑1.5B‑Instruct (Traditional Chinese) – OpenAI‑compatible", |
| description="Chat endpoint (`/v1/chat/completions`) + modern web UI (`/`).", |
| version="0.1.0" |
| ) |
|
|
| |
| app.mount("/static", StaticFiles(directory="static"), name="static") |
|
|
| |
| |
| |
| class Message(BaseModel): |
| role: str = Field(..., description="`system`, `user` or `assistant`") |
| content: str |
|
|
| class ChatRequest(BaseModel): |
| model: str = Field(default="qwen2.5-1.5b-instruct") |
| messages: list[Message] |
| temperature: float = Field(default=0.7, ge=0.0, le=2.0) |
| max_tokens: int = Field(default=512, ge=1, le=2048) |
| top_p: float = Field(default=0.9, ge=0.0, le=1.0) |
| stream: bool = Field(default=False) |
|
|
| class Choice(BaseModel): |
| index: int = 0 |
| message: Message |
| finish_reason: str = "stop" |
|
|
| class Usage(BaseModel): |
| prompt_tokens: int |
| completion_tokens: int |
| total_tokens: int |
|
|
| class ChatResponse(BaseModel): |
| id: str = "chatcmpl-123" |
| object: str = "chat.completion" |
| created: int = Field(default_factory=lambda: int(time.time())) |
| model: str |
| choices: list[Choice] |
| usage: Usage |
|
|
| |
| |
| |
| def _generate(request: ChatRequest) -> str: |
| |
| msgs = [{"role": m.role, "content": m.content} for m in request.messages] |
|
|
| |
| prompt = tokenizer.apply_chat_template( |
| msgs, |
| tokenize=False, |
| add_generation_prompt=True |
| ) |
|
|
| |
| inputs = tokenizer([prompt], return_tensors="pt").to(model.device) |
|
|
| |
| with torch.no_grad(): |
| output_ids = model.generate( |
| **inputs, |
| max_new_tokens=request.max_tokens, |
| temperature=request.temperature, |
| top_p=request.top_p, |
| do_sample=True, |
| repetition_penalty=1.1, |
| pad_token_id=tokenizer.eos_token_id, |
| ) |
|
|
| |
| generated = output_ids[0][len(inputs.input_ids[0]):] |
| text = tokenizer.decode(generated, skip_special_tokens=True) |
| return text |
|
|
| |
| |
| |
| @app.post("/v1/chat/completions", response_model=ChatResponse) |
| async def chat_completions(req: ChatRequest): |
| try: |
| answer = _generate(req) |
|
|
| |
| prompt_tokens = len(tokenizer.encode(req.messages[-1].content)) |
| completion_tokens = len(tokenizer.encode(answer)) |
| total_tokens = prompt_tokens + completion_tokens |
|
|
| response = ChatResponse( |
| model=req.model, |
| choices=[ |
| Choice( |
| index=0, |
| message=Message(role="assistant", content=answer), |
| finish_reason="stop" |
| ) |
| ], |
| usage=Usage( |
| prompt_tokens=prompt_tokens, |
| completion_tokens=completion_tokens, |
| total_tokens=total_tokens, |
| ), |
| ) |
| return response |
| except Exception as exc: |
| raise HTTPException(status_code=500, detail=str(exc)) |
|
|
| |
| |
| |
| @app.get("/", response_class=HTMLResponse) |
| async def index(): |
| |
| with open(os.path.join("static", "index.html"), encoding="utf-8") as f: |
| return HTMLResponse(f.read()) |
|
|
| |
| |
| |
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|