jing-ju commited on
Commit
8577247
·
verified ·
1 Parent(s): f381b1b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -39
app.py CHANGED
@@ -1,12 +1,28 @@
1
  import os
 
 
 
 
2
  import gradio as gr
3
  from huggingface_hub import InferenceClient
4
 
5
- # -------- Settings --------
 
 
 
 
 
 
 
 
6
  DEFAULT_MODEL = os.getenv("HYMT_MODEL", "tencent/Hunyuan-MT-7B-fp8")
7
- HF_TOKEN = os.getenv("HF_TOKEN", None) # có thể để trống (ẩn danh, sẽ bị rate-limit)
8
 
9
- # Ngôn ngữ được model hỗ trợ (trích từ model card)
 
 
 
 
 
10
  LANGS = [
11
  ("Chinese (简体中文)", "zh"),
12
  ("Traditional Chinese (繁體中文)", "zh-Hant"),
@@ -47,29 +63,36 @@ LANGS = [
47
  ("Mongolian (Монгол)", "mn"),
48
  ("Uyghur (ئۇيغۇرچە)", "ug"),
49
  ]
50
-
51
  ZH_CODES = {"zh", "zh-Hant", "yue"}
52
 
 
53
  def build_prompt(src_lang: str, tgt_lang: str, text: str) -> str:
54
  """
55
- Theo gợi ý prompt trong model card:
56
- - ZH <=> XX: dùng template tiếng Trung
57
- - XX <=> XX (không ZH): dùng template tiếng Anh
58
  """
 
 
 
59
  if src_lang in ZH_CODES or tgt_lang in ZH_CODES:
60
- # Template ZH <=> XX
61
- return f"把下面的文本翻译成{tgt_lang},不要额外解释。\n\n{text.strip()}"
62
  else:
63
- # Template XX <=> XX (không ZH)
64
- return f"Translate the following segment into {tgt_lang}, without additional explanation.\n\n{text.strip()}"
65
 
 
 
 
66
  def call_hf_inference(model: str, prompt: str) -> str:
67
  """
68
- Gọi Serverless Inference API (text-generation).
69
- Không cần GPU trên Space. Có thể dùng ẩn danh hoặc set HF_TOKEN trong Secrets.
70
  """
 
 
71
  client = InferenceClient(token=HF_TOKEN)
72
- # Tham số khuyến nghị từ model card
73
  try:
74
  out = client.text_generation(
75
  model=model,
@@ -79,32 +102,69 @@ def call_hf_inference(model: str, prompt: str) -> str:
79
  top_p=0.6,
80
  repetition_penalty=1.05,
81
  stream=False,
82
- # truncate không bật để tránh cắt prompt
83
  )
84
- return out.strip()
85
  except Exception as e:
86
  return f"[Lỗi] Không thể gọi Inference API: {e}"
87
 
88
- def translate(text: str, src: str, tgt: str, model_choice: str):
 
89
  if not text or not text.strip():
90
  return "Vui lòng nhập nội dung cần dịch."
91
- if src == tgt:
 
 
92
  return text.strip()
 
 
 
 
 
 
 
 
 
 
93
 
94
- prompt = build_prompt(src, tgt, text)
95
- # Lưu ý: Hunyuan-MT là causal LM định hướng prompt, không yêu cầu định dạng chat đặc biệt
96
- result = call_hf_inference(model_choice, prompt)
97
- return result
98
 
99
- def ui():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  with gr.Blocks(title="Hunyuan-MT Translation (HF Inference API)", fill_height=True) as demo:
101
  gr.Markdown(
102
  """
103
  # Tencent Hunyuan-MT (Serverless)
104
  Chạy trên **Hugging Face Space (CPU free)** bằng **Serverless Inference API**.
105
- - Chọn mô hình `tencent/Hunyuan-MT-7B` hoặc `tencent/Hunyuan-MT-7B-fp8`.
106
  - Chọn ngôn ngữ nguồn/đích rồi bấm **Dịch**.
107
- > Gợi ý: vào *Settings → Repository secrets* thêm `HF_TOKEN` để tăng hạn mức.
108
  """
109
  )
110
 
@@ -119,31 +179,97 @@ def ui():
119
  )
