{"cells": [{"cell_type": "code", "execution_count": null, "id": "f924fa39", "metadata": {}, "outputs": [], "source": "\"\"\"\nAkkadian Inference \u2014 godgod3/akkadian-new-processing ByT5-large model\n  - eval geo_mean: 41.54 (BLEU 31.5, chrF 54.8) on competition data\n  - ByT5-large (d_model=1536, 18 encoder + 6 decoder layers)\n  - IMPORTANT: keep Unicode diacritics in output (reference translations use them!)\n  - Only strip double-quotes to avoid CSV evaluator crash\n\"\"\"\nimport os, re, glob, torch, pandas as pd\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\n\nprint(f'CUDA: {torch.cuda.is_available()}')\nif torch.cuda.is_available():\n    print(f'GPU:  {torch.cuda.get_device_name(0)}')\n    print(f'VRAM: {torch.cuda.get_device_properties(0).total_memory/1e9:.1f}GB')\n\n# \u2500\u2500 Find competition data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfor _try in [\n    '/kaggle/input/deep-past-initiative-machine-translation',\n    '/kaggle/input/competitions/deep-past-initiative-machine-translation',\n]:\n    if os.path.exists(os.path.join(_try, 'test.csv')):\n        COMP_DIR = _try\n        break\nelse:\n    hits = glob.glob('/kaggle/input/**/test.csv', recursive=True)\n    if hits:\n        COMP_DIR = os.path.dirname(hits[0])\n    else:\n        raise FileNotFoundError('test.csv not found under /kaggle/input')\n\nprint(f'Competition data: {COMP_DIR}')\n\n# \u2500\u2500 Find model \u2014 godgod3/akkadian-new-processing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nMODEL_DIR = None\nsearch_paths = [\n    '/kaggle/input/akkadian-new-processing/byt5-akkadian-final',\n    '/kaggle/input/godgod3/akkadian-new-processing/byt5-akkadian-final',\n]\nfor p in search_paths:\n    if os.path.exists(os.path.join(p, 'config.json')):\n        MODEL_DIR = p\n        break\n\nif MODEL_DIR is None:\n    # Fallback: find any ByT5 model dir\n    hits = glob.glob('/kaggle/input/**/config.json', recursive=True)\n    for h in hits:\n        d = os.path.dirname(h)\n        try:\n            import json\n            cfg = json.load(open(h))\n            if cfg.get('model_type') == 't5' and cfg.get('tokenizer_class') == 'ByT5Tokenizer':\n                MODEL_DIR = d\n                break\n        except Exception:\n            pass\n\nif MODEL_DIR is None:\n    raise FileNotFoundError('godgod3 byt5-akkadian-final model not found. Add dataset: godgod3/akkadian-new-processing')\n\nprint(f'Model dir: {MODEL_DIR}')\nprint(f'Contents:  {os.listdir(MODEL_DIR)[:10]}')\n\n# \u2500\u2500 Load test data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ntest = pd.read_csv(os.path.join(COMP_DIR, 'test.csv'))\nprint(f'Test rows: {len(test)}')\nprint(test[['id', 'transliteration']].to_string())\n\n# \u2500\u2500 Preprocessing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndef preprocess(text):\n    if not isinstance(text, str) or not text.strip():\n        return ''\n    # Normalize double-low-9 quotation mark used as subscript 2\n    text = text.replace('\\u201e', '2').replace('\\u201c', '').replace('\\u201d', '')\n    # Gap normalization: multiple formats \u2192 <gap>\n    text = re.sub(r'\\u2026+|\\.{3,}', '<gap>', text)      # \u2026 or ...\n    text = re.sub(r'\\bxx+\\b|\\s+x\\s+|\\bx\\b', '<gap>', text, flags=re.IGNORECASE)\n    text = re.sub(r'\\[\\s*\\?*\\.+\\s*\\]|\\[\\s*\\]', '<gap>', text)  # [...] []\n    # Deduplicate consecutive <gap> tokens\n    text = re.sub(r'(<gap>\\s*)+', '<gap> ', text)\n    # Strip remaining damage brackets (keep content)\n    text = re.sub(r'[\u2308\u2309\u230a\u230b\\[\\]]', '', text)\n    text = re.sub(r'\\s+', ' ', text).strip()\n    return 'translate Akkadian to English: ' + text\n\n# \u2500\u2500 Postprocessing \u2014 KEEP Unicode diacritics! \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Reference translations contain Unicode (A\u0161\u0161ur, \u1e62illi-Adad, etc.)\n# Stripping diacritics penalizes chrF++ significantly\ndef postprocess(text):\n    if not isinstance(text, str) or not text.strip():\n        return 'unknown'\n    # Only strip double-quotes (they crash the Kaggle evaluator)\n    text = text.replace('\"', '')\n    # Strip any remaining gap tokens in output\n    text = re.sub(r'<gap>', '...', text)\n    text = re.sub(r'\\s+', ' ', text).strip()\n    if not text:\n        return 'unknown'\n    return text\n\n# \u2500\u2500 Load model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nprint('Loading model...')\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\ntokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)\nmodel = AutoModelForSeq2SeqLM.from_pretrained(MODEL_DIR, torch_dtype=torch.float16)\nmodel = model.to(device)\nmodel.eval()\nprint(f'Model loaded on {device}')\n\n# \u2500\u2500 Inference \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ntranslations = []\nfor i, row in test.iterrows():\n    src = preprocess(row['transliteration'])\n    if not src:\n        translations.append('unknown')\n        continue\n\n    print(f'\\n[{i}] INPUT:  {src[:120]}...' if len(src) > 120 else f'\\n[{i}] INPUT:  {src}')\n\n    inputs = tokenizer(\n        src,\n        return_tensors='pt',\n        max_length=1024,\n        truncation=True,\n        padding=True,\n    ).to(device)\n\n    with torch.no_grad():\n        out = model.generate(\n            **inputs,\n            max_new_tokens=512,\n            num_beams=8,\n            length_penalty=1.0,\n            repetition_penalty=1.1,\n            no_repeat_ngram_size=4,\n            early_stopping=True,\n        )\n\n    pred = tokenizer.decode(out[0], skip_special_tokens=True)\n    pred = postprocess(pred)\n    print(f'[{i}] OUTPUT: {pred}')\n    translations.append(pred)\n\n# \u2500\u2500 Output \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nsub = pd.DataFrame({'id': test['id'], 'translation': translations})\nprint('\\nSubmission:')\nprint(sub.to_string())\n\n# Sanity checks\nassert all(isinstance(t, str) and len(t) > 0 for t in sub['translation']), 'Empty translation!'\nassert not any('\"' in t for t in sub['translation']), 'Double-quote in output!'\n\nsub.to_csv('submission.csv', index=False)\nprint('\\nSaved submission.csv')\nprint(sub.to_csv(index=False))\n"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10.0"}}, "nbformat": 4, "nbformat_minor": 5}