{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":121150,"databundleVersionId":15061024,"sourceType":"competition"},{"sourceId":14661932,"sourceType":"datasetVersion","datasetId":9366631}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# AKKADIAN TRANSLATION - ULTRA-OPTIMIZED INFERENCE\n# Output: submission.csv with columns [id, translation]\n# ============================================================\n\ntry:\n    from optimum.bettertransformer import BetterTransformer\n    print(\"✅ BetterTransformer available!\")\nexcept ImportError:\n    print(\"❌ BetterTransformer NOT available\")\n\nimport os\nos.environ['OMP_NUM_THREADS'] = '4'\nos.environ['MKL_NUM_THREADS'] = '4'\nos.environ['CUDA_LAUNCH_BLOCKING'] = '0'\nos.environ['TORCH_CUDNN_V8_API_ENABLED'] = '1'\nos.environ['TOKENIZERS_PARALLELISM'] = 'true'\n\nimport re\nimport logging\nimport warnings\nfrom pathlib import Path\nfrom typing import List\nfrom dataclasses import dataclass\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader, Sampler\nfrom torch.cuda.amp import autocast\nfrom transformers import AutoTokenizer, AutoModelForSeq2SeqLM\nfrom tqdm.auto import tqdm\nimport json\nimport random\n\nwarnings.filterwarnings('ignore')\n\nprint(f\"PyTorch: {torch.__version__} | CUDA: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)} | {torch.cuda.get_device_properties(0).total_memory/1e9:.1f}GB\")\n\n# ============================================================\n# CONFIGURATION\n# ============================================================\n@dataclass\nclass UltraConfig:\n    # Paths\n    test_data_path: str = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    model_path: str = \"/kaggle/input/final-byt5/byt5-akkadian-optimized-34x\"\n    output_dir: str = \"/kaggle/working/\"\n    \n    # Processing\n    max_length: int = 512\n    batch_size: int = 8\n    num_workers: int = 4\n    \n    # Generation (tuned parameters - do not change)\n    num_beams: int = 8\n    max_new_tokens: int = 512\n    length_penalty: float = 1.3\n    early_stopping: bool = True\n    no_repeat_ngram_size: int = 0\n    \n    # Optimizations\n    use_mixed_precision: bool = True\n    use_better_transformer: bool = True\n    use_bucket_batching: bool = True\n    use_vectorized_postproc: bool = True\n    use_adaptive_beams: bool = True\n    use_auto_batch_size: bool = False\n    \n    tiny_dataset_threshold: int = 64\n    num_buckets: int = 4\n    aggressive_postprocessing: bool = True\n    checkpoint_freq: int = 100\n    \n    def __post_init__(self):\n        self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n        Path(self.output_dir).mkdir(exist_ok=True, parents=True)\n        if not torch.cuda.is_available():\n            self.use_mixed_precision = False\n            self.use_better_transformer = False\n\nconfig = UltraConfig()\n\n# ============================================================\n# LOGGING\n# ============================================================\ndef setup_logging(output_dir: str):\n    Path(output_dir).mkdir(exist_ok=True, parents=True)\n    for h in logging.root.handlers[:]:\n        logging.root.removeHandler(h)\n    logging.basicConfig(\n        level=logging.INFO,\n        format='%(asctime)s - %(levelname)s - %(message)s',\n        handlers=[logging.StreamHandler(), logging.FileHandler(Path(output_dir)/'inference.log')]\n    )\n    return logging.getLogger(__name__)\n\nlogger = setup_logging(config.output_dir)\n\n# ============================================================\n# PREPROCESSOR\n# ============================================================\nclass OptimizedPreprocessor:\n    def __init__(self):\n        self.patterns = {\n            'big_gap': re.compile(r'(\\.{3,}|…+|……)'),\n            'small_gap': re.compile(r'(xx+|\\s+x\\s+)'),\n        }\n    \n    def preprocess_batch(self, texts: List[str]) -> List[str]:\n        s = pd.Series(texts).fillna(\"\").astype(str)\n        s = s.str.replace(self.patterns['big_gap'], '<big_gap>', regex=True)\n        s = s.str.replace(self.patterns['small_gap'], '<gap>', regex=True)\n        return s.tolist()\n\n# ============================================================\n# POSTPROCESSOR\n# ============================================================\nclass VectorizedPostprocessor:\n    def __init__(self, aggressive: bool = True):\n        self.aggressive = aggressive\n        self.patterns = {\n            'gap': re.compile(r'(\\[x\\]|\\(x\\)|\\bx\\b)', re.I),\n            'big_gap': re.compile(r'(\\.{3,}|…|\\[\\.+\\])'),\n            'annotations': re.compile(r'\\((fem|plur|pl|sing|singular|plural|\\?|!)\\..\\s*\\w*\\)', re.I),\n            'repeated_words': re.compile(r'\\b(\\w+)(?:\\s+\\1\\b)+'),\n            'whitespace': re.compile(r'\\s+'),\n            'punct_space': re.compile(r'\\s+([.,:])'),\n            'repeated_punct': re.compile(r'([.,])\\1+'),\n        }\n        self.subscript_trans = str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\")\n        self.special_chars_trans = str.maketrans('ḫḪ', 'hH')\n        self.forbidden_trans = str.maketrans('', '', '!?()\"——<>⌈⌋⌊[]+ʾ/;')\n    \n    def postprocess_batch(self, translations: List[str]) -> List[str]:\n        s = pd.Series(translations)\n        s[~s.apply(lambda x: isinstance(x, str) and bool(x.strip()))] = \"\"\n        \n        s = s.str.translate(self.special_chars_trans)\n        s = s.str.translate(self.subscript_trans)\n        s = s.str.replace(self.patterns['whitespace'], ' ', regex=True).str.strip()\n        \n        if self.aggressive:\n            s = s.str.replace(self.patterns['gap'], '<gap>', regex=True)\n            s = s.str.replace(self.patterns['big_gap'], '<big_gap>', regex=True)\n            s = s.str.replace('<gap> <gap>', '<big_gap>', regex=False)\n            s = s.str.replace('<big_gap> <big_gap>', '<big_gap>', regex=False)\n            s = s.str.replace(self.patterns['annotations'], '', regex=True)\n            \n            # Protect gaps during forbidden char removal\n            s = s.str.replace('<gap>', '\\x00GAP\\x00', regex=False)\n            s = s.str.replace('<big_gap>', '\\x00BIG\\x00', regex=False)\n            s = s.str.translate(self.forbidden_trans)\n            s = s.str.replace('\\x00GAP\\x00', ' <gap> ', regex=False)\n            s = s.str.replace('\\x00BIG\\x00', ' <big_gap> ', regex=False)\n            \n            # Fractions\n            for pat, repl in [(r'(\\d+)\\.5\\b', r'\\1½'), (r'\\b0\\.5\\b', '½'),\n                              (r'(\\d+)\\.25\\b', r'\\1¼'), (r'\\b0\\.25\\b', '¼'),\n                              (r'(\\d+)\\.75\\b', r'\\1¾'), (r'\\b0\\.75\\b', '¾')]:\n                s = s.str.replace(pat, repl, regex=True)\n            \n            # Remove repeated words/ngrams\n            s = s.str.replace(self.patterns['repeated_words'], r'\\1', regex=True)\n            for n in range(4, 1, -1):\n                pat = r'\\b((?:\\w+\\s+){' + str(n-1) + r'}\\w+)(?:\\s+\\1\\b)+'\n                s = s.str.replace(pat, r'\\1', regex=True)\n            \n            s = s.str.replace(self.patterns['punct_space'], r'\\1', regex=True)\n            s = s.str.replace(self.patterns['repeated_punct'], r'\\1', regex=True)\n            s = s.str.replace(self.patterns['whitespace'], ' ', regex=True)\n            s = s.str.strip().str.strip('-').str.strip()\n        \n        return s.tolist()\n\n# ============================================================\n# DATASET & SAMPLER\n# ============================================================\nclass AkkadianDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, preprocessor):\n        self.sample_ids = df['id'].tolist()\n        preprocessed = preprocessor.preprocess_batch(df['transliteration'].tolist())\n        self.input_texts = [\"translate Akkadian to English: \" + t for t in preprocessed]\n        logger.info(f\"Dataset: {len(self.sample_ids)} samples\")\n    \n    def __len__(self): return len(self.sample_ids)\n    def __getitem__(self, i): return self.sample_ids[i], self.input_texts[i]\n\nclass BucketBatchSampler(Sampler):\n    def __init__(self, dataset, batch_size: int, num_buckets: int = 4):\n        lengths = [len(t.split()) for _, t in dataset]\n        sorted_idx = sorted(range(len(lengths)), key=lambda i: lengths[i])\n        bucket_size = max(1, len(sorted_idx) // num_buckets)\n        self.buckets = [sorted_idx[i*bucket_size:(i+1)*bucket_size if i < num_buckets-1 else None]\n                        for i in range(num_buckets) if sorted_idx[i*bucket_size:]]\n        self.batch_size = batch_size\n    \n    def __iter__(self):\n        for bucket in self.buckets:\n            for i in range(0, len(bucket), self.batch_size):\n                yield bucket[i:i+self.batch_size]\n    \n    def __len__(self):\n        return sum((len(b) + self.batch_size - 1) // self.batch_size for b in self.buckets)\n\n# ============================================================\n# INFERENCE ENGINE\n# ============================================================\nclass UltraInferenceEngine:\n    def __init__(self, config: UltraConfig):\n        self.config = config\n        self.preprocessor = OptimizedPreprocessor()\n        self.postprocessor = VectorizedPostprocessor(aggressive=config.aggressive_postprocessing)\n        self._load_model()\n    \n    def _load_model(self):\n        logger.info(f\"Loading model: {self.config.model_path}\")\n        self.model = AutoModelForSeq2SeqLM.from_pretrained(self.config.model_path).to(self.config.device).eval()\n        self.tokenizer = AutoTokenizer.from_pretrained(self.config.model_path)\n        logger.info(f\"Model: {sum(p.numel() for p in self.model.parameters()):,} params\")\n        \n        if self.config.use_better_transformer and torch.cuda.is_available():\n            try:\n                from optimum.bettertransformer import BetterTransformer\n                self.model = BetterTransformer.transform(self.model)\n                logger.info(\"✅ BetterTransformer applied\")\n            except Exception as e:\n                logger.warning(f\"⚠️ BetterTransformer skipped: {e}\")\n                self.config.use_better_transformer = False\n    \n    def _collate_fn(self, batch):\n        ids = [s[0] for s in batch]\n        texts = [s[1] for s in batch]\n        tok = self.tokenizer(texts, max_length=self.config.max_length, padding=True, truncation=True, return_tensors=\"pt\")\n        return ids, tok\n    \n    def run_inference(self, test_df: pd.DataFrame) -> pd.DataFrame:\n        logger.info(\"🚀 Starting inference\")\n        \n        # Simplify for tiny datasets\n        if len(test_df) < self.config.tiny_dataset_threshold:\n            self.config.use_bucket_batching = False\n            self.config.num_workers = 0\n        \n        dataset = AkkadianDataset(test_df, self.preprocessor)\n        \n        if self.config.use_bucket_batching:\n            sampler = BucketBatchSampler(dataset, self.config.batch_size, self.config.num_buckets)\n            loader = DataLoader(dataset, batch_sampler=sampler, num_workers=self.config.num_workers,\n                                collate_fn=self._collate_fn, pin_memory=True)\n        else:\n            loader = DataLoader(dataset, batch_size=self.config.batch_size, shuffle=False,\n                                num_workers=self.config.num_workers, collate_fn=self._collate_fn, pin_memory=True)\n        \n        gen_cfg = {\n            \"max_new_tokens\": self.config.max_new_tokens,\n            \"length_penalty\": self.config.length_penalty,\n            \"early_stopping\": self.config.early_stopping,\n            \"use_cache\": True,\n            \"num_beams\": self.config.num_beams,\n        }\n        if self.config.no_repeat_ngram_size > 0:\n            gen_cfg[\"no_repeat_ngram_size\"] = self.config.no_repeat_ngram_size\n        \n        results = []\n        with torch.inference_mode():\n            for batch_ids, tok in tqdm(loader, desc=\"🚀 Translating\"):\n                input_ids = tok.input_ids.to(self.config.device)\n                attn_mask = tok.attention_mask.to(self.config.device)\n                \n                # Adaptive beams: fewer for short sequences\n                if self.config.use_adaptive_beams:\n                    avg_len = attn_mask.sum(dim=1).float().mean().item()\n                    gen_cfg[\"num_beams\"] = max(4, self.config.num_beams // 2) if avg_len < 100 else self.config.num_beams\n                \n                if self.config.use_mixed_precision:\n                    with autocast():\n                        outputs = self.model.generate(input_ids=input_ids, attention_mask=attn_mask, **gen_cfg)\n                else:\n                    outputs = self.model.generate(input_ids=input_ids, attention_mask=attn_mask, **gen_cfg)\n                \n                translations = self.tokenizer.batch_decode(outputs, skip_special_tokens=True)\n                cleaned = self.postprocessor.postprocess_batch(translations)\n                results.extend(zip(batch_ids, cleaned))\n        \n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n        \n        logger.info(\"✅ Inference complete\")\n        return pd.DataFrame(results, columns=['id', 'translation'])\n\n# ============================================================\n# MAIN EXECUTION\n# ============================================================\nlogger.info(f\"Loading test data: {config.test_data_path}\")\ntest_df = pd.read_csv(config.test_data_path, encoding='utf-8')\nlogger.info(f\"✅ Loaded {len(test_df)} samples\")\n\nengine = UltraInferenceEngine(config)\nresults_df = engine.run_inference(test_df)\n\n# Save submission.csv\noutput_path = Path(config.output_dir) / 'submission.csv'\nresults_df.to_csv(output_path, index=False)\nlogger.info(f\"✅ Saved: {output_path}\")\n\n# Validation\nprint(\"\\n\" + \"=\"*60)\nprint(\"📊 SUBMISSION SUMMARY\")\nprint(\"=\"*60)\nprint(f\"Total rows: {len(results_df)}\")\nprint(f\"Empty translations: {results_df['translation'].str.strip().eq('').sum()}\")\nprint(f\"Length range: [{results_df['translation'].str.len().min()}, {results_df['translation'].str.len().max()}]\")\nprint(f\"\\nFirst 3:\")\nprint(results_df.head(3).to_string(index=False))\nprint(\"=\"*60)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}