# Training Pipeline & Model Deployment Design **Date**: 2026-02-27 **Status**: Approved **Tracks**: Two independent parallel tracks ## Overview Wire the complete training-to-serving pipeline on Kapsule. Track 1 handles training job dispatch (fxt CLI → gRPC → PostgreSQL → K8s GPU Jobs). Track 2 handles model serving and promotion (S3 checkpoints → model loader → inference → operator-gated promotion). Both tracks share S3 (`foxhunt-models/`) as the handoff point. ## Track 1: Training Pipeline ### Goal `fxt train start tft ES.FUT --epochs 50` creates a real GPU training job on Kapsule. ### Flow ``` fxt train start tft ES.FUT --epochs 50 → gRPC StartTraining (ml_training_service:50053) → service.rs handler → JobSpawner::spawn_batch() → PostgreSQL → In-process queue consumer (tokio::spawn, 5s poll) → JobSpawner::get_next_pending_job() → K8sDispatcher::dispatch() → batch/v1 Job ├── training container (GPU, train_baseline_rl/supervised) └── uploader sidecar (S3 → foxhunt-models/) → Job completion callback → update status in PostgreSQL ``` ### Existing Code | Component | File | Status | |-----------|------|--------| | fxt train start | `bin/fxt/src/commands/train/start.rs` | Done (sends gRPC) | | fxt train status | `bin/fxt/src/commands/train/status.rs` | Done | | fxt train list | `bin/fxt/src/commands/train/list.rs` | Done | | K8sDispatcher | `services/ml_training_service/src/k8s_dispatcher.rs` | Done (builds Jobs) | | JobSpawner | `services/ml_training_service/src/job_spawner.rs` | Done (PostgreSQL CRUD) | | Job template | `infra/k8s/training/job-template.yaml` | Done (reference only) | ### Changes Needed 1. **Wire gRPC handler to JobSpawner**: `service.rs` StartTraining handler must call `JobSpawner::spawn_batch()` instead of returning a stub response. 2. **Add queue consumer loop**: In `main.rs`, `tokio::spawn` a loop that polls `get_next_pending_job()` every 5s and calls `K8sDispatcher::dispatch()`. 3. **Job completion callback**: The uploader sidecar already has `CALLBACK_ENDPOINT`. Add a gRPC or HTTP endpoint in ml_training_service that receives completion notifications and calls `JobSpawner::update_job_status()`. 4. **Deploy training-output-pvc**: Currently only `training-data-pvc` is deployed. Need `training-output-pvc` for job artifact staging. 5. **PostgreSQL schema**: Deploy `batch_jobs` and `child_jobs` tables. Add sqlx migration. 6. **Docker image**: Rebuild ml-training-service image with kube-rs K8sDispatcher compiled in. ### K8s Resources - `ml-training-service` Deployment (exists, needs redeploy with new image) - `training-data-pvc` (exists, 10Gi scw-bssd) - `training-output-pvc` (new, needs creation) - `s3-credentials` Secret (exists) - `scw-registry` Secret (exists) - `gpu-training` node pool (Scaleway, autoscales 0→N) ## Track 2: Model Serving & Validation ### Goal Trained model checkpoints deploy to a serving endpoint with operator-gated promotion and automatic rollback. ### Flow ``` Training job completes → uploader sidecar → S3 (foxhunt-models/) → Callback to ml_training_service → PromotionManager::register_completion() ├── No active model → auto-register └── Compare metrics → PendingPromotion / NoImprovement → fxt model promote (operator approval) → DeploymentPipeline::deploy() ├── Rolling update ├── Health check (inference smoke test) └── Auto-rollback on failure ``` ### Existing Code | Component | File | Status | |-----------|------|--------| | S3ModelLoader | `crates/model_loader/src/lib.rs` | Done (S3 + LRU cache + versioning) | | PromotionManager | `services/ml_training_service/src/promotion_manager.rs` | Done (metric comparison, pending/active tracking) | | DeploymentPipeline | `services/ml_training_service/src/deployment_pipeline.rs` | Done (rolling update, health check, rollback) | | ValidationPipeline | `services/ml_training_service/src/validation_pipeline.rs` | Exists | ### Changes Needed 1. **fxt model commands**: Add `fxt model promote`, `fxt model list`, `fxt model status` CLI subcommands. 2. **Wire promotion callback**: Uploader sidecar completion → PromotionManager::register_completion(). Extract metrics from training output. 3. **Model serving deployment**: K8s Deployment that loads active model from S3 via S3ModelLoader and serves predictions via gRPC. 4. **Validation test**: Send sample market data to inference endpoint, verify prediction response shape and latency. 5. **Proto additions**: Add `PromoteModel`, `ListModels`, `GetModelStatus` RPCs to ml_training proto. ## Shared Infrastructure | Resource | Status | Notes | |----------|--------|-------| | PostgreSQL | Running in foxhunt ns | Needs schema migration | | S3 foxhunt-models | Exists | Bucket + credentials | | training-data-pvc | Deployed | 10Gi, read-only for jobs | | training-output-pvc | Needs creation | Staging for job artifacts | | scw-registry secret | Deployed | Image pull auth | | gpu-training pool | Configured | Autoscales 0→N | ## Decision Log | Decision | Choice | Rationale | |----------|--------|-----------| | Job dispatch path | DB-backed (JobSpawner) | Full history, batch orchestration, rollback | | Queue consumer | In-process tokio task | Zero additional infra, simple | | Track split | Training vs Serving | Independent concerns, parallel development | | Promotion gating | Operator approval | Production safety for HFT system |