{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14661932,"sourceType":"datasetVersion","datasetId":9366631},{"sourceId":171067769,"sourceType":"kernelVersion"}],"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 (WITH LOCAL OPTIMUM)\n# Deep Past Challenge - Translate Akkadian to English\n#\n# KEY POINTS:\n# - Uses optimum from /kaggle/input/optimum-installation (no pip install)\n# - BetterTransformer enabled if present\n# - Keeps all tuned hyperparameters and optimizations\n# =============================================================================\n\n# === ENVIRONMENT OPTIMIZATION ===\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\n# === PREPEND LOCAL OPTIMUM PATH ===\nimport sys\nOPTIMUM_PATH = \"/kaggle/input/optimum-installation\"\nif os.path.isdir(OPTIMUM_PATH):\n    sys.path.insert(0, OPTIMUM_PATH)\n\n# === IMPORTS ===\nimport re\nimport logging\nimport warnings\nfrom pathlib import Path\nfrom typing import List\nfrom dataclasses import dataclass\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\nprint(f\"PyTorch: {torch.__version__}\")\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\"Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB\")\n\n# =============================================================================\n# CONFIGURATION\n# =============================================================================\n@dataclass\nclass UltraConfig:\n    \"\"\"Ultra-optimized configuration\"\"\"\n\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 = 12\n    num_workers: int = 4\n\n    # === GENERATION (preserved tuned values) ===\n    num_beams: int = 8\n    max_new_tokens: int = 512\n    length_penalty: float = 1.09\n    early_stopping: bool = True\n    no_repeat_ngram_size: int = 0\n    repetition_penalty: float = 1.05\n\n    # === OPTIMIZATIONS ===\n    use_mixed_precision: bool = True\n    use_better_transformer: bool = True    # now backed by local optimum\n    use_torch_compile: bool = True\n    use_bucket_batching: bool = True\n    use_vectorized_postproc: bool = True\n    use_adaptive_beams: bool = True\n    use_gpu_warmup: bool = True\n\n    # === OTHER ===\n    aggressive_postprocessing: bool = True\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            self.use_torch_compile = False\n            self.use_gpu_warmup = False\n\nconfig = UltraConfig()\nprint(f\"\\n📋 Config: batch={config.batch_size}, beams={config.num_beams}\")\n\n# =============================================================================\n# LOGGING\n# =============================================================================\ndef setup_logging(output_dir: str = './outputs'):\n    Path(output_dir).mkdir(exist_ok=True, parents=True)\n    log_file = Path(output_dir) / 'inference_ultra.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=[logging.StreamHandler(), logging.FileHandler(log_file)]\n    )\n    return logging.getLogger(__name__)\n\nlogger = setup_logging(config.output_dir)\n\n# =============================================================================\n# PREPROCESSOR\n# =============================================================================\nclass OptimizedPreprocessor:\n    \"\"\"Preprocessor with pre-compiled patterns\"\"\"\n\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    \"\"\"Ultra-fast vectorized postprocessing\"\"\"\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        self.forbidden_chars = '!?()\"——<>⌈⌋⌊[]+ʾ/;'\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 bool(x.strip()))\n        s = s.where(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            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            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            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            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# BUCKET SAMPLER\n# =============================================================================\nclass BucketBatchSampler(Sampler):\n    \"\"\"Batch by length with index tracking for proper reordering\"\"\"\n\n    def __init__(self, dataset, batch_size: int, num_buckets: int = 4):\n        self.batch_size = batch_size\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) // num_buckets)\n        self.buckets = []\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            bucket = sorted_indices[start:end]\n            if bucket:\n                self.buckets.append(bucket)\n\n        logger.info(f\"Created {len(self.buckets)} buckets\")\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# DATASET\n# =============================================================================\nclass AkkadianDataset(Dataset):\n    def __init__(self, dataframe: pd.DataFrame, preprocessor: OptimizedPreprocessor):\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: \" + t for t in preprocessed]\n        logger.info(f\"Dataset: {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# ULTRA 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.results = []\n        self._load_model()\n\n    def _load_model(self):\n        logger.info(f\"Loading model from {self.config.model_path}\")\n\n        dtype = torch.float16 if self.config.use_mixed_precision else torch.float32\n\n        self.model = AutoModelForSeq2SeqLM.from_pretrained(\n            self.config.model_path,\n            torch_dtype=dtype,\n        ).to(self.config.device).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        logger.info(f\"Model: {num_params:,} parameters\")\n\n        # BetterTransformer via local optimum\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\n        if self.config.use_torch_compile and hasattr(torch, \"compile\"):\n            try:\n                self.model = torch.compile(self.model, mode=\"reduce-overhead\")\n                logger.info(\"✅ torch.compile() applied\")\n            except Exception as e:\n                logger.warning(f\"⚠️ torch.compile() skipped: {e}\")\n\n        if self.config.use_gpu_warmup and torch.cuda.is_available():\n            self._warmup_gpu()\n\n    def _warmup_gpu(self):\n        logger.info(\"🔥 GPU warmup...\")\n        dummy = torch.zeros(1, 32, dtype=torch.long, device=self.config.device)\n        with torch.inference_mode():\n            try:\n                if self.config.use_mixed_precision:\n                    with autocast():\n                        _ = self.model.generate(dummy, max_new_tokens=8, num_beams=2)\n                else:\n                    _ = self.model.generate(dummy, max_new_tokens=8, num_beams=2)\n                torch.cuda.synchronize()\n                logger.info(\"✅ GPU warmed up\")\n            except Exception:\n                logger.warning(\"⚠️ GPU warmup failed (ignored)\")\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 _get_adaptive_beam_size(self, attention_mask):\n        if not self.config.use_adaptive_beams:\n            return self.config.num_beams\n\n        avg_len = attention_mask.sum(dim=1).float().mean().item()\n        if avg_len < 100:\n            return max(4, self.config.num_beams // 2)\n        return self.config.num_beams\n\n    def run_inference(self, test_df: pd.DataFrame) -> pd.DataFrame:\n        logger.info(\"🚀 Starting inference\")\n\n        dataset = AkkadianDataset(test_df, self.preprocessor)\n\n        if self.config.use_bucket_batching:\n            batch_sampler = BucketBatchSampler(dataset, self.config.batch_size, self.config.num_buckets)\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 if self.config.num_workers > 0 else None,\n                persistent_workers=self.config.num_workers > 0,\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            )\n\n        logger.info(f\"DataLoader: {len(dataloader)} batches\")\n\n        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            \"use_cache\": True,\n        }\n        if self.config.no_repeat_ngram_size > 0:\n            gen_config[\"no_repeat_ngram_size\"] = self.config.no_repeat_ngram_size\n        if self.config.repetition_penalty != 1.0:\n            gen_config[\"repetition_penalty\"] = self.config.repetition_penalty\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                    beam_size = self._get_adaptive_beam_size(attention_mask)\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                                num_beams=beam_size,\n                                **gen_config,\n                            )\n                    else:\n                        outputs = self.model.generate(\n                            input_ids=input_ids,\n                            attention_mask=attention_mask,\n                            num_beams=beam_size,\n                            **gen_config,\n                        )\n\n                    translations = self.tokenizer.batch_decode(outputs, skip_special_tokens=True)\n                    cleaned = self.postprocessor.postprocess_batch(translations)\n\n                    self.results.extend(zip(batch_ids, cleaned))\n\n                    if torch.cuda.is_available() and batch_idx % 10 == 0:\n                        torch.cuda.empty_cache()\n\n                except Exception as e:\n                    logger.error(f\"❌ Batch {batch_idx} error: {e}\")\n                    self.results.extend([(bid, \"\") for bid in batch_ids])\n\n        logger.info(\"✅ Inference completed\")\n\n        results_df = pd.DataFrame(self.results, columns=[\"id\", \"translation\"])\n        results_df = results_df.sort_values(\"id\").reset_index(drop=True)\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\"].str.strip().eq(\"\").sum()\n        print(f\"Empty: {empty} ({empty / len(df) * 100:.2f}%)\")\n\n        lengths = df[\"translation\"].str.len()\n        print(f\"Length: mean={lengths.mean():.1f}, min={lengths.min()}, max={lengths.max()}\")\n\n        if df[\"id\"].tolist() == sorted(df[\"id\"].tolist()):\n            print(\"✅ Output order: CORRECT\")\n        else:\n            print(\"❌ Output order: WRONG\")\n\n        print(\"\\n📝 Sample translations:\")\n        for idx in [0, len(df) // 2, len(df) - 1]:\n            s = df.iloc[idx]\n            preview = s[\"translation\"][:60] + \"...\" if len(s[\"translation\"]) > 60 else s[\"translation\"]\n            print(f\"   ID {s['id']:4d}: {preview}\")\n\n        print(\"=\" * 60 + \"\\n\")\n\n# =============================================================================\n# MAIN\n# =============================================================================\nif __name__ == \"__main__\":\n    logger.info(f\"Loading {config.test_data_path}\")\n    test_df = pd.read_csv(config.test_data_path, encoding=\"utf-8\")\n    logger.info(f\"✅ {len(test_df)} samples\")\n\n    engine = UltraInferenceEngine(config)\n    results_df = engine.run_inference(test_df)\n\n    output_path = Path(config.output_dir) / \"submission.csv\"\n    results_df.to_csv(output_path, index=False)\n\n    print(f\"\\n🎉 Saved to {output_path}\")\n    print(f\"Total: {len(results_df)} translations\")\n    print(results_df.head())\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}