| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| workspace="/mnt/pvc/users/simon/agentptb/runs/sol-max-s1/workspace" |
| eval_marker="$workspace/state/step150-eval-complete" |
| eval_session="agentptb-step150-eval-watcher" |
| train_session="agentptb-sft-continuation" |
| target_steps=1800 |
| source_output="$workspace/outputs/sft-agent-mix-clean-full-v1" |
| continuation_log="$workspace/logs/sft-agent-mix-clean-continuation-launch.log" |
| full_config="$workspace/configs/sft-agent-mix-clean-full-step1650-resume.toml" |
| selective_config="$workspace/configs/sft-agent-mix-clean-full-step1650-selective-resume.toml" |
| gpu_optim_config="$workspace/configs/sft-agent-mix-clean-full-step1650-gpuoptim-resume.toml" |
| noact_config="$workspace/configs/sft-agent-mix-clean-full-step1650-noactoffload-resume.toml" |
| mb10_config="$workspace/configs/sft-agent-mix-clean-full-mb10-resume.toml" |
| mb12_config="$workspace/configs/sft-agent-mix-clean-full-mb12-resume.toml" |
| bench_pid="" |
|
|
| cleanup_bench() { |
| if [[ -n "$bench_pid" ]]; then |
| |
| |
| kill -TERM -- "-$bench_pid" 2>/dev/null || true |
| for _ in {1..12}; do |
| kill -0 -- "-$bench_pid" 2>/dev/null || break |
| sleep 5 |
| done |
| kill -KILL -- "-$bench_pid" 2>/dev/null || true |
| wait "$bench_pid" 2>/dev/null || true |
| fi |
| } |
|
|
| run_benchmark() { |
| local config="$1" output="$2" log="$3" |
| mkdir -p "$output/checkpoints" "$output/weights" |
| if [[ ! -e "$output/checkpoints/step_150" ]]; then |
| cp -Rl "$source_output/checkpoints/step_150" "$output/checkpoints/step_150" |
| fi |
| if [[ ! -e "$output/weights/step_150" ]]; then |
| cp -Rl "$source_output/weights/step_150" "$output/weights/step_150" |
| fi |
| if [[ -f "$output/checkpoints/step_152/trainer/.metadata" \ |
| && -f "$output/weights/step_152/STABLE" \ |
| && -f "$log" ]] \ |
| && grep -q 'Step 152 |' "$log"; then |
| return 0 |
| fi |
|
|
| cd /root/work/a/prime-rl |
| setsid bash -c 'exec "$@"' _ \ |
| env CUDA_VISIBLE_DEVICES=4,5,6,7 \ |
| TMPDIR=/tmp/agentptb-s1 \ |
| PYTHONUNBUFFERED=1 \ |
| uv run --no-sync sft @ "$config" >"$log" 2>&1 & |
| bench_pid=$! |
|
|
| local ok=false |
| for _ in {1..180}; do |
| if [[ -f "$output/checkpoints/step_152/trainer/.metadata" \ |
| && -f "$output/weights/step_152/STABLE" ]] \ |
| && grep -q 'Step 152 |' "$log"; then |
| ok=true |
| break |
| fi |
| kill -0 "$bench_pid" 2>/dev/null || break |
| sleep 10 |
| done |
| cleanup_bench |
| bench_pid="" |
| [[ "$ok" == true ]] |
| } |
|
|
| read_metrics() { |
| python - "$1" "$2" "$3" <<'PY' |
| import json |
| import math |
| import re |
| import sys |
|
|
| text = open(sys.argv[1], errors="replace").read() |
| metrics_path = sys.argv[2] |
| batch_size = int(sys.argv[3]) |
| matches = re.findall( |
| r"Step 152 \|.*?Loss ([^ |]+) \| Grad\. Norm ([^ |]+) \| " |
| r"LR ([^ |]+) \| Throughput [0-9.]+ tokens/s.*?Peak Mem\. ([0-9.]+)/", |
| text, |
| ) |
| if not matches: |
| print("0 999 nan nan nan 0") |
| else: |
| loss, grad, lr, memory = matches[-1] |
| step_metrics = {} |
| try: |
| with open(metrics_path) as handle: |
| for line in handle: |
| row = json.loads(line) |
| if row.get("step") == 152: |
| step_metrics.update(row) |
| effective_step_seconds = ( |
| float(step_metrics["time/step"]) |
| - float(step_metrics.get("time/save_ckpt", 0.0)) |
| ) |
| throughput = batch_size * 32768 / effective_step_seconds |
| except (OSError, ValueError, KeyError, ZeroDivisionError, json.JSONDecodeError): |
| effective_step_seconds = math.nan |
| throughput = 0.0 |
| values = [ |
| float(loss), |
| float(grad), |
| float(lr), |
| float(throughput), |
| float(memory), |
| effective_step_seconds, |
| ] |
| valid = int( |
| all(math.isfinite(value) for value in values) |
| and 1.9e-5 <= values[2] <= 2.1e-5 |
| and values[4] <= 170 |
| and values[5] > 0 |
| ) |
| print(f"{throughput:.3f}", memory, loss, grad, lr, valid) |
| PY |
| } |
| trap cleanup_bench EXIT INT TERM |
|
|
| while [[ ! -f "$eval_marker" ]]; do |
| sleep 30 |
| done |
| while tmux has-session -t "$eval_session" 2>/dev/null; do |
| sleep 10 |
| done |
|
|
| [[ -f "$source_output/checkpoints/step_150/trainer/.metadata" ]] |
| [[ -f "$source_output/weights/step_150/STABLE" ]] |
| if curl --fail --silent http://127.0.0.1:8200/health >/dev/null 2>&1; then |
| echo "refusing to train while inference still owns port 8200" >&2 |
| exit 1 |
| fi |
| tmux has-session -t "$train_session" 2>/dev/null && { |
| echo "continuation session already exists" >&2 |
| exit 1 |
| } |
|
|
| baseline_save=false |
| baseline_throughput=0 |
| baseline_memory=999 |
| baseline_loss=nan |
| baseline_grad=nan |
| baseline_lr=nan |
| baseline_valid=0 |
| if run_benchmark \ |
| "$workspace/configs/sft-baseline-bench-resume150.toml" \ |
| "$workspace/outputs/baseline-bench" \ |
| "$workspace/logs/sft-baseline-bench.log"; then |
| baseline_save=true |
| read -r baseline_throughput baseline_memory baseline_loss baseline_grad baseline_lr baseline_valid \ |
| < <(read_metrics "$workspace/logs/sft-baseline-bench.log" \ |
| "$workspace/outputs/baseline-bench/metrics.jsonl" 128) |
| fi |
|
|
| mb10_save=false |
| mb10_throughput=0 |
| mb10_memory=999 |
| mb10_loss=nan |
| mb10_grad=nan |
| mb10_lr=nan |
| mb10_valid=0 |
| if run_benchmark \ |
| "$workspace/configs/sft-mb10-bench-resume150.toml" \ |
| "$workspace/outputs/mb10-bench" \ |
| "$workspace/logs/sft-mb10-bench.log"; then |
| mb10_save=true |
| read -r mb10_throughput mb10_memory mb10_loss mb10_grad mb10_lr mb10_valid \ |
| < <(read_metrics "$workspace/logs/sft-mb10-bench.log" \ |
| "$workspace/outputs/mb10-bench/metrics.jsonl" 120) |
| fi |
|
|
| mb12_save=false |
| mb12_throughput=0 |
| mb12_memory=999 |
| mb12_loss=nan |
| mb12_grad=nan |
| mb12_lr=nan |
| mb12_valid=0 |
| if run_benchmark \ |
| "$workspace/configs/sft-mb12-bench-resume150.toml" \ |
| "$workspace/outputs/mb12-bench" \ |
| "$workspace/logs/sft-mb12-bench.log"; then |
| mb12_save=true |
| read -r mb12_throughput mb12_memory mb12_loss mb12_grad mb12_lr mb12_valid \ |
| < <(read_metrics "$workspace/logs/sft-mb12-bench.log" \ |
| "$workspace/outputs/mb12-bench/metrics.jsonl" 96) |
| fi |
|
|
| selective_save=false |
| selective_throughput=0 |
| selective_memory=999 |
| selective_loss=nan |
| selective_grad=nan |
| selective_lr=nan |
| selective_valid=0 |
| if run_benchmark \ |
| "$workspace/configs/sft-ac-selective-bench-resume150.toml" \ |
| "$workspace/outputs/ac-selective-bench" \ |
| "$workspace/logs/sft-ac-selective-bench.log"; then |
| selective_save=true |
| read -r selective_throughput selective_memory selective_loss selective_grad selective_lr selective_valid \ |
| < <(read_metrics "$workspace/logs/sft-ac-selective-bench.log" \ |
| "$workspace/outputs/ac-selective-bench/metrics.jsonl" 128) |
| fi |
|
|
| gpu_save=false |
| gpu_throughput=0 |
| gpu_memory=999 |
| gpu_loss=nan |
| gpu_grad=nan |
| gpu_lr=nan |
| gpu_valid=0 |
| if run_benchmark \ |
| "$workspace/configs/sft-optim-gpu-bench-resume150.toml" \ |
| "$workspace/outputs/optim-gpu-bench" \ |
| "$workspace/logs/sft-optim-gpu-bench.log"; then |
| gpu_save=true |
| read -r gpu_throughput gpu_memory gpu_loss gpu_grad gpu_lr gpu_valid \ |
| < <(read_metrics "$workspace/logs/sft-optim-gpu-bench.log" \ |
| "$workspace/outputs/optim-gpu-bench/metrics.jsonl" 128) |
| fi |
|
|
| noact_save=false |
| noact_throughput=0 |
| noact_memory=999 |
| noact_loss=nan |
| noact_grad=nan |
| noact_lr=nan |
| noact_valid=0 |
| if run_benchmark \ |
| "$workspace/configs/sft-noactoffload-bench-resume150.toml" \ |
| "$workspace/outputs/noactoffload-bench" \ |
| "$workspace/logs/sft-noactoffload-bench.log"; then |
| noact_save=true |
| read -r noact_throughput noact_memory noact_loss noact_grad noact_lr noact_valid \ |
| < <(read_metrics "$workspace/logs/sft-noactoffload-bench.log" \ |
| "$workspace/outputs/noactoffload-bench/metrics.jsonl" 128) |
| fi |
|
|
| chosen="$full_config" |
| best_throughput="$baseline_throughput" |
| chosen_batch_size=128 |
| mb10_eligible=0 |
| mb12_eligible=0 |
| selective_eligible=0 |
| gpu_eligible=0 |
| noact_eligible=0 |
| if [[ "$baseline_valid" == 1 && "$mb10_valid" == 1 ]] \ |
| && awk -v new="$mb10_throughput" -v base="$baseline_throughput" \ |
| 'BEGIN { exit !(new >= base * 1.03) }'; then |
| mb10_eligible=1 |
| chosen="$mb10_config" |
| chosen_batch_size=120 |
| best_throughput="$mb10_throughput" |
| fi |
| if [[ "$baseline_valid" == 1 && "$mb12_valid" == 1 ]] \ |
| && awk -v new="$mb12_throughput" -v base="$baseline_throughput" \ |
| 'BEGIN { exit !(new >= base * 1.03) }'; then |
| mb12_eligible=1 |
| if awk -v new="$mb12_throughput" -v old="$best_throughput" \ |
| 'BEGIN { exit !(new > old) }'; then |
| chosen="$mb12_config" |
| chosen_batch_size=96 |
| best_throughput="$mb12_throughput" |
| fi |
| fi |
| if [[ "$baseline_valid" == 1 && "$selective_valid" == 1 ]] \ |
| && awk -v new="$selective_throughput" -v base="$baseline_throughput" \ |
| 'BEGIN { exit !(new >= base * 1.03) }'; then |
| selective_eligible=1 |
| if awk -v new="$selective_throughput" -v old="$best_throughput" \ |
| 'BEGIN { exit !(new > old) }'; then |
| chosen="$selective_config" |
| chosen_batch_size=128 |
| best_throughput="$selective_throughput" |
| fi |
| fi |
| if [[ "$baseline_valid" == 1 && "$gpu_valid" == 1 ]] \ |
| && awk -v new="$gpu_throughput" -v base="$baseline_throughput" \ |
| 'BEGIN { exit !(new >= base * 1.03) }'; then |
| gpu_eligible=1 |
| if awk -v new="$gpu_throughput" -v old="$best_throughput" \ |
| 'BEGIN { exit !(new > old) }'; then |
| chosen="$gpu_optim_config" |
| chosen_batch_size=128 |
| best_throughput="$gpu_throughput" |
| fi |
| fi |
| if [[ "$baseline_valid" == 1 && "$noact_valid" == 1 ]] \ |
| && awk -v new="$noact_throughput" -v base="$baseline_throughput" \ |
| 'BEGIN { exit !(new >= base * 1.03) }'; then |
| noact_eligible=1 |
| if awk -v new="$noact_throughput" -v old="$best_throughput" \ |
| 'BEGIN { exit !(new > old) }'; then |
| chosen="$noact_config" |
| chosen_batch_size=128 |
| best_throughput="$noact_throughput" |
| fi |
| fi |
| trap - EXIT INT TERM |
|
|
| |
| |
| |
| |
| |
| |
| remaining_seconds="$(python - "$workspace/DEADLINE" <<'PY' |
| import sys |
| import time |
| |
| print(max(0, int(float(open(sys.argv[1]).read().strip()) - time.time()))) |
| PY |
| )" |
| planning_throughput="$best_throughput" |
| if ! awk -v value="$planning_throughput" \ |
| 'BEGIN { exit !(value ~ /^[0-9]+([.][0-9]+)?$/ && value >= 1000) }'; then |
| |
| |
| planning_throughput=26000 |
| fi |
| final_eval_reserve_seconds=$((18 * 3600)) |
| startup_guard_seconds=1800 |
| read -r target_steps projected_step_seconds < <( |
| python - "$remaining_seconds" "$planning_throughput" \ |
| "$final_eval_reserve_seconds" "$startup_guard_seconds" \ |
| "$chosen_batch_size" <<'PY' |
| import sys |
|
|
| remaining = int(sys.argv[1]) |
| throughput = float(sys.argv[2]) |
| reserve = int(sys.argv[3]) |
| startup_guard = int(sys.argv[4]) |
| batch_size = int(sys.argv[5]) |
| tokens_per_update = batch_size * 32768 |
| projected_step_seconds = tokens_per_update / throughput * 1.05 + 1.5 |
| available = max(0, remaining - reserve - startup_guard) |
| updates = int(available / projected_step_seconds) |
| |
| |
| |
| token_equivalent_cap = 150 + int((2050 - 150) * 128 / batch_size) |
| target = max(151, min(token_equivalent_cap, 150 + updates)) |
| print(target, f"{projected_step_seconds:.3f}") |
| PY |
| ) |
|
|
| printf 'baseline_checkpoint_save_succeeded=%s\nbaseline_throughput=%s\nbaseline_peak_memory_gib=%s\nbaseline_loss=%s\nbaseline_grad_norm=%s\nbaseline_lr=%s\nbaseline_valid=%s\nmb10_checkpoint_save_succeeded=%s\nmb10_throughput=%s\nmb10_peak_memory_gib=%s\nmb10_loss=%s\nmb10_grad_norm=%s\nmb10_lr=%s\nmb10_valid=%s\nmb10_eligible=%s\nmb12_checkpoint_save_succeeded=%s\nmb12_throughput=%s\nmb12_peak_memory_gib=%s\nmb12_loss=%s\nmb12_grad_norm=%s\nmb12_lr=%s\nmb12_valid=%s\nmb12_eligible=%s\nselective_checkpoint_save_succeeded=%s\nselective_throughput=%s\nselective_peak_memory_gib=%s\nselective_loss=%s\nselective_grad_norm=%s\nselective_lr=%s\nselective_valid=%s\nselective_eligible=%s\ngpu_optim_checkpoint_save_succeeded=%s\ngpu_optim_throughput=%s\ngpu_optim_peak_memory_gib=%s\ngpu_optim_loss=%s\ngpu_optim_grad_norm=%s\ngpu_optim_lr=%s\ngpu_optim_valid=%s\ngpu_optim_eligible=%s\nnoact_checkpoint_save_succeeded=%s\nnoact_throughput=%s\nnoact_peak_memory_gib=%s\nnoact_loss=%s\nnoact_grad_norm=%s\nnoact_lr=%s\nnoact_valid=%s\nnoact_eligible=%s\nchosen_config=%s\nchosen_batch_size=%s\nremaining_seconds_at_plan=%s\nplanning_throughput=%s\nprojected_step_seconds=%s\nfinal_eval_reserve_seconds=%s\nstartup_guard_seconds=%s\ntarget_steps=%s\n' \ |
| "$baseline_save" "$baseline_throughput" "$baseline_memory" "$baseline_loss" "$baseline_grad" "$baseline_lr" "$baseline_valid" \ |
| "$mb10_save" "$mb10_throughput" "$mb10_memory" "$mb10_loss" "$mb10_grad" "$mb10_lr" "$mb10_valid" "$mb10_eligible" \ |
| "$mb12_save" "$mb12_throughput" "$mb12_memory" "$mb12_loss" "$mb12_grad" "$mb12_lr" "$mb12_valid" "$mb12_eligible" \ |
| "$selective_save" "$selective_throughput" "$selective_memory" "$selective_loss" "$selective_grad" "$selective_lr" "$selective_valid" "$selective_eligible" \ |
| "$gpu_save" "$gpu_throughput" "$gpu_memory" "$gpu_loss" "$gpu_grad" "$gpu_lr" "$gpu_valid" "$gpu_eligible" \ |
| "$noact_save" "$noact_throughput" "$noact_memory" "$noact_loss" "$noact_grad" "$noact_lr" "$noact_valid" "$noact_eligible" "$chosen" "$chosen_batch_size" \ |
| "$remaining_seconds" "$planning_throughput" "$projected_step_seconds" \ |
| "$final_eval_reserve_seconds" "$startup_guard_seconds" "$target_steps" \ |
| >"$workspace/state/training-variant-benchmarks.txt" |
|
|
| tmux has-session -t "$train_session" 2>/dev/null && { |
| echo "continuation session already exists" >&2 |
| exit 1 |
| } |
| tmux new-session -d -s "$train_session" \ |
| "cd /root/work/a/prime-rl && env CUDA_VISIBLE_DEVICES=4,5,6,7 TMPDIR=/tmp/agentptb-s1 PYTHONUNBUFFERED=1 uv run --no-sync sft @ '$chosen' --max-steps '$target_steps' > '$continuation_log' 2>&1" |
| printf 'config=%s\nbatch_size=%s\nmax_steps=%s\n' "$chosen" "$chosen_batch_size" "$target_steps" \ |
| >"$workspace/state/continuation-config.txt" |
| touch "$workspace/state/continuation-launched" |
|
|