TigreGotico/nos-coda_iacobus-en-es-onnx
nos-coda_iacobus-en-es — ONNX
ONNX export of `proxectonos/nos-coda_iacobus-en-es`, a English → Spanish translation model.
The original is an OpenNMT-py 3.2 checkpoint, which optimum-cli cannot read.
Parity with the original
Both the fp32 and int8 graphs were checked against onmt_translate at beam 4 on 15 sentences.
Conversion route
OpenNMT-py .pt → PegasusForConditionalGeneration → optimum-cli export onnx.
Pegasus is the HF architecture that matches OpenNMT-py's transformer: pre-norm blocks, a final encoder and decoder layer norm, no layernorm_embedding, a sinusoidal position table, and a final_logits_bias to carry OpenNMT's generator bias.
Two details are load-bearing:
- OpenNMT's position table is interleaved sin/cos, unlike Pegasus's
[sin | cos]. HF drops position weights on save and rebuilds them from its own formula, so the real table has to be force-persisted. - OpenNMT keeps separate source and target vocabularies. They are concatenated as
[target | source]; encoder input ids are offset by17864(= target vocab size) and the source half is masked out of the output distribution withfinal_logits_bias = -1e9.
Preprocessing (required)
These models do not ship an AutoTokenizer. As with the original, input must be Moses-tokenized and then BPE-segmented with the published subword-nmt codes (en_35k.code, included here).
import json, re, torch
from sacremoses import MosesTokenizer, MosesDetokenizer
from subword_nmt.apply_bpe import BPE
from optimum.onnxruntime import ORTModelForSeq2SeqLM
repo = "TigreGotico/nos-coda_iacobus-en-es-onnx"
model = ORTModelForSeq2SeqLM.from_pretrained(repo) # add subfolder="int8" for int8
meta = json.load(open("onmt_vocab.json")) # from this repo
bpe = BPE(open("en_35k.code", encoding="utf-8"))
mt, md = MosesTokenizer(lang="en"), MosesDetokenizer(lang="es")
src_ix = {t: i for i, t in enumerate(meta["source_vocab"])}
off, UNK, PAD = meta["source_offset"], 0, 1
def translate(text):
toks = bpe.process_line(" ".join(mt.tokenize(text, escape=False))).split()
ids = torch.tensor([[src_ix.get(t, UNK) + off for t in toks]])
out = model.generate(input_ids=ids,
attention_mask=torch.ones_like(ids),
num_beams=4, max_length=256)
pieces = [meta["target_vocab"][i] for i in out[0].tolist() if i not in (1, 2, 3)]
return md.detokenize(re.sub(r"@\s*", "", " ".join(pieces)).split())
print(translate('The children are playing in the garden.'))@@ is the subword-nmt continuation marker. Strip it with re.sub(r"@\s*", "", ...), which is the upstream sed 's/@\s*//g' rule — a plain replace("@@ ", "") is not equivalent and will corrupt words before punctuation.
<unk> is kept in the output rather than silently dropped, so you can see where the model failed. Expect some — the original onmt_translate emits <unk> in exactly the same places. Upstream hides them with -replace_unk, which copies the aligned source word using the decoder's cross-attention weights; that is not reproducible from an exported graph, so they are left visible here. Separately, the original was trained on text tokenized with Linguakit's tokenizer.pl, for which Moses is a close but not identical substitute.
Language codes
Single direction: en → es. No language tag is needed.
Files
encoder_model.onnx,decoder_model.onnx,decoder_with_past_model.onnx— fp32int8/— dynamically quantized (MatMul only, output projection excluded)onmt_vocab.json— source vocab, target vocab, id offseten_35k.code— subword-nmt BPE codes
Licence and attribution
MIT, unchanged from the original. The model was trained by Proxecto Nós (Universidade de Santiago de Compostela) with CODA, Faculdade de Letras, Universidade do Porto, funded through the ILENIA project (2022/TL22/00215336) and the IACOBUS programme. All credit for the model belongs to them; this repository only changes the serialization format.
