Download python/opus_mt_ax.py from HY-2012/Opus-MT.AXERA: direct link, hf CLI and curl.
- Browser
- Download file 5.18 kB
-
https://huggingface.co/HY-2012/Opus-MT.AXERA/resolve/main/python/opus_mt_ax.py
- Command line
-
hf download hf://HY-2012/Opus-MT.AXERA/python/opus_mt_ax.py
-
curl -L -o opus_mt_ax.py https://huggingface.co/HY-2012/Opus-MT.AXERA/resolve/main/python/opus_mt_ax.py
5.18 kB
| #!/usr/bin/env python3 | |
| """OPUS-MT (Marian) translation on AX650, sentencepiece-only tokenizer.""" | |
| import json | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| import sentencepiece as spm | |
| DEFAULT_PREFIX = ">>cmn_Hans<< " | |
| class Tokenizer: | |
| def __init__(self, model_dir): | |
| d = Path(model_dir) | |
| self.piece_to_id = json.loads((d / "vocab.json").read_text(encoding="utf-8")) | |
| self.id_to_piece = {v: k for k, v in self.piece_to_id.items()} | |
| self.spm_src = spm.SentencePieceProcessor(model_file=str(d / "source.spm")) | |
| self.spm_tgt = spm.SentencePieceProcessor(model_file=str(d / "target.spm")) | |
| cfg = json.loads((d / "model_meta.json").read_text(encoding="utf-8")) | |
| self.eos = int(cfg["eos_token_id"]) | |
| self.pad = int(cfg["pad_token_id"]) | |
| self.start = int(cfg["decoder_start_token_id"]) | |
| self.src_len = int(cfg["src_len"]) | |
| self.tgt_len = int(cfg["tgt_len"]) | |
| self.unk = self.piece_to_id.get("<unk>", 1) | |
| self.special = {self.eos, self.pad, self.unk} | |
| def encode(self, text): | |
| code = None | |
| rest = text | |
| if text.startswith(">>"): | |
| end = text.find("<<") | |
| if end != -1: | |
| code = text[: end + 2] | |
| rest = text[end + 2 :] | |
| pieces = ([code] if code else []) + self.spm_src.encode(rest, out_type=str) | |
| ids = [self.piece_to_id.get(p, self.unk) for p in pieces] | |
| truncated = len(ids) > self.src_len - 1 | |
| ids = ids[: self.src_len - 1] + [self.eos] | |
| return ids, truncated | |
| def decode(self, ids): | |
| pieces = [] | |
| for i in ids: | |
| i = int(i) | |
| if i in self.special: | |
| continue | |
| p = self.id_to_piece.get(i) | |
| if p is None or (p.startswith(">>") and p.endswith("<<")): | |
| continue | |
| pieces.append(p) | |
| return self.spm_src.decode_pieces(pieces).replace("\u2581", " ").strip() | |
| class Translator: | |
| def __init__(self, direction, model_root=None, max_new_tokens=None): | |
| if direction not in ("zh-en", "en-zh"): | |
| raise ValueError("direction must be zh-en or en-zh") | |
| self.direction = direction | |
| root = Path(model_root) if model_root else Path(__file__).resolve().parents[1] / "models" | |
| self.model_dir = Path(root) / direction | |
| self.tok = Tokenizer(self.model_dir) | |
| import axengine as axe | |
| self.enc = axe.InferenceSession(str(self.model_dir / "encoder.axmodel")) | |
| self.dec = axe.InferenceSession(str(self.model_dir / "decoder.axmodel")) | |
| self.max_new_tokens = int(max_new_tokens or self.tok.tgt_len) | |
| self.warmup() | |
| def _prepare(self, text): | |
| if self.direction == "en-zh" and not text.startswith(">>"): | |
| text = DEFAULT_PREFIX + text | |
| ids, truncated = self.tok.encode(text) | |
| n = len(ids) | |
| x = np.full((1, self.tok.src_len), self.tok.pad, dtype=np.int32) | |
| x[0, :n] = ids | |
| m = np.zeros((1, self.tok.src_len), dtype=np.float32) | |
| m[0, :n] = 1.0 | |
| return x, m, truncated | |
| def warmup(self): | |
| for _ in range(2): | |
| self.translate("你好" if self.direction == "zh-en" else "Hello") | |
| def translate(self, text, return_stats=False): | |
| if not text.strip(): | |
| return ("", {"src_tokens": 0, "out_tokens": 0, "enc_ms": 0.0, | |
| "dec_ms": 0.0, "dec_step_ms": 0.0, "truncated": False}) if return_stats else "" | |
| x, m, truncated = self._prepare(text) | |
| t0 = time.perf_counter() | |
| hidden = self.enc.run(None, {"input_ids": x, "attention_mask": m})[0] | |
| t_enc = time.perf_counter() - t0 | |
| out = [self.tok.start] | |
| dec_times = [] | |
| for _ in range(self.max_new_tokens): | |
| t = len(out) | |
| dec = np.full((1, self.tok.tgt_len), self.tok.pad, dtype=np.int32) | |
| dec[0, self.tok.tgt_len - t :] = out | |
| dm = np.zeros((1, self.tok.tgt_len), dtype=np.int32) | |
| dm[0, self.tok.tgt_len - t :] = 1 | |
| pos = np.zeros((1, self.tok.tgt_len), dtype=np.int32) | |
| pos[0, self.tok.tgt_len - t :] = np.arange(t, dtype=np.int32) | |
| t0 = time.perf_counter() | |
| logits = self.dec.run(None, { | |
| "decoder_input_ids": dec, | |
| "decoder_attention_mask": dm, | |
| "position_ids": pos, | |
| "encoder_hidden_states": hidden, | |
| "encoder_attention_mask": m, | |
| })[0][0, 0].astype(np.float32) | |
| dec_times.append(time.perf_counter() - t0) | |
| logits[self.tok.pad] = -np.inf | |
| nxt = int(np.argmax(logits)) | |
| out.append(nxt) | |
| if nxt == self.tok.eos: | |
| break | |
| result = self.tok.decode(out[1:]) | |
| if not return_stats: | |
| return result | |
| return result, { | |
| "src_tokens": int(m.sum()), | |
| "out_tokens": len(out) - 1, | |
| "enc_ms": t_enc * 1000, | |
| "dec_ms": float(np.sum(dec_times)) * 1000, | |
| "dec_step_ms": float(np.mean(dec_times)) * 1000 if dec_times else 0.0, | |
| "truncated": truncated, | |
| } | |