Closes the rebuild plan's R8 scope: production runner shape for the
integrated RL trainer. Three artifacts wired end-to-end:
1. `crates/ml-alpha/examples/alpha_rl_train.rs` — clap CLI driving
`IntegratedTrainer::step_with_lobsim` against MBP-10 windows
loaded via `MultiHorizonLoader::next_sequence_pair` (R2) for
true `(s_t, s_{t+1})` adjacency. Per `feedback_mbp10_mandatory`,
`--mbp10-data-dir` is required — no synthetic-data fallback in
the production path.
2. `infra/k8s/argo/alpha-rl-template.yaml` — WorkflowTemplate
mirroring alpha-perception's DAG (check-cache → ensure-binary →
train; warmup-gpu parallel). Binary cache slot is
`/data/bin/<sha>/alpha_rl_train` (distinct from `alpha_train`
so the two binaries coexist at the same SHA).
3. `scripts/argo-alpha-rl.sh` — dispatcher with three rebuild-plan
guards baked in.
## Dispatcher guards (per the rebuild plan's feedback list)
`feedback_default_to_l40s_pool` (2026-05-09): default `--gpu-pool`
is `ci-training-l40s` (sm_89). H100 (sm_90) is opt-in for production
scale-up only. Cubins must match the device, so the dispatcher
derives `cuda-compute-cap` from the pool name and threads it into
the workflow params.
`feedback_argo_template_must_apply` (2026-05-21 canonical incident):
`argo submit --from=wftmpl/<name>` reads the cluster CRD, NOT the
on-disk YAML; unknown `-p` parameters silently no-op without a prior
`kubectl apply`. Dispatcher applies the local template BEFORE every
submission (overrideable via `--skip-template-apply` for the rare
case where you've already applied manually).
`feedback_push_before_deploy` (2026-05-20 canonical incident): the
in-cluster `ensure-binary` pod fetches source from `origin/<branch>`,
NOT the local working tree. Submitting before `git push` deploys the
last-pushed SHA, which can lag local diff by N commits. Dispatcher
verifies `git rev-parse HEAD == git rev-parse origin/<branch>` and
hard-errors with the explicit push command otherwise. Bypass via
`--skip-push-check` (only when intentionally deploying a previously-
pushed SHA via `--sha`).
## CLI: gate G8 (NaN abort)
Per `feedback_stop_on_anomaly` + `feedback_kill_runs_on_anomaly_quickly`,
the CLI checks every per-head loss (l_bce / l_q / l_pi / l_v / l_aux
/ l_total) for finiteness after each `step_with_lobsim` call.
Non-finite at any step → write summary with `nan_abort_step` set →
`process::exit(2)`. R9's cluster smoke tail-watcher kills the
workflow on the non-zero exit code, satisfying gate G8 from the
rebuild plan.
## CLI: knobs that ARE on the CLI
Structural / boundary parameters only (per
`pearl_controller_anchors_isv_driven`: every adaptive knob lives in
ISV, not CLI flags):
* `--mbp10-data-dir / --predecoded-dir / --out` — I/O paths.
* `--n-steps` — wall-budget control (1000 R9 smoke / 50k+ prod).
* `--seq-len / --n-backtests / --per-capacity` — structural
sizing. seq_len threads into the loader's multi-resolution
`1:<seq_len>` config; n_backtests into both LobSimCuda and
PerceptionTrainerConfig.n_batch.
* `--seed` — reproducibility per
`pearl_scoped_init_seed_for_reproducibility` (forks deterministic
sub-seeds for dqn / ppo / per).
* `--instrument-mode` — MBP-10 filter (all / front-month / id=N).
* `--gpu-idx` — CUDA device selection.
What's NOT on the CLI: γ / τ / ε / entropy_coef / per_α / reward_scale
/ per-head LRs — all live in ISV[400..417] and are driven by R5's
controllers from EMA-tracked diagnostics. Per the rebuild plan
A1: "every adaptive bound is signal-driven, not tuned."
## Cluster smoke entry point
```bash
# R9 validation smoke (after pre-cluster local CUDA tests green).
./scripts/argo-alpha-rl.sh --n-steps 1000 --instrument-mode front-month
# Production scale-up (gated by R9's multi-fold pass).
./scripts/argo-alpha-rl.sh --n-steps 50000 --n-backtests 32 \
--per-capacity 100000 # GPU sum-tree R-future when capacity > 4096
```
## What's NOT in this commit
The R9 cluster smoke run itself is out of band — this commit ships
the entry points. R9 will execute the pre-cluster validation
checklist + first 1000-step smoke + multi-fold walk-forward G8 gate
per the rebuild plan §"Cluster smoke discipline".
The summary JSON's schema is intentionally narrow (final-step losses
+ replay len + completion state + NaN abort marker). R-future may
add per-epoch breakdowns + per-ISV-slot snapshots once the cluster
smoke tells us which diagnostics are actually load-bearing for kill
decisions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
195 lines
8.0 KiB
Bash
Executable File
195 lines
8.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Submit the alpha-rl (integrated RL trainer) workflow.
|
|
#
|
|
# Drives `IntegratedTrainer::step_with_lobsim` (DQN + PPO on shared
|
|
# Mamba2 → CfC encoder, R7d off-policy replay) against MBP-10 from the
|
|
# training-data PVC, using LobSimCuda as the synthetic-book environment.
|
|
# Emits alpha_rl_train_summary.json to the feature-cache PVC.
|
|
#
|
|
# Guards (enforced by the rebuild plan A9 feedback list):
|
|
# * `feedback_default_to_l40s_pool` — defaults to ci-training-l40s;
|
|
# H100 (sm_90) is opt-in for scale-up sweeps only.
|
|
# * `feedback_push_before_deploy` — verifies local HEAD == origin
|
|
# HEAD before submission, refuses to submit otherwise.
|
|
# * `feedback_argo_template_must_apply` — kubectl-applies the local
|
|
# alpha-rl-template.yaml BEFORE `argo submit --from=wftmpl/alpha-rl`
|
|
# (the submit reads the cluster CRD, NOT the on-disk YAML, so
|
|
# unknown -p params silently no-op without the apply).
|
|
#
|
|
# Usage:
|
|
# ./scripts/argo-alpha-rl.sh # 1k step smoke on HEAD
|
|
# ./scripts/argo-alpha-rl.sh --branch main
|
|
# ./scripts/argo-alpha-rl.sh --n-steps 50000 --n-backtests 32 # production sweep
|
|
# ./scripts/argo-alpha-rl.sh --gpu-pool ci-training-h100 # opt into H100
|
|
# ./scripts/argo-alpha-rl.sh --watch
|
|
#
|
|
# Plan reference: docs/superpowers/plans/2026-05-23-integrated-rl-trainer-rebuild.md
|
|
|
|
set -euo pipefail
|
|
|
|
SHA=HEAD
|
|
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "ml-alpha-phase-a")
|
|
GPU_POOL=ci-training-l40s
|
|
N_STEPS=1000
|
|
SEQ_LEN=32
|
|
N_BACKTESTS=1
|
|
PER_CAPACITY=4096
|
|
SEED=16962
|
|
INSTRUMENT_MODE=front-month
|
|
WATCH=false
|
|
SKIP_PUSH_CHECK=false
|
|
SKIP_TEMPLATE_APPLY=false
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 [OPTIONS]
|
|
--sha <commit> Git SHA (default: HEAD on the current branch)
|
|
--branch <branch> Git branch (default: $BRANCH)
|
|
--gpu-pool <pool> GPU pool (default: $GPU_POOL).
|
|
Per feedback_default_to_l40s_pool: L40S is
|
|
the default. Opt into ci-training-h100 only
|
|
for scale-up runs that need sm_90 / 80GB.
|
|
--n-steps <n> step_with_lobsim count (default: $N_STEPS).
|
|
R9 smoke = 1000; production = 50000+.
|
|
--seq-len <n> Encoder sequence length (default: $SEQ_LEN).
|
|
--n-backtests <n> Parallel LobSim instances per step
|
|
(default: $N_BACKTESTS).
|
|
--per-capacity <n> PER buffer capacity (default: $PER_CAPACITY).
|
|
--seed <n> Random seed (default: $SEED).
|
|
--instrument-mode <m> all | front-month | id=<N> (default: $INSTRUMENT_MODE)
|
|
--watch Follow logs via argo watch
|
|
--skip-push-check Bypass the feedback_push_before_deploy guard
|
|
(rare; use only when submitting against a
|
|
previously-pushed SHA via --sha).
|
|
--skip-template-apply Bypass the feedback_argo_template_must_apply
|
|
guard (rare; only when you've manually
|
|
kubectl applied the template already).
|
|
-h, --help Show this help
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--sha) SHA="$2"; shift 2 ;;
|
|
--branch) BRANCH="$2"; shift 2 ;;
|
|
--gpu-pool) GPU_POOL="$2"; shift 2 ;;
|
|
--n-steps) N_STEPS="$2"; shift 2 ;;
|
|
--seq-len) SEQ_LEN="$2"; shift 2 ;;
|
|
--n-backtests) N_BACKTESTS="$2"; shift 2 ;;
|
|
--per-capacity) PER_CAPACITY="$2"; shift 2 ;;
|
|
--seed) SEED="$2"; shift 2 ;;
|
|
--instrument-mode) INSTRUMENT_MODE="$2"; shift 2 ;;
|
|
--watch) WATCH=true; shift ;;
|
|
--skip-push-check) SKIP_PUSH_CHECK=true; shift ;;
|
|
--skip-template-apply) SKIP_TEMPLATE_APPLY=true; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown option: $1"; usage; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ── GPU pool → CUDA_COMPUTE_CAP ─────────────────────────────────────
|
|
# Per `feedback_default_to_l40s_pool` (2026-05-09): SP-chain training
|
|
# defaults to L40S (sm_89). H100 (sm_90) is opt-in for scale-up only.
|
|
# Cubins must match the device, so the dispatcher derives the cap from
|
|
# the pool name and threads it into the workflow params.
|
|
case "$GPU_POOL" in
|
|
ci-training-l40s|ci-training)
|
|
SM=89 ;;
|
|
ci-training-h100)
|
|
SM=90 ;;
|
|
*)
|
|
echo "ERROR: unknown gpu-pool: $GPU_POOL"
|
|
echo " Supported: ci-training-l40s, ci-training-h100"
|
|
exit 1 ;;
|
|
esac
|
|
|
|
# ── Guard 1: feedback_argo_template_must_apply ───────────────────────
|
|
# `argo submit --from=wftmpl/<name>` reads the cluster CRD, NOT the
|
|
# on-disk YAML. If the template has been edited locally but never
|
|
# `kubectl apply -f`'d, the submit silently uses the stale cluster
|
|
# version + drops any newly-added parameters. Pearl ref: 2026-05-21
|
|
# canonical incident.
|
|
TEMPLATE_FILE="infra/k8s/argo/alpha-rl-template.yaml"
|
|
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
|
echo "ERROR: template not found at $TEMPLATE_FILE (run from repo root)"
|
|
exit 1
|
|
fi
|
|
if [[ "$SKIP_TEMPLATE_APPLY" == "false" ]]; then
|
|
echo "Applying alpha-rl WorkflowTemplate to cluster..."
|
|
kubectl apply -n foxhunt -f "$TEMPLATE_FILE" >/dev/null
|
|
echo " applied: $TEMPLATE_FILE"
|
|
else
|
|
echo "WARNING: --skip-template-apply set — argo will use whatever's"
|
|
echo " already in the cluster CRD (may be stale)."
|
|
fi
|
|
|
|
# ── Guard 2: feedback_push_before_deploy ─────────────────────────────
|
|
# Per the 2026-05-20 incident: cluster pods pull source from origin
|
|
# `<BRANCH>`, NOT from the local working tree. Submitting before
|
|
# `git push` deploys whatever was last on origin, which can lag the
|
|
# local diff by N commits. Verify HEAD == origin/<branch> before
|
|
# submission unless explicitly bypassed.
|
|
if [[ "$SKIP_PUSH_CHECK" == "false" ]]; then
|
|
echo "Verifying local HEAD == origin/$BRANCH (feedback_push_before_deploy)..."
|
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
|
echo "ERROR: not in a git repo; cannot verify push state"
|
|
exit 1
|
|
fi
|
|
git fetch --quiet origin "$BRANCH" 2>/dev/null || true
|
|
LOCAL_HEAD=$(git rev-parse HEAD)
|
|
ORIGIN_HEAD=$(git rev-parse "origin/$BRANCH" 2>/dev/null || echo "MISSING")
|
|
if [[ "$ORIGIN_HEAD" == "MISSING" ]]; then
|
|
echo "ERROR: origin/$BRANCH does not exist — push the branch first:"
|
|
echo " git push -u origin $BRANCH"
|
|
exit 1
|
|
fi
|
|
if [[ "$LOCAL_HEAD" != "$ORIGIN_HEAD" ]]; then
|
|
echo "ERROR: local HEAD ($LOCAL_HEAD) != origin/$BRANCH ($ORIGIN_HEAD)"
|
|
echo " Push first:"
|
|
echo " git push origin $BRANCH"
|
|
echo " Or override with --skip-push-check if you're knowingly"
|
|
echo " deploying a previously-pushed SHA via --sha."
|
|
exit 1
|
|
fi
|
|
echo " HEAD verified: $LOCAL_HEAD == origin/$BRANCH"
|
|
fi
|
|
|
|
# Resolve SHA=HEAD to a concrete SHA before submission so the in-cluster
|
|
# check-cache pod doesn't need git access. Mirrors alpha-perception.
|
|
if [ "$SHA" = "HEAD" ]; then
|
|
SHA=$(git rev-parse "origin/$BRANCH" 2>/dev/null \
|
|
|| git rev-parse "$BRANCH" 2>/dev/null \
|
|
|| git rev-parse HEAD)
|
|
echo "Resolved HEAD → $SHA"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Submitting alpha-rl workflow..."
|
|
echo " branch: $BRANCH"
|
|
echo " sha: $SHA"
|
|
echo " gpu-pool: $GPU_POOL (sm_$SM)"
|
|
echo " n-steps: $N_STEPS"
|
|
echo " seq-len: $SEQ_LEN"
|
|
echo " n-backtests: $N_BACKTESTS"
|
|
echo " per-capacity: $PER_CAPACITY"
|
|
echo " seed: $SEED"
|
|
echo " instrument: $INSTRUMENT_MODE"
|
|
|
|
WATCH_FLAG=""
|
|
if [[ "$WATCH" == "true" ]]; then
|
|
WATCH_FLAG="--watch"
|
|
fi
|
|
|
|
argo submit -n foxhunt --from=wftmpl/alpha-rl \
|
|
-p commit-sha="$SHA" \
|
|
-p git-branch="$BRANCH" \
|
|
-p cuda-compute-cap="$SM" \
|
|
-p gpu-pool="$GPU_POOL" \
|
|
-p n-steps="$N_STEPS" \
|
|
-p seq-len="$SEQ_LEN" \
|
|
-p n-backtests="$N_BACKTESTS" \
|
|
-p per-capacity="$PER_CAPACITY" \
|
|
-p seed="$SEED" \
|
|
-p instrument-mode="$INSTRUMENT_MODE" \
|
|
$WATCH_FLAG
|