120
 
121
  with gr.Row():
122
- src = gr.Dropdown(choices=[l for l, _ in LANGS], value="English (English)", label="Nguồn")
123
- tgt = gr.Dropdown(choices=[l for l, _ in LANGS], value="Vietnamese (Tiếng Việt)", label="Đích")
124
-
125
- # Map label -> code cho back-end
126
- label2code = {label: code for label, code in LANGS}
127
-
128
- def _on_translate(text, src_label, tgt_label, model_id):
129
- src_code = label2code[src_label]
130
- tgt_code = label2code[tgt_label]
131
- return translate(text, src_code, tgt_code, model_id)
132
 
133
  inp = gr.Textbox(label="Nội dung cần dịch", lines=8, placeholder="Nhập văn bản…")
134
- btn = gr.Button("Dịch", variant="primary")
135
  out = gr.Textbox(label="Kết quả", lines=8)
 
 
 
 
 
 
 
 
 
136
 
137
  btn.click(_on_translate, [inp, src, tgt, model_choice], [out])
138
 
139
  gr.Markdown(
140
  """
141
  #### Lưu ý
142
- - Đây demo qua **Serverless Inference API** nên tốc độ/phản hồi phụ thuộc hạn mức serverless.
143
- - Với lượng lớn/nhanh hơn, hãy nâng cấp phần cứng (GPU) hoặc tự triển khai TGI/vLLM.
144
  """
145
  )
146
  return demo
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  if __name__ == "__main__":
149
- ui().launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import time
3
+ from collections import defaultdict, deque
4
+ from typing import Tuple
5
+
6
  import gradio as gr
7
  from huggingface_hub import InferenceClient
8
 
9
+ # =========================
10
+ # CONFIG & AUTH
11
+ # =========================
12
+ # Lấy token từ biến môi trường (khuyên dùng: add tại Settings → Repository secrets)
13
+ # Nếu cần hardcode để test nhanh (KHÔNG an toàn repo public):
14
+ # HF_TOKEN = "hf_XXXXXXXXXXXXXXXXXXXXXXXX" # <--- DÁN TOKEN Ở ĐÂY (không khuyến nghị)
15
+ HF_TOKEN = os.getenv("HF_TOKEN", None)
16
+
17
+ # Model mặc định cho UI (có thể đổi qua dropdown)
18
  DEFAULT_MODEL = os.getenv("HYMT_MODEL", "tencent/Hunyuan-MT-7B-fp8")
 
19
 
20
+ # Model cố định cho API
21
+ FIXED_MODEL = "tencent/Hunyuan-MT-7B-fp8"
22
+
23
+ # =========================
24
+ # NGÔN NGỮ & PROMPT
25
+ # =========================
26
  LANGS = [
27
  ("Chinese (简体中文)", "zh"),
28
  ("Traditional Chinese (繁體中文)", "zh-Hant"),
 
63
  ("Mongolian (Монгол)", "mn"),
64
  ("Uyghur (ئۇيغۇرچە)", "ug"),
65
  ]
66
+ LABEL2CODE = {label: code for label, code in LANGS}
67
  ZH_CODES = {"zh", "zh-Hant", "yue"}
68
 
69
+
70
  def build_prompt(src_lang: str, tgt_lang: str, text: str) -> str:
71
  """
72
+ Prompt template tham khảo từ model card:
73
+ - Nếu tiếng Trung (zh/zh-Hant/yue) ở nguồn hoặc đích -> dùng template tiếng Trung
74
+ - Nếu không -> template tiếng Anh
75
  """
76
+ txt = (text or "").strip()
77
+ if not txt:
78
+ return ""
79
  if src_lang in ZH_CODES or tgt_lang in ZH_CODES:
80
+ return f"把下面的文本翻译成{tgt_lang},不要额外解释。\n\n{txt}"
 
81
  else:
82
+ return f"Translate the following segment into {tgt_lang}, without additional explanation.\n\n{txt}"
83
+
84
 
85
+ # =========================
86
+ # INFERENCE HELPER
87
+ # =========================
88
  def call_hf_inference(model: str, prompt: str) -> str:
