{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":121150,"databundleVersionId":15061024,"sourceType":"competition"},{"sourceId":14661932,"sourceType":"datasetVersion","datasetId":9366631}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":64.652861,"end_time":"2026-02-01T00:21:00.226487","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-02-01T00:19:55.573626","version":"2.6.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"029dce65fed84df8ba54b9e0511c3121":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"0f0649cabdd94f3184f365b4f943a5b1":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"2cf6cb12fdf14cc1a3bfa2dfe7036ee1":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"ProgressView","bar_style":"success","description":"","description_allow_html":false,"layout":"IPY_MODEL_4711f095e5304a128aa4c88dc2f85029","max":4,"min":0,"orientation":"horizontal","style":"IPY_MODEL_ef3b679d827c48ceab034b4acb772b4e","tabbable":null,"tooltip":null,"value":4}},"4711f095e5304a128aa4c88dc2f85029":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"51464367416d4c688379528717c89f0f":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_f8fa921dea584b1ca41cb9a8811728a7","IPY_MODEL_2cf6cb12fdf14cc1a3bfa2dfe7036ee1","IPY_MODEL_73333740fa564416942926484db0ed72"],"layout":"IPY_MODEL_6f337eff443749e2b54b44ce528a1e4d","tabbable":null,"tooltip":null}},"55712be8da464e82b69fad262177d4ec":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","background":null,"description_width":"","font_size":null,"text_color":null}},"6f337eff443749e2b54b44ce528a1e4d":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"73333740fa564416942926484db0ed72":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_0f0649cabdd94f3184f365b4f943a5b1","placeholder":"​","style":"IPY_MODEL_55712be8da464e82b69fad262177d4ec","tabbable":null,"tooltip":null,"value":" 4/4 [00:10&lt;00:00,  2.62s/it]"}},"ef3b679d827c48ceab034b4acb772b4e":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"f8fa921dea584b1ca41cb9a8811728a7":{"model_module":"@jupyter-widgets/controls","model_module_version":"2.0.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"2.0.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"2.0.0","_view_name":"HTMLView","description":"","description_allow_html":false,"layout":"IPY_MODEL_fcd18f3a45b34a63b00abacc411008b6","placeholder":"​","style":"IPY_MODEL_029dce65fed84df8ba54b9e0511c3121","tabbable":null,"tooltip":null,"value":"🚀 Translating: 100%"}},"fcd18f3a45b34a63b00abacc411008b6":{"model_module":"@jupyter-widgets/base","model_module_version":"2.0.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"2.0.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"2.0.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border_bottom":null,"border_left":null,"border_right":null,"border_top":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"6e6d48f6-8f5a-449b-9e09-369facc909f1","cell_type":"code","source":"# ------------------------------------------------------------\n# SCORE-OPTIMIZED ByT5 Inference Script for BLEU & chrF++\n# ------------------------------------------------------------\n# Key improvements for better scores:\n# 1. Enhanced preprocessing to preserve linguistic structure\n# 2. Smarter postprocessing that doesn't over-clean\n# 3. Optimized beam search parameters\n# 4. Better handling of gaps and special tokens\n# ------------------------------------------------------------\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 json\nimport random\nimport logging\nimport warnings\nfrom pathlib import Path\nfrom dataclasses import dataclass\nfrom typing import List, Dict\n\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\n\nwarnings.filterwarnings(\"ignore\")\n\n# ------------------------------------------------------------\n# CONFIGURATION - OPTIMIZED FOR SCORE\n# ------------------------------------------------------------\n@dataclass\nclass ScoreOptimizedConfig:\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 = 6  # Reduced for more beams\n    num_workers: int = 4\n\n    # ============ GENERATION - OPTIMIZED FOR QUALITY ============\n    num_beams: int = 15  # Increased for better search\n    max_new_tokens: int = 512\n    length_penalty: float = 1.0  # Neutral - let model decide\n    early_stopping: bool = False  # Explore more\n    no_repeat_ngram_size: int = 3  # Prevent repetition\n    repetition_penalty: float = 1.2  # Discourage repetition\n    \n    # NEW: Multiple hypothesis generation\n    num_return_sequences: int = 1  # Can increase for ensemble\n    use_ensemble: bool = False  # Generate multiple, pick best\n\n    # ============ OPTIMIZATIONS ============\n    use_mixed_precision: bool = True\n    use_better_transformer: bool = True\n    use_bucket_batching: bool = True\n    use_smart_postproc: bool = True  # Less aggressive\n    use_adaptive_beams: bool = False  # Use consistent high beams\n\n    # ============ POSTPROCESSING ============\n    minimal_postprocessing: bool = True  # Preserve model output\n    preserve_punctuation: bool = True\n    preserve_numbers: bool = True\n    \n    # ============ OTHER ============\n    checkpoint_freq: int = 100\n    num_buckets: int = 4\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        \n        if not torch.cuda.is_available():\n            self.use_mixed_precision = False\n            self.use_better_transformer = False\n\n# ------------------------------------------------------------\n# LOGGING\n# ------------------------------------------------------------\ndef setup_logging(output_dir: str) -> logging.Logger:\n    Path(output_dir).mkdir(exist_ok=True, parents=True)\n    log_file = Path(output_dir) / \"inference_score_optimized.log\"\n    \n    for handler in logging.root.handlers[:]:\n        logging.root.removeHandler(handler)\n    \n    logging.basicConfig(\n        level=logging.INFO,\n        format=\"%(asctime)s - %(levelname)s - %(message)s\",\n        handlers=[\n            logging.StreamHandler(),\n            logging.FileHandler(log_file),\n        ],\n    )\n    \n    return logging.getLogger(__name__)\n\n# ------------------------------------------------------------\n# SMART PREPROCESSOR - Preserves linguistic structure\n# ------------------------------------------------------------\nclass SmartPreprocessor:\n    def __init__(self):\n        # More conservative patterns\n        self.patterns = {\n            \"big_gap\": re.compile(r\"(\\.{4,}|…{2,})\"),  # Only very large gaps\n            \"small_gap\": re.compile(r\"(xxx+)\"),  # Only multiple 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).strip()\n        \n        # Minimal gap normalization\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        # Normalize whitespace\n        cleaned_text = re.sub(r'\\s+', ' ', cleaned_text).strip()\n        \n        return cleaned_text\n\n    def preprocess_batch(self, texts: List[str]) -> List[str]:\n        return [self.preprocess_input_text(t) for t in texts]\n\n# ------------------------------------------------------------\n# MINIMAL POSTPROCESSOR - Preserves model output quality\n# ------------------------------------------------------------\nclass MinimalPostprocessor:\n    def __init__(self, minimal: bool = True):\n        self.minimal = minimal\n        \n        # Only essential patterns\n        self.patterns = {\n            \"whitespace\": re.compile(r'\\s+'),\n            \"space_before_punct\": re.compile(r'\\s+([.,;:!?])'),\n            \"repeated_spaces\": re.compile(r'  +'),\n        }\n\n    def postprocess_single(self, text: str) -> str:\n        if not isinstance(text, str) or not text.strip():\n            return \"\"\n        \n        cleaned = text.strip()\n        \n        if self.minimal:\n            # Only basic cleanup\n            cleaned = self.patterns[\"whitespace\"].sub(\" \", cleaned)\n            cleaned = self.patterns[\"space_before_punct\"].sub(r\"\\1\", cleaned)\n            cleaned = cleaned.strip()\n        else:\n            # Slightly more cleanup but still conservative\n            # Normalize subscripts\n            cleaned = cleaned.translate(str.maketrans(\"₀₁₂₃₄₅₆₇₈₉\", \"0123456789\"))\n            \n            # Fix spacing\n            cleaned = self.patterns[\"whitespace\"].sub(\" \", cleaned)\n            cleaned = self.patterns[\"space_before_punct\"].sub(r\"\\1\", cleaned)\n            \n            # Remove only truly problematic characters\n            cleaned = cleaned.replace(\"——\", \"-\")\n            \n            cleaned = cleaned.strip()\n        \n        return cleaned\n\n    def postprocess_batch(self, translations: List[str]) -> List[str]:\n        return [self.postprocess_single(t) for t in translations]\n\n# ------------------------------------------------------------\n# BUCKET BATCH SAMPLER\n# ------------------------------------------------------------\nclass BucketBatchSampler(Sampler):\n    def __init__(self, dataset: Dataset, batch_size: int, num_buckets: int, logger: logging.Logger, shuffle: bool = False):\n        self.dataset = dataset\n        self.batch_size = batch_size\n        self.shuffle = shuffle\n        self.logger = logger\n\n        lengths = [len(text.split()) for _, text in dataset]\n        sorted_indices = sorted(range(len(lengths)), 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            for i in range(0, len(bucket), self.batch_size):\n                yield bucket[i : i + self.batch_size]\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        return total\n\n# ------------------------------------------------------------\n# DATASET\n# ------------------------------------------------------------\nclass AkkadianDataset(Dataset):\n    def __init__(self, dataframe: pd.DataFrame, preprocessor: SmartPreprocessor, logger: logging.Logger):\n        self.sample_ids = dataframe[\"id\"].tolist()\n        raw_texts = dataframe[\"transliteration\"].tolist()\n        preprocessed = preprocessor.preprocess_batch(raw_texts)\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]\n\n# ------------------------------------------------------------\n# SCORE-OPTIMIZED INFERENCE ENGINE\n# ------------------------------------------------------------\nclass ScoreOptimizedEngine:\n    def __init__(self, config: ScoreOptimizedConfig, logger: logging.Logger):\n        self.config = config\n        self.logger = logger\n        self.preprocessor = SmartPreprocessor()\n        self.postprocessor = MinimalPostprocessor(minimal=config.minimal_postprocessing)\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        \n        self.model = AutoModelForSeq2SeqLM.from_pretrained(self.config.model_path)\n        self.model = self.model.to(self.config.device)\n        self.model = self.model.eval()\n        \n        self.tokenizer = AutoTokenizer.from_pretrained(self.config.model_path)\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        if self.config.use_better_transformer and torch.cuda.is_available():\n            try:\n                from optimum.bettertransformer import BetterTransformer\n                self.logger.info(\"Applying BetterTransformer...\")\n                self.model = BetterTransformer.transform(self.model)\n                self.logger.info(\"✅ BetterTransformer applied\")\n            except ImportError:\n                self.logger.warning(\"⚠️  'optimum' not installed, skipping BetterTransformer\")\n            except Exception as exc:\n                self.logger.warning(f\"⚠️  BetterTransformer failed: {exc}\")\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        \n        return batch_ids, tokenized\n\n    def _save_checkpoint(self):\n        if len(self.results) > 0 and len(self.results) % self.config.checkpoint_freq == 0:\n            checkpoint_path = 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_path, index=False)\n            self.logger.info(f\"💾 Checkpoint: {len(self.results)} translations\")\n\n    def run_inference(self, test_df: pd.DataFrame) -> pd.DataFrame:\n        self.logger.info(\"🚀 Starting SCORE-OPTIMIZED inference\")\n        \n        dataset = AkkadianDataset(test_df, self.preprocessor, self.logger)\n\n        if self.config.use_bucket_batching:\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        else:\n            dataloader = DataLoader(\n                dataset,\n                batch_size=self.config.batch_size,\n                shuffle=False,\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(\"Score optimization settings:\")\n        self.logger.info(f\"  🎯 Num Beams: {self.config.num_beams}\")\n        self.logger.info(f\"  🎯 Length Penalty: {self.config.length_penalty}\")\n        self.logger.info(f\"  🎯 Repetition Penalty: {self.config.repetition_penalty}\")\n        self.logger.info(f\"  🎯 No Repeat N-gram: {self.config.no_repeat_ngram_size}\")\n        self.logger.info(f\"  🎯 Minimal Postproc: {self.config.minimal_postprocessing}\")\n\n        # Build generation config - FIXED: Removed incompatible parameters\n        gen_config = {\n            \"max_new_tokens\": self.config.max_new_tokens,\n            \"num_beams\": self.config.num_beams,\n            \"length_penalty\": self.config.length_penalty,\n            \"repetition_penalty\": self.config.repetition_penalty,\n            \"early_stopping\": self.config.early_stopping,\n            \"no_repeat_ngram_size\": self.config.no_repeat_ngram_size,\n            \"use_cache\": True,\n            \"num_return_sequences\": self.config.num_return_sequences,\n        }\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                    # Generate\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                    # Decode\n                    translations = self.tokenizer.batch_decode(outputs, skip_special_tokens=True)\n\n                    # Handle multiple sequences per input\n                    if self.config.num_return_sequences > 1:\n                        # Group by input\n                        grouped_translations = []\n                        for i in range(0, len(translations), self.config.num_return_sequences):\n                            candidates = translations[i:i + self.config.num_return_sequences]\n                            # Pick longest or use ensemble logic\n                            best = max(candidates, key=len)\n                            grouped_translations.append(best)\n                        translations = grouped_translations\n\n                    # Minimal postprocessing\n                    cleaned = self.postprocessor.postprocess_batch(translations)\n\n                    # Store\n                    self.results.extend(list(zip(batch_ids, cleaned)))\n\n                    # Save checkpoint\n                    self._save_checkpoint()\n\n                    # Periodic cache clearing\n                    if torch.cuda.is_available() and batch_idx % 10 == 0:\n                        torch.cuda.empty_cache()\n\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\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        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\n# ------------------------------------------------------------\n# IO HELPERS\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\")\n\n    try:\n        from optimum.bettertransformer import BetterTransformer\n        print(\"✅ BetterTransformer available!\")\n    except ImportError:\n        print(\"❌ BetterTransformer NOT available\")\n\ndef save_outputs(results_df: pd.DataFrame, config: ScoreOptimizedConfig, output_dir: str, logger: logging.Logger):\n    output_path = Path(output_dir) / \"submission.csv\"\n    config_path = Path(output_dir) / \"score_optimized_config.json\"\n\n    results_df.to_csv(output_path, index=False)\n    logger.info(f\"✅ Submission saved to {output_path}\")\n\n    config_dict = {\n        \"batch_size\": config.batch_size,\n        \"num_beams\": config.num_beams,\n        \"length_penalty\": config.length_penalty,\n        \"repetition_penalty\": config.repetition_penalty,\n        \"no_repeat_ngram_size\": config.no_repeat_ngram_size,\n        \"minimal_postprocessing\": config.minimal_postprocessing,\n        \"optimizations\": {\n            \"mixed_precision\": config.use_mixed_precision,\n            \"better_transformer\": config.use_better_transformer,\n            \"bucket_batching\": config.use_bucket_batching,\n        },\n    }\n\n    with open(config_path, \"w\", encoding=\"utf-8\") as f:\n        json.dump(config_dict, f, indent=2)\n\n    print(\"\\n\" + \"=\" * 60)\n    print(\"🎉 SCORE-OPTIMIZED 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_score_optimized.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\"\\nSubmission shape: {submission.shape}\")\n    print(\"\\nFirst 10 translations:\")\n    print(submission.head(10))\n    print(\"\\nLast 10 translations:\")\n    print(submission.tail(10))\n\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\n    if empty > 0:\n        print(\"\\nEmpty translation IDs:\")\n        print(submission[submission[\"translation\"].astype(str).str.strip().eq(\"\")][\"id\"].tolist())\n\n# ------------------------------------------------------------\n# MAIN\n# ------------------------------------------------------------\ndef main():\n    config = ScoreOptimizedConfig()\n    logger = setup_logging(config.output_dir)\n    logger.info(\"Logging initialized\")\n\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(f\"  Repetition Penalty: {config.repetition_penalty}\")\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 = ScoreOptimizedEngine(config, logger)\n    results_df = engine.run_inference(test_df)\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-03T08:23:29.916772Z","iopub.execute_input":"2026-02-03T08:23:29.917179Z","iopub.status.idle":"2026-02-03T08:24:28.988324Z","shell.execute_reply.started":"2026-02-03T08:23:29.917147Z","shell.execute_reply":"2026-02-03T08:24:28.987497Z"}},"outputs":[],"execution_count":null}]}