3v324v23 commited on
Commit
eb85d99
·
0 Parent(s):

Add MiniCPM5-1B inference space with Gradio UI and OpenAI-compatible API

Browse files
Files changed (4) hide show
  1. .gitignore +3 -0
  2. README.md +59 -0
  3. app.py +158 -0
  4. requirements.txt +7 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
README.md ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: MiniCPM5-1B
3
+ emoji: 🧠
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: custom
7
+ sdk_custom_command: uvicorn app:app --host 0.0.0.0 --port 7860
8
+ pinned: false
9
+ license: mit
10
+ short_description: MiniCPM5-1B inference with OpenAI-compatible API
11
+ ---
12
+
13
+ # MiniCPM5-1B Chat
14
+
15
+ MiniCPM5-1B inference service on Hugging Face Spaces.
16
+
17
+ ## Environment Variables (set in Space Secrets)
18
+
19
+ | Variable | Default | Description |
20
+ |----------|---------|-------------|
21
+ | `MODEL_ID` | `GnLOLot/MiniCPM5-1B-Claude-Opus-Fable5-V2-Thinking` | Hugging Face model ID |
22
+ | `API_KEY` | `wsh101007` | API key for OpenAI-compatible endpoints |
23
+ | `MAX_TOKENS` | `2048` | Maximum generation tokens |
24
+
25
+ ## API Usage
26
+
27
+ ### List Models
28
+ ```bash
29
+ curl -H "Authorization: Bearer wsh101007" https://{your-space}.hf.space/v1/models
30
+ ```
31
+
32
+ ### Chat Completion
33
+ ```bash
34
+ curl -X POST https://{your-space}.hf.space/v1/chat/completions \
35
+ -H "Authorization: Bearer wsh101007" \
36
+ -H "Content-Type: application/json" \
37
+ -d '{
38
+ "model": "minicpm5-1b",
39
+ "messages": [{"role": "user", "content": "Hello!"}],
40
+ "temperature": 0.7,
41
+ "max_tokens": 512
42
+ }'
43
+ ```
44
+
45
+ ### Python (OpenAI SDK)
46
+ ```python
47
+ from openai import OpenAI
48
+
49
+ client = OpenAI(
50
+ base_url="https://{your-space}.hf.space/v1",
51
+ api_key="wsh101007"
52
+ )
53
+
54
+ response = client.chat.completions.create(
55
+ model="minicpm5-1b",
56
+ messages=[{"role": "user", "content": "Hello!"}]
57
+ )
58
+ print(response.choices[0].message.content)
59
+ ```
app.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import json
4
+ import torch
5
+ from typing import Optional, List, AsyncGenerator
6
+ from fastapi import FastAPI, Request, HTTPException
7
+ from fastapi.responses import JSONResponse, StreamingResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from pydantic import BaseModel
10
+ from contextlib import asynccontextmanager
11
+ import gradio as gr
12
+ from transformers import AutoModelForCausalLM, AutoTokenizer
13
+
14
+ MODEL_ID = os.getenv("MODEL_ID", "GnLOLot/MiniCPM5-1B-Claude-Opus-Fable5-V2-Thinking")
15
+ API_KEY = os.getenv("API_KEY", "wsh101007")
16
+ MAX_TOKENS = int(os.getenv("MAX_TOKENS", "2048"))
17
+
18
+ model = None
19
+ tokenizer = None
20
+
21
+ def load_model():
22
+ global model, tokenizer
23
+ if model is not None:
24
+ return model, tokenizer
25
+ print(f"Loading model: {MODEL_ID}")
26
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
27
+ model = AutoModelForCausalLM.from_pretrained(
28
+ MODEL_ID,
29
+ torch_dtype=torch.bfloat16,
30
+ device_map="auto",
31
+ trust_remote_code=True
32
+ )
33
+ if tokenizer.pad_token is None:
34
+ tokenizer.pad_token = tokenizer.eos_token
35
+ print("Model loaded successfully")
36
+ return model, tokenizer
37
+
38
+ def verify_auth(request: Request):
39
+ auth = request.headers.get("Authorization", "")
40
+ if not auth.startswith("Bearer ") or auth[7:] != API_KEY:
41
+ raise HTTPException(status_code=401, detail="Invalid API key")
42
+
43
+ @asynccontextmanager
44
+ async def lifespan(app: FastAPI):
45
+ load_model()
46
+ yield
47
+
48
+ app = FastAPI(lifespan=lifespan, docs_url=None, redoc_url=None)
49
+
50
+ app.add_middleware(
51
+ CORSMiddleware,
52
+ allow_origins=["*"],
53
+ allow_methods=["*"],
54
+ allow_headers=["*"],
55
+ )
56
+
57
+ class ChatMessage(BaseModel):
58
+ role: str
59
+ content: str
60
+
61
+ class ChatCompletionRequest(BaseModel):
62
+ model: str = "minicpm5-1b"
63
+ messages: List[ChatMessage]
64
+ temperature: Optional[float] = 0.7
65
+ top_p: Optional[float] = 0.9
66
+ max_tokens: Optional[int] = None
67
+ stream: Optional[bool] = False
68
+
69
+ @app.get("/")
70
+ async def root():
71
+ return {"message": "MiniCPM5-1B API is running", "model": MODEL_ID}
72
+
73
+ @app.get("/v1/models")
74
+ async def list_models(request: Request):
75
+ verify_auth(request)
76
+ return {
77
+ "object": "list",
78
+ "data": [{
79
+ "id": "minicpm5-1b",
80
+ "object": "model",
81
+ "created": int(time.time()),
82
+ "owned_by": "user"
83
+ }]
84
+ }
85
+
86
+ @app.post("/v1/chat/completions")
87
+ async def chat_completions(request: Request, body: ChatCompletionRequest):
88
+ verify_auth(request)
89
+ m, tok = load_model()
90
+
91
+ messages = [{"role": msg.role, "content": msg.content} for msg in body.messages]
92
+ prompt = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
93
+ inputs = tok(prompt, return_tensors="pt").to(m.device)
94
+ prompt_len = inputs.input_ids.shape[1]
95
+
96
+ max_new_tokens = min(body.max_tokens or MAX_TOKENS, MAX_TOKENS)
97
+
98
+ with torch.no_grad():
99
+ outputs = m.generate(
100
+ **inputs,
101
+ max_new_tokens=max_new_tokens,
102
+ temperature=body.temperature,
103
+ top_p=body.top_p,
104
+ do_sample=body.temperature > 0,
105
+ pad_token_id=tok.pad_token_id,
106
+ )
107
+
108
+ response = tok.decode(outputs[0][prompt_len:], skip_special_tokens=True)
109
+
110
+ return {
111
+ "id": f"chatcmpl-{int(time.time())}",
112
+ "object": "chat.completion",
113
+ "created": int(time.time()),
114
+ "model": body.model,
115
+ "choices": [{
116
+ "index": 0,
117
+ "message": {"role": "assistant", "content": response.strip()},
118
+ "finish_reason": "stop"
119
+ }],
120
+ "usage": {
121
+ "prompt_tokens": prompt_len,
122
+ "completion_tokens": outputs.shape[1] - prompt_len,
123
+ "total_tokens": outputs.shape[1]
124
+ }
125
+ }
126
+
127
+ def chat_fn(message, history):
128
+ m, tok = load_model()
129
+ messages = []
130
+ for h in history:
131
+ messages.append({"role": "user", "content": h[0]})
132
+ messages.append({"role": "assistant", "content": h[1]})
133
+ messages.append({"role": "user", "content": message})
134
+
135
+ prompt = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
136
+ inputs = tok(prompt, return_tensors="pt").to(m.device)
137
+
138
+ with torch.no_grad():
139
+ outputs = m.generate(
140
+ **inputs,
141
+ max_new_tokens=512,
142
+ temperature=0.7,
143
+ top_p=0.9,
144
+ do_sample=True,
145
+ pad_token_id=tok.pad_token_id
146
+ )
147
+
148
+ return tok.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True).strip()
149
+
150
+ with gr.Blocks(title="MiniCPM5-1B Chat", theme=gr.themes.Soft()) as demo_ui:
151
+ gr.Markdown(f"# MiniCPM5-1B Chat\n**Model:** `{MODEL_ID}`")
152
+ gr.ChatInterface(
153
+ fn=chat_fn,
154
+ title=None,
155
+ description="Chat with the model. API available at `/v1/chat/completions` (requires Bearer token)."
156
+ )
157
+
158
+ app = gr.mount_gradio_app(app, demo_ui, path="/")
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio>=4.21.0,<5.0
2
+ fastapi>=0.100.0
3
+ uvicorn>=0.23.0
4
+ transformers>=4.36.0
5
+ accelerate>=0.25.0
6
+ torch>=2.1.0
7
+ sentencepiece>=0.1.99