llm-gateway/packages/fine-tuner/scripts/train_blog_direct.py
Rene Fichtmueller 2ca77d0aee feat: Phase 2F — Multi-Agent Integration (ADRs + Client Fallback + Tests)
- ADR-0001: Multi-Agent Coworking Architecture with LLM Gateway Orchestrator
- ADR-0002: Tier Assignment Strategy for Model Selection (cost-first escalation)
- ADR-0003: Confidence Gate Thresholds & Learning Cycle Intervals (6h/12h/24h cycles)
- ADR-0004: External Provider Fallback Chain Ordering (Cerebras → Groq → Mistral)
- Enhanced client SDK: Offline Ollama fallback, health checks, exponential backoff retry
- Integration tests: claude-code-integration.test.ts (14 test cases)
- PHASE_2F_DEPLOYMENT.md: Pre-deployment checklist, automated deploy, rollback plan
- Post-deployment verification procedures for health, client fallback, metrics
2026-04-19 21:39:44 +02:00

147 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
Train BlogLLM directly from JSONL without database dependency.
Loads blog-training-alpaca.jsonl and runs LoRA fine-tuning.
Usage:
python3 scripts/train_blog_direct.py
python3 scripts/train_blog_direct.py --model Qwen/Qwen2.5-7B-Instruct --output models/fo-blog-v4
"""
import json
import sys
from pathlib import Path
from typing import Optional
# Add package to path
REPO_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(REPO_ROOT.parent.parent))
sys.path.insert(0, str(REPO_ROOT))
from src.trainer import run_lora_training
import yaml
def load_jsonl(path: Path) -> list[dict]:
"""Load Alpaca-format JSONL samples and convert to trainer format."""
samples = []
with open(path) as f:
for line in f:
if line.strip():
sample = json.loads(line)
# Convert Alpaca format to trainer's expected format
# trainer.py expects: system_prompt, input_text, output_text
samples.append({
"system_prompt": "You are an expert blog writer specializing in optical transceiver technology and network infrastructure. Write clear, practical blog posts targeted at network engineers and architects.",
"input_text": sample.get("instruction", ""),
"output_text": sample.get("output", ""),
})
return samples
def main():
import argparse
parser = argparse.ArgumentParser(
description="Train BlogLLM from JSONL directly",
)
parser.add_argument(
"--model",
default="Qwen/Qwen2.5-14B-Instruct",
help="Base model ID (default: Qwen/Qwen2.5-14B-Instruct)",
)
parser.add_argument(
"--output",
default="models/fo-blog-v4",
help="Output directory for adapters",
)
parser.add_argument(
"--config",
default="config/fine_tuner.yaml",
help="Path to fine_tuner.yaml",
)
parser.add_argument(
"--jsonl",
default="data/blog-training-alpaca.jsonl",
help="Path to training JSONL",
)
args = parser.parse_args()
# Load config
config_path = REPO_ROOT / args.config
with open(config_path) as f:
cfg = yaml.safe_load(f)
training_cfg = cfg["training"]
eval_cfg = cfg["evaluation"]
llama_cfg = cfg["llama_cpp"]
models_cfg = cfg["models"]
# Load training data
jsonl_path = REPO_ROOT / args.jsonl
print(f"📂 Loading {jsonl_path}...")
if not jsonl_path.exists():
print(f"❌ File not found: {jsonl_path}")
sys.exit(1)
samples = load_jsonl(jsonl_path)
print(f"✅ Loaded {len(samples)} training samples")
# Split into train/val (80/20)
split_idx = int(len(samples) * 0.8)
train_samples = samples[:split_idx]
val_samples = samples[split_idx:]
print(f" Train: {len(train_samples)} | Val: {len(val_samples)}")
# Run training
output_dir = REPO_ROOT / args.output
output_dir.mkdir(parents=True, exist_ok=True)
print(f"\n🚀 Starting LoRA fine-tuning...")
print(f" Model: {args.model}")
print(f" Output: {output_dir}")
print(f" Epochs: {training_cfg['sft']['num_epochs']}")
print(f" LoRA r={training_cfg['lora_r']}, alpha={training_cfg['lora_alpha']}")
print(f" Max seq length: {training_cfg['max_seq_length']}")
print()
try:
metrics = run_lora_training(
base_model_path=args.model,
train_examples=train_samples,
val_examples=val_samples,
output_dir=str(output_dir),
task_type="tip_blog",
lora_r=training_cfg["lora_r"],
lora_alpha=training_cfg["lora_alpha"],
lora_dropout=training_cfg["lora_dropout"],
max_seq_length=training_cfg["max_seq_length"],
num_epochs=training_cfg["sft"]["num_epochs"],
batch_size=training_cfg["sft"]["batch_size"],
gradient_accumulation_steps=training_cfg["sft"]["gradient_accumulation"],
learning_rate=training_cfg["sft"]["learning_rate"],
warmup_ratio=training_cfg["sft"]["warmup_ratio"],
)
print(f"\n✅ Training complete!")
print(f" Metrics: {metrics}")
print(f"\n📦 Converting to GGUF for Ollama...")
# Optional: Convert to GGUF
gguf_path = output_dir / "model.gguf"
print(f" Output: {gguf_path}")
print(f"\n🎯 Next: Load into Ollama with: ollama create fo-blog-v4 -f Modelfile")
except Exception as e:
print(f"\n❌ Training failed: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()