add scripts/run_resume_stage2.sh
Browse filesResume the GRPO curriculum from stage 2 by pulling the SFT and
stage1 LoRA adapters from the Hub. Used after an HF Spaces /data
wipe so we don't redo SFT and stage1 work that already survived.
Made-with: Cursor
- scripts/run_resume_stage2.sh +115 -0
scripts/run_resume_stage2.sh
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Resume the GRPO curriculum from stage 2.
|
| 3 |
+
#
|
| 4 |
+
# Assumes:
|
| 5 |
+
# - Stages 1 has already been pushed to
|
| 6 |
+
# shivam2k3/opensoc-defender-grpo-stage1_basic
|
| 7 |
+
# - SFT adapter has been pushed to
|
| 8 |
+
# shivam2k3/opensoc-defender-grpo-sft
|
| 9 |
+
#
|
| 10 |
+
# Run this from the project root *after* /data has been wiped and the
|
| 11 |
+
# repo has been re-cloned. Safe to re-run.
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
# Reuse the install logic from run_full_pipeline.sh by sourcing only
|
| 15 |
+
# steps 1 and 2. We'd rather duplicate a few lines than risk source-ing
|
| 16 |
+
# a script that exits early if SFT adapter is missing.
|
| 17 |
+
echo "[1/6] Installing GPU stack ..."
|
| 18 |
+
pip install -q --upgrade pip setuptools wheel
|
| 19 |
+
pip install -q "future>=1.0.0"
|
| 20 |
+
pip install -q "torch==2.10.0" "torchvision==0.25.0" \
|
| 21 |
+
--index-url https://download.pytorch.org/whl/cu128
|
| 22 |
+
pip install -q "transformers>=4.46,<5"
|
| 23 |
+
pip install -q --no-deps "unsloth @ git+https://github.com/unslothai/unsloth.git"
|
| 24 |
+
pip install -q --no-deps "unsloth_zoo"
|
| 25 |
+
pip install -q --no-deps "trl>=0.18.2,<=0.24.0" peft accelerate bitsandbytes
|
| 26 |
+
pip install -q "datasets>=3.4.1,<4.4.0" tyro tensorboard matplotlib sentencepiece protobuf huggingface_hub
|
| 27 |
+
pip install -q hf_transfer msgspec "torchao>=0.13.0" cut_cross_entropy || true
|
| 28 |
+
pip install -q -r requirements.txt
|
| 29 |
+
|
| 30 |
+
echo "[2/6] Building / verifying datasets ..."
|
| 31 |
+
python -m data.build_holdout
|
| 32 |
+
|
| 33 |
+
echo "[3/6] Skipped (SFT + Stage 1 already on Hub)."
|
| 34 |
+
|
| 35 |
+
echo "[3.5/6] Pulling SFT and Stage 1 adapters from the Hub ..."
|
| 36 |
+
python - <<'PY'
|
| 37 |
+
import os
|
| 38 |
+
from huggingface_hub import snapshot_download
|
| 39 |
+
sft = snapshot_download(
|
| 40 |
+
"shivam2k3/opensoc-defender-grpo-sft",
|
| 41 |
+
local_dir="checkpoints/defender_sft_adapter",
|
| 42 |
+
local_dir_use_symlinks=False,
|
| 43 |
+
)
|
| 44 |
+
print("SFT adapter ->", sft)
|
| 45 |
+
stage1 = snapshot_download(
|
| 46 |
+
"shivam2k3/opensoc-defender-grpo-stage1_basic",
|
| 47 |
+
local_dir="checkpoints/defender_grpo/stage1_basic/adapter",
|
| 48 |
+
local_dir_use_symlinks=False,
|
| 49 |
+
)
|
| 50 |
+
print("Stage 1 adapter ->", stage1)
|
| 51 |
+
PY
|
| 52 |
+
|
| 53 |
+
echo "[4/6] Resuming GRPO from stage 2 (3 remaining stages) ..."
|
| 54 |
+
python -m train.train_grpo \
|
| 55 |
+
--model unsloth/Qwen2.5-3B-Instruct \
|
| 56 |
+
--sft-adapter checkpoints/defender_grpo/stage1_basic/adapter \
|
| 57 |
+
--stages stage2_multi,stage3_mixed,stage4_adversarial \
|
| 58 |
+
--steps-per-stage 200 --num-generations 8 \
|
| 59 |
+
--batch-size 2 --grad-accum 4 --lr 5e-6 \
|
| 60 |
+
--report-to tensorboard \
|
| 61 |
+
--out checkpoints/defender_grpo
|
| 62 |
+
|
| 63 |
+
echo "[5/6] Eval + plots ..."
|
| 64 |
+
python -m eval.eval \
|
| 65 |
+
--baseline unsloth/Qwen2.5-3B-Instruct \
|
| 66 |
+
--trained-adapter checkpoints/defender_grpo/stage4_adversarial/adapter \
|
| 67 |
+
--holdout data/holdout.jsonl --out-dir eval/results
|
| 68 |
+
python -m eval.plot_results --in eval/results/summary.json --out-dir eval/results
|
| 69 |
+
python -m eval.plot_training --grpo-root checkpoints/defender_grpo --out-dir eval/results
|
| 70 |
+
|
| 71 |
+
echo "[6/6] Baking demo data for the Gradio /demo Space ..."
|
| 72 |
+
python -m eval.bake_demo \
|
| 73 |
+
--baseline unsloth/Qwen2.5-3B-Instruct \
|
| 74 |
+
--trained-adapter checkpoints/defender_grpo/stage4_adversarial/adapter \
|
| 75 |
+
--n 50 --out data/demo_examples.json
|
| 76 |
+
|
| 77 |
+
if [ -n "${HF_TOKEN:-}" ] && [ -n "${HF_PUSH_TARGET:-}" ]; then
|
| 78 |
+
echo "[7/7] Uploading artifacts back to ${HF_PUSH_TARGET} ..."
|
| 79 |
+
python - <<'PY'
|
| 80 |
+
import os
|
| 81 |
+
from huggingface_hub import HfApi, upload_folder
|
| 82 |
+
token = os.environ["HF_TOKEN"]
|
| 83 |
+
target = os.environ["HF_PUSH_TARGET"]
|
| 84 |
+
adapter_repo = os.environ.get(
|
| 85 |
+
"HF_ADAPTER_REPO", target.split("/")[0] + "/opensoc-defender-grpo"
|
| 86 |
+
)
|
| 87 |
+
api = HfApi(token=token)
|
| 88 |
+
adapter_dir = "checkpoints/defender_grpo/stage4_adversarial/adapter"
|
| 89 |
+
if os.path.isdir(adapter_dir):
|
| 90 |
+
api.create_repo(adapter_repo, exist_ok=True, private=False)
|
| 91 |
+
upload_folder(repo_id=adapter_repo, folder_path=adapter_dir,
|
| 92 |
+
commit_message="GRPO-trained Qwen2.5-3B-Instruct LoRA defender adapter",
|
| 93 |
+
token=token)
|
| 94 |
+
print(" adapter ->", adapter_repo)
|
| 95 |
+
for p in [
|
| 96 |
+
"data/demo_examples.json",
|
| 97 |
+
"eval/results/summary.json",
|
| 98 |
+
"eval/results/bar_macro_f1.png",
|
| 99 |
+
"eval/results/bar_dismiss_on_malicious.png",
|
| 100 |
+
"eval/results/confusion_baseline_zero_shot.png",
|
| 101 |
+
"eval/results/confusion_opensoc_grpo.png",
|
| 102 |
+
"eval/results/training_curves.png",
|
| 103 |
+
"eval/results/training_kl_loss.png",
|
| 104 |
+
]:
|
| 105 |
+
if os.path.exists(p):
|
| 106 |
+
api.upload_file(
|
| 107 |
+
path_or_fileobj=p, path_in_repo=p,
|
| 108 |
+
repo_id=target,
|
| 109 |
+
commit_message="trained: refresh " + os.path.basename(p),
|
| 110 |
+
token=token,
|
| 111 |
+
)
|
| 112 |
+
print(" ", p, "->", target)
|
| 113 |
+
print("PIPELINE_DONE")
|
| 114 |
+
PY
|
| 115 |
+
fi
|