sadidft commited on
Commit
5e2bd43
·
verified ·
1 Parent(s): a75e005

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +78 -82
main.py CHANGED
@@ -14,56 +14,57 @@ from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStream
14
  import torch
15
 
16
  # ==========================================
17
- # OPTIMASI CORE (2 vCPU / 16GB RAM)
18
  # ==========================================
19
  torch.set_num_threads(1)
20
  inference_pool = ThreadPoolExecutor(max_workers=2)
21
  ml_models = {}
22
 
23
- # Konfigurasi Identitas
24
- MODEL_ID = "trico-a-522m"
25
- MODEL_NAME = "Trico A 522m"
26
  POWERED_BY = "SFTNexus"
27
 
28
  @asynccontextmanager
29
  async def lifespan(app: FastAPI):
30
- print(f"🚀 [STARTUP] Memuat {MODEL_NAME} by {POWERED_BY}...")
31
-
32
- # Path model disesuaikan dengan input Anda
33
- tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
34
  with torch.no_grad():
35
  model = AutoModelForCausalLM.from_pretrained(
36
- MODEL_ID,
37
  torch_dtype=torch.float32,
38
  low_cpu_mem_usage=True
39
  ).eval()
40
-
41
  ml_models["tokenizer"] = tokenizer
42
  ml_models["model"] = model
43
- print(f"✅ [STARTUP] {MODEL_NAME} siap di Hugging Face Spaces!")
44
  yield
45
  ml_models.clear()
46
  inference_pool.shutdown(wait=True)
47
 
48
  # ==========================================
49
- # AUTHENTIKASI & SCHEMA OPENAI
50
  # ==========================================
51
  VALID_API_KEYS = set(os.getenv("APIKEY", "sk-default").split(","))
52
  api_key_header = APIKeyHeader(name="Authorization", auto_error=True)
53
 
54
  def verify_api_key(auth_header: str = Security(api_key_header)):
55
  # Mendukung format "Bearer sk-..."
56
- token = auth_header.replace("Bearer ", "") if "Bearer " in auth_header else auth_header
57
  if token not in VALID_API_KEYS:
58
  raise HTTPException(status_code=401, detail="Invalid API Key")
59
  return token
60
 
 
 
 
 
 
61
  class ChatMessage(BaseModel):
62
  role: str
63
  content: str
64
 
65
  class ChatCompletionRequest(BaseModel):
66
- model: str = MODEL_ID
67
  messages: List[ChatMessage]
68
  temperature: Optional[float] = 0.7
69
  top_p: Optional[float] = 0.9
@@ -71,117 +72,112 @@ class ChatCompletionRequest(BaseModel):
71
  stream: Optional[bool] = False
72
 
73
  # ==========================================
74
- # UTILS & PROMPT ENGINEERING
75
  # ==========================================
76
- def format_chat_to_alpaca(messages: List[ChatMessage]) -> str:
77
- """Mengonversi chat history ke format instruksi agar model tidak 'ngaco'."""
78
- prompt = f"System: Anda adalah {MODEL_NAME}, asisten AI native Indonesia yang handal. Powered by {POWERED_BY}.\n\n"
79
- for msg in messages:
80
- if msg.role == "user":
81
- prompt += f"### Instruksi:\n{msg.content}\n\n"
82
- elif msg.role == "assistant":
83
- prompt += f"### Respons:\n{msg.content}\n\n"
84
- prompt += "### Respons:\n"
85
- return prompt
86
 
87
- async def openai_stream_generator(request_id: str, prompt: str, params: dict):
 
 
 
88
  tokenizer = ml_models["tokenizer"]
89
  model = ml_models["model"]
 
 
90
 
 
91
  inputs = tokenizer([prompt], return_tensors="pt")
92
  streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
93
 
94
  gen_kwargs = dict(
95
  **inputs,
96
  streamer=streamer,
97
- max_new_tokens=params["max_tokens"],
98
- temperature=params["temperature"],
99
- top_p=params["top_p"],
100
- do_sample=params["temperature"] > 0,
101
  pad_token_id=tokenizer.eos_token_id
102
  )
103
 
104
  loop = asyncio.get_running_loop()
105
  loop.run_in_executor(inference_pool, lambda: model.generate(**gen_kwargs))
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- created_time = int(time.time())
108
- for chunk in streamer:
109
- if chunk:
110
- data = {
111
- "id": request_id,
112
- "object": "chat.completion.chunk",
113
- "created": created_time,
114
- "model": MODEL_ID,
115
- "choices": [{"index": 0, "delta": {"content": chunk}, "finish_reason": None}]
116
- }
117
- yield f"data: {json.dumps(data)}\n\n"
118
- await asyncio.sleep(0.01)
119
-
120
  yield "data: [DONE]\n\n"
121
 
122
  # ==========================================
123
  # ENDPOINTS
