|
|
| """SENTINEL LLM training entry point."""
|
| from __future__ import annotations
|
|
|
| import argparse
|
| import logging
|
| import sys
|
| import time
|
| import warnings
|
| from pathlib import Path
|
|
|
| warnings.filterwarnings("ignore", category=RuntimeWarning)
|
| warnings.filterwarnings("ignore", category=UserWarning)
|
|
|
| import os
|
| os.environ["TRANSFORMERS_VERBOSITY"] = "error"
|
|
|
| logging.basicConfig(
|
| level=logging.INFO,
|
| format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
| datefmt="%H:%M:%S",
|
| )
|
| logger = logging.getLogger("sentinel.train")
|
|
|
|
|
| def parse_args() -> argparse.Namespace:
|
| parser = argparse.ArgumentParser(
|
| description="Train or evaluate a SENTINEL LLM agent via GRPO",
|
| formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
| )
|
| parser.add_argument(
|
| "--agent", choices=["holmes", "forge", "argus", "hermes", "oracle"], default="holmes",
|
| help="Which agent role to train.",
|
| )
|
| parser.add_argument(
|
| "--model",
|
| default="unsloth/Qwen2.5-7B-Instruct-bnb-4bit",
|
| help="HuggingFace / Unsloth model name or local path.",
|
| )
|
| parser.add_argument(
|
| "--load-in-4bit",
|
| dest="load_in_4bit",
|
| action="store_true",
|
| default=True,
|
| help="Load the base model in 4-bit quantized mode.",
|
| )
|
| parser.add_argument(
|
| "--no-4bit",
|
| dest="load_in_4bit",
|
| action="store_false",
|
| help="Disable 4-bit loading and use full-precision/bf16 weights instead.",
|
| )
|
| parser.add_argument("--episodes", type=int, default=200, help="Training episodes.")
|
| parser.add_argument("--batch-size", type=int, default=4, help="Per-device GRPO batch size.")
|
| parser.add_argument("--lora-r", type=int, default=16, help="LoRA rank.")
|
| parser.add_argument("--lora-alpha", type=int, default=32, help="LoRA alpha.")
|
| parser.add_argument("--checkpoint-dir", default="checkpoints", help="Checkpoint directory.")
|
| parser.add_argument("--log-file", default="training_log.jsonl", help="Episode metrics JSONL.")
|
| parser.add_argument("--env-spec", default="env_spec.yaml", help="Path to env_spec.yaml.")
|
| parser.add_argument("--resume", action="store_true", help="Resume from latest checkpoint.")
|
| parser.add_argument("--eval-only", action="store_true", help="Skip training and run evaluation.")
|
| parser.add_argument("--eval-episodes", type=int, default=20, help="Episodes per tier in evaluation.")
|
| parser.add_argument("--seed", type=int, default=42, help="Random seed.")
|
| return parser.parse_args()
|
|
|
|
|
| def main() -> int:
|
| args = parse_args()
|
|
|
| logger.info("Importing SENTINEL modules …")
|
| from sentinel.env import Sentinel_Env
|
| from sentinel.training.evaluate import print_eval_report, run_evaluation
|
| from sentinel.training.pipeline import (
|
| TrainingConfig,
|
| build_grpo_trainer,
|
| load_latest_checkpoint,
|
| run_training_loop,
|
| )
|
|
|
| logger.info("Initialising environment from %s …", args.env_spec)
|
| env = Sentinel_Env(config_path=args.env_spec)
|
| reward_fn = env.reward_function
|
|
|
| config = TrainingConfig(
|
| agent=args.agent,
|
| model_name=args.model,
|
| load_in_4bit=args.load_in_4bit,
|
| batch_size=args.batch_size,
|
| max_steps=args.episodes,
|
| lora_r=args.lora_r,
|
| lora_alpha=args.lora_alpha,
|
| checkpoint_dir=str(Path(args.checkpoint_dir) / args.agent),
|
| log_file=args.log_file,
|
| )
|
|
|
| logger.info("=" * 54)
|
| logger.info(" SENTINEL Mode : LLM (GRPO)")
|
| logger.info(" Agent : %s", args.agent)
|
| logger.info(" Model : %s", args.model)
|
| logger.info(" 4-bit : %s", "on" if args.load_in_4bit else "off")
|
| logger.info("=" * 54)
|
|
|
| trainer, llm_agent = build_grpo_trainer(
|
| agent=args.agent,
|
| env=env,
|
| config=config,
|
| )
|
|
|
| start_episode = 0
|
| if args.resume:
|
| ckpt = load_latest_checkpoint(config.checkpoint_dir)
|
| if ckpt:
|
| start_episode = ckpt.get("episode", 0) + 1
|
| logger.info("Resuming from episode %d …", start_episode)
|
| else:
|
| logger.info("No checkpoint found in %s; starting from episode 0.", config.checkpoint_dir)
|
|
|
| if args.eval_only:
|
| logger.info("Eval-only mode: running %d episodes per tier …", args.eval_episodes)
|
| results = run_evaluation(
|
| env,
|
| reward_fn,
|
| llm_agent=llm_agent,
|
| episodes_per_tier=args.eval_episodes,
|
| seed=args.seed,
|
| )
|
| print_eval_report(results)
|
| return 0
|
|
|
| logger.info(
|
| "Starting training | agent=%s | mode=LLM (GRPO) | episodes=%d | start=%d",
|
| args.agent, args.episodes, start_episode,
|
| )
|
| t0 = time.perf_counter()
|
|
|
| all_metrics = run_training_loop(
|
| trainer=trainer,
|
| env=env,
|
| config=config,
|
| reward_fn=reward_fn,
|
| start_episode=start_episode,
|
| llm_agent=llm_agent,
|
| )
|
|
|
| elapsed = time.perf_counter() - t0
|
| logger.info(
|
| "Training complete: %d episodes in %.1f s (%.2f s/ep)",
|
| len(all_metrics), elapsed, elapsed / max(len(all_metrics), 1),
|
| )
|
|
|
| if all_metrics:
|
| last_10 = all_metrics[-10:]
|
| avg_reward = sum(m.total_reward for m in last_10) / len(last_10)
|
| avg_mttr = sum(m.mttr for m in last_10) / len(last_10)
|
| logger.info(
|
| "Last 10 episodes | avg_reward=%.3f | avg_MTTR=%.1f",
|
| avg_reward, avg_mttr,
|
| )
|
|
|
| logger.info("Running post-training evaluation (%d eps/tier) …", args.eval_episodes)
|
| results = run_evaluation(
|
| env,
|
| reward_fn,
|
| llm_agent=llm_agent,
|
| episodes_per_tier=args.eval_episodes,
|
| seed=args.seed,
|
| )
|
| print_eval_report(results)
|
| return 0
|
|
|
|
|
| if __name__ == "__main__":
|
| sys.exit(main())
|
|
|