Toilatop1sever commited on
Commit
58615c9
·
verified ·
1 Parent(s): 0a73fa9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -36
app.py CHANGED
@@ -20,80 +20,119 @@ app.add_middleware(
20
  MODEL_REPO = "unsloth/Qwen3-4B-GGUF"
21
  MODEL_FILE = "Qwen3-4B-Q4_K_M.gguf"
22
 
23
- MAX_HISTORY = 4 # Giảm để tiết kiệm token xử lý
24
- MAX_CTX = 4096 # Khung ngữ cảnh tối đa
25
- MAX_TOKENS = 2048 # Giới hạn token sinh ra để giải phóng tài nguyên sớm
26
- THREADS = 2 # Số luồng CPU, khớp với vCPU bạn
 
 
 
 
 
 
 
 
 
27
 
28
  llm: Optional[Llama] = None
29
 
 
30
  @app.on_event("startup")
31
  async def startup_event():
32
  global llm
33
- # 1. Tải model (xóa file cũ nếu lỗi)
34
  if os.path.exists(MODEL_FILE) and os.path.getsize(MODEL_FILE) < 1_000_000:
35
  os.remove(MODEL_FILE)
 
36
  if not os.path.exists(MODEL_FILE):
37
  print(f"Downloading {MODEL_FILE}...")
38
  hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir=".")
39
  print("Download done!")
40
 