124
  # ==========================================
125
- app = FastAPI(title=f"{MODEL_NAME} API", lifespan=lifespan)
126
-
127
  @app.get("/health")
128
  async def health():
129
- return {"status": "ready", "model": MODEL_NAME, "provider": POWERED_BY}
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  @app.post("/v1/chat/completions")
132
- @app.post("/generate") # Menjaga kompatibilitas request sebelumnya
133
- async def chat_completions(req: ChatCompletionRequest, api_key: str = Depends(verify_api_key)):
134
- prompt = format_chat_to_alpaca(req.messages)
135
- request_id = f"chatcmpl-{uuid.uuid4()}"
136
-
137
- # Mode Streaming (SSE)
138
- if req.stream:
139
- return StreamingResponse(
140
- openai_stream_generator(request_id, prompt, req.model_dump()),
141
- media_type="text/event-stream"
142
- )
143
 
144
- # Mode Non-Streaming
145
  tokenizer = ml_models["tokenizer"]
146
  model = ml_models["model"]
147
-
148
  inputs = tokenizer([prompt], return_tensors="pt")
149
- loop = asyncio.get_running_loop()
150
 
151
- output_tokens = await loop.run_in_executor(
 
152
  inference_pool,
153
  lambda: model.generate(
154
  **inputs,
155
- max_new_tokens=req.max_tokens,
156
- temperature=req.temperature,
157
- top_p=req.top_p,
158
- do_sample=req.temperature > 0,
159
  pad_token_id=tokenizer.eos_token_id
160
  )
161
  )
162
 
163
- full_text = tokenizer.decode(output_tokens[0], skip_special_tokens=True)
164
- # Ambil hanya bagian setelah Respons terakhir
165
  response_text = full_text.split("### Respons:\n")[-1].strip()
166
 
167
  return {
168
- "id": request_id,
169
  "object": "chat.completion",
170
  "created": int(time.time()),
171
- "model": MODEL_ID,
172
  "choices": [{
173
- "index": 0,
174
  "message": {"role": "assistant", "content": response_text},
175
- "finish_reason": "stop"
 
176
  }],
177
- "usage": {
178
- "prompt_tokens": len(inputs['input_ids'][0]),
179
- "completion_tokens": len(output_tokens[0]) - len(inputs['input_ids'][0]),
180
- "total_tokens": len(output_tokens[0])
181
- },
182
- "system_fingerprint": POWERED_BY
183
- }
184
-
185
- if __name__ == "__main__":
186
- import uvicorn
187
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
14
  import torch
15
 
16
  # ==========================================
17
+ # KONFIGURASI & OPTIMASI CPU
18
  # ==========================================
19
  torch.set_num_threads(1)
20
  inference_pool = ThreadPoolExecutor(max_workers=2)
21
  ml_models = {}
22
 
23
+ # Rebranding Info
24
+ REAL_MODEL_ID = "abdiharyadi/cahya-gpt2-large-indonesian-522M-instruct"
25
+ DISPLAY_MODEL_ID = "trico-a-522m"
26
  POWERED_BY = "SFTNexus"
27
 
28
  @asynccontextmanager
29
  async def lifespan(app: FastAPI):
30
+ print(f"🚀 [STARTUP] Memuat {DISPLAY_MODEL_ID} (Powered by {POWERED_BY})...")
31
+ tokenizer = AutoTokenizer.from_pretrained(REAL_MODEL_ID)
 
 
32
  with torch.no_grad():
33
  model = AutoModelForCausalLM.from_pretrained(
34
+ REAL_MODEL_ID,
35
  torch_dtype=torch.float32,
36
  low_cpu_mem_usage=True
37
  ).eval()
 
38
  ml_models["tokenizer"] = tokenizer
39
  ml_models["model"] = model
 
40
  yield
41
  ml_models.clear()
42
  inference_pool.shutdown(wait=True)
43
 
44
  # ==========================================
45
+ # AUTHENTICATION
46
  # ==========================================
47
  VALID_API_KEYS = set(os.getenv("APIKEY", "sk-default").split(","))
48
  api_key_header = APIKeyHeader(name="Authorization", auto_error=True)
49
 
50
  def verify_api_key(auth_header: str = Security(api_key_header)):
51
  # Mendukung format "Bearer sk-..."
52
+ token = auth_header.replace("Bearer ", "")
53
  if token not in VALID_API_KEYS:
54
  raise HTTPException(status_code=401, detail="Invalid API Key")
55
  return token
56
 
57
+ app = FastAPI(title=f"Trico A API", lifespan=lifespan)
58
+
59
+ # ==========================================
60
+ # OPENAI COMPATIBLE SCHEMAS
61
+ # ==========================================
62
  class ChatMessage(BaseModel):
63
  role: str
64
  content: str
65
 
66
  class ChatCompletionRequest(BaseModel):
