# app.py — HF Spaces Free (CPU), Hunyuan-MT 7B-fp8, đa ngôn ngữ, chia đoạn, UI + API import os, re from typing import List, Optional import gradio as gr import torch from transformers import AutoTokenizer, AutoModelForCausalLM # ===== Cấu hình ===== DEFAULT_MODEL = "tencent/Hunyuan-MT-7B-fp8" # đổi bằng env MODEL_NAME nếu muốn MODEL_NAME = os.getenv("MODEL_NAME", DEFAULT_MODEL) GEN_KW = dict( # tham số sinh nhẹ cho CPU max_new_tokens=256, top_k=20, top_p=0.6, repetition_penalty=1.05, temperature=0.7, do_sample=True, ) MAX_INPUT_TOKENS = int(os.getenv("MAX_INPUT_TOKENS", "800")) # giới hạn input mỗi mảnh # ===== Load tokenizer & model (fp8 bằng dict quantization_config) ===== tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) quant_cfg = {"quantization_method": "fp8", "ignore": []} # tránh lỗi ignore=None model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, trust_remote_code=True, quantization_config=quant_cfg, ) DEVICE = getattr(model, "device", torch.device("cpu")) # ===== Chuẩn hóa tên ngôn ngữ ===== LANG_ALIASES = { "vi": "Vietnamese", "vie": "Vietnamese", "vietnamese": "Vietnamese", "tiếng việt": "Vietnamese", "zh": "Chinese", "chi": "Chinese", "zho": "Chinese", "chinese": "Chinese", "tiếng trung": "Chinese", "hán ngữ": "Chinese", "mandarin": "Chinese", "en": "English", "eng": "English", "tiếng anh": "English", "english": "English", "ja": "Japanese", "jpn": "Japanese", "tiếng nhật": "Japanese", "japanese": "Japanese", "ko": "Korean", "kor": "Korean", "tiếng hàn": "Korean", "korean": "Korean", "fr": "French", "fra": "French", "fre": "French", "tiếng pháp": "French", "french": "French", "de": "German", "deu": "German", "ger": "German", "tiếng đức": "German", "german": "German", "es": "Spanish", "spa": "Spanish", "tiếng tây ban nha": "Spanish", "spanish": "Spanish", "th": "Thai", "tha": "Thai", "tiếng thái": "Thai", "thai": "Thai", "id": "Indonesian", "ind": "Indonesian", "tiếng indonesia": "Indonesian", "indonesian": "Indonesian", "ms": "Malay", "msa": "Malay", "tiếng malaysia": "Malay", "malay": "Malay", "pt": "Portuguese", "por": "Portuguese", "tiếng bồ đào nha": "Portuguese", "portuguese": "Portuguese", "ru": "Russian", "rus": "Russian", "tiếng nga": "Russian", "russian": "Russian", } LANG_CHOICES = sorted(set(LANG_ALIASES.values())) def norm_lang(s: Optional[str]) -> Optional[str]: if not s: return None k = s.strip().lower() return LANG_ALIASES.get(k, s.strip()) # ===== Chia văn bản theo token ===== def chunk_by_tokens(text: str, max_tokens: int) -> List[str]: text = text.strip() if not text: return [] rough = re.split(r"(?<=[\.!?。!?])\s+", text) chunks, buf = [], "" def tok_len(s: str) -> int: return tokenizer(s, add_special_tokens=False, return_length=True)["length"] for part in rough: cand = (buf + " " + part).strip() if buf else part if tok_len(cand) <= max_tokens: buf = cand else: if buf: chunks.append(buf); buf = "" if tok_len(part) <= max_tokens: buf = part else: ids = tokenizer(part, add_special_tokens=False)["input_ids"] for i in range(0, len(ids), max_tokens): piece = tokenizer.decode(ids[i:i+max_tokens], skip_special_tokens=True) if piece.strip(): chunks.append(piece.strip()) if buf: chunks.append(buf) return [c for c in chunks if c.strip()] # ===== Core translate (chat template) ===== @torch.inference_mode() def translate_text(text: str, target_lang: str, source_lang: Optional[str]=None) -> str: tgt = norm_lang(target_lang) or "Vietnamese" src = norm_lang(source_lang) sys_prompt = (f"Translate the following segment from {src} into {tgt}, without additional explanation." if src else f"Translate the following segment into {tgt}, without additional explanation.") outs = [] for piece in chunk_by_tokens(text, MAX_INPUT_TOKENS): msgs = [{"role":"user","content": f"{sys_prompt}\n\n{piece}"}] inputs = tokenizer.apply_chat_template(msgs, tokenize=True, add_generation_prompt=False, return_tensors="pt") out_ids = model.generate(inputs.to(DEVICE), **GEN_KW) outs.append(tokenizer.decode(out_ids[0], skip_special_tokens=True).strip()) return "\n".join(outs).strip() def translate_batch(texts: List[str], target_lang: str, source_lang: Optional[str]=None) -> List[str]: return [translate_text(t, target_lang, source_lang) for t in texts] # ===== Gradio UI + API ===== with gr.Blocks() as demo: gr.Markdown("## Hunyuan-MT 7B-fp8 — Multilingual Translation (HF Free CPU)\nChia đoạn theo token, UI + API (Gradio).") with gr.Tab("Single"): src = gr.Textbox(label="Văn bản nguồn", lines=10, placeholder="Dán văn bản cần dịch…") with gr.Row(): src_lang = gr.Textbox(label="Ngôn ngữ nguồn (tùy chọn)", placeholder="Ví dụ: Vietnamese/Chinese/English…") tgt_lang = gr.Dropdown(label="Ngôn ngữ đích", choices=LANG_CHOICES, value="Vietnamese") out = gr.Textbox(label="Bản dịch", lines=10) gr.Button("Dịch").click(translate_text, inputs=[src, tgt_lang, src_lang], outputs=out, api_name="translate_text") with gr.Tab("Batch"): src_list = gr.Textbox(label="Mỗi dòng 1 câu/đoạn", lines=10) with gr.Row(): src_lang_b = gr.Textbox(label="Ngôn ngữ nguồn (tùy chọn)") tgt_lang_b = gr.Dropdown(label="Ngôn ngữ đích", choices=LANG_CHOICES, value="Vietnamese") out_list = gr.Textbox(label="Kết quả (mỗi dòng tương ứng 1 đầu vào)", lines=10) def _batch(txts_raw: str, tgt: str, src_: Optional[str]): texts = [x for x in txts_raw.splitlines() if x.strip()] return "\n".join(translate_batch(texts, tgt, src_)) gr.Button("Dịch Batch").click(_batch, inputs=[src_list, tgt_lang_b, src_lang_b], outputs=out_list, api_name="translate_batch") demo.queue(concurrency_count=1, max_size=2).launch()