Files
foxhunt/infra/scripts/train.sh
jgrusewski e4870b17b9 fix: tune log levels across workspace — demote noisy warn to debug/trace
Reduce log noise for non-critical operational paths: connection retries,
expected fallbacks, graceful degradation, and optional feature absence.
Keeps warn/error for genuine failures requiring attention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 11:35:15 +01:00

720 lines
25 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
# ---------------------------------------------------------------------------
# train.sh - Training orchestrator: submits K8s Jobs to the GPU pool
# ---------------------------------------------------------------------------
# Usage:
# ./infra/scripts/train.sh <preset> [--model MODEL] [--symbol SYMBOL]
# [--trials N] [--max-steps N] [--timeout N]
# [--commit SHA] [--poll-interval N]
#
# Presets:
# quick-test - fast sanity check (max-steps=500, requires --model)
# single-model - full training run for one model (requires --model)
# full-ensemble - trains all 10 models sequentially (dqn ppo tft mamba2 tggn tlob liquid kan xlstm diffusion)
# hyperopt - hyperparameter optimisation (requires --model, uses --trials)
# ci-train - wait for Argo CI to pass, then auto-submit training (requires --model)
# ---------------------------------------------------------------------------
# --- Defaults -------------------------------------------------------------
SYMBOL="ES.FUT"
TRIALS=20
MAX_STEPS=2000
TIMEOUT=3600 # default; hyperopt preset overrides to 6h below
DATA_DIR="/data/futures-baseline"
NAMESPACE="foxhunt"
IMAGE="gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/foxhunt-training-runtime:latest"
MODEL=""
PRESET=""
COMMIT=""
POLL_INTERVAL=30
TRAIN_PRESET="hyperopt"
TIMESTAMP="$(date +%s)"
S3_BUCKET="foxhunt-models"
MINIO_ENDPOINT="http://minio.foxhunt.svc.cluster.local:9000"
RUN_ID="$(date +%Y%m%d-%H%M%S)"
ALL_MODELS=(dqn ppo tft mamba2 tggn tlob liquid kan xlstm diffusion)
# --- Model-to-binary mapping ----------------------------------------------
declare -A MODEL_BINARY=(
[dqn]=train_baseline_rl
[ppo]=train_baseline_rl
[tft]=train_baseline_supervised
[mamba2]=train_baseline_supervised
[tggn]=train_baseline_supervised
[tlob]=train_baseline_supervised
[liquid]=train_baseline_supervised
[kan]=train_baseline_supervised
[xlstm]=train_baseline_supervised
[diffusion]=train_baseline_supervised
)
# Hyperopt uses separate binaries
declare -A HYPEROPT_BINARY=(
[dqn]=hyperopt_baseline_rl
[ppo]=hyperopt_baseline_rl
[tft]=hyperopt_baseline_supervised
[mamba2]=hyperopt_baseline_supervised
[tggn]=hyperopt_baseline_supervised
[tlob]=hyperopt_baseline_supervised
[liquid]=hyperopt_baseline_supervised
[kan]=hyperopt_baseline_supervised
[xlstm]=hyperopt_baseline_supervised
[diffusion]=hyperopt_baseline_supervised
)
EVAL_BINARY="evaluate_baseline"
# --- Helpers --------------------------------------------------------------
usage() {
cat <<'USAGE'
Usage: ./infra/scripts/train.sh <preset> [OPTIONS]
Presets:
quick-test Fast sanity check (requires --model, max-steps=500)
single-model Full training run (requires --model)
full-ensemble Trains all 10 models sequentially (dqn ppo tft mamba2 tggn tlob liquid kan xlstm diffusion)
hyperopt Hyperparameter search (requires --model, uses --trials)
evaluate Evaluate trained models (requires --run-id from prior training run)
ci-train Wait for Argo CI pipeline, then auto-submit training (requires --model)
Options:
--model MODEL Model name: dqn | ppo | tft | mamba2 | tggn | tlob | liquid | kan | xlstm | diffusion
--symbol SYMBOL Trading symbol (default: ES.FUT)
--trials N Hyperopt trial count (default: 20)
--max-steps N Max steps per epoch (default: 2000)
--timeout N Job timeout in seconds (default: 3600)
--commit SHA Git commit to track CI for (default: HEAD of main)
--poll-interval N CI poll interval seconds (default: 30)
--train-preset P Training preset after CI (default: hyperopt; for ci-train)
-h, --help Show this help message
USAGE
exit 0
}
die() { echo "ERROR: $*" >&2; exit 1; }
validate_model() {
local m="$1"
[[ -n "${MODEL_BINARY[$m]+x}" ]] || die "Unknown model '${m}'. Valid: ${!MODEL_BINARY[*]}"
}
# --- Parse arguments -------------------------------------------------------
[[ $# -eq 0 ]] && usage
[[ "$1" == "-h" || "$1" == "--help" ]] && usage
PRESET="$1"; shift
while [[ $# -gt 0 ]]; do
case "$1" in
--model) MODEL="$2"; shift 2 ;;
--symbol) SYMBOL="$2"; shift 2 ;;
--trials) TRIALS="$2"; shift 2 ;;
--max-steps) MAX_STEPS="$2"; shift 2 ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
--run-id) RUN_ID="$2"; shift 2 ;;
--commit) COMMIT="$2"; shift 2 ;;
--poll-interval) POLL_INTERVAL="$2"; shift 2 ;;
--train-preset) TRAIN_PRESET="$2"; shift 2 ;;
-h|--help) usage ;;
*) die "Unknown option: $1" ;;
esac
done
# --- Preset validation -----------------------------------------------------
case "$PRESET" in
quick-test)
[[ -z "$MODEL" ]] && die "quick-test preset requires --model"
validate_model "$MODEL"
MAX_STEPS=500
;;
single-model)
[[ -z "$MODEL" ]] && die "single-model preset requires --model"
validate_model "$MODEL"
;;
full-ensemble)
# Trains all models; --model is ignored if provided
MODEL=""
;;
hyperopt)
[[ -z "$MODEL" ]] && die "hyperopt preset requires --model"
validate_model "$MODEL"
# Hyperopt runs 20 trials × 8 epochs — needs 6h unless user specified --timeout
if [[ "$TIMEOUT" -eq 3600 ]]; then
TIMEOUT=21600
fi
;;
evaluate)
[[ -z "$RUN_ID" ]] && die "evaluate preset requires --run-id (from a previous training run)"
;;
ci-train)
[[ -z "$MODEL" ]] && die "ci-train preset requires --model"
validate_model "$MODEL"
# Resolve commit SHA if not provided
if [[ -z "$COMMIT" ]]; then
COMMIT="$(git rev-parse HEAD 2>/dev/null || echo "")"
[[ -z "$COMMIT" ]] && die "Could not determine commit SHA. Use --commit SHA"
fi
;;
*)
die "Unknown preset '${PRESET}'. Valid: quick-test, single-model, full-ensemble, hyperopt, evaluate, ci-train"
;;
esac
# --- CI Monitoring --------------------------------------------------------
# Find the Argo Workflow for a given commit SHA.
# Argo CI workflows are submitted with parameter commit-sha=<sha>.
# We search for workflows matching the ci-pipeline template.
find_ci_workflow() {
local sha="$1"
local short_sha="${sha:0:8}"
# List recent ci-pipeline workflows and find the one matching our commit.
# Argo Events labels workflows with events.argoproj.io/trigger (not
# workflows.argoproj.io/workflow-template), so use that selector.
kubectl -n "${NAMESPACE}" get workflows \
-l "events.argoproj.io/trigger=ci-pipeline" \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\t"}{range .spec.arguments.parameters[*]}{.name}={.value}{","}{end}{"\n"}{end}' \
2>/dev/null \
| grep "commit-sha=${sha}\|commit-sha=${short_sha}" \
| head -1 \
| cut -f1
}
# Get workflow phase: Pending, Running, Succeeded, Failed, Error
get_workflow_phase() {
local wf_name="$1"
kubectl -n "${NAMESPACE}" get workflow "${wf_name}" \
-o jsonpath='{.status.phase}' 2>/dev/null
}
# Get workflow node statuses (one-liner summary)
get_workflow_summary() {
local wf_name="$1"
kubectl -n "${NAMESPACE}" get workflow "${wf_name}" \
-o jsonpath='{range .status.nodes[*]}{.displayName}: {.phase} {end}' 2>/dev/null
}
# Get workflow start time and duration
get_workflow_timing() {
local wf_name="$1"
local started finished
started=$(kubectl -n "${NAMESPACE}" get workflow "${wf_name}" \
-o jsonpath='{.status.startedAt}' 2>/dev/null)
finished=$(kubectl -n "${NAMESPACE}" get workflow "${wf_name}" \
-o jsonpath='{.status.finishedAt}' 2>/dev/null)
if [[ -n "$finished" && "$finished" != "null" ]]; then
echo "started=${started} finished=${finished}"
elif [[ -n "$started" ]]; then
echo "started=${started} (still running)"
else
echo "(not started yet)"
fi
}
# Print Prometheus / log links for CI monitoring
print_monitor_links() {
local wf_name="$1"
echo ""
echo " Argo UI: kubectl -n ${NAMESPACE} get workflow ${wf_name} -o yaml"
echo " Logs: kubectl -n ${NAMESPACE} logs -l workflows.argoproj.io/workflow=${wf_name} --prefix -f"
echo " Prometheus: http://prometheus-stack-kube-prom-prometheus.foxhunt.svc.cluster.local:9090/graph?g0.expr=argo_workflow_status_phase"
echo ""
}
# Wait for CI workflow to complete. Returns 0 on success, 1 on failure.
wait_for_ci() {
local sha="$1"
local short_sha="${sha:0:8}"
local wf_name=""
local phase=""
local attempt=0
local max_wait_for_wf=60 # max attempts to find the workflow (60 * poll = 30 min)
echo "======================================================================"
echo " Waiting for Argo CI pipeline to complete"
echo " Commit : ${sha} (${short_sha})"
echo " Poll : every ${POLL_INTERVAL}s"
echo "======================================================================"
echo ""
# Phase 1: Find the workflow (it may not exist yet if webhook is delayed)
echo "--- Looking for CI workflow for commit ${short_sha}..."
while [[ -z "$wf_name" ]]; do
wf_name=$(find_ci_workflow "$sha")
if [[ -n "$wf_name" ]]; then
echo " Found workflow: ${wf_name}"
print_monitor_links "$wf_name"
break
fi
attempt=$((attempt + 1))
if [[ $attempt -ge $max_wait_for_wf ]]; then
echo "ERROR: No CI workflow found for commit ${short_sha} after $((attempt * POLL_INTERVAL))s"
echo " Verify the GitLab webhook triggered. Check:"
echo " kubectl -n ${NAMESPACE} get workflows -l workflows.argoproj.io/workflow-template=ci-pipeline"
return 1
fi
echo " Workflow not found yet (attempt ${attempt}/${max_wait_for_wf}), retrying in ${POLL_INTERVAL}s..."
sleep "${POLL_INTERVAL}"
done
# Phase 2: Poll until terminal phase
echo "--- Monitoring workflow ${wf_name}..."
while true; do
phase=$(get_workflow_phase "$wf_name")
local timing
timing=$(get_workflow_timing "$wf_name")
case "$phase" in
Succeeded)
echo ""
echo " CI PASSED (${timing})"
echo ""
return 0
;;
Failed|Error)
echo ""
echo " CI FAILED phase=${phase} (${timing})"
echo ""
echo " Workflow summary:"
get_workflow_summary "$wf_name" | tr ' ' '\n' | sed 's/^/ /'
echo ""
echo " Logs: kubectl -n ${NAMESPACE} logs -l workflows.argoproj.io/workflow=${wf_name} --prefix"
return 1
;;
Running|Pending|"")
local step_info
step_info=$(get_workflow_summary "$wf_name" 2>/dev/null | tr ' ' '\n' | grep -c "Running" || echo "0")
printf " [%s] phase=%-8s running_steps=%s %s\n" \
"$(date +%H:%M:%S)" "${phase:-Pending}" "${step_info}" "${timing}"
;;
*)
echo " [$(date +%H:%M:%S)] phase=${phase} (unexpected)"
;;
esac
sleep "${POLL_INTERVAL}"
done
}
# --- Generate K8s Job manifest --------------------------------------------
generate_job_manifest() {
local model="$1"
local job_name="train-${model}-${TIMESTAMP}"
local binary
if [[ "$PRESET" == "hyperopt" ]]; then
binary="${HYPEROPT_BINARY[$model]}"
else
binary="${MODEL_BINARY[$model]}"
fi
# Build CLI args (excluding binary name)
local cli_args=("--model" "$model" "--symbol" "$SYMBOL" "--data-dir" "/data/futures-baseline")
cli_args+=("--mbp10-data-dir" "/data/futures-baseline-mbp10")
cli_args+=("--trades-data-dir" "/data/futures-baseline-trades")
if [[ "$PRESET" == "hyperopt" ]]; then
# hyperopt binary: --output is a JSON file path, --trials count
cli_args+=("--output" "/output/hyperopt_results.json")
cli_args+=("--trials" "$TRIALS")
else
# training binary: --output-dir is a directory for checkpoints
cli_args+=("--output-dir" "/output")
fi
# Convert args array to YAML list items
local args_yaml=""
for arg in "${cli_args[@]}"; do
args_yaml="${args_yaml}
- \"${arg}\""
done
cat <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: ${job_name}
namespace: ${NAMESPACE}
labels:
foxhunt/job-type: training
foxhunt/model: ${model}
foxhunt/preset: ${PRESET}
foxhunt/run-id: "${RUN_ID}"
spec:
activeDeadlineSeconds: ${TIMEOUT}
backoffLimit: 1
ttlSecondsAfterFinished: 600
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9094"
prometheus.io/path: "/metrics"
labels:
app.kubernetes.io/part-of: foxhunt
app.kubernetes.io/component: training-workflow
foxhunt/job-type: training
foxhunt/model: ${model}
foxhunt/preset: ${PRESET}
spec:
restartPolicy: Never
nodeSelector:
k8s.scaleway.com/pool-name: ci-training-h100
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
- key: node.cilium.io/agent-not-ready
operator: Exists
effect: NoSchedule
imagePullSecrets:
- name: gitlab-registry
initContainers:
- name: fetch-binary
image: ${IMAGE}
command: ["/bin/sh", "-c"]
args:
- |
set -e
BINARY="${binary}"
curl -fSL -o "/binaries/\${BINARY}" \\
--header "DEPLOY-TOKEN: \${GITLAB_DEPLOY_TOKEN}" \\
"\${GITLAB_API}/projects/1/packages/generic/foxhunt-training/\${FOXHUNT_RELEASE}/\${BINARY}"
chmod +x "/binaries/\${BINARY}"
echo "Fetched \${BINARY} \${FOXHUNT_RELEASE} (\$(stat -c%s /binaries/\${BINARY}) bytes)"
env:
- name: GITLAB_DEPLOY_TOKEN
valueFrom:
secretKeyRef:
name: gitlab-deploy-token
key: token
- name: GITLAB_API
value: "http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181/api/v4"
- name: FOXHUNT_RELEASE
value: "latest"
volumeMounts:
- name: binaries
mountPath: /binaries
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
- name: sync-training-data
image: ${IMAGE}
command: ["/bin/sh", "-c"]
args:
- |
set -e
RCLONE_FLAGS="--s3-provider=Minio --s3-endpoint=http://minio.foxhunt.svc.cluster.local:9000 --s3-access-key-id=\${MINIO_ACCESS_KEY} --s3-secret-access-key=\${MINIO_SECRET_KEY} --s3-no-check-bucket"
echo "Syncing OHLCV data..."
rclone sync ":s3:foxhunt-training-data/futures-baseline" /data/futures-baseline \$RCLONE_FLAGS --stats-one-line -v
echo "Syncing MBP-10 data..."
rclone sync ":s3:foxhunt-training-data/futures-baseline-mbp10" /data/futures-baseline-mbp10 \$RCLONE_FLAGS --stats-one-line -v
echo "Syncing trades data..."
rclone sync ":s3:foxhunt-training-data/futures-baseline-trades" /data/futures-baseline-trades \$RCLONE_FLAGS --stats-one-line -v
echo "Data sync complete:"
du -sh /data/futures-baseline/ /data/futures-baseline-mbp10/ /data/futures-baseline-trades/ 2>/dev/null || true
env:
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: access-key
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: secret-key
volumeMounts:
- name: training-data
mountPath: /data
resources:
requests:
cpu: "1"
memory: 512Mi
limits:
cpu: "2"
memory: 1Gi
containers:
- name: training
image: ${IMAGE}
command: ["/binaries/${binary}"]
args:${args_yaml}
env:
- name: RUST_LOG
value: info
- name: SQLX_OFFLINE
value: "true"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://tempo.foxhunt.svc.cluster.local:4317"
resources:
requests:
nvidia.com/gpu: "1"
cpu: "4"
memory: "16Gi"
limits:
nvidia.com/gpu: "1"
cpu: "8"
memory: "32Gi"
volumeMounts:
- name: training-data
mountPath: /data
readOnly: true
- name: output
mountPath: /output
- name: binaries
mountPath: /binaries
readOnly: true
volumes:
- name: training-data
persistentVolumeClaim:
claimName: training-data-pvc
- name: output
emptyDir:
sizeLimit: 2Gi
- name: binaries
emptyDir:
sizeLimit: 500Mi
EOF
}
generate_eval_manifest() {
local job_name="eval-${RUN_ID}-${TIMESTAMP}"
cat <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: ${job_name}
namespace: ${NAMESPACE}
labels:
foxhunt/job-type: evaluate
foxhunt/run-id: "${RUN_ID}"
spec:
activeDeadlineSeconds: ${TIMEOUT}
backoffLimit: 1
ttlSecondsAfterFinished: 600
template:
metadata:
labels:
app.kubernetes.io/part-of: foxhunt
app.kubernetes.io/component: training-workflow
foxhunt/job-type: evaluate
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9094"
prometheus.io/path: "/metrics"
spec:
restartPolicy: Never
nodeSelector:
k8s.scaleway.com/pool-name: ci-training-h100
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
- key: node.cilium.io/agent-not-ready
operator: Exists
effect: NoSchedule
imagePullSecrets:
- name: gitlab-registry
initContainers:
- name: fetch-binary
image: ${IMAGE}
command: ["/bin/sh", "-c"]
args:
- |
set -e
BINARY="${EVAL_BINARY}"
curl -fSL -o "/binaries/\${BINARY}" \\
--header "DEPLOY-TOKEN: \${GITLAB_DEPLOY_TOKEN}" \\
"\${GITLAB_API}/projects/1/packages/generic/foxhunt-training/\${FOXHUNT_RELEASE}/\${BINARY}"
chmod +x "/binaries/\${BINARY}"
echo "Fetched \${BINARY} \${FOXHUNT_RELEASE} (\$(stat -c%s /binaries/\${BINARY}) bytes)"
env:
- name: GITLAB_DEPLOY_TOKEN
valueFrom:
secretKeyRef:
name: gitlab-deploy-token
key: token
- name: GITLAB_API
value: "http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181/api/v4"
- name: FOXHUNT_RELEASE
value: "latest"
volumeMounts:
- name: binaries
mountPath: /binaries
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
- name: sync-training-data
image: ${IMAGE}
command: ["/bin/sh", "-c"]
args:
- |
set -e
RCLONE_FLAGS="--s3-provider=Minio --s3-endpoint=http://minio.foxhunt.svc.cluster.local:9000 --s3-access-key-id=\${MINIO_ACCESS_KEY} --s3-secret-access-key=\${MINIO_SECRET_KEY} --s3-no-check-bucket"
echo "Syncing OHLCV data..."
rclone sync ":s3:foxhunt-training-data/futures-baseline" /data/futures-baseline \$RCLONE_FLAGS --stats-one-line -v
echo "Syncing MBP-10 data..."
rclone sync ":s3:foxhunt-training-data/futures-baseline-mbp10" /data/futures-baseline-mbp10 \$RCLONE_FLAGS --stats-one-line -v
echo "Syncing trades data..."
rclone sync ":s3:foxhunt-training-data/futures-baseline-trades" /data/futures-baseline-trades \$RCLONE_FLAGS --stats-one-line -v
echo "Data sync complete:"
du -sh /data/futures-baseline/ /data/futures-baseline-mbp10/ /data/futures-baseline-trades/ 2>/dev/null || true
env:
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: access-key
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: secret-key
volumeMounts:
- name: training-data
mountPath: /data
resources:
requests:
cpu: "1"
memory: 512Mi
limits:
cpu: "2"
memory: 1Gi
containers:
- name: evaluate
image: ${IMAGE}
command: ["/binaries/${EVAL_BINARY}"]
args:
- "--model=both"
- "--data-dir=/data/futures-baseline"
- "--models-dir=/output/models"
env:
- name: RUST_LOG
value: info
- name: SQLX_OFFLINE
value: "true"
resources:
requests:
nvidia.com/gpu: "1"
cpu: "4"
memory: "16Gi"
limits:
nvidia.com/gpu: "1"
cpu: "8"
memory: "32Gi"
volumeMounts:
- name: training-data
mountPath: /data
readOnly: true
- name: output
mountPath: /output
- name: binaries
mountPath: /binaries
readOnly: true
volumes:
- name: training-data
persistentVolumeClaim:
claimName: training-data-pvc
- name: output
emptyDir:
sizeLimit: 2Gi
- name: binaries
emptyDir:
sizeLimit: 500Mi
EOF
}
# --- Submit a single job and collect its name ------------------------------
submitted_jobs=()
submit_job() {
local model="$1"
local job_name="train-${model}-${TIMESTAMP}"
echo "--- Submitting job: ${job_name} (model=${model}, preset=${PRESET})"
generate_job_manifest "$model" | kubectl apply -f -
submitted_jobs+=("$job_name")
}
# --- Main dispatch ---------------------------------------------------------
echo "======================================================================"
echo " Foxhunt Training Orchestrator"
echo " Preset : ${PRESET}"
if [[ "$PRESET" == "ci-train" ]]; then
echo " Commit : ${COMMIT:0:12}"
echo " After : ${TRAIN_PRESET} --model ${MODEL}"
echo " Poll : every ${POLL_INTERVAL}s"
fi
echo " Symbol : ${SYMBOL}"
echo " Image : ${IMAGE}"
echo " NS : ${NAMESPACE}"
echo " Run ID : ${RUN_ID}"
echo " MinIO : minio://${S3_BUCKET}/runs/${RUN_ID}/"
echo "======================================================================"
echo ""
case "$PRESET" in
quick-test|single-model|hyperopt)
submit_job "$MODEL"
;;
full-ensemble)
for m in "${ALL_MODELS[@]}"; do
submit_job "$m"
echo " Waiting for ${m} to complete before next model..."
kubectl -n "${NAMESPACE}" wait --for=condition=complete "job/train-${m}-${TIMESTAMP}" --timeout="${TIMEOUT}s" 2>/dev/null || true
done
;;
evaluate)
echo "--- Submitting evaluation job for run ${RUN_ID}"
generate_eval_manifest | kubectl apply -f -
submitted_jobs+=("eval-${RUN_ID}-${TIMESTAMP}")
;;
ci-train)
echo "--- CI-Train: monitoring CI for commit ${COMMIT:0:8}, then auto-submitting ${TRAIN_PRESET} job"
echo ""
if wait_for_ci "$COMMIT"; then
echo "======================================================================"
echo " CI passed — submitting ${TRAIN_PRESET} training for model=${MODEL}"
echo "======================================================================"
echo ""
# Switch to the training preset for job generation
PRESET="$TRAIN_PRESET"
# Apply hyperopt timeout if needed
if [[ "$TRAIN_PRESET" == "hyperopt" && "$TIMEOUT" -eq 3600 ]]; then
TIMEOUT=21600
fi
submit_job "$MODEL"
else
echo "======================================================================"
echo " CI failed — NOT submitting training job"
echo "======================================================================"
exit 1
fi
;;
esac
echo ""
echo "======================================================================"
echo " All jobs submitted."
echo "======================================================================"
echo ""
echo "Monitor with:"
echo ""
for jn in "${submitted_jobs[@]}"; do
echo " kubectl -n ${NAMESPACE} get job ${jn}"
echo " kubectl -n ${NAMESPACE} logs job/${jn} -f"
echo ""
done
echo " kubectl -n ${NAMESPACE} get jobs -l foxhunt/preset=${PRESET}"
echo " kubectl -n ${NAMESPACE} get pods -l foxhunt/job-type=training --watch"