41
- # 2. Khởi tạo model với các tham số tối ưu cho CPU và RAM
42
- print("Loading model with RAM-optimized settings...")
43
  llm = Llama(
44
- model_path = MODEL_FILE,
45
- n_ctx = MAX_CTX,
46
- n_threads = THREADS,
47
- n_threads_batch = THREADS,
48
- n_batch = MAX_CTX, # Quan trọng: bằng với n_ctx để tối ưu cache
49
- n_ubatch = 512, # Kích thước micro-batch, cân bằng với n_batch
50
- n_gpu_layers = 0,
51
- verbose = False,
52
- use_mmap = False, # Tắt mmap, load toàn bộ model vào RAM
53
- use_mlock = True, # Khóa model trong RAM, tránh swap ra ổ cứng
54
- flash_attn = True, # Bật Flash Attention, giảm tải bộ nhớ
55
- cache_type_k = "q8_0", # Lượng tử hóa KV cache (Key) xuống 8-bit
56
- cache_type_v = "q8_0", # Lượng tử hóa KV cache (Value) xuống 8-bit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  )
58
- print("Model ready! All weights and caches are locked in RAM.")
 
59
 
60
  class Message(BaseModel):
61
  role: str
62
  content: str
63
 
 
64
  class ChatRequest(BaseModel):
65
  prompt: str
66
  history: List[Message] = []
67
- system_prompt: Optional[str] = None
68
  max_tokens: int = MAX_TOKENS
69
  temperature: float = 0.7
70
  top_p: float = 0.9
71
 
 
72
  def build_messages(req: ChatRequest) -> list:
73
- # System prompt ngắn gọn, tiết kiệm token
74
- system = req.system_prompt or "Bạn là trợ lý AI, trả lời bằng tiếng Việt ngắn gọn."
 
75
  msgs = [{"role": "system", "content": system}]
76
-
77
- # Chỉ giữ lại lịch sử tối đa MAX_HISTORY tin nhắn (mỗi lượt gồm user + assistant)
78
- recent_history = req.history[-(MAX_HISTORY * 2):]
79
- for msg in recent_history:
80
  if msg.role in ("user", "assistant") and msg.content.strip():
81
  if msgs[-1]["role"] != msg.role:
82
  msgs.append({"role": msg.role, "content": msg.content.strip()})
83
-
84
- # Tránh trùng lặp role user cuối
85
  if msgs[-1]["role"] == "user":
86
  msgs.pop()
87
  msgs.append({"role": "user", "content": req.prompt.strip()})
88
  return msgs
89
 
 
90
  @app.post("/chat")
91
  async def chat(req: ChatRequest):
92
  if llm is None:
93
  raise HTTPException(503, "Model chưa sẵn sàng, thử lại sau!")
94
  if not req.prompt.strip():
95
  raise HTTPException(400, "Prompt trống")
96
- if len(req.prompt) > 4000:
97
  raise HTTPException(400, "Prompt quá dài")
98
 
99
  messages = build_messages(req)
@@ -111,11 +150,11 @@ async def chat(req: ChatRequest):
111
  delta = chunk["choices"][0]["delta"].get("content", "")
112
  if delta:
113
  full += delta
114
- yield f"data: {json.dumps({'delta': delta})}\n\n"
115
  except Exception as e:
116
  yield f"data: {json.dumps({'delta': f'[Lỗi: {str(e)}]'})}\n\n"
117
  finally:
118
- print(f">> Done: {full[:80]}")
119
  yield "data: [DONE]\n\n"
120
 
121
  return StreamingResponse(
@@ -124,17 +163,20 @@ async def chat(req: ChatRequest):
124
  headers = {"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
125
  )
126
 
 
127
  @app.get("/")
128
  async def root():
129
  return {
130
- "status" : "ok" if llm else "loading",
131
- "model" : MODEL_FILE,
132
- "message" : "Model ready (RAM-optimized)!" if llm else "Model đang tải...",
133
  }
134
 
 
135
  @app.get("/health")
136
  async def health():
137
  return {"status": "healthy", "model_loaded": llm is not None}
138
 
 
139
  if __name__ == "__main__":
140
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
20
  MODEL_REPO = "unsloth/Qwen3-4B-GGUF"
21
  MODEL_FILE = "Qwen3-4B-Q4_K_M.gguf"
22
 
23
+ # ── Triết tối ưu ───────────────────────────────────────────────────────
24
+ # RAM 18GB dả → nhét hết vào RAM, dùng prefix cache để CPU
25
+ # không phải recompute system prompt mỗi request
26
+ # n_batch = 4096 (sweet spot) đủ để prefill nhanh mà không gây RAM spike
27
+ # ─────────────────────────────────────────────────────────────────────────
28
+ MAX_HISTORY = 6
29
+ MAX_CTX = 8192
30
+ MAX_TOKENS = 2048
31
+ THREADS = 2
32
+
33
+ # System prompt cố định — sẽ được cache sẵn vào KV cache lúc startup
34
+ # CPU chỉ tính 1 lần duy nhất, mọi request sau dùng lại cache này
35
+ DEFAULT_SYSTEM = "Bạn là trợ lý AI, trả lời bằng tiếng Việt ngắn gọn."
36
 
37
  llm: Optional[Llama] = None
38
 
39
+
40
  @app.on_event("startup")
41
  async def startup_event():
42
  global llm
43
+
44
  if os.path.exists(MODEL_FILE) and os.path.getsize(MODEL_FILE) < 1_000_000:
45
  os.remove(MODEL_FILE)
46
+
47
  if not os.path.exists(MODEL_FILE):
48
  print(f"Downloading {MODEL_FILE}...")
49
  hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir=".")
50
  print("Download done!")
51
 
52
+ print("Loading model RAM-heavy, CPU-light mode...")
 
53
  llm = Llama(
54
+ model_path = MODEL_FILE,
55
+
56
+ # ── Context & batch ───────────────────────────────────────────────
57
+ n_ctx = MAX_CTX,
58
+ n_batch = 512 , # Nhỏ vừa tay CPU: 2 vCPU không bị nghẹt khi prefill
59
+ n_ubatch = 512 , # Giữ nhỏ: ổn định hơn khi decode
60
+
61
+ # ── CPU ───────────────────────────────────────────────────────────
62
+ n_threads = THREADS,
63
+ n_threads_batch = THREADS,
64
+ n_gpu_layers = 0,
65
+
66
+ # ── RAM: load toàn bộ, khóa lại, không swap ──────────────────────
67
+ use_mmap = False,
68
+ use_mlock = True,
69
+
70
+ # ── KV Cache quantize — ăn RAM ít hơn, CPU vẫn nhẹ ───────────────
71
+ cache_type_k = "q4_0",
72
+ cache_type_v = "q4_0",
73
+
74
+ # ── Prefix cache: CPU tính system prompt 1 lần rồi thôi ──────────
75
+ last_n_tokens_size = 64, # Cửa sổ detect prefix trùng
76
+
77
+ flash_attn = True,
78
+ verbose = False,
79
+ )
80
+
81
+ # ── Warm up prefix cache với system prompt ────────────────────────────
82
+ # Gọi 1 lần lúc startup để KV cache của system prompt được lưu sẵn
83
+ # Mọi request sau có cùng system prompt → CPU bỏ qua phần này hoàn toàn
84
+ print("Warming up prefix cache...")
85
+ warmup_msgs = [
86
+ {"role": "system", "content": DEFAULT_SYSTEM},
87
+ {"role": "user", "content": "hi"},
88
+ ]
89
+ _ = llm.create_chat_completion(
90
+ messages = warmup_msgs,
91
+ max_tokens = 1,
92
+ stream = False,
93
  )
94
+ print("Prefix cache warmed up! Model ready.")
95
+
96
 
97
  class Message(BaseModel):
98
  role: str
99
  content: str
100
 
101
+
102
  class ChatRequest(BaseModel):
103
  prompt: str
104
  history: List[Message] = []
105
+ system_prompt: Optional[str] = None # Để None → tận dụng prefix cache
106
  max_tokens: int = MAX_TOKENS
107
  temperature: float = 0.7
108
  top_p: float = 0.9
109
 
110
+
111
  def build_messages(req: ChatRequest) -> list:
112
+ # Dùng DEFAULT_SYSTEM nếu không truyền system_prompt
113
+ # prefix cache luôn hit, CPU không recompute
114
+ system = req.system_prompt or DEFAULT_SYSTEM
115
  msgs = [{"role": "system", "content": system}]
116
+
117
+ recent = req.history[-(MAX_HISTORY * 2):]
118
+ for msg in recent:
 
119
  if msg.role in ("user", "assistant") and msg.content.strip():
120
  if msgs[-1]["role"] != msg.role:
121
  msgs.append({"role": msg.role, "content": msg.content.strip()})
122
+
 
123
  if msgs[-1]["role"] == "user":
124
  msgs.pop()
125
  msgs.append({"role": "user", "content": req.prompt.strip()})
126
  return msgs
127
 
128
+
129
  @app.post("/chat")
130
  async def chat(req: ChatRequest):
131
  if llm is None:
132
  raise HTTPException(503, "Model chưa sẵn sàng, thử lại sau!")
133
  if not req.prompt.strip():
134
  raise HTTPException(400, "Prompt trống")
135
+ if len(req.prompt) > 8000:
136
  raise HTTPException(400, "Prompt quá dài")
137
 
138
  messages = build_messages(req)
 
150
  delta = chunk["choices"][0]["delta"].get("content", "")
151
  if delta:
152
  full += delta
153
+ yield f"data: {json.dumps({'delta': delta}, ensure_ascii=False)}\n\n"
154
  except Exception as e:
155
  yield f"data: {json.dumps({'delta': f'[Lỗi: {str(e)}]'})}\n\n"
156
  finally:
157
+ print(f">> Done ({len(full)} chars): {full[:80]}")
158
  yield "data: [DONE]\n\n"
159
 
160
  return StreamingResponse(
 
163
  headers = {"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
164
  )
165
 
166
+
167
  @app.get("/")
168
  async def root():
169
  return {
170
+ "status" : "ok" if llm else "loading",
171
+ "model" : MODEL_FILE,
172
+ "message" : "Model ready (prefix cache active)!" if llm else "Model đang tải...",
173
  }
174
 
175
+
176
  @app.get("/health")
177
  async def health():
178
  return {"status": "healthy", "model_loaded": llm is not None}
179
 
180
+
181
  if __name__ == "__main__":
182
  uvicorn.run(app, host="0.0.0.0", port=7860)