{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":"gpu","dataSources":[{"sourceType":"competition","sourceId":121150,"databundleVersionId":15745837},{"sourceType":"datasetVersion","sourceId":14899351,"datasetId":9366631,"databundleVersionId":15763950},{"sourceType":"modelInstanceVersion","sourceId":754941,"databundleVersionId":15740514,"modelInstanceId":573226,"modelId":585575}],"dockerImageVersionId":31287,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%%writefile Logger.py\nimport logging\nimport time\n\nt1 = time.time()\n\ndef get_logger(Format: str | None = None) -> logging.Logger:\n    if Format is None:\n        Format='%(asctime)s %(clientip)-15s %(user)-8s %(message)s'\n\n    logging.basicConfig(\n        format=Format\n    )\n    logger = logging.getLogger(__name__)\n    logger.info(f\"Logger ready to use in {time.time() - t1}\")\n    return logger","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:29:37.229072Z","iopub.execute_input":"2026-02-21T15:29:37.229271Z","iopub.status.idle":"2026-02-21T15:29:37.238504Z","shell.execute_reply.started":"2026-02-21T15:29:37.229249Z","shell.execute_reply":"2026-02-21T15:29:37.237813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile processors.py\n\nimport re\nfrom typing import List\nimport pandas as pd\n\nclass TransliterationProcessor:\n    def __init__(self):\n        self.patterns = {\n            # regex\n            \"big_gap\": re.compile(r\"(\\.{3,}|…+|……)\"),\n            \"small_gap\": re.compile(r\"(xx+|\\s+x\\s+)\"),\n        }\n\n    def preprocess_input_text(self, text: str) -> str:\n        if pd.isna(text):\n            return \"\"\n\n        cleaned_text = str(text)\n\n        cleaned_text = self.patterns[\"big_gap\"].sub(\"<big_gap>\", cleaned_text)\n        cleaned_text = self.patterns[\"small_gap\"].sub(\"<gap>\", cleaned_text)\n\n        return cleaned_text\n\n    def preprocess_batch(self, texts: List[str]) -> List[str]:\n        s = pd.Series(texts).fillna(\"\")\n        s = s.astype(str)\n\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\n        return s.tolist()\n\nclass TranslationProcessor:\n    '''\n    Akkadian post-processor for checking punctuations,\n    gaps and annotations. Post-processing in batching for \n    efficiency.\n    '''\n    \n    def __init__(self, aggressive: bool = True):\n        self.aggressive = aggressive\n\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\n        self.subscript_trans = str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\")\n        self.special_chars_trans = str.maketrans(\"ḫḪ\", \"hH\")\n\n        self.forbidden_chars = '!?()\"——<>⌈⌋⌊[]+ʾ/;'\n\n        self.forbidden_trans = str.maketrans(\"\", \"\", self.forbidden_chars)\n\n    def postprocess_batch(self, translations: List[str]) -> List[str]:\n        s = pd.Series(translations)\n\n        valid_mask = s.apply(lambda x: isinstance(x, str) and x.strip())\n        if not valid_mask.all():\n            s[~valid_mask] = \"\"\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)\n        s = s.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\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\n            s = s.str.replace(self.patterns[\"annotations\"], \"\", regex = True)\n\n            s = s.str.replace(\"<gap>\", \"\\x00GAP\\x00\", regex = False)\n            s = s.str.replace(\"<big_gap>\", \"\\x00BIG\\x00\", regex = False)\n\n            s = s.str.translate(self.forbidden_trans)\n\n            s = s.str.replace(\"\\x00GAP\\x00\", \" <gap> \", regex = False)\n            s = s.str.replace(\"\\x00BIG\\x00\", \" <big_gap> \", regex = False)\n\n            s = s.str.replace(r\"(\\d+)\\.5\\b\", r\"\\1½\", regex = True)\n            s = s.str.replace(r\"\\b0\\.5\\b\", \"½\", regex = True)\n            s = s.str.replace(r\"(\\d+)\\.25\\b\", r\"\\1¼\", regex = True)\n            s = s.str.replace(r\"\\b0\\.25\\b\", \"¼\", regex = True)\n            s = s.str.replace(r\"(\\d+)\\.75\\b\", r\"\\1¾\", regex = True)\n            s = s.str.replace(r\"\\b0\\.75\\b\", \"¾\", regex = True)\n\n            s = s.str.replace(self.patterns[\"repeated_words\"], r\"\\1\", regex = True)\n\n            for n in range(4, 1, -1):\n                pattern = r\"\\b((?:\\w+\\s+){\" + str(n - 1) + r\"}\\w+)(?:\\s+\\1\\b)+\"\n                s = s.str.replace(pattern, 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\n            s = s.str.replace(self.patterns[\"whitespace\"], \" \", regex = True)\n            s = s.str.strip().str.strip(\"-\").str.strip()\n\n        return s.tolist()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:29:37.240004Z","iopub.execute_input":"2026-02-21T15:29:37.240193Z","iopub.status.idle":"2026-02-21T15:29:37.254858Z","shell.execute_reply.started":"2026-02-21T15:29:37.240175Z","shell.execute_reply":"2026-02-21T15:29:37.254067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile bucket_batch_sampler.py\n\nimport torch\nfrom torch.utils.data import Sampler, Dataset\nfrom torch.cuda.amp import autocast\nimport logging\n\nclass BucketBatchSampler(Sampler):\n    \"\"\"\n    Bucket Batch Sampling\n    Typically, the `sampler` will be a `RandomSampler` allowing the user to toggle between\n    random batches and sorted batches. A larger `bucket_size_multiplier` is more sorted and vice\n    versa.\n\n    References:\n        https://github.com/pytorch/pytorch/issues/46176\n        https://pytorchnlp.readthedocs.io/en/latest/_modules/torchnlp/samplers/bucket_batch_sampler.html\n        https://stackoverflow.com/questions/49367871/concept-of-bucketing-in-seq2seq-model\n        https://github.com/allenai/allennlp/blob/master/allennlp/data/iterators/bucket_iterator.py\n        https://github.com/pytorch/text/blob/master/torchtext/data/iterator.py#L225\n\n    1. TODO: A̶d̶d̶ ̶s̶u̶p̶p̶o̶r̶t̶ ̶f̶o̶r̶ ̶s̶h̶u̶f̶f̶l̶i̶n̶g̶ ̶t̶h̶e̶ ̶i̶n̶t̶i̶m̶a̶t̶e̶l̶y̶ ̶f̶e̶t̶c̶h̶e̶d̶ ̶i̶n̶d̶i̶c̶e̶s̶.̶\n    2. TODO: W̶r̶i̶t̶e̶ ̶t̶h̶e̶ ̶c̶l̶a̶s̶s̶ ̶a̶n̶d̶ ̶u̶s̶e̶ ̶t̶h̶e̶ ̶l̶o̶g̶g̶e̶r̶ ̶i̶n̶s̶i̶d̶e̶ ̶o̶f̶ ̶i̶t̶.\n    \"\"\"\n\n    def __init__(self, \n                 dataset: Dataset, \n                 batch_size: int, \n                 num_buckets: int, \n                 logger: logging.Logger, \n                 shuffle: bool = False):\n        self.dataset = dataset\n        self.batch_size = batch_size\n        self.num_buckets = num_buckets\n        self.logger = logger\n        self.shuffle = shuffle\n\n        lengths = [len(text.split()) for _, text in dataset]\n        sorted_indices = sorted(\n            range(len(lengths)),\n            key = lambda i: lengths[i]\n        )\n        bucket_size = max(1, len(sorted_indices) // max(1, num_buckets))\n        self.buckets = []\n\n        for i in range(num_buckets):\n            start = i * bucket_size\n            end = None if i == num_buckets - 1 else (i + 1) * bucket_size\n            self.buckets.append(sorted_indices[start : end])\n\n        self.logger.info(f\"Created {num_buckets} buckets:\")\n        for i, bucket in enumerate(self.buckets):\n            bucket_lengths = [lengths[idx] for idx in bucket] if len(bucket) > 0 else [0]\n            self.logger.info(\n                f\"Bucket {i}: {len(bucket)} samples, length range [{min(bucket_lengths)}, {max(bucket_lengths)}]\"\n            )\n\n    def __iter__(self):\n        for bucket in self.buckets:\n            if self.shuffle:\n                random.shuffle(bucket)\n\n            for i in range(0, len(bucket), self.batch_size):\n                yield bucket[i : i + self.batch_size]\n\n\n    def __len__(self):\n        total = 0\n        for bucket in self.buckets:\n            total += (len(bucket) + self.batch_size - 1) // self.batch_size\n        \n        return total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:29:37.255821Z","iopub.execute_input":"2026-02-21T15:29:37.256118Z","iopub.status.idle":"2026-02-21T15:29:37.268821Z","shell.execute_reply.started":"2026-02-21T15:29:37.256093Z","shell.execute_reply":"2026-02-21T15:29:37.268145Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile dataloader.py\n\nfrom torch.utils.data import Dataset, DataLoader, Sampler\nimport logging\nimport pandas as pd\nfrom processors import TransliterationProcessor\n\nclass AkkadianDataset(Dataset):\n    def __init__(self, dataframe: pd.DataFrame, preprocessor: TransliterationProcessor, logger = logging.Logger):\n        self.sample_ids = dataframe[\"id\"].tolist()\n        raw_texts = dataframe[\"transliteration\"].tolist()\n        preprocessed = preprocessor.preprocess_batch(raw_texts)\n\n        # Input with prompts usually score better\n        self.input_texts = [\"translate Akkadian to English: \" + text for text in preprocessed]\n        logger.info(f\"Dataset created with {len(self.sample_ids)} samples\")\n\n    def __len__(self):\n        return len(self.sample_ids)\n\n    def __getitem__(self, index: int):\n        return self.sample_ids[index], self.input_texts[index]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:29:37.269706Z","iopub.execute_input":"2026-02-21T15:29:37.269974Z","iopub.status.idle":"2026-02-21T15:29:37.283664Z","shell.execute_reply.started":"2026-02-21T15:29:37.269955Z","shell.execute_reply":"2026-02-21T15:29:37.282956Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile get_env.py\nimport torch\n\ndef print_environment_info():\n    print(f\"PyTorch version: {torch.__version__}\")\n    print(f\"CUDA available: {torch.cuda.is_available()}\")\n\n    if torch.cuda.is_available():\n        print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n        total_mem_gb = torch.cuda.get_device_properties(0).total_memory / 1e9\n        print(f\"GPU Memory: {total_mem_gb:.2f} GB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:29:37.284491Z","iopub.execute_input":"2026-02-21T15:29:37.284738Z","iopub.status.idle":"2026-02-21T15:29:37.298937Z","shell.execute_reply.started":"2026-02-21T15:29:37.284707Z","shell.execute_reply":"2026-02-21T15:29:37.298239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile inference.py\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 numpy as np\nimport pandas as pd\nfrom dataclasses import dataclass\nimport torch\nfrom pathlib import Path\n\nfrom processors import TransliterationProcessor, TranslationProcessor\nfrom Logger import get_logger\nfrom get_env import print_environment_info\nfrom bucket_batch_sampler import BucketBatchSampler\nfrom dataloader import AkkadianDataset\nfrom tqdm.auto import tqdm\n\nimport transformers\nfrom transformers import (\n    AutoModelForSeq2SeqLM,\n    AutoTokenizer\n)\nfrom torch.cuda.amp import autocast\nimport re\nimport unicodedata\nimport json\nimport random\nfrom torch.utils.data import DataLoader\n\nimport logging\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n@dataclass\nclass CFG:\n    # Dataset files could be anything, not just test.csv, added paths for global access.\n    test_data_path: str = \"/kaggle/input/deep-past-initiative-machine-translation/test.csv\"\n    model_path: str = \"/kaggle/input/datasets/assiaben/final-byt5/byt5-akkadian-optimized-34x\"\n    output_dir: str = \"/kaggle/working/\"\n\n    # Read about the tokenizer at: https://huggingface.co/docs/transformers/en/model_doc/byt5\n    max_length: int = 524\n    batch_size: int = 4\n    num_workers: int = 4\n\n    # Metrics and adaptive beam searches.\n    num_beams: int = 4\n    max_new_tokens: int = 524\n    length_penalty: float = 1.3\n    early_stopping: bool = True\n    no_repeat_ngram_size: int = 0\n\n    # Optimizations for better pre/post processing\n    use_mixed_precision: bool = True\n    use_bucket_batching: bool = True\n    use_vectorized_postproc: bool = True\n\n    # Miscellaneous configurations for custom classes.\n    aggressive_postprocessing: bool = True\n    checkpoint_freq: int = 100\n    num_buckets: int = 4\n    cache_batch_frequency: int = 10\n\n    def __post_init__(self):\n        self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n        # Create output directory\n        Path(self.output_dir).mkdir(exist_ok = True, parents = True)\n        # Disable CUDA-dependent optimizations if no GPU\n        if not torch.cuda.is_available():\n            self.use_mixed_precision = False\n\nclass Byt5Inference:\n    def __init__(self, config: CFG, logger: logging.Logger):\n        self.config = config\n        self.logger = logger\n        self.preprocessor = TransliterationProcessor()\n        self.postprocessor = TranslationProcessor(\n            aggressive = config.aggressive_postprocessing\n        )\n\n        self.results = []\n        self._load_model()\n\n    def _load_model(self):\n        self.logger.info(f\"Loading model from {self.config.model_path}\")\n        self.model = AutoModelForSeq2SeqLM.from_pretrained(\n            self.config.model_path, \n            local_files_only=True\n        )\n        self.model = self.model.to(self.config.device)\n        self.model = self.model.eval()\n        self.tokenizer = AutoTokenizer.from_pretrained(\n            self.config.model_path,\n            local_files_only=True,\n        )\n        num_params = sum(p.numel() for p in self.model.parameters())\n        self.logger.info(f\"Model loaded: {num_params:,} parameters\")\n\n    def _collate_fn(self, batch_samples):\n        batch_ids = [s[0] for s in batch_samples]\n        batch_texts = [s[1] for s in batch_samples]\n        \n        tokenized = self.tokenizer(\n            batch_texts,\n            max_length=self.config.max_length,\n            padding=True,\n            truncation=True,\n            return_tensors=\"pt\",\n        ) \n        return batch_ids, tokenized\n\n    def _save_checkpoint(self) -> None:\n        if len(self.results) > 0 and len(self.results) % self.config.checkpoint_freq == 0:\n            checkpoint_pth = Path(self.config.output_dir) / f\"checkpoint_{len(self.results)}.csv\"\n            df = pd.DataFrame(self.results, columns=[\"id\", \"translation\"])\n            df.to_csv(checkpoint_pth, index=False)\n            \n            self.logger.info(f\"💾 Checkpoint: {len(self.results)} translations\")\n\n    def inference(self, test_df: pd.DataFrame) -> pd.DataFrame:\n        self.logger.info(\"🚀 Starting OPTIMIZED inference\")\n        dataset = AkkadianDataset(test_df, self.preprocessor, self.logger)\n\n        batch_sampler = BucketBatchSampler(\n            dataset=dataset,\n            batch_size=self.config.batch_size,\n            num_buckets=self.config.num_buckets,\n            logger=self.logger,\n            shuffle=False,\n        )\n        dataloader = DataLoader(\n            dataset,\n            batch_sampler=batch_sampler,\n            num_workers=self.config.num_workers,\n            collate_fn=self._collate_fn,\n            pin_memory=True,\n            prefetch_factor=2,\n            persistent_workers=True if self.config.num_workers > 0 else False,\n        )\n\n        self.logger.info(f\"DataLoader created: {len(dataloader)} batches\")\n        self.logger.info(\"Active optimizations:\")\n        self.logger.info(f\"  ✅ Mixed Precision: {self.config.use_mixed_precision}\")\n        self.logger.info(f\"  ✅ Bucket Batching: {self.config.use_bucket_batching}\")\n        self.logger.info(f\"  ✅ Vectorized Postproc: {self.config.use_vectorized_postproc}\")\n\n        base_gen_config = {\n            \"max_new_tokens\": self.config.max_new_tokens,\n            \"length_penalty\": self.config.length_penalty,\n            \"early_stopping\": self.config.early_stopping,\n            \"num_beams\": self.config.num_beams,\n            \"use_cache\": True,\n        }\n\n        if self.config.no_repeat_ngram_size > 0:\n            base_gen_config[\"no_repeat_ngram_size\"] = self.config.no_repeat_ngram_size\n\n        self.results = []\n\n        with torch.inference_mode():\n            for batch_idx, (batch_ids, tokenized) in enumerate(tqdm(dataloader, desc = \"🚀 Translating\")):\n                try:\n                    input_ids = tokenized.input_ids.to(self.config.device)\n                    attention_mask = tokenized.attention_mask.to(self.config.device)\n\n                    gen_config = dict(base_gen_config)\n\n                    if self.config.use_mixed_precision:\n                        with autocast():\n                            outputs = self.model.generate(\n                                input_ids=input_ids,\n                                attention_mask=attention_mask,\n                                **gen_config\n                            )\n                    else:\n                        outputs = self.model.generate(\n                            input_ids=input_ids,\n                            attention_mask=attention_mask,\n                            **gen_config\n                        )\n\n                    translations = self.tokenizer.batch_decode(\n                        outputs, skip_special_tokens=True,\n                    )\n                    if self.config.use_vectorized_postproc:\n                        cleaned = self.postprocessor.postprocess_batch(translations)\n                    else:\n                        cleaned = [self.postprocessor.postprocess_batch([t])[0] for t in translations]\n\n                    self.results.extend(list(zip(batch_ids, cleaned)))\n                    self._save_checkpoint()\n                    \n                    if torch.cuda.is_available() and batch_idx % self.config.cache_batch_frequency == 0:\n                        torch.cuda.empty_cache()\n                except Exception as exc:\n                    self.logger.error(f\"❌ Batch {batch_idx} error: {exc}\")\n                    self.results.extend([(bid, \"\") for bid in batch_ids])\n        \n        self.logger.info(\"✅ Inference completed\")\n\n        results_df = pd.DataFrame(self.results, columns=[\"id\", \"translation\"])\n        self._validate_results(results_df)\n        return results_df\n\n    def _validate_results(self, df: pd.DataFrame):\n        print(\"\\n\" + \"=\" * 60)\n        print(\"📊 VALIDATION REPORT\")\n        print(\"=\" * 60)\n\n        empty = df[\"translation\"].astype(str).str.strip().eq(\"\").sum()\n        print(f\"\\nEmpty: {empty} ({(empty / max(1, len(df))) * 100:.2f}%)\")\n\n        lengths = df[\"translation\"].astype(str).str.len()\n        print(\"\\n📏 Length stats:\")\n        print(f\"   Mean: {lengths.mean():.1f}, Median: {lengths.median():.1f}\")\n        print(f\"   Min: {lengths.min()}, Max: {lengths.max()}\")\n\n        short = ((lengths < 5) & (lengths > 0)).sum()\n        if short > 0:\n            print(f\"   ⚠️  {short} very short translations\")\n\n        print(\"\\n📝 Sample translations:\")\n\n        sample_indices = [0]\n        if len(df) > 2:\n            sample_indices.append(len(df) // 2)\n        if len(df) > 1:\n            sample_indices.append(len(df) - 1)\n\n        for idx in sample_indices:\n            row = df.iloc[idx]\n            text = str(row[\"translation\"])\n            preview = text[:70] + \"...\" if len(text) > 70 else text\n            print(f\"   ID {int(row['id']):4d}: {preview}\")\n\n        print(\"\\n\" + \"=\" * 60 + \"\\n\")\n\ndef save_outputs(\n    results_df: pd.DataFrame,\n    config: CFG,\n    output_dir: str,\n    logger: logging.Logger,\n):\n    output_path = Path(output_dir) / \"submission.csv\"\n    config_path = Path(output_dir) / \"inference_logs.json\"\n    results_df[\"translation\"] = results_df[\"translation\"].fillna(\"<gap><big_gap><gap>\")\n    results_df.to_csv(\n        output_path, index=False\n    )\n\n    logger.info(f\"✅ Submission saved to {output_path}\")\n\n    print(\"\\n\" + \"=\" * 60)\n    print(\"🎉 INFERENCE COMPLETE!\")\n    print(\"=\" * 60)\n    print(f\"Submission file: {output_path}\")\n    print(f\"Config file: {config_path}\")\n    print(f\"Log file: {Path(output_dir) / 'inference.log'}\")\n    print(f\"Total translations: {len(results_df)}\")\n    print(\"=\" * 60)\n\ndef inspect_results(output_dir: str):\n    submission_path = Path(output_dir) / \"submission.csv\"\n    submission = pd.read_csv(submission_path)\n\n    print(f\"Submission shape: {submission.shape}\")\n    print(\"\\nFirst 10 translations:\")\n    print(submission.head(10))\n\n    print(\"\\nLast 10 translations:\")\n    print(submission.tail(10))\n    lengths = submission[\"translation\"].astype(str).str.len()\n    print(\"\\nLength distribution:\")\n    print(lengths.describe())\n\n    empty = submission[\"translation\"].astype(str).str.strip().eq(\"\").sum()\n    print(f\"\\nEmpty translations: {empty}\")\n    if empty > 0:\n        print(\"\\nEmpty translation IDs:\")\n        print(submission[submission[\"translation\"].astype(str).str.strip().eq(\"\")][\"id\"].tolist())\n\ndef main():\n    config = CFG()\n\n    logger = get_logger()\n    logger.info(\"Logging initialized\")\n    print_environment_info()\n\n    logger.info(\"Configuration:\")\n    logger.info(f\"  Device: {config.device}\")\n    logger.info(f\"  Batch size: {config.batch_size}\")\n    logger.info(f\"  Beams: {config.num_beams}\")\n    logger.info(\"Optimizations:\")\n    logger.info(f\"  Mixed Precision: {config.use_mixed_precision}\")\n    logger.info(f\"  Bucket Batching: {config.use_bucket_batching}\")\n    logger.info(f\"  Vectorized Postproc: {config.use_vectorized_postproc}\")\n\n    logger.info(f\"Loading test data from {config.test_data_path}\")\n    test_df = pd.read_csv(config.test_data_path, encoding=\"utf-8\")\n    logger.info(f\"✅ Loaded {len(test_df)} test samples\")\n\n    print(\"\\nFirst 5 samples:\")\n    print(test_df.head())\n\n    engine = Byt5Inference(config, logger)\n    results_df = engine.inference(test_df)\n\n    save_outputs(results_df, config, config.output_dir, logger)\n    inspect_results(config.output_dir)\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:31:56.340843Z","iopub.execute_input":"2026-02-21T15:31:56.341215Z","iopub.status.idle":"2026-02-21T15:31:56.351820Z","shell.execute_reply.started":"2026-02-21T15:31:56.341181Z","shell.execute_reply":"2026-02-21T15:31:56.351177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python inference.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T15:29:37.313587Z","iopub.execute_input":"2026-02-21T15:29:37.313851Z","iopub.status.idle":"2026-02-21T15:30:24.315572Z","shell.execute_reply.started":"2026-02-21T15:29:37.313823Z","shell.execute_reply":"2026-02-21T15:30:24.314873Z"}},"outputs":[],"execution_count":null}]}