67
+ model: str = DISPLAY_MODEL_ID
68
  messages: List[ChatMessage]
69
  temperature: Optional[float] = 0.7
70
  top_p: Optional[float] = 0.9
 
72
  stream: Optional[bool] = False
73
 
74
  # ==========================================
75
+ # PROMPT ENGINEERING (CHAT TO ALPACA)
76
  # ==========================================
77
+ def format_openai_to_alpaca(messages: List[ChatMessage]) -> str:
78
+ # Mengambil pesan terakhir sebagai instruksi, sisanya sebagai context jika ada
79
+ instruction = messages[-1].content
80
+ return (
81
+ "Di bawah ini adalah instruksi yang menjelaskan suatu tugas. "
82
+ "Tuliskan respons yang melengkapi permintaan tersebut dengan tepat.\n\n"
83
+ f"### Instruksi:\n{instruction}\n\n### Respons:\n"
84
+ )
 
 
85
 
86
+ # ==========================================
87
+ # CORE INFERENCE (STREAMING & NON-STREAMING)
88
+ # ==========================================
89
+ async def chat_generator(request: ChatCompletionRequest):
90
  tokenizer = ml_models["tokenizer"]
91
  model = ml_models["model"]
92
+ created_time = int(time.time())
93
+ completion_id = f"chatcmpl-{uuid.uuid4()}"
94
 
95
+ prompt = format_openai_to_alpaca(request.messages)
96
  inputs = tokenizer([prompt], return_tensors="pt")
97
  streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
98
 
99
  gen_kwargs = dict(
100
  **inputs,
101
  streamer=streamer,
102
+ max_new_tokens=request.max_tokens,
103
+ temperature=request.temperature,
104
+ top_p=request.top_p,
105
+ do_sample=request.temperature > 0,
106
  pad_token_id=tokenizer.eos_token_id
107
  )
108
 
109
  loop = asyncio.get_running_loop()
110
  loop.run_in_executor(inference_pool, lambda: model.generate(**gen_kwargs))
111
+
112
+ for token in streamer:
113
+ chunk = {
114
+ "id": completion_id,
115
+ "object": "chat.completion.chunk",
116
+ "created": created_time,
117
+ "model": DISPLAY_MODEL_ID,
118
+ "system_fingerprint": f"powered_by_{POWERED_BY}",
119
+ "choices": [{"index": 0, "delta": {"content": token}, "finish_reason": None}]
120
+ }
121
+ yield f"data: {json.dumps(chunk)}\n\n"
122
+ await asyncio.sleep(0.01)
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  yield "data: [DONE]\n\n"
125
 
126
  # ==========================================
127
  # ENDPOINTS
128
  # ==========================================
 
 
129
  @app.get("/health")
130
  async def health():
131
+ return {"status": "ok", "model": DISPLAY_MODEL_ID, "provider": POWERED_BY}
132
+
133
+ @app.get("/v1/models")
134
+ async def list_models():
135
+ return {
136
+ "object": "list",
137
+ "data": [{
138
+ "id": DISPLAY_MODEL_ID,
139
+ "object": "model",
140
+ "created": 1700000000,
141
+ "owned_by": POWERED_BY
142
+ }]
143
+ }
144
 
145
  @app.post("/v1/chat/completions")
146
+ async def chat_completions(request: ChatCompletionRequest, _ = Depends(verify_api_key)):
147
+ if request.stream:
148
+ return StreamingResponse(chat_generator(request), media_type="text/event-stream")
 
 
 
 
 
 
 
 
149
 
150
+ # Non-streaming Logic
151
  tokenizer = ml_models["tokenizer"]
152
  model = ml_models["model"]
153
+ prompt = format_openai_to_alpaca(request.messages)
154
  inputs = tokenizer([prompt], return_tensors="pt")
 
155
 
156
+ loop = asyncio.get_running_loop()
157
+ output_ids = await loop.run_in_executor(
158
  inference_pool,
159
  lambda: model.generate(
160
  **inputs,
161
+ max_new_tokens=request.max_tokens,
162
+ temperature=request.temperature,
163
+ do_sample=request.temperature > 0,
 
164
  pad_token_id=tokenizer.eos_token_id
165
  )
166
  )
167
 
168
+ full_text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
169
+ # Ambil hanya bagian setelah "### Respons:"
170
  response_text = full_text.split("### Respons:\n")[-1].strip()
171
 
172
  return {
173
+ "id": f"chatcmpl-{uuid.uuid4()}",
174
  "object": "chat.completion",
175
  "created": int(time.time()),
176
+ "model": DISPLAY_MODEL_ID,
177
  "choices": [{
 
178
  "message": {"role": "assistant", "content": response_text},
179
+ "finish_reason": "stop",
180
+ "index": 0
181
  }],
182
+ "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0} # Placeholder
183
+ }