Files
foxhunt/infra/k8s/argo/lob-backtest-sweep-template.yaml
jgrusewski 25c78c5913 fix(infra): lob-backtest-sweep secretName git-ssh-key -> argo-git-ssh-key
Matches the alpha-perception-template (which is the known-good
template in this namespace). Without the fix, ensure-binary +
run-cell + aggregate pods all stayed Init for ~20m with
'MountVolume.SetUp failed for volume "git-ssh-key" : secret
"git-ssh-key" not found' before the workflow timed out.

The actual secret in the foxhunt namespace is named
'argo-git-ssh-key' (see kubectl get secrets -n foxhunt). Same
template misconfig as the training-data PVC name.
2026-05-19 12:12:34 +02:00

349 lines
12 KiB
YAML

# Real-LOB backtest sweep workflow.
#
# Fans out a parameter grid across N parallel GPU pods, each running
# `fxt-backtest run` against the same MBP-10 data + checkpoint with one
# cell's worth of overrides. A final CPU pod runs `fxt-backtest aggregate`
# to produce aggregate.parquet + pareto_frontier.json at the sweep root.
#
# The `# __SWEEP_CELLS__` marker on the dag.tasks line is replaced by
# scripts/argo-lob-sweep.sh with N generated WorkflowTask stanzas before
# submission. Same convention as train-multi-seed-template.yaml's
# # __MATRIX_TASKS__ marker.
#
# DAG:
# ensure-binary ──> [N parallel run-cell-<i> tasks on ci-training-l40s,
# each with its own outputDir on the shared PVC]
# │
# └──> aggregate (CPU node, runs fxt-backtest aggregate
# against the sweep root)
#
# Per `feedback_default_to_l40s_pool.md` (2026-05-09): defaults to
# ci-training-l40s + sm_89. Override via --gpu-pool ci-training-h100
# for sm_90 / 80 GB.
#
# Usage:
# ./scripts/argo-lob-sweep.sh --grid config/ml/sweep_decision_stride_example.yaml
# ./scripts/argo-lob-sweep.sh --grid <path> --sha abc1234 --gpu-pool ci-training-h100
# ./scripts/argo-lob-sweep.sh --grid <path> --watch
# ./scripts/argo-lob-sweep.sh --grid <path> --dry-run > /tmp/wf.yaml
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: lob-backtest-sweep
namespace: foxhunt
labels:
app.kubernetes.io/name: lob-backtest-sweep
app.kubernetes.io/part-of: foxhunt
app.kubernetes.io/component: backtest
spec:
entrypoint: sweep-matrix
onExit: notify-result
serviceAccountName: argo-workflow
archiveLogs: true
podMetadata:
labels:
app.kubernetes.io/part-of: foxhunt
app.kubernetes.io/component: backtest
securityContext:
fsGroup: 0
ttlStrategy:
secondsAfterCompletion: 3600
# LOB sweep cells are much faster than training (~minutes vs hours);
# 2h walltime is generous even for N=128 cells.
activeDeadlineSeconds: 7200
arguments:
parameters:
- name: commit-sha
value: HEAD
- name: git-branch
value: main
- name: cuda-compute-cap
value: "89"
- name: gpu-pool
value: ci-training-l40s
- name: data-root
value: /mnt/training-data/futures-baseline/ES.FUT
- name: predecoded-dir
value: /mnt/training-data/futures-baseline/ES.FUT
- name: checkpoint
value: "" # empty = trunk runs with --seed (random init);
# set to a path on the data PVC for trained weights
- name: sweep-root
value: /mnt/training-data/sweeps/lob-backtest
- name: sweep-tag
value: default # subdirectory: <sweep-root>/<sweep-tag>/
# Operational knobs. argo-lob-sweep.sh sets these per-grid.
- name: n-parallel
value: "1"
- name: latency-ns
value: "100000000"
- name: target-annual-vol-units
value: "50.0"
- name: annualisation-factor
value: "825.0"
- name: max-lots
value: "5"
- name: max-events
value: "0"
volumes:
- name: training-data
persistentVolumeClaim:
claimName: training-data-pvc
- name: git-ssh-key
secret:
secretName: argo-git-ssh-key
defaultMode: 0400
- name: cargo-target-cuda
persistentVolumeClaim:
claimName: cargo-target-cuda
templates:
# ── DAG entrypoint ──────────────────────────────────────────────
- name: sweep-matrix
dag:
tasks:
- name: ensure-binary
template: ensure-binary
# __SWEEP_CELLS__ — replaced by argo-lob-sweep.sh
- name: aggregate
template: aggregate
dependencies: [ensure-binary] # plus all run-cell-* via shell-injected list
arguments:
parameters:
- name: sha
value: "{{tasks.ensure-binary.outputs.parameters.sha}}"
# ── ensure-binary: cache-or-compile fxt-backtest by commit SHA ─
# Same shape as train-multi-seed-template.yaml's ensure-binary but
# for the fxt-backtest binary only.
- name: ensure-binary
outputs:
parameters:
- name: sha
valueFrom:
path: /tmp/sha
nodeSelector:
k8s.scaleway.com/pool-name: ci-compile-cpu
topology.kubernetes.io/zone: fr-par-2
tolerations:
- key: node.cilium.io/agent-not-ready
operator: Exists
effect: NoSchedule
container:
image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/ci-builder:latest
imagePullPolicy: Always
command: ["/bin/bash", "-c"]
env:
- name: SQLX_OFFLINE
value: "true"
- name: CARGO_TARGET_DIR
value: /cargo-target
- name: CARGO_HOME
value: /cargo-target/cargo-home
- name: RUSTC_WRAPPER
value: sccache
- name: SCCACHE_DIR
value: /cargo-target/sccache
- name: SCCACHE_CACHE_SIZE
value: "40G"
- name: CARGO_INCREMENTAL
value: "0"
- name: FOXHUNT_CUDA_ARCH
value: "sm_{{workflow.parameters.cuda-compute-cap}}"
- name: GITLAB_PAT
valueFrom:
secretKeyRef:
name: gitlab-pat
key: token
resources:
requests:
cpu: "14"
memory: 32Gi
limits:
cpu: "30"
memory: 64Gi
volumeMounts:
- name: git-ssh-key
mountPath: /etc/git-ssh
readOnly: true
- name: cargo-target-cuda
mountPath: /cargo-target
- name: training-data
mountPath: /mnt/training-data
args:
- |
set -e
SHA="{{workflow.parameters.commit-sha}}"
BRANCH="{{workflow.parameters.git-branch}}"
mkdir -p ~/.ssh
cp /etc/git-ssh/ssh-privatekey ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
printf 'Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n' > ~/.ssh/config
REPO="ssh://git@gitlab-gitlab-shell.foxhunt.svc.cluster.local:2222/root/foxhunt.git"
BUILD="/cargo-target/src"
git config --global --add safe.directory "$BUILD"
if [ "$SHA" = "HEAD" ]; then
if [ -d "$BUILD/.git" ]; then
cd "$BUILD"; git fetch origin
SHA=$(git rev-parse "origin/$BRANCH"); cd /
else
git clone --filter=blob:none "$REPO" "$BUILD"
cd "$BUILD"; git checkout "origin/$BRANCH"
SHA=$(git rev-parse HEAD); cd /
fi
fi
SHORT_SHA=$(echo "$SHA" | cut -c1-9)
echo "Resolved SHA: $SHA (short: $SHORT_SHA)"
BIN_DIR="/mnt/training-data/bin/$SHORT_SHA"
mkdir -p "$BIN_DIR"
if [ -x "$BIN_DIR/fxt-backtest" ]; then
echo "=== Cache HIT: fxt-backtest present in $BIN_DIR ==="
ls -lh "$BIN_DIR/fxt-backtest"
echo "$SHORT_SHA" > /tmp/sha
exit 0
fi
echo "=== Cache MISS: compiling fxt-backtest for $SHORT_SHA ==="
if [ -d "$BUILD/.git" ]; then
cd "$BUILD"; git fetch origin; git checkout "$SHA"
else
git clone --filter=blob:none "$REPO" "$BUILD"
cd "$BUILD"; git checkout "$SHA"
fi
cargo build -p fxt-backtest --release
cp "$BUILD/target/release/fxt-backtest" "$BIN_DIR/fxt-backtest"
echo "$SHORT_SHA" > /tmp/sha
ls -lh "$BIN_DIR/fxt-backtest"
# ── run-cell: one sweep cell against a GPU pod ─────────────────
# Inputs: cell name + every Run arg that varies across the grid.
# The shell-rendered DAG tasks supply these per-cell.
- name: run-cell
inputs:
parameters:
- name: sha
- name: cell-name
- name: decision-stride
value: "4"
- name: latency-ns
value: "{{workflow.parameters.latency-ns}}"
- name: target-annual-vol-units
value: "{{workflow.parameters.target-annual-vol-units}}"
- name: annualisation-factor
value: "{{workflow.parameters.annualisation-factor}}"
- name: max-lots
value: "{{workflow.parameters.max-lots}}"
- name: max-events
value: "{{workflow.parameters.max-events}}"
- name: seed
value: "0xC0FFEE"
nodeSelector:
k8s.scaleway.com/pool-name: "{{workflow.parameters.gpu-pool}}"
topology.kubernetes.io/zone: fr-par-2
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
container:
image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/training-runtime:latest
imagePullPolicy: Always
command: ["/bin/bash", "-c"]
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
volumeMounts:
- name: training-data
mountPath: /mnt/training-data
args:
- |
set -e
SHA="{{inputs.parameters.sha}}"
CELL="{{inputs.parameters.cell-name}}"
BIN="/mnt/training-data/bin/$SHA/fxt-backtest"
CKPT="{{workflow.parameters.checkpoint}}"
OUT="{{workflow.parameters.sweep-root}}/{{workflow.parameters.sweep-tag}}/$CELL"
mkdir -p "$OUT"
echo "=== sweep cell $CELL on $(hostname) → $OUT ==="
CKPT_FLAG=""
if [ -n "$CKPT" ]; then
CKPT_FLAG="--checkpoint $CKPT"
fi
"$BIN" run \
--data "{{workflow.parameters.data-root}}" \
--predecoded-dir "{{workflow.parameters.predecoded-dir}}" \
--n-parallel "{{workflow.parameters.n-parallel}}" \
--decision-stride "{{inputs.parameters.decision-stride}}" \
--latency-ns "{{inputs.parameters.latency-ns}}" \
--target-annual-vol-units "{{inputs.parameters.target-annual-vol-units}}" \
--annualisation-factor "{{inputs.parameters.annualisation-factor}}" \
--max-lots "{{inputs.parameters.max-lots}}" \
--max-events "{{inputs.parameters.max-events}}" \
--seed "{{inputs.parameters.seed}}" \
$CKPT_FLAG \
--out "$OUT"
echo "=== cell $CELL done ==="
ls -lh "$OUT"
# ── aggregate: final CPU pod runs fxt-backtest aggregate ───────
- name: aggregate
inputs:
parameters:
- name: sha
nodeSelector:
k8s.scaleway.com/pool-name: ci-compile-cpu
topology.kubernetes.io/zone: fr-par-2
tolerations:
- key: node.cilium.io/agent-not-ready
operator: Exists
effect: NoSchedule
container:
image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/training-runtime:latest
imagePullPolicy: Always
command: ["/bin/bash", "-c"]
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 8Gi
volumeMounts:
- name: training-data
mountPath: /mnt/training-data
args:
- |
set -e
SHA="{{inputs.parameters.sha}}"
BIN="/mnt/training-data/bin/$SHA/fxt-backtest"
SWEEP_DIR="{{workflow.parameters.sweep-root}}/{{workflow.parameters.sweep-tag}}"
echo "=== aggregate sweep at $SWEEP_DIR ==="
"$BIN" aggregate "$SWEEP_DIR"
ls -lh "$SWEEP_DIR"
# ── notify-result: exit hook (placeholder; real impl emits to Slack/MinIO) ─
- name: notify-result
container:
image: alpine:3.20
command: ["sh", "-c"]
args:
- |
echo "lob-backtest-sweep workflow {{workflow.name}} finished with status {{workflow.status}}"
echo "results under {{workflow.parameters.sweep-root}}/{{workflow.parameters.sweep-tag}}"