Add kaggle/train_grpo.py
Browse files- kaggle/train_grpo.py +51 -15
kaggle/train_grpo.py
CHANGED
|
@@ -173,11 +173,13 @@ _TOOL_CALL_RE = re.compile(
|
|
| 173 |
|
| 174 |
|
| 175 |
def _env_tool_callables(env: Any) -> list[Callable[..., str]]:
|
| 176 |
-
|
| 177 |
-
|
|
|
|
| 178 |
for name, _ in inspect.getmembers(type(env), predicate=inspect.isfunction)
|
| 179 |
if name not in ("reset", "get_reward") and not name.startswith("_")
|
| 180 |
]
|
|
|
|
| 181 |
|
| 182 |
|
| 183 |
def _parse_tool_calls(text: str) -> list[dict[str, Any]]:
|
|
@@ -227,7 +229,8 @@ def run_heldout_agent_eval(
|
|
| 227 |
messages = [{"role": "user", "content": str(prompt or "")}]
|
| 228 |
|
| 229 |
final_text = ""
|
| 230 |
-
|
|
|
|
| 231 |
try:
|
| 232 |
prompt_text = tokenizer.apply_chat_template(
|
| 233 |
messages,
|
|
@@ -242,19 +245,40 @@ def run_heldout_agent_eval(
|
|
| 242 |
except Exception:
|
| 243 |
prompt_text = messages[-1]["content"] if messages else ""
|
| 244 |
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
final_text = completion
|
| 257 |
-
calls = _parse_tool_calls(completion)
|
| 258 |
messages.append({"role": "assistant", "content": completion})
|
| 259 |
if not calls:
|
| 260 |
break
|
|
@@ -295,6 +319,11 @@ def run_heldout_agent_eval(
|
|
| 295 |
"reward_total": reward,
|
| 296 |
"resolved": bool(report.get("resolved")),
|
| 297 |
"resolution": report.get("resolution"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
}
|
| 299 |
)
|
| 300 |
except Exception as e: # noqa: BLE001
|
|
@@ -311,6 +340,10 @@ def run_heldout_agent_eval(
|
|
| 311 |
}
|
| 312 |
)
|
| 313 |
finally:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
try:
|
| 315 |
env._cleanup()
|
| 316 |
except Exception:
|
|
@@ -373,6 +406,9 @@ def langfuse_log_ckpt_eval(
|
|
| 373 |
"pass_frac": r.get("pass_frac"),
|
| 374 |
"reward_total": r.get("reward_total"),
|
| 375 |
"resolved": r.get("resolved"),
|
|
|
|
|
|
|
|
|
|
| 376 |
}
|
| 377 |
)
|
| 378 |
summary = {
|
|
|
|
| 173 |
|
| 174 |
|
| 175 |
def _env_tool_callables(env: Any) -> list[Callable[..., str]]:
|
| 176 |
+
# Bound methods so held-out eval can call fn(**kwargs); TRL uses class functions itself.
|
| 177 |
+
names = [
|
| 178 |
+
name
|
| 179 |
for name, _ in inspect.getmembers(type(env), predicate=inspect.isfunction)
|
| 180 |
if name not in ("reset", "get_reward") and not name.startswith("_")
|
| 181 |
]
|
| 182 |
+
return [getattr(env, name) for name in names]
|
| 183 |
|
| 184 |
|
| 185 |
def _parse_tool_calls(text: str) -> list[dict[str, Any]]:
|
|
|
|
| 229 |
messages = [{"role": "user", "content": str(prompt or "")}]
|
| 230 |
|
| 231 |
final_text = ""
|
| 232 |
+
lf = getattr(env, "_lf", None)
|
| 233 |
+
for turn in range(max_turns):
|
| 234 |
try:
|
| 235 |
prompt_text = tokenizer.apply_chat_template(
|
| 236 |
messages,
|
|
|
|
| 245 |
except Exception:
|
| 246 |
prompt_text = messages[-1]["content"] if messages else ""
|
| 247 |
|
| 248 |
+
def _generate_once() -> str:
|
| 249 |
+
inputs_local = tokenizer(prompt_text, return_tensors="pt")
|
| 250 |
+
inputs_local = {k: v.to(model.device) for k, v in inputs_local.items()}
|
| 251 |
+
with torch.no_grad():
|
| 252 |
+
out_ids = model.generate(
|
| 253 |
+
**inputs_local,
|
| 254 |
+
max_new_tokens=max_new_tokens,
|
| 255 |
+
do_sample=False,
|
| 256 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 257 |
+
)
|
| 258 |
+
gen_ids = out_ids[0][inputs_local["input_ids"].shape[-1] :]
|
| 259 |
+
return tokenizer.decode(gen_ids, skip_special_tokens=True)
|
| 260 |
+
|
| 261 |
+
if lf is not None and getattr(env, "_root", None) is not None:
|
| 262 |
+
with lf.start_as_current_observation(
|
| 263 |
+
name=f"llm-turn-{turn}",
|
| 264 |
+
as_type="generation",
|
| 265 |
+
input={"messages_tail": messages[-2:], "turn": turn},
|
| 266 |
+
metadata={"phase": "ckpt_eval", "turn": turn},
|
| 267 |
+
) as gen_span:
|
| 268 |
+
completion = _generate_once()
|
| 269 |
+
calls = _parse_tool_calls(completion)
|
| 270 |
+
gen_span.update(
|
| 271 |
+
output={
|
| 272 |
+
"completion": completion[:4000],
|
| 273 |
+
"n_tool_calls": len(calls),
|
| 274 |
+
"tool_names": [c["name"] for c in calls],
|
| 275 |
+
}
|
| 276 |
+
)
|
| 277 |
+
else:
|
| 278 |
+
completion = _generate_once()
|
| 279 |
+
calls = _parse_tool_calls(completion)
|
| 280 |
+
|
| 281 |
final_text = completion
|
|
|
|
| 282 |
messages.append({"role": "assistant", "content": completion})
|
| 283 |
if not calls:
|
| 284 |
break
|
|
|
|
| 319 |
"reward_total": reward,
|
| 320 |
"resolved": bool(report.get("resolved")),
|
| 321 |
"resolution": report.get("resolution"),
|
| 322 |
+
"n_tool_calls": len(getattr(env, "_tool_calls", []) or []),
|
| 323 |
+
"tools_used": [
|
| 324 |
+
c.get("name") for c in (getattr(env, "_tool_calls", []) or [])
|
| 325 |
+
],
|
| 326 |
+
"langfuse_trace_id": getattr(env, "_trace_id", "") or "",
|
| 327 |
}
|
| 328 |
)
|
| 329 |
except Exception as e: # noqa: BLE001
|
|
|
|
| 340 |
}
|
| 341 |
)
|
| 342 |
finally:
|
| 343 |
+
try:
|
| 344 |
+
env._end_trace()
|
| 345 |
+
except Exception:
|
| 346 |
+
pass
|
| 347 |
try:
|
| 348 |
env._cleanup()
|
| 349 |
except Exception:
|
|
|
|
| 406 |
"pass_frac": r.get("pass_frac"),
|
| 407 |
"reward_total": r.get("reward_total"),
|
| 408 |
"resolved": r.get("resolved"),
|
| 409 |
+
"n_tool_calls": r.get("n_tool_calls"),
|
| 410 |
+
"tools_used": r.get("tools_used"),
|
| 411 |
+
"rollout_trace_id": r.get("langfuse_trace_id"),
|
| 412 |
}
|
| 413 |
)
|
| 414 |
summary = {
|