89
  """
90
+ Gọi Hugging Face Serverless Inference API (text-generation).
91
+ Không cần GPU, phù hợp Space free.
92
  """
93
+ if not prompt:
94
+ return ""
95
  client = InferenceClient(token=HF_TOKEN)
 
96
  try:
97
  out = client.text_generation(
98
  model=model,
 
102
  top_p=0.6,
103
  repetition_penalty=1.05,
104
  stream=False,
 
105
  )
106
+ return (out or "").strip()
107
  except Exception as e:
108
  return f"[Lỗi] Không thể gọi Inference API: {e}"
109
 
110
+
111
+ def translate(text: str, src_code: str, tgt_code: str, model_choice: str) -> str:
112
  if not text or not text.strip():
113
  return "Vui lòng nhập nội dung cần dịch."
114
+ if not src_code or not tgt_code:
115
+ return "Thiếu mã ngôn ngữ nguồn/đích."
116
+ if src_code == tgt_code:
117
  return text.strip()
118
+ prompt = build_prompt(src_code, tgt_code, text)
119
+ return call_hf_inference(model_choice, prompt)
120
+
121
+
122
+ # =========================
123
+ # RATE LIMIT THEO IP (IN-MEMORY)
124
+ # =========================
125
+ # SỬA TẠI ĐÂY nếu muốn: tối đa bao nhiêu request / mỗi cửa sổ thời gian
126
+ RATE_WINDOW_SEC = int(os.getenv("RATE_WINDOW_SEC", "60")) # ví dụ: 60 giây
127
+ RATE_MAX_REQ = int(os.getenv("RATE_MAX_REQ", "10")) # ví dụ: 10 request / IP / 60s
128
 
129
+ _ip_buckets: dict[str, deque] = defaultdict(deque)
 
 
 
130
 
131
+
132
+ def _rate_limited(request: gr.Request) -> Tuple[bool, str]:
133
+ """
134
+ Trả (ok, msg). ok=False nếu vượt ngưỡng.
135
+ Lưu ý: in-memory -> reset khi container restart.
136
+ """
137
+ try:
138
+ ip = (request.client.host if request and request.client else "unknown") or "unknown"
139
+ except Exception:
140
+ ip = "unknown"
141
+
142
+ now = time.time()
143
+ dq = _ip_buckets[ip]
144
+
145
+ # Bỏ các dấu thời gian ngoài cửa sổ
146
+ while dq and (now - dq[0] > RATE_WINDOW_SEC):
147
+ dq.popleft()
148
+
149
+ if len(dq) >= RATE_MAX_REQ:
150
+ wait_sec = max(1, int(RATE_WINDOW_SEC - (now - dq[0])))
151
+ return False, f"Bạn đã vượt giới hạn {RATE_MAX_REQ} yêu cầu / {RATE_WINDOW_SEC}s. Hãy thử lại sau ~{wait_sec}s."
152
+ dq.append(now)
153
+ return True, ""
154
+
155
+
156
+ # =========================
157
+ # UI (GRADIO BLOCKS)
158
+ # =========================
159
+ def build_ui() -> gr.Blocks:
160
  with gr.Blocks(title="Hunyuan-MT Translation (HF Inference API)", fill_height=True) as demo:
161
  gr.Markdown(
162
  """
163
  # Tencent Hunyuan-MT (Serverless)
164
  Chạy trên **Hugging Face Space (CPU free)** bằng **Serverless Inference API**.
165
+ - Chọn mô hình `tencent/Hunyuan-MT-7B-fp8` (khuyến nghị) hoặc `tencent/Hunyuan-MT-7B`.
166
  - Chọn ngôn ngữ nguồn/đích rồi bấm **Dịch**.
167
+ > Mẹo: vào *Settings → Repository secrets* thêm `HF_TOKEN` để tăng hạn mức.
168
  """
169
  )
170
 
 
179
  )
180
 
181
  with gr.Row():
182
+ src = gr.Dropdown(
183
+ choices=[l for l, _ in LANGS],
184
+ value="English (English)",
185
+ label="Nguồn"
186
+ )
187
+ tgt = gr.Dropdown(
188
+ choices=[l for l, _ in LANGS],
189
+ value="Vietnamese (Tiếng Việt)",
190
+ label="Đích"
191
+ )
192
 
193
  inp = gr.Textbox(label="Nội dung cần dịch", lines=8, placeholder="Nhập văn bản…")
 
194
  out = gr.Textbox(label="Kết quả", lines=8)
195
+ btn = gr.Button("Dịch", variant="primary")
196
+
197
+ def _on_translate(text, src_label, tgt_label, model_id, request: gr.Request = None):
198
+ ok, msg = _rate_limited(request)
199
+ if not ok:
200
+ return msg
201
+ src_code = LABEL2CODE[src_label]
202
+ tgt_code = LABEL2CODE[tgt_label]
203
+ return translate(text, src_code, tgt_code, model_id)
204
 
205
  btn.click(_on_translate, [inp, src, tgt, model_choice], [out])
206
 
207
  gr.Markdown(
208
  """
209
  #### Lưu ý
210
+ - Demo gọi **Serverless Inference API** nên tốc độ phụ thuộc hạn mức.
211
+ - Cần throughput cao hơn? Hãy cân nhắc GPU hoặc tự triển khai TGI/vLLM.
212
  """
213
  )
214
  return demo
215
 
216
+
217
+ # =========================
218
+ # API CỐ ĐỊNH (MODEL KHÓA)
219
+ # =========================
220
+ def api_translate_fixed(text: str, src_code: str, tgt_code: str, request: gr.Request = None) -> str:
221
+ """
222
+ API cho website: nhận mã ngôn ngữ (vd: 'en', 'vi', 'zh', ...),
223
+ dùng model cố định Hunyuan-MT-7B-fp8.
224
+ """
225
+ ok, msg = _rate_limited(request)
226
+ if not ok:
227
+ return msg
228
+ if not text or not text.strip():
229
+ return ""
230
+ if not src_code or not tgt_code:
231
+ return "Thiếu mã ngôn ngữ nguồn/đích."
232
+ if src_code == tgt_code:
233
+ return text.strip()
234
+ prompt = build_prompt(src_code, tgt_code, text)
235
+ return call_hf_inference(FIXED_MODEL, prompt)
236
+
237
+
238
+ def build_api_interface() -> gr.Interface:
239
+ """
240
+ Tạo Interface riêng để có endpoint REST.
241
+ - Endpoint kiểu /run/<function_name> (nếu Gradio/HF hỗ trợ)
242
+ - Hoặc /run/predict với fn_index tương ứng
243
+ """
244
+ return gr.Interface(
245
+ fn=api_translate_fixed,
246
+ inputs=[
247
+ gr.Textbox(label="text"),
248
+ gr.Textbox(label="src_code"),
249
+ gr.Textbox(label="tgt_code"),
250
+ ],
251
+ outputs=gr.Textbox(label="translation"),
252
+ title="Hunyuan-MT Fixed API",
253
+ description="POST JSON tới endpoint để nhận bản dịch. Model cố định: tencent/Hunyuan-MT-7B-fp8."
254
+ )
255
+
256
+
257
+ # =========================
258
+ # MAIN
259
+ # =========================
260
  if __name__ == "__main__":
261
+ ui_app = build_ui()
262
+ api_iface = build_api_interface()
263
+
264
+ # G��p UI + API vào cùng server
265
+ demo = gr.TabbedInterface([ui_app, api_iface], tab_names=["App", "API"])
266
+
267
+ # Giới hạn đồng thời & hàng chờ (có thể chỉnh bằng biến môi trường)
268
+ # SỬA TẠI ĐÂY nếu muốn thay đổi:
269
+ CONCURRENCY = int(os.getenv("GRADIO_CONCURRENCY", "2")) # ví dụ: 2 job chạy song song
270
+ QUEUE_MAX = int(os.getenv("GRADIO_QUEUE_MAX", "20")) # ví dụ: 20 job có thể chờ
271
+
272
+ demo = demo.queue(concurrency_count=CONCURRENCY, max_size=QUEUE_MAX, status_update_rate=2)
273
+
274
+ # Bật REST API
275
+ demo.launch(enable_api=True)