comm
Browse files- README.md +4 -4
- eval.py +67 -4
- plot_results.py +28 -4
- results/invalid_action_rate.png +2 -2
- results/model_comparison.png +2 -2
- results/reward_curve.png +2 -2
- results/sft_eval.jsonl +6 -0
- results/success_by_task.png +2 -2
- training/train_sft.py +8 -2
README.md
CHANGED
|
@@ -100,12 +100,12 @@ See [`docs/lightning_hf_runbook.md`](docs/lightning_hf_runbook.md) for the short
|
|
| 100 |
Evaluate all model stages through the same environment:
|
| 101 |
|
| 102 |
```powershell
|
| 103 |
-
uv run python eval.py --policy scripted_weak --label baseline
|
| 104 |
-
uv run python eval.py --policy oracle --label oracle
|
| 105 |
-
uv run python plot_results.py --inputs results/
|
| 106 |
```
|
| 107 |
|
| 108 |
-
For trained adapters on a GPU box, use `eval.py --policy hf --model <base_model> --adapter <adapter_path>`.
|
| 109 |
|
| 110 |
## OpenEnv validation
|
| 111 |
|
|
|
|
| 100 |
Evaluate all model stages through the same environment:
|
| 101 |
|
| 102 |
```powershell
|
| 103 |
+
uv run python eval.py --policy scripted_weak --label baseline
|
| 104 |
+
uv run python eval.py --policy oracle --label oracle
|
| 105 |
+
uv run python plot_results.py --inputs results/runs --output-dir results
|
| 106 |
```
|
| 107 |
|
| 108 |
+
For trained adapters on a GPU box, use `eval.py --policy hf --model <base_model> --adapter <adapter_path>`. If `--output` is omitted, eval files are saved under `results/runs/<model-adapter-label>/` with a `metadata.json` summary.
|
| 109 |
|
| 110 |
## OpenEnv validation
|
| 111 |
|
eval.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
"""Evaluate CORP-ENV policies through the real OpenEnv environment.
|
| 2 |
|
| 3 |
Examples:
|
| 4 |
-
uv run python eval.py --policy scripted_weak --
|
| 5 |
-
uv run python eval.py --policy oracle --
|
| 6 |
uv run python eval.py --policy openai --model Qwen/Qwen2.5-7B-Instruct
|
| 7 |
uv run python eval.py --policy hf --model outputs/sft_adapter --adapter outputs/grpo_adapter
|
| 8 |
"""
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
| 12 |
import argparse
|
| 13 |
import json
|
| 14 |
import os
|
|
|
|
| 15 |
import sys
|
| 16 |
from pathlib import Path
|
| 17 |
from typing import Any, Dict, List, Optional
|
|
@@ -241,6 +242,60 @@ def summarize(rows: List[Dict[str, Any]], label: str) -> None:
|
|
| 241 |
)
|
| 242 |
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
def main() -> None:
|
| 245 |
parser = argparse.ArgumentParser(description="Evaluate CORP-ENV model stages.")
|
| 246 |
parser.add_argument("--policy", choices=["scripted_weak", "oracle", "openai", "hf"], default="scripted_weak")
|
|
@@ -251,7 +306,12 @@ def main() -> None:
|
|
| 251 |
parser.add_argument("--episodes", type=int, default=1)
|
| 252 |
parser.add_argument("--max-steps", type=int, default=30)
|
| 253 |
parser.add_argument("--max-new-tokens", type=int, default=1536)
|
| 254 |
-
parser.add_argument("--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
args = parser.parse_args()
|
| 256 |
|
| 257 |
os.environ.setdefault("CORP_STUB_WORKERS", "1")
|
|
@@ -278,14 +338,17 @@ def main() -> None:
|
|
| 278 |
row = run_model_episode(task_id=task_id, policy=policy, max_steps=max_steps)
|
| 279 |
row["episode_index"] = ep
|
| 280 |
row["model_stage"] = args.label or args.policy
|
|
|
|
| 281 |
row["model"] = args.model
|
| 282 |
row["adapter"] = args.adapter
|
| 283 |
rows.append(row)
|
| 284 |
|
| 285 |
-
out = Path(args.output)
|
| 286 |
write_jsonl(out, rows)
|
|
|
|
| 287 |
summarize(rows, args.label or args.policy)
|
| 288 |
print(f"\nWrote {out}")
|
|
|
|
| 289 |
|
| 290 |
|
| 291 |
if __name__ == "__main__":
|
|
|
|
| 1 |
"""Evaluate CORP-ENV policies through the real OpenEnv environment.
|
| 2 |
|
| 3 |
Examples:
|
| 4 |
+
uv run python eval.py --policy scripted_weak --label baseline
|
| 5 |
+
uv run python eval.py --policy oracle --label oracle
|
| 6 |
uv run python eval.py --policy openai --model Qwen/Qwen2.5-7B-Instruct
|
| 7 |
uv run python eval.py --policy hf --model outputs/sft_adapter --adapter outputs/grpo_adapter
|
| 8 |
"""
|
|
|
|
| 12 |
import argparse
|
| 13 |
import json
|
| 14 |
import os
|
| 15 |
+
import re
|
| 16 |
import sys
|
| 17 |
from pathlib import Path
|
| 18 |
from typing import Any, Dict, List, Optional
|
|
|
|
| 242 |
)
|
| 243 |
|
| 244 |
|
| 245 |
+
def slugify(value: str, fallback: str = "unknown") -> str:
|
| 246 |
+
value = (value or "").strip()
|
| 247 |
+
if not value:
|
| 248 |
+
return fallback
|
| 249 |
+
value = value.replace("\\", "/").rstrip("/")
|
| 250 |
+
value = value.split("/")[-1] if "/" in value else value
|
| 251 |
+
value = re.sub(r"[^A-Za-z0-9._-]+", "-", value)
|
| 252 |
+
value = value.strip("-._")
|
| 253 |
+
return value or fallback
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def default_output_path(args: argparse.Namespace) -> Path:
|
| 257 |
+
stage = slugify(args.label or args.policy, "eval")
|
| 258 |
+
model_slug = slugify(args.model, args.policy)
|
| 259 |
+
adapter_slug = slugify(args.adapter, "no-adapter")
|
| 260 |
+
if args.adapter:
|
| 261 |
+
run_slug = f"{model_slug}__{adapter_slug}__{stage}"
|
| 262 |
+
else:
|
| 263 |
+
run_slug = f"{model_slug}__{stage}"
|
| 264 |
+
return Path(args.results_root) / "runs" / run_slug / f"{stage}_eval.jsonl"
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def write_run_metadata(path: Path, args: argparse.Namespace, rows: List[Dict[str, Any]]) -> None:
|
| 268 |
+
by_task: Dict[str, List[Dict[str, Any]]] = {}
|
| 269 |
+
for row in rows:
|
| 270 |
+
by_task.setdefault(row["task_id"], []).append(row)
|
| 271 |
+
summary = {
|
| 272 |
+
"model_stage": args.label or args.policy,
|
| 273 |
+
"policy": args.policy,
|
| 274 |
+
"model": args.model,
|
| 275 |
+
"adapter": args.adapter,
|
| 276 |
+
"tasks": [t.strip() for t in args.tasks.split(",") if t.strip()],
|
| 277 |
+
"episodes": args.episodes,
|
| 278 |
+
"max_steps": args.max_steps,
|
| 279 |
+
"max_new_tokens": args.max_new_tokens,
|
| 280 |
+
"eval_file": str(path),
|
| 281 |
+
"metrics_by_task": {},
|
| 282 |
+
}
|
| 283 |
+
for task_id, task_rows in by_task.items():
|
| 284 |
+
summary["metrics_by_task"][task_id] = {
|
| 285 |
+
"avg_terminal_reward": round(
|
| 286 |
+
sum(r["terminal_reward"] for r in task_rows) / len(task_rows), 6
|
| 287 |
+
),
|
| 288 |
+
"avg_verifier_pass_rate": round(
|
| 289 |
+
sum(r["verifier_pass_rate"] for r in task_rows) / len(task_rows), 6
|
| 290 |
+
),
|
| 291 |
+
"success_rate": round(
|
| 292 |
+
sum(1 for r in task_rows if r["success"]) / len(task_rows), 6
|
| 293 |
+
),
|
| 294 |
+
}
|
| 295 |
+
metadata_path = path.with_name("metadata.json")
|
| 296 |
+
metadata_path.write_text(json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8")
|
| 297 |
+
|
| 298 |
+
|
| 299 |
def main() -> None:
|
| 300 |
parser = argparse.ArgumentParser(description="Evaluate CORP-ENV model stages.")
|
| 301 |
parser.add_argument("--policy", choices=["scripted_weak", "oracle", "openai", "hf"], default="scripted_weak")
|
|
|
|
| 306 |
parser.add_argument("--episodes", type=int, default=1)
|
| 307 |
parser.add_argument("--max-steps", type=int, default=30)
|
| 308 |
parser.add_argument("--max-new-tokens", type=int, default=1536)
|
| 309 |
+
parser.add_argument("--results-root", default="results", help="Root for auto-organized eval output.")
|
| 310 |
+
parser.add_argument(
|
| 311 |
+
"--output",
|
| 312 |
+
default="",
|
| 313 |
+
help="Explicit JSONL path. If omitted, writes under results/runs/<model-adapter-label>/.",
|
| 314 |
+
)
|
| 315 |
args = parser.parse_args()
|
| 316 |
|
| 317 |
os.environ.setdefault("CORP_STUB_WORKERS", "1")
|
|
|
|
| 338 |
row = run_model_episode(task_id=task_id, policy=policy, max_steps=max_steps)
|
| 339 |
row["episode_index"] = ep
|
| 340 |
row["model_stage"] = args.label or args.policy
|
| 341 |
+
row["policy"] = args.policy
|
| 342 |
row["model"] = args.model
|
| 343 |
row["adapter"] = args.adapter
|
| 344 |
rows.append(row)
|
| 345 |
|
| 346 |
+
out = Path(args.output) if args.output else default_output_path(args)
|
| 347 |
write_jsonl(out, rows)
|
| 348 |
+
write_run_metadata(out, args, rows)
|
| 349 |
summarize(rows, args.label or args.policy)
|
| 350 |
print(f"\nWrote {out}")
|
| 351 |
+
print(f"Wrote {out.with_name('metadata.json')}")
|
| 352 |
|
| 353 |
|
| 354 |
if __name__ == "__main__":
|
plot_results.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Create hackathon result plots from CORP-ENV eval JSONL files."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
@@ -9,15 +9,39 @@ from pathlib import Path
|
|
| 9 |
from typing import Any, Dict, Iterable, List
|
| 10 |
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
def read_rows(paths: Iterable[str]) -> List[Dict[str, Any]]:
|
| 13 |
rows: List[Dict[str, Any]] = []
|
| 14 |
-
for
|
| 15 |
-
path = Path(path_s)
|
| 16 |
with path.open("r", encoding="utf-8") as f:
|
| 17 |
for line in f:
|
| 18 |
if line.strip():
|
| 19 |
row = json.loads(line)
|
| 20 |
row.setdefault("model_stage", path.stem.replace("_eval", ""))
|
|
|
|
|
|
|
| 21 |
steps = max(float(row.get("steps", 0) or 0), 1.0)
|
| 22 |
row["invalid_action_rate"] = float(row.get("invalid_action_count", 0) or 0) / steps
|
| 23 |
rows.append(row)
|
|
@@ -95,7 +119,7 @@ def plot_reward_curve(rows: List[Dict[str, Any]], output: Path) -> None:
|
|
| 95 |
|
| 96 |
def main() -> None:
|
| 97 |
parser = argparse.ArgumentParser(description="Plot CORP-ENV eval results.")
|
| 98 |
-
parser.add_argument("--inputs", nargs="+", required=True, help="Eval JSONL files.")
|
| 99 |
parser.add_argument("--output-dir", default="results")
|
| 100 |
args = parser.parse_args()
|
| 101 |
|
|
|
|
| 1 |
+
"""Create hackathon result plots from CORP-ENV eval JSONL files or run folders."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
| 9 |
from typing import Any, Dict, Iterable, List
|
| 10 |
|
| 11 |
|
| 12 |
+
def expand_inputs(inputs: Iterable[str]) -> List[Path]:
|
| 13 |
+
paths: List[Path] = []
|
| 14 |
+
for raw in inputs:
|
| 15 |
+
path = Path(raw)
|
| 16 |
+
if path.is_dir():
|
| 17 |
+
paths.extend(sorted(path.rglob("*_eval.jsonl")))
|
| 18 |
+
paths.extend(sorted(path.rglob("eval.jsonl")))
|
| 19 |
+
elif path.exists():
|
| 20 |
+
paths.append(path)
|
| 21 |
+
else:
|
| 22 |
+
matches = sorted(Path().glob(raw))
|
| 23 |
+
paths.extend([m for m in matches if m.is_file()])
|
| 24 |
+
# Preserve order but remove duplicates.
|
| 25 |
+
seen = set()
|
| 26 |
+
out: List[Path] = []
|
| 27 |
+
for path in paths:
|
| 28 |
+
key = str(path.resolve())
|
| 29 |
+
if key not in seen:
|
| 30 |
+
seen.add(key)
|
| 31 |
+
out.append(path)
|
| 32 |
+
return out
|
| 33 |
+
|
| 34 |
+
|
| 35 |
def read_rows(paths: Iterable[str]) -> List[Dict[str, Any]]:
|
| 36 |
rows: List[Dict[str, Any]] = []
|
| 37 |
+
for path in expand_inputs(paths):
|
|
|
|
| 38 |
with path.open("r", encoding="utf-8") as f:
|
| 39 |
for line in f:
|
| 40 |
if line.strip():
|
| 41 |
row = json.loads(line)
|
| 42 |
row.setdefault("model_stage", path.stem.replace("_eval", ""))
|
| 43 |
+
if "run_id" not in row:
|
| 44 |
+
row["run_id"] = path.parent.name
|
| 45 |
steps = max(float(row.get("steps", 0) or 0), 1.0)
|
| 46 |
row["invalid_action_rate"] = float(row.get("invalid_action_count", 0) or 0) / steps
|
| 47 |
rows.append(row)
|
|
|
|
| 119 |
|
| 120 |
def main() -> None:
|
| 121 |
parser = argparse.ArgumentParser(description="Plot CORP-ENV eval results.")
|
| 122 |
+
parser.add_argument("--inputs", nargs="+", required=True, help="Eval JSONL files, folders, or glob patterns.")
|
| 123 |
parser.add_argument("--output-dir", default="results")
|
| 124 |
args = parser.parse_args()
|
| 125 |
|
results/invalid_action_rate.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
results/model_comparison.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
results/reward_curve.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
results/sft_eval.jsonl
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"task_id": "e1_launch_readiness", "policy_kind": "model", "steps": 3, "total_reward": 1.39, "terminal_reward": 0.91, "reward_trace": [0.29000000000000004, 0.19, 0.9099999999999999], "verifier_pass_rate": 1.0, "passed_checks": ["qa_report_present", "final_rec_valid", "no_missed_milestones"], "failed_checks": [], "milestones_total": 2, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 4, "success": true, "episode_index": 0, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
|
| 2 |
+
{"task_id": "m1_budget_reallocation", "policy_kind": "model", "steps": 8, "total_reward": 1.815, "terminal_reward": 0.685, "reward_trace": [0.09000000000000001, 0.29000000000000004, 0.19, 0.19, -0.01, 0.39, -0.01, 0.685], "verifier_pass_rate": 0.833333, "passed_checks": ["required_agents_consulted", "conflict_logged", "conflict_resolved", "budget_constraint_acknowledged", "reasoning_documented"], "failed_checks": ["phased_plan"], "milestones_total": 3, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 2, "env_error_count": 2, "errors": ["invalid_action: Expecting value: line 1 column 58 (char 57)", "invalid_action: Expecting value: line 1 column 58 (char 57)"], "final_swd_version": 7, "success": false, "episode_index": 0, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
|
| 3 |
+
{"task_id": "e1_launch_readiness", "policy_kind": "model", "steps": 3, "total_reward": 1.39, "terminal_reward": 0.91, "reward_trace": [0.29000000000000004, 0.19, 0.9099999999999999], "verifier_pass_rate": 1.0, "passed_checks": ["qa_report_present", "final_rec_valid", "no_missed_milestones"], "failed_checks": [], "milestones_total": 2, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 4, "success": true, "episode_index": 1, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
|
| 4 |
+
{"task_id": "m1_budget_reallocation", "policy_kind": "model", "steps": 5, "total_reward": 1.766667, "terminal_reward": 0.806667, "reward_trace": [0.09000000000000001, 0.29000000000000004, 0.19, 0.39, 0.8066666666666666], "verifier_pass_rate": 0.666667, "passed_checks": ["required_agents_consulted", "conflict_logged", "conflict_resolved", "phased_plan"], "failed_checks": ["budget_constraint_acknowledged", "reasoning_documented"], "milestones_total": 3, "milestones_complete": 3, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 6, "success": false, "episode_index": 1, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
|
| 5 |
+
{"task_id": "e1_launch_readiness", "policy_kind": "model", "steps": 3, "total_reward": 1.39, "terminal_reward": 0.91, "reward_trace": [0.29000000000000004, 0.19, 0.9099999999999999], "verifier_pass_rate": 1.0, "passed_checks": ["qa_report_present", "final_rec_valid", "no_missed_milestones"], "failed_checks": [], "milestones_total": 2, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 4, "success": true, "episode_index": 2, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
|
| 6 |
+
{"task_id": "m1_budget_reallocation", "policy_kind": "model", "steps": 6, "total_reward": 1.776667, "terminal_reward": 0.626667, "reward_trace": [0.09000000000000001, 0.29000000000000004, 0.19, 0.19, 0.39, 0.6266666666666666], "verifier_pass_rate": 0.666667, "passed_checks": ["required_agents_consulted", "conflict_logged", "conflict_resolved", "reasoning_documented"], "failed_checks": ["phased_plan", "budget_constraint_acknowledged"], "milestones_total": 3, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 7, "success": false, "episode_index": 2, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
|
results/success_by_task.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
training/train_sft.py
CHANGED
|
@@ -57,9 +57,10 @@ def main() -> None:
|
|
| 57 |
args = parser.parse_args()
|
| 58 |
|
| 59 |
try:
|
|
|
|
|
|
|
| 60 |
from datasets import Dataset
|
| 61 |
from trl import SFTConfig, SFTTrainer
|
| 62 |
-
from unsloth import FastLanguageModel
|
| 63 |
except ImportError as exc:
|
| 64 |
raise SystemExit(
|
| 65 |
"SFT training requires datasets, trl, and unsloth. On Lightning AI, install with:\n"
|
|
@@ -72,6 +73,11 @@ def main() -> None:
|
|
| 72 |
dtype=None,
|
| 73 |
load_in_4bit=True,
|
| 74 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
model = FastLanguageModel.get_peft_model(
|
| 76 |
model,
|
| 77 |
r=32,
|
|
@@ -95,7 +101,7 @@ def main() -> None:
|
|
| 95 |
config = SFTConfig(
|
| 96 |
output_dir=args.output,
|
| 97 |
dataset_text_field="text",
|
| 98 |
-
|
| 99 |
per_device_train_batch_size=args.batch_size,
|
| 100 |
gradient_accumulation_steps=args.grad_accum,
|
| 101 |
num_train_epochs=args.epochs,
|
|
|
|
| 57 |
args = parser.parse_args()
|
| 58 |
|
| 59 |
try:
|
| 60 |
+
# Unsloth must load before trl/transformers/peft for full optimizations.
|
| 61 |
+
from unsloth import FastLanguageModel
|
| 62 |
from datasets import Dataset
|
| 63 |
from trl import SFTConfig, SFTTrainer
|
|
|
|
| 64 |
except ImportError as exc:
|
| 65 |
raise SystemExit(
|
| 66 |
"SFT training requires datasets, trl, and unsloth. On Lightning AI, install with:\n"
|
|
|
|
| 73 |
dtype=None,
|
| 74 |
load_in_4bit=True,
|
| 75 |
)
|
| 76 |
+
if getattr(tokenizer, "pad_token", None) is None and getattr(
|
| 77 |
+
tokenizer, "eos_token", None
|
| 78 |
+
) is not None:
|
| 79 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 80 |
+
|
| 81 |
model = FastLanguageModel.get_peft_model(
|
| 82 |
model,
|
| 83 |
r=32,
|
|
|
|
| 101 |
config = SFTConfig(
|
| 102 |
output_dir=args.output,
|
| 103 |
dataset_text_field="text",
|
| 104 |
+
max_length=args.max_seq_length,
|
| 105 |
per_device_train_batch_size=args.batch_size,
|
| 106 |
gradient_accumulation_steps=args.grad_accum,
|
| 107 |
num_train_epochs=args.epochs,
|