Smoke mode + detailed train entry
Browse files- kaggle/train_grpo.py +275 -1
kaggle/train_grpo.py
CHANGED
|
@@ -775,6 +775,7 @@ class Cfg:
|
|
| 775 |
max_eval_turns: int = 8,
|
| 776 |
max_train_instances: int = 16,
|
| 777 |
lora_r: int = 16,
|
|
|
|
| 778 |
) -> None:
|
| 779 |
self.dataset_id = dataset_id
|
| 780 |
self.hub_model_id = hub_model_id
|
|
@@ -791,6 +792,8 @@ class Cfg:
|
|
| 791 |
self.max_eval_turns = max_eval_turns
|
| 792 |
self.max_train_instances = max_train_instances
|
| 793 |
self.lora_r = lora_r
|
|
|
|
|
|
|
| 794 |
|
| 795 |
|
| 796 |
def _load_swe_splits(cfg: Cfg):
|
|
@@ -829,10 +832,275 @@ def _load_swe_splits(cfg: Cfg):
|
|
| 829 |
return Dataset.from_list(train_rows), eval_rows
|
| 830 |
|
| 831 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
def main(cfg: Cfg | None = None) -> None:
|
| 833 |
global _TOKENIZER
|
| 834 |
cfg = cfg or Cfg()
|
| 835 |
-
_vlog(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 836 |
load_secrets()
|
| 837 |
_ensure_package()
|
| 838 |
|
|
@@ -851,6 +1119,12 @@ def main(cfg: Cfg | None = None) -> None:
|
|
| 851 |
except Exception as e: # noqa: BLE001
|
| 852 |
_vlog("main.logging_configure_failed", error=str(e))
|
| 853 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 854 |
import torch
|
| 855 |
import wandb
|
| 856 |
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|
|
|
|
| 775 |
max_eval_turns: int = 8,
|
| 776 |
max_train_instances: int = 16,
|
| 777 |
lora_r: int = 16,
|
| 778 |
+
mode: str | None = None,
|
| 779 |
) -> None:
|
| 780 |
self.dataset_id = dataset_id
|
| 781 |
self.hub_model_id = hub_model_id
|
|
|
|
| 792 |
self.max_eval_turns = max_eval_turns
|
| 793 |
self.max_train_instances = max_train_instances
|
| 794 |
self.lora_r = lora_r
|
| 795 |
+
# mode: "smoke" = 1 instance + reward checks then exit; "full" = GRPO train
|
| 796 |
+
self.mode = (mode or os.getenv("DARL_MODE") or "full").strip().lower()
|
| 797 |
|
| 798 |
|
| 799 |
def _load_swe_splits(cfg: Cfg):
|
|
|
|
| 832 |
return Dataset.from_list(train_rows), eval_rows
|
| 833 |
|
| 834 |
|
| 835 |
+
def run_smoke_one_case(cfg: Cfg) -> dict[str, Any]:
|
| 836 |
+
"""One SWE instance end-to-end: env tools + empty-patch reward + 1 agent rollout.
|
| 837 |
+
|
| 838 |
+
Hard-fails if grader/env/reward plumbing is broken. Soft-warns if the small
|
| 839 |
+
model makes zero tool calls or scores 0 (common before training).
|
| 840 |
+
"""
|
| 841 |
+
import torch
|
| 842 |
+
import wandb
|
| 843 |
+
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|
| 844 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
|
| 845 |
+
|
| 846 |
+
from deepagents_rl.train.swe_env import DeepAgentSWEEnv
|
| 847 |
+
|
| 848 |
+
out_dir = Path("/kaggle/working/grpo-out")
|
| 849 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 850 |
+
|
| 851 |
+
# Force single-case data
|
| 852 |
+
cfg.max_train_instances = 1
|
| 853 |
+
cfg.n_eval = 1
|
| 854 |
+
cfg.max_eval_turns = max(cfg.max_eval_turns, 10)
|
| 855 |
+
train, eval_problems = _load_swe_splits(cfg)
|
| 856 |
+
if eval_problems:
|
| 857 |
+
problem = dict(eval_problems[0])
|
| 858 |
+
else:
|
| 859 |
+
problem = dict(train[0])
|
| 860 |
+
instance_id = problem.get("instance_id") or problem.get("task_id")
|
| 861 |
+
_vlog("smoke.instance", instance_id=instance_id, keys=list(problem.keys())[:20])
|
| 862 |
+
|
| 863 |
+
checks: dict[str, Any] = {
|
| 864 |
+
"instance_id": instance_id,
|
| 865 |
+
"grader_health": False,
|
| 866 |
+
"env_reset": False,
|
| 867 |
+
"ls_root_ok": False,
|
| 868 |
+
"ls_src_aliased": False,
|
| 869 |
+
"empty_patch_reward_ok": False,
|
| 870 |
+
"empty_patch_reward": None,
|
| 871 |
+
"empty_patch_report": {},
|
| 872 |
+
"agent_reward": None,
|
| 873 |
+
"agent_n_tool_calls": 0,
|
| 874 |
+
"agent_tools_used": [],
|
| 875 |
+
"agent_resolved": False,
|
| 876 |
+
"langfuse_trace_id": "",
|
| 877 |
+
"hard_ok": False,
|
| 878 |
+
"soft_ok": False,
|
| 879 |
+
"errors": [],
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
import requests
|
| 883 |
+
|
| 884 |
+
grader = os.environ["SWE_GRADER_URL"].rstrip("/")
|
| 885 |
+
headers = {"ngrok-skip-browser-warning": "1"}
|
| 886 |
+
if os.environ.get("SWE_GRADER_TOKEN"):
|
| 887 |
+
headers["Authorization"] = f"Bearer {os.environ['SWE_GRADER_TOKEN']}"
|
| 888 |
+
health = requests.get(f"{grader}/health", headers=headers, timeout=30)
|
| 889 |
+
health.raise_for_status()
|
| 890 |
+
checks["grader_health"] = True
|
| 891 |
+
_vlog("smoke.grader_health", body=health.json())
|
| 892 |
+
|
| 893 |
+
os.environ["WANDB_PROJECT"] = os.getenv("WANDB_PROJECT", "deepagents-rl-coding")
|
| 894 |
+
wandb.login(key=os.environ["WANDB_API_KEY"])
|
| 895 |
+
wandb.init(
|
| 896 |
+
project=os.environ["WANDB_PROJECT"],
|
| 897 |
+
name="kaggle-grpo-swe-smoke",
|
| 898 |
+
job_type="smoke",
|
| 899 |
+
config={"mode": "smoke", "instance_id": instance_id},
|
| 900 |
+
)
|
| 901 |
+
|
| 902 |
+
# --- Phase 1: deterministic env + empty-patch grader ---
|
| 903 |
+
env = DeepAgentSWEEnv()
|
| 904 |
+
try:
|
| 905 |
+
reset_kwargs = {
|
| 906 |
+
k: v
|
| 907 |
+
for k, v in problem.items()
|
| 908 |
+
if k
|
| 909 |
+
not in (
|
| 910 |
+
"prompt",
|
| 911 |
+
"problem_statement",
|
| 912 |
+
)
|
| 913 |
+
}
|
| 914 |
+
# Ensure docker_image key for reset
|
| 915 |
+
if "docker_image" not in reset_kwargs and problem.get("docker_image"):
|
| 916 |
+
reset_kwargs["docker_image"] = problem["docker_image"]
|
| 917 |
+
tip = env.reset(**reset_kwargs)
|
| 918 |
+
checks["env_reset"] = True
|
| 919 |
+
_vlog("smoke.env.reset", tip_preview=str(tip)[:200])
|
| 920 |
+
|
| 921 |
+
ls_root = env.ls(".")
|
| 922 |
+
checks["ls_root_ok"] = bool(ls_root) and "path_not_found" not in ls_root.lower()
|
| 923 |
+
_vlog("smoke.ls_root", preview=str(ls_root)[:500], ok=checks["ls_root_ok"])
|
| 924 |
+
|
| 925 |
+
ls_src = env.ls("src")
|
| 926 |
+
# Must NOT be bare ERROR path_not_found — alias/fallback should list root
|
| 927 |
+
checks["ls_src_aliased"] = (
|
| 928 |
+
"path_not_found" not in ls_src.lower()
|
| 929 |
+
or "repo root" in ls_src.lower()
|
| 930 |
+
or "showing repo root" in ls_src.lower()
|
| 931 |
+
or "listing repo root" in ls_src.lower()
|
| 932 |
+
or "not used in this repo" in ls_src.lower()
|
| 933 |
+
)
|
| 934 |
+
_vlog("smoke.ls_src", preview=str(ls_src)[:500], ok=checks["ls_src_aliased"])
|
| 935 |
+
|
| 936 |
+
empty_reward = float(env.get_reward())
|
| 937 |
+
empty_report = getattr(env, "_last_report", {}) or {}
|
| 938 |
+
checks["empty_patch_reward"] = empty_reward
|
| 939 |
+
checks["empty_patch_report"] = {
|
| 940 |
+
k: empty_report.get(k)
|
| 941 |
+
for k in (
|
| 942 |
+
"pass_frac",
|
| 943 |
+
"resolved",
|
| 944 |
+
"resolution",
|
| 945 |
+
"error",
|
| 946 |
+
"reward_total",
|
| 947 |
+
)
|
| 948 |
+
}
|
| 949 |
+
# Hard OK if grader returned a numeric reward without transport failure
|
| 950 |
+
err = str(empty_report.get("error") or "")
|
| 951 |
+
transport_fail = any(
|
| 952 |
+
x in err.lower()
|
| 953 |
+
for x in ("connection", "timeout", "refused", "ngrok", "502", "503", "404")
|
| 954 |
+
)
|
| 955 |
+
checks["empty_patch_reward_ok"] = (not transport_fail) and isinstance(
|
| 956 |
+
empty_reward, float
|
| 957 |
+
)
|
| 958 |
+
checks["langfuse_trace_id"] = getattr(env, "_trace_id", "") or ""
|
| 959 |
+
_vlog(
|
| 960 |
+
"smoke.empty_patch_reward",
|
| 961 |
+
reward=empty_reward,
|
| 962 |
+
report=checks["empty_patch_report"],
|
| 963 |
+
ok=checks["empty_patch_reward_ok"],
|
| 964 |
+
)
|
| 965 |
+
except Exception as e: # noqa: BLE001
|
| 966 |
+
checks["errors"].append(f"env_phase: {type(e).__name__}: {e}")
|
| 967 |
+
_vlog("smoke.env_phase.error", error=str(e))
|
| 968 |
+
traceback.print_exc()
|
| 969 |
+
finally:
|
| 970 |
+
try:
|
| 971 |
+
env._end_trace()
|
| 972 |
+
except Exception:
|
| 973 |
+
pass
|
| 974 |
+
try:
|
| 975 |
+
env._cleanup()
|
| 976 |
+
except Exception:
|
| 977 |
+
pass
|
| 978 |
+
|
| 979 |
+
# --- Phase 2: one agent rollout (model may score 0; still validates loop) ---
|
| 980 |
+
model_id = resolve_model()
|
| 981 |
+
major, minor = (0, 0)
|
| 982 |
+
if torch.cuda.is_available():
|
| 983 |
+
major, minor = torch.cuda.get_device_capability(0)
|
| 984 |
+
use_bf16 = major >= 8 and torch.cuda.is_bf16_supported()
|
| 985 |
+
compute_dtype = torch.bfloat16 if use_bf16 else torch.float16
|
| 986 |
+
bnb = BitsAndBytesConfig(
|
| 987 |
+
load_in_4bit=True,
|
| 988 |
+
bnb_4bit_compute_dtype=compute_dtype,
|
| 989 |
+
bnb_4bit_quant_type="nf4",
|
| 990 |
+
bnb_4bit_use_double_quant=True,
|
| 991 |
+
)
|
| 992 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 993 |
+
model_id, token=os.environ["HF_TOKEN"], trust_remote_code=True
|
| 994 |
+
)
|
| 995 |
+
if tokenizer.pad_token is None:
|
| 996 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 997 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 998 |
+
model_id,
|
| 999 |
+
quantization_config=bnb,
|
| 1000 |
+
device_map="auto",
|
| 1001 |
+
token=os.environ["HF_TOKEN"],
|
| 1002 |
+
trust_remote_code=True,
|
| 1003 |
+
)
|
| 1004 |
+
model = prepare_model_for_kbit_training(model)
|
| 1005 |
+
model = get_peft_model(
|
| 1006 |
+
model,
|
| 1007 |
+
LoraConfig(
|
| 1008 |
+
r=cfg.lora_r,
|
| 1009 |
+
lora_alpha=cfg.lora_r * 2,
|
| 1010 |
+
lora_dropout=0.0,
|
| 1011 |
+
bias="none",
|
| 1012 |
+
task_type="CAUSAL_LM",
|
| 1013 |
+
target_modules=[
|
| 1014 |
+
"q_proj",
|
| 1015 |
+
"k_proj",
|
| 1016 |
+
"v_proj",
|
| 1017 |
+
"o_proj",
|
| 1018 |
+
"gate_proj",
|
| 1019 |
+
"up_proj",
|
| 1020 |
+
"down_proj",
|
| 1021 |
+
],
|
| 1022 |
+
),
|
| 1023 |
+
)
|
| 1024 |
+
|
| 1025 |
+
agent_rows = run_heldout_agent_eval(
|
| 1026 |
+
model,
|
| 1027 |
+
tokenizer,
|
| 1028 |
+
[problem],
|
| 1029 |
+
max_turns=cfg.max_eval_turns,
|
| 1030 |
+
max_new_tokens=512,
|
| 1031 |
+
)
|
| 1032 |
+
row = agent_rows[0] if agent_rows else {}
|
| 1033 |
+
checks["agent_reward"] = float(row.get("reward_total") or 0.0)
|
| 1034 |
+
checks["agent_n_tool_calls"] = int(row.get("n_tool_calls") or 0)
|
| 1035 |
+
checks["agent_tools_used"] = list(row.get("tools_used") or [])
|
| 1036 |
+
checks["agent_resolved"] = bool(row.get("resolved"))
|
| 1037 |
+
if row.get("langfuse_trace_id"):
|
| 1038 |
+
checks["langfuse_trace_id"] = row["langfuse_trace_id"]
|
| 1039 |
+
checks["agent_completion_preview"] = str(row.get("completion") or "")[:800]
|
| 1040 |
+
_vlog(
|
| 1041 |
+
"smoke.agent_done",
|
| 1042 |
+
reward=checks["agent_reward"],
|
| 1043 |
+
n_tool_calls=checks["agent_n_tool_calls"],
|
| 1044 |
+
tools=checks["agent_tools_used"],
|
| 1045 |
+
resolved=checks["agent_resolved"],
|
| 1046 |
+
)
|
| 1047 |
+
|
| 1048 |
+
checks["hard_ok"] = all(
|
| 1049 |
+
[
|
| 1050 |
+
checks["grader_health"],
|
| 1051 |
+
checks["env_reset"],
|
| 1052 |
+
checks["ls_root_ok"],
|
| 1053 |
+
checks["ls_src_aliased"],
|
| 1054 |
+
checks["empty_patch_reward_ok"],
|
| 1055 |
+
not checks["errors"],
|
| 1056 |
+
]
|
| 1057 |
+
)
|
| 1058 |
+
checks["soft_ok"] = checks["agent_n_tool_calls"] > 0
|
| 1059 |
+
|
| 1060 |
+
report_path = out_dir / "smoke_report.json"
|
| 1061 |
+
report_path.write_text(json.dumps(checks, indent=2, default=str), encoding="utf-8")
|
| 1062 |
+
_vlog("smoke.report_written", path=str(report_path), hard_ok=checks["hard_ok"])
|
| 1063 |
+
|
| 1064 |
+
wandb.log(
|
| 1065 |
+
{
|
| 1066 |
+
"smoke/hard_ok": 1.0 if checks["hard_ok"] else 0.0,
|
| 1067 |
+
"smoke/soft_ok": 1.0 if checks["soft_ok"] else 0.0,
|
| 1068 |
+
"smoke/empty_patch_reward": float(checks["empty_patch_reward"] or 0.0),
|
| 1069 |
+
"smoke/agent_reward": float(checks["agent_reward"] or 0.0),
|
| 1070 |
+
"smoke/agent_n_tool_calls": float(checks["agent_n_tool_calls"]),
|
| 1071 |
+
"smoke/instance_id": str(instance_id or ""),
|
| 1072 |
+
"smoke/ls_root_ok": 1.0 if checks["ls_root_ok"] else 0.0,
|
| 1073 |
+
"smoke/ls_src_aliased": 1.0 if checks["ls_src_aliased"] else 0.0,
|
| 1074 |
+
}
|
| 1075 |
+
)
|
| 1076 |
+
wandb.finish()
|
| 1077 |
+
|
| 1078 |
+
print("=== SMOKE REPORT ===", flush=True)
|
| 1079 |
+
print(json.dumps(checks, indent=2, default=str), flush=True)
|
| 1080 |
+
if not checks["hard_ok"]:
|
| 1081 |
+
raise RuntimeError(
|
| 1082 |
+
"SMOKE HARD FAIL — grader/env/reward plumbing broken. "
|
| 1083 |
+
f"errors={checks['errors']} report={report_path}"
|
| 1084 |
+
)
|
| 1085 |
+
if not checks["soft_ok"]:
|
| 1086 |
+
print(
|
| 1087 |
+
"SMOKE SOFT WARN: agent made 0 tool calls (prompt/model issue). "
|
| 1088 |
+
"Plumbing OK — full GRPO may still train tool use.",
|
| 1089 |
+
flush=True,
|
| 1090 |
+
)
|
| 1091 |
+
else:
|
| 1092 |
+
print("SMOKE PASS (hard+soft). Safe to run full GRPO.", flush=True)
|
| 1093 |
+
return checks
|
| 1094 |
+
|
| 1095 |
+
|
| 1096 |
def main(cfg: Cfg | None = None) -> None:
|
| 1097 |
global _TOKENIZER
|
| 1098 |
cfg = cfg or Cfg()
|
| 1099 |
+
_vlog(
|
| 1100 |
+
"main.begin",
|
| 1101 |
+
cfg_dataset=getattr(cfg, "dataset_id", None),
|
| 1102 |
+
mode=getattr(cfg, "mode", "full"),
|
| 1103 |
+
)
|
| 1104 |
load_secrets()
|
| 1105 |
_ensure_package()
|
| 1106 |
|
|
|
|
| 1119 |
except Exception as e: # noqa: BLE001
|
| 1120 |
_vlog("main.logging_configure_failed", error=str(e))
|
| 1121 |
|
| 1122 |
+
if str(getattr(cfg, "mode", "full")).lower() == "smoke":
|
| 1123 |
+
_vlog("main.branch_smoke")
|
| 1124 |
+
run_smoke_one_case(cfg)
|
| 1125 |
+
_vlog("main.smoke_done_exit")
|
| 1126 |
+
return
|
| 1127 |
+
|
| 1128 |
import torch
|
| 1129 |
import wandb
|
| 1130 |
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|