+ "source": "import math\nimport gc\nimport numpy as np\nfrom datasets import Dataset\nfrom transformers import AutoTokenizer, TrainingArguments\nfrom datafog_pii_ner.data.collator import PiiDataCollator\nfrom datafog_pii_ner.data.label_schema import NUM_LABELS\nfrom datafog_pii_ner.model.pii_model import PiiNerConfig, PiiNerModel\nfrom datafog_pii_ner.training.train import PiiTrainer\n\n\ndef run_preflight(batch_size, seq_len, max_char_len, backbone, lr_backbone, lr_head, fp16, bf16):\n \"\"\"Run 5 training steps and return (passed: bool, loss: float).\"\"\"\n label = \"BF16\" if bf16 else \"FP16\" if fp16 else \"FP32\"\n print(f\"\\n--- Testing {label} (batch_size={batch_size}) ---\")\n \n tokenizer = AutoTokenizer.from_pretrained(backbone)\n n_samples = batch_size * 2\n\n fake_data = {\n \"input_ids\": np.random.randint(1, 1000, (n_samples, seq_len)).tolist(),\n \"attention_mask\": np.ones((n_samples, seq_len), dtype=int).tolist(),\n \"labels\": np.random.randint(0, NUM_LABELS, (n_samples, seq_len)).tolist(),\n \"char_ids\": np.random.randint(0, 100, (n_samples, seq_len, max_char_len)).tolist(),\n }\n ds = Dataset.from_dict(fake_data)\n collator = PiiDataCollator(tokenizer=tokenizer, max_char_len=max_char_len)\n config = PiiNerConfig(backbone=backbone, num_labels=NUM_LABELS)\n model = PiiNerModel(config)\n\n args = TrainingArguments(\n output_dir=\"/tmp/preflight_check\",\n num_train_epochs=1,\n per_device_train_batch_size=batch_size,\n learning_rate=lr_backbone,\n fp16=fp16,\n bf16=bf16,\n report_to=\"none\",\n logging_steps=1,\n max_steps=5,\n remove_unused_columns=False,\n save_strategy=\"no\",\n )\n\n trainer = PiiTrainer(\n model=model, args=args, train_dataset=ds, data_collator=collator,\n lr_backbone=lr_backbone, lr_head=lr_head,\n )\n\n try:\n result = trainer.train()\n loss = result.training_loss\n except Exception as e:\n print(f\" FAIL: {type(e).__name__}: {e}\")\n del model, trainer, ds, collator, args\n gc.collect(); torch.cuda.empty_cache()\n return False, float(\"nan\")\n\n # NaN checks\n passed = True\n\n if math.isnan(loss) or math.isinf(loss):\n print(f\" FAIL: Training loss is {loss}\")\n passed = False\n else:\n print(f\" PASS: Training loss = {loss:.4f}\")\n\n nan_params = [n for n, p in model.named_parameters() if torch.isnan(p).any()]\n if nan_params:\n print(f\" FAIL: NaN in {len(nan_params)} parameter tensors\")\n passed = False\n else:\n print(f\" PASS: All parameter tensors are finite\")\n\n log_losses = [h[\"loss\"] for h in trainer.state.log_history if \"loss\" in h]\n if log_losses and any(math.isnan(l) for l in log_losses):\n print(f\" FAIL: NaN in step losses: {log_losses}\")\n passed = False\n elif len(log_losses) >= 2 and log_losses[-1] < log_losses[0]:\n print(f\" PASS: Loss decreasing ({log_losses[0]:.1f} → {log_losses[-1]:.1f})\")\n\n del model, trainer, ds, collator, args, result\n gc.collect(); torch.cuda.empty_cache()\n return passed, loss\n\n\n# === Run preflight with auto-fallback ===\nprint(\"=== PREFLIGHT CHECK ===\")\n\npreflight_ok = False\npreflight_args = dict(\n batch_size=CONFIG[\"batch_size\"],\n seq_len=CONFIG[\"max_seq_len\"],\n max_char_len=CONFIG[\"max_char_len\"],\n backbone=CONFIG[\"backbone\"],\n lr_backbone=CONFIG[\"lr_backbone\"],\n lr_head=CONFIG[\"lr_head\"],\n)\n\n# Try 1: BF16 (if GPU supports it)\nif CONFIG.get(\"_try_bf16\", False):\n ok, loss = run_preflight(**preflight_args, fp16=False, bf16=True)\n if ok:\n CONFIG[\"fp16\"] = False\n CONFIG[\"bf16\"] = True\n preflight_ok = True\n print(f\"\\n=== BF16 PREFLIGHT PASSED (loss={loss:.4f}) ===\")\n else:\n print(f\"\\nBF16 failed — falling back to FP32\")\n\n# Try 2: FP32 (guaranteed fallback)\nif not preflight_ok:\n ok, loss = run_preflight(**preflight_args, fp16=False, bf16=False)\n if ok:\n CONFIG[\"fp16\"] = False\n CONFIG[\"bf16\"] = False\n preflight_ok = True\n print(f\"\\n=== FP32 PREFLIGHT PASSED (loss={loss:.4f}) ===\")\n else:\n raise RuntimeError(\n \"PREFLIGHT FAILED on FP32 — something is fundamentally broken. \"\n \"Check model architecture and data pipeline.\"\n )\n\nprecision = \"BF16\" if CONFIG[\"bf16\"] else \"FP16\" if CONFIG[\"fp16\"] else \"FP32\"\nprint(f\"\\nSelected precision: {precision}\")\nprint(f\"Training will proceed with: fp16={CONFIG['fp16']}, bf16={CONFIG['bf16']}\")",
0 commit comments