{"cells": [{"cell_type": "code", "execution_count": null, "id": "92271589", "metadata": {}, "outputs": [], "source": "\"\"\"\nByT5 Akkadian MT Inference\n\nKernel: byt5-mt-inference-v1-0-2\nModel: byt5-skeleton-akkadian\n\nGenerates translations using ByT5 model trained on skeleton-structured Akkadian.\n\"\"\"\n\nimport os\nimport re\n\n# Suppress progress bars and warnings\nos.environ[\"TQDM_DISABLE\"] = \"1\"\nos.environ[\"HF_HUB_DISABLE_PROGRESS_BARS\"] = \"1\"\n\n# Monkey-patch huggingface_hub to allow local paths with multiple slashes\n# This is needed because newer versions validate repo_id format before checking if it's a local path\nimport huggingface_hub.utils._validators as hf_validators\n\n_original_validate_repo_id = hf_validators.validate_repo_id\n\n\ndef _patched_validate_repo_id(repo_id: str) -> None:\n    \"\"\"Allow local paths (starting with /) to bypass repo_id validation.\"\"\"\n    if repo_id and repo_id.startswith(\"/\"):\n        return  # Skip validation for local paths\n    return _original_validate_repo_id(repo_id)\n\n\nhf_validators.validate_repo_id = _patched_validate_repo_id\n\nimport pandas as pd\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\n\nprint(f\"PyTorch: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    torch.cuda.empty_cache()\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"}, {"cell_type": "code", "execution_count": null, "id": "f8259965", "metadata": {}, "outputs": [], "source": "# Configuration\nCONFIG = {\n    # Model loaded from Kaggle input (attached via metadata)\n    # Path format: /kaggle/input/{model-slug}/{framework}/{variation}/{version}\n    \"model_path\": \"/kaggle/input/byt5-skeleton-akkadian/transformers/transformers/1\",\n    \"test_source\": \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\",\n    \"source_column\": \"transliteration\",\n    \"id_column\": \"id\",\n    \"output_column\": \"translation\",\n    \"batch_size\": 4,\n    \"max_source_length\": 512,\n    \"max_target_length\": 384,\n    \"num_beams\": 4,\n    \"repetition_penalty\": 1.2,\n    \"no_repeat_ngram_size\": 3,\n    \"prefix\": \"translate Akkadian to English: \",\n}\nprint(f\"Config: {CONFIG}\")"}, {"cell_type": "code", "execution_count": null, "id": "145fb57d", "metadata": {}, "outputs": [], "source": "# Load Model\nprint(f\"\\nLoading model from: {CONFIG['model_path']}\")\ntokenizer = AutoTokenizer.from_pretrained(CONFIG[\"model_path\"], local_files_only=True)\nmodel = AutoModelForSeq2SeqLM.from_pretrained(\n    CONFIG[\"model_path\"], local_files_only=True\n).to(DEVICE)\nmodel.eval()\nprint(f\"Parameters: {sum(p.numel() for p in model.parameters()):,}\")\nprint(f\"Model on: {DEVICE}\")"}, {"cell_type": "code", "execution_count": null, "id": "cd5fb3f4", "metadata": {"lines_to_next_cell": 2}, "outputs": [], "source": "# Load Test Data\ntest_df = pd.read_csv(CONFIG[\"test_source\"])\nprint(f\"\\nTest samples: {len(test_df)}\")\nprint(test_df.head())"}, {"cell_type": "code", "execution_count": null, "id": "241737fe", "metadata": {}, "outputs": [], "source": "# Skeleton Preprocessing (model was trained with this format)\nSUBSCRIPT_MAP = {\n    \"\u2080\": \"0\",\n    \"\u2081\": \"1\",\n    \"\u2082\": \"2\",\n    \"\u2083\": \"3\",\n    \"\u2084\": \"4\",\n    \"\u2085\": \"5\",\n    \"\u2086\": \"6\",\n    \"\u2087\": \"7\",\n    \"\u2088\": \"8\",\n    \"\u2089\": \"9\",\n}\nVOWEL_PATTERN = re.compile(r\"[aeiu\u0101\u0113\u012b\u016b\u00e2\u00ea\u00ee\u00fb\u00e0\u00e8\u00ec\u00f9\u00e1\u00e9\u00ed\u00fa\u00c0\u00c8\u00cc\u00d9\u00c1\u00c9\u00cd\u00da\u00c2\u00ca\u00ce\u00db]\", re.IGNORECASE)\nSUBSCRIPTS = \"\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2093\"\n\n\ndef normalize_subscripts(text: str) -> str:\n    for subscript, digit in SUBSCRIPT_MAP.items():\n        text = text.replace(subscript, digit)\n    return text\n\n\ndef normalize_gaps(text: str) -> str:\n    text = re.sub(r\"(?:x\\s+){2,}x\", \"<big_gap>\", text)\n    text = re.sub(r\"\\bx\\b\", \"<gap>\", text)\n    text = re.sub(r\"(?:<gap>\\s*)+\", \"<gap> \", text)\n    text = re.sub(r\"(?:<big_gap>\\s*)+\", \"<big_gap> \", text)\n    return text\n\n\ndef preprocess_source(text: str) -> str:\n    text = str(text)\n    text = normalize_subscripts(text)\n    text = normalize_gaps(text)\n    text = re.sub(r\"\\s+\", \" \", text).strip()\n    return text\n\n\ndef is_logogram(token):\n    clean = \"\".join(c for c in token if c not in \".\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2093\")\n    return clean.isupper() and len(clean) > 0\n\n\ndef is_number(token):\n    try:\n        float(token.replace(\",\", \".\"))\n        return True\n    except ValueError:\n        return bool(re.match(r\"^[\\d.,/]+$\", token))\n\n\ndef strip_determinatives(token):\n    return re.sub(r\"^\\([a-zA-Z]{1,3}\\)\", \"\", token)\n\n\ndef to_skeleton(text):\n    \"\"\"Convert text to consonantal skeleton.\"\"\"\n    if not text:\n        return \"\"\n    result = []\n    for token in str(text).split():\n        if is_logogram(token) or is_number(token):\n            result.append(token)\n            continue\n        normalized = strip_determinatives(token)\n        normalized = \"\".join(c for c in normalized if c not in SUBSCRIPTS)\n        skeleton = VOWEL_PATTERN.sub(\"\", normalized)\n        skeleton = re.sub(r\"-+\", \"\", skeleton)\n        result.append(skeleton if skeleton else \"_\")\n    return \" \".join(result)\n\n\ndef create_skeleton_input(text: str) -> str:\n    \"\"\"Create input with skeleton prefix: [SKL:skeleton] original_text\"\"\"\n    if not text:\n        return \"\"\n    processed = preprocess_source(text)\n    skeleton = to_skeleton(processed)\n    return f\"[SKL:{skeleton}] {processed}\"\n\n\ndef clean_translation(text):\n    \"\"\"Clean up translation output.\"\"\"\n    text = re.sub(r\"\\s*\\(\\s*s\\s*\\)\\s*\", \"s \", text)\n    text = re.sub(r\"\\s*\\(\\s*(or|and)\\s*\\)\\s*\", r\" \\1 \", text)\n    text = re.sub(r\"\\s*\\(\\s*(ki|d|m|f|lu|munus)\\s*\\)\\s*\", \" \", text)\n    text = \" \".join(text.split())\n    return text.strip()"}, {"cell_type": "code", "execution_count": null, "id": "ae5c1d9c", "metadata": {}, "outputs": [], "source": "# Generate Translations\nprint(\"\\n\" + \"=\" * 60)\nprint(\"GENERATING TRANSLATIONS\")\nprint(\"=\" * 60)\n\ntexts = test_df[CONFIG[\"source_column\"]].tolist()\nids = test_df[CONFIG[\"id_column\"]].tolist()\n\n# Apply skeleton preprocessing and prefix\nprefix = CONFIG[\"prefix\"]\nprefixed_texts = [prefix + create_skeleton_input(t) for t in texts]\n\nprint(f\"Sample input: {prefixed_texts[0][:150]}...\")\n\ntranslations = []\nbatch_size = CONFIG[\"batch_size\"]\n\nfor i in range(0, len(prefixed_texts), batch_size):\n    batch = prefixed_texts[i : i + batch_size]\n\n    inputs = tokenizer(\n        batch,\n        return_tensors=\"pt\",\n        padding=True,\n        truncation=True,\n        max_length=CONFIG[\"max_source_length\"],\n    ).to(DEVICE)\n\n    with torch.no_grad():\n        outputs = model.generate(\n            **inputs,\n            num_beams=CONFIG[\"num_beams\"],\n            max_new_tokens=CONFIG[\"max_target_length\"],\n            early_stopping=True,\n            repetition_penalty=CONFIG[\"repetition_penalty\"],\n            no_repeat_ngram_size=CONFIG[\"no_repeat_ngram_size\"],\n        )\n\n    decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True)\n    cleaned = [clean_translation(t) for t in decoded]\n    translations.extend(cleaned)\n\n    if (i // batch_size + 1) % 20 == 0:\n        print(\n            f\"  Batch {i // batch_size + 1}/{(len(prefixed_texts) + batch_size - 1) // batch_size}\"\n        )\n\nprint(f\"\\nGenerated {len(translations)} translations\")"}, {"cell_type": "code", "execution_count": null, "id": "9d57b43b", "metadata": {}, "outputs": [], "source": "# Sample Translations\nprint(\"\\n\" + \"=\" * 60)\nprint(\"SAMPLE TRANSLATIONS\")\nprint(\"=\" * 60)\nfor i in range(min(5, len(texts))):\n    print(f\"\\n[{i + 1}] Source: {texts[i][:100]}...\")\n    print(f\"    Translation: {translations[i]}\")"}, {"cell_type": "code", "execution_count": null, "id": "2e7575e8", "metadata": {}, "outputs": [], "source": "# Create Submission\nsubmission = pd.DataFrame(\n    {CONFIG[\"id_column\"]: ids, CONFIG[\"output_column\"]: translations}\n)\nprint(f\"\\nSubmission shape: {submission.shape}\")\nprint(submission.head())\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(f\"\\nSaved submission.csv\")\nprint(f\"File size: {os.path.getsize('submission.csv'):,} bytes\")"}, {"cell_type": "code", "execution_count": null, "id": "5694d548", "metadata": {}, "outputs": [], "source": "print(\"\\n\" + \"=\" * 60)\nprint(\"INFERENCE COMPLETE\")\nprint(\"=\" * 60)"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}}, "nbformat": 4, "nbformat_minor": 5}