Files
foxhunt/docs/guides/HYPEROPT_DEPLOYMENT_GUIDE.md
jgrusewski e393a8af89 chore(cleanup): Cleanup Wave 3 - Archive reports, organize docs, fix security issues
## Summary
Third major cleanup wave after investigating 287 remaining root files.
Archived historical reports, organized documentation, removed regeneratable
artifacts, and fixed critical security issue.

## Files Cleaned (119 total)
- Archived: 78 files (7 WAVE reports + 71 summaries) → docs/archive/
- Archived: 7 build logs → docs/archive/build_logs/
- Organized: 10 markdown files → docs/guides/ + docs/checklists/
- Deleted: 17 test/coverage artifacts (regeneratable)
- Deleted: 7 empty/obsolete files (docker override, clippy baselines)
- Deleted: 3 large files (119MB - .venv, ppo_hyperopt_output.txt, backup)

## Space Recovered
- Total: ~120.7 MB
- Large files: 119.25 MB (.venv, ppo_hyperopt_output.txt)
- Archives: 1.04 MB (summaries + build logs)
- Test artifacts: 980 KB

## Security Fix (CRITICAL)
- Fixed: certs/security.env removed from git tracking (contained JWT secrets)
- Updated: .gitignore to prevent future tracking of sensitive cert files
- Removed: 4 files from git history (security.env, production.env.template, *.serial)

## Documentation Organization
- Created: docs/archive/ (wave_reports/, summaries/, build_logs/)
- Created: docs/guides/ (7 detailed implementation guides)
- Created: docs/checklists/ (3 operational checklists)
- Retained: 30 essential .md files in root (quick refs, CLAUDE.md)

## Investigation Reports Created
- MARKDOWN_ORGANIZATION_REPORT.md
- TXT_FILES_INVENTORY_AND_ARCHIVAL_PLAN.md
- ROOT_CONFIG_FILES_ANALYSIS_REPORT.md
- DOCKER_ROOT_FILES_ANALYSIS.md
- DATABASE_INITIALIZATION_AND_SETUP_ANALYSIS.md
- (6 additional investigation/index files)

## Cleanup Wave Progress
- Wave 1: 899 files deleted (1,071,884 lines)
- Wave 2: 543 files archived/deleted (~34GB)
- Wave 3: 119 files archived/deleted/organized (~121MB)
- Total: 1,561 files cleaned, ~35.1GB space recovered

## Result
Root directory: 287 files → ~180 files (excluding investigation reports)
Clean, organized, production-ready structure maintained.

Related: Second cleanup wave (previous commit)
2025-10-30 01:46:39 +01:00

21 KiB
Raw Blame History

Hyperparameter Optimization Deployment Guide

Last Updated: 2025-10-27 Status: Production Ready Framework: Argmin (Nelder-Mead Simplex)


Table of Contents

  1. Quick Start
  2. System Overview
  3. Model Adapters
  4. Parameter Space Customization
  5. Cost Estimation
  6. Runtime Calculations
  7. Results Interpretation
  8. Production Deployment
  9. Troubleshooting

Quick Start

# Demo run (10 trials, ~20 minutes)
cargo run -p ml --example hyperopt_mamba2_demo --release --features cuda -- \
  --parquet-file test_data/ES_FUT_180d.parquet \
  --trials 10 \
  --epochs 20

# Production run (50 trials, ~2 hours)
cargo run -p ml --example hyperopt_mamba2_demo --release --features cuda -- \
  --parquet-file test_data/ES_FUT_180d.parquet \
  --trials 50 \
  --epochs 50

Direct API Usage

use ml::hyperopt::{ArgminOptimizer, HyperparameterOptimizable};
use ml::hyperopt::adapters::mamba2::Mamba2Trainer;

// Create trainer
let trainer = Mamba2Trainer::new("test_data/ES_FUT_180d.parquet", 50)?;

// Configure optimizer
let optimizer = ArgminOptimizer::builder()
    .max_trials(30)      // Total optimization trials
    .n_initial(5)         // Initial random samples (for diversity)
    .seed(42)             // Random seed (for reproducibility)
    .build();

// Run optimization
let result = optimizer.optimize(trainer)?;

println!("Best loss: {:.6}", result.best_objective);
println!("Best learning rate: {:.6}", result.best_params.learning_rate);
println!("Best batch size: {}", result.best_params.batch_size);

System Overview

Architecture

The hyperparameter optimization system consists of:

  1. Optimizer (ArgminOptimizer): Nelder-Mead simplex algorithm
  2. Traits (HyperparameterOptimizable, ParameterSpace): Generic interfaces
  3. Adapters: Model-specific implementations (MAMBA-2, DQN, PPO, TFT)
┌─────────────────────────────────────────────┐
│          ArgminOptimizer                     │
│  (Nelder-Mead Simplex Algorithm)            │
└──────────────┬──────────────────────────────┘
               │
               │ optimize()
               ▼
┌─────────────────────────────────────────────┐
│     HyperparameterOptimizable Trait         │
│  • train_with_params()                      │
│  • extract_objective()                      │
└──────────────┬──────────────────────────────┘
               │
               │ implements
               ▼
┌─────────────────────────────────────────────┐
│         Model Adapters                      │
│  • Mamba2Trainer (✅ Active)                │
│  • DQNTrainer (⏳ Needs API alignment)      │
│  • PPOTrainer (⏳ Needs API alignment)      │
│  • TFTTrainer (⏳ Needs API alignment)      │
└─────────────────────────────────────────────┘

Optimization Algorithm

Nelder-Mead Simplex:

  • Type: Derivative-free optimization
  • Strengths:
    • No gradient computation required
    • Robust to noisy objectives
    • Works well with 4-5 hyperparameters
  • Best for: Models with 2-10 hyperparameters
  • Convergence: Typically 20-50 trials for 4-5 parameters

Test Coverage

Component Tests Status
Optimizer 2/2 (1 ignored) 100%
Traits 2/2 100%
MAMBA-2 Adapter 3/3 100%
Egobox Tests (Legacy) 28/28 100%
Total 33/34 (97%) Production Ready

Note: 1 test ignored (Rosenbrock function, long-running validation test)


Model Adapters

MAMBA-2 ( Production Ready)

Status: Fully functional and tested

Optimized Parameters:

  • learning_rate: 1e-5 to 1e-2 (log-scale)
  • batch_size: 16 to 256 (linear, integer)
  • dropout: 0.0 to 0.5 (linear)
  • weight_decay: 1e-6 to 1e-3 (log-scale)

Fixed Parameters:

  • epochs: Set at trainer creation
  • d_model: 256 (architecture)
  • n_layers: 4 (architecture)
  • device: Auto-detected (CUDA preferred)

Usage:

use ml::hyperopt::adapters::mamba2::{Mamba2Trainer, Mamba2Params};

let trainer = Mamba2Trainer::new("data.parquet", 50)?;
let optimizer = ArgminOptimizer::with_trials(30, 5);
let result = optimizer.optimize(trainer)?;

Expected Runtime: ~2 minutes per trial (50 epochs, RTX 3050 Ti)


DQN ( Needs API Alignment)

Status: Adapter implemented, needs integration with latest DQN API

Optimized Parameters:

  • learning_rate: 1e-5 to 1e-3 (log-scale)
  • batch_size: 32 to 230 (linear, GPU-constrained)
  • gamma: 0.95 to 0.99 (discount factor)
  • epsilon_decay: 0.990 to 0.999 (log-scale)
  • buffer_size: 10,000 to 1,000,000 (log-scale)

Fixed Parameters:

  • state_dim: 225 (Wave D features)
  • num_actions: 3 (Buy, Sell, Hold)
  • hidden_dims: [128, 64, 32]

Expected Runtime: ~15 seconds per trial (100 epochs)

Activation Steps:

  1. Uncomment in ml/src/hyperopt/adapters/mod.rs
  2. Verify API compatibility with ml/src/trainers/dqn.rs
  3. Run tests: cargo test -p ml --lib hyperopt::adapters::dqn

PPO ( Needs API Alignment)

Status: Adapter implemented, needs integration with latest PPO API

Optimized Parameters:

  • policy_learning_rate: 1e-6 to 1e-3 (log-scale)
  • value_learning_rate: 1e-5 to 1e-3 (log-scale)
  • clip_epsilon: 0.1 to 0.3 (PPO clipping)
  • value_loss_coeff: 0.5 to 2.0 (loss weighting)
  • entropy_coeff: 0.001 to 0.1 (exploration, log-scale)

Fixed Parameters:

  • state_dim: 225 (Wave D features)
  • num_actions: 3 (Buy, Sell, Hold)
  • policy_hidden_dims: [128, 64]
  • value_hidden_dims: [256, 128, 64]

Expected Runtime: ~7 seconds per trial (1000 episodes)

Activation Steps:

  1. Uncomment in ml/src/hyperopt/adapters/mod.rs
  2. Verify API compatibility with ml/src/ppo/ppo.rs
  3. Run tests: cargo test -p ml --lib hyperopt::adapters::ppo

TFT ( Needs API Alignment)

Status: Adapter implemented, needs integration with latest TFT API

Optimized Parameters:

  • learning_rate: 1e-5 to 1e-2 (log-scale)
  • batch_size: 16 to 256 (linear, integer)
  • dropout: 0.0 to 0.5 (linear)
  • num_heads: 4, 8, 16 (discrete, attention heads)
  • hidden_dim: 128 to 512 (linear, integer)

Fixed Parameters:

  • epochs: Set at trainer creation
  • seq_len: 60 (lookback window)
  • device: Auto-detected (CUDA preferred)

Expected Runtime: ~2 minutes per trial (50 epochs, RTX 3050 Ti)

Activation Steps:

  1. Uncomment in ml/src/hyperopt/adapters/mod.rs
  2. Verify API compatibility with ml/src/tft/mod.rs
  3. Run tests: cargo test -p ml --lib hyperopt::adapters::tft

Parameter Space Customization

Creating Custom Parameter Spaces

use ml::hyperopt::traits::ParameterSpace;
use ml::MLError;

#[derive(Debug, Clone)]
pub struct CustomParams {
    pub learning_rate: f64,
    pub batch_size: usize,
    pub dropout: f64,
}

impl ParameterSpace for CustomParams {
    fn continuous_bounds() -> Vec<(f64, f64)> {
        vec![
            (1e-5_f64.ln(), 1e-2_f64.ln()), // learning_rate (log-scale)
            (16.0, 256.0),                   // batch_size (linear)
            (0.0, 0.5),                      // dropout (linear)
        ]
    }

    fn from_continuous(x: &[f64]) -> Result<Self, MLError> {
        if x.len() != 3 {
            return Err(MLError::ConfigError {
                reason: format!("Expected 3 parameters, got {}", x.len()),
            });
        }

        Ok(Self {
            learning_rate: x[0].exp(),      // Undo log transformation
            batch_size: x[1].round() as usize, // Round to integer
            dropout: x[2].clamp(0.0, 0.5),  // Clamp to bounds
        })
    }

    fn to_continuous(&self) -> Vec<f64> {
        vec![
            self.learning_rate.ln(),        // Apply log transformation
            self.batch_size as f64,
            self.dropout,
        ]
    }

    fn param_names() -> Vec<&'static str> {
        vec!["learning_rate", "batch_size", "dropout"]
    }
}

Log-Scale vs Linear Scale

Use Log-Scale When:

  • Parameter spans multiple orders of magnitude
  • Examples: learning rate (1e-5 to 1e-2), weight decay, buffer size

Use Linear Scale When:

  • Parameter spans a single order of magnitude
  • Examples: dropout (0.0 to 0.5), batch size (16 to 256), gamma (0.95 to 0.99)

Why It Matters:

  • Log-scale ensures uniform exploration across orders of magnitude
  • Linear scale is more efficient for narrow ranges

Cost Estimation

GPU Time Costs (Runpod Pricing)

GPU Model $/hour MAMBA-2 (50 trials) DQN (50 trials) PPO (50 trials)
RTX A4000 $0.25 $4.17 (~100 min) $0.31 (~12 min) $0.15 (~6 min)
Tesla V100 $0.10 $1.67 (~100 min) $0.12 (~12 min) $0.06 (~6 min)
RTX 4090 $0.40 $6.67 (~100 min) $0.50 (~12 min) $0.24 (~6 min)

Based on: MAMBA-2 ~2 min/trial, DQN ~15s/trial, PPO ~7s/trial

Local Development Costs

Electricity Cost (assuming $0.12/kWh, 200W GPU power draw):

  • MAMBA-2 (50 trials): ~$0.40
  • DQN (50 trials): ~$0.05
  • PPO (50 trials): ~$0.02

Optimization Strategy Cost Comparison

Strategy Trials MAMBA-2 Runtime Cost (RTX A4000) Expected Improvement
Quick Test 10 ~20 min $0.83 Baseline
Standard 30 ~60 min $2.50 +15-25%
Thorough 50 ~100 min $4.17 +25-35%
Exhaustive 100 ~200 min $8.33 +30-40%

Recommendation: Start with 30 trials for production deployment.


Runtime Calculations

Formula

Total Runtime = (Trials × Time_per_Trial) + Initialization_Time

Where:
- Time_per_Trial = (Epochs × Time_per_Epoch) + Overhead
- Initialization_Time ≈ 10-30 seconds (data loading, model compilation)
- Overhead ≈ 5-10 seconds (parameter setup, metrics extraction)

MAMBA-2 Example

Configuration:

  • 50 trials
  • 50 epochs per trial
  • RTX 3050 Ti GPU

Calculation:

Time_per_Epoch = 2.4 seconds (from benchmarks)
Time_per_Trial = (50 epochs × 2.4s) + 10s overhead = 130s
Total_Runtime = (50 trials × 130s) + 30s init = 6530s ≈ 109 minutes

DQN Example

Configuration:

  • 50 trials
  • 100 epochs per trial
  • RTX 3050 Ti GPU

Calculation:

Time_per_Epoch = 0.15 seconds (from benchmarks)
Time_per_Trial = (100 epochs × 0.15s) + 5s overhead = 20s
Total_Runtime = (50 trials × 20s) + 10s init = 1010s ≈ 17 minutes

Scaling Factors

Hardware Speed Multiplier MAMBA-2 (50 trials) DQN (50 trials)
RTX 3050 Ti 1.0x (baseline) 109 minutes 17 minutes
RTX A4000 1.1x 99 minutes 15 minutes
RTX 4090 1.8x 61 minutes 9 minutes
CPU (fallback) 0.1x ~18 hours ~3 hours

Results Interpretation

Optimization Result Structure

pub struct OptimizationResult<P: ParameterSpace> {
    /// Best hyperparameters found
    pub best_params: P,

    /// Best objective value (loss)
    pub best_objective: f64,

    /// All trial results
    pub trials: Vec<TrialResult<P>>,

    /// Trial index where best was found
    pub convergence_trial: usize,
}

pub struct TrialResult<P: ParameterSpace> {
    /// Trial index
    pub trial_num: usize,

    /// Hyperparameters used
    pub params: P,

    /// Objective value (loss)
    pub objective: f64,
}

Key Metrics to Examine

1. Best Objective Value

  • Lower is better (loss minimization)
  • Compare against default parameters baseline
  • Expected improvement: 10-30% for well-tuned models

2. Convergence Trial

  • Trial where best parameters were found
  • Early convergence (< 30% of trials): May need more exploration
  • Late convergence (> 70% of trials): Good exploration, consider more trials

3. Trial Distribution

  • Plot objective vs trial number
  • Look for decreasing trend
  • Plateau indicates convergence

Example Analysis

let result = optimizer.optimize(trainer)?;

// 1. Best parameters
println!("Best loss: {:.6}", result.best_objective);
println!("Best learning rate: {:.6}", result.best_params.learning_rate);

// 2. Improvement over default
let default_loss = 0.0123; // From default run
let improvement_pct = (default_loss - result.best_objective) / default_loss * 100.0;
println!("Improvement: {:.1}%", improvement_pct);

// 3. Convergence analysis
let convergence_pct = result.convergence_trial as f64 / result.trials.len() as f64 * 100.0;
println!("Converged at: {:.1}% of trials", convergence_pct);

// 4. Top 5 trials
let mut sorted = result.trials.clone();
sorted.sort_by(|a, b| a.objective.partial_cmp(&b.objective).unwrap());
for (i, trial) in sorted.iter().take(5).enumerate() {
    println!("#{}: Loss={:.6}, LR={:.6}", i+1, trial.objective, trial.params.learning_rate);
}

Warning Signs

No Improvement:

  • Best loss similar to worst loss
  • Action: Check parameter bounds, increase trials

Divergent Loss:

  • Loss increasing or NaN/Inf
  • Action: Narrow learning rate range, add gradient clipping

High Variance:

  • Large spread in trial losses
  • Action: Increase epochs per trial, stabilize training

Early Plateau:

  • Best found in first 10% of trials
  • Action: Increase initial random samples (n_initial)

Production Deployment

Step 1: Validate on Holdout Data

// Run optimization on training data
let train_result = optimizer.optimize(train_trainer)?;

// Validate best parameters on holdout data
let mut val_trainer = Mamba2Trainer::new("holdout_data.parquet", 100)?;
let val_metrics = val_trainer.train_with_params(train_result.best_params)?;

println!("Holdout validation loss: {:.6}", val_metrics.val_loss);

Step 2: Retrain with Best Parameters

use ml::mamba::Mamba2Config;

// Extract best hyperparameters
let best_params = result.best_params;

// Create production config
let config = Mamba2Config {
    d_model: 256,
    n_layers: 4,
    learning_rate: best_params.learning_rate,
    batch_size: best_params.batch_size,
    dropout: best_params.dropout,
    weight_decay: best_params.weight_decay,
    // ... other fixed parameters
};

// Train final model with more epochs
let final_model = train_mamba2_production(config, "full_data.parquet", 200)?;

Step 3: Save Optimization Results

use std::fs::File;
use std::io::Write;

// Serialize results to JSON
let json = serde_json::to_string_pretty(&result)?;
let mut file = File::create("hyperopt_results.json")?;
file.write_all(json.as_bytes())?;

// Also save best parameters separately
let params_json = serde_json::to_string_pretty(&result.best_params)?;
let mut params_file = File::create("best_params.json")?;
params_file.write_all(params_json.as_bytes())?;

Step 4: Integration with Trading System

Update Service Configuration:

// services/ml_training/src/config.rs

pub struct Mamba2TrainingConfig {
    // Use optimized hyperparameters
    pub learning_rate: f64,  // From hyperopt: 0.000234
    pub batch_size: usize,   // From hyperopt: 128
    pub dropout: f64,        // From hyperopt: 0.18
    pub weight_decay: f64,   // From hyperopt: 0.000045

    // Production settings
    pub epochs: usize,       // Increase for production: 200
    pub checkpointing: bool, // Enable: true
    pub early_stopping: bool, // Enable: true
}

Deploy via Docker:

# Build with optimized parameters
docker build -f Dockerfile.runpod -t jgrusewski/foxhunt:mamba2-optimized .

# Deploy to Runpod
python3 scripts/runpod_deploy.py \
  --gpu-type "RTX A4000" \
  --model-config best_params.json

Troubleshooting

Issue: "No improvement over random sampling"

Symptoms:

  • Best trial is within first 5 trials
  • All trials have similar loss

Solutions:

  1. Increase n_initial to explore more (try 10-15)
  2. Verify parameter bounds are wide enough
  3. Check if training is converging (increase epochs per trial)

Issue: "NaN or Inf loss during optimization"

Symptoms:

  • Some trials return NaN/Inf loss
  • Optimization crashes

Solutions:

  1. Narrow learning rate bounds (try 1e-5 to 1e-3)
  2. Add gradient clipping to model
  3. Reduce maximum batch size
  4. Check for data normalization issues

Issue: "Optimization is too slow"

Symptoms:

  • Each trial takes much longer than expected
  • Total runtime exceeds budget

Solutions:

  1. Reduce epochs per trial (start with 20-30)
  2. Use smaller batch sizes for faster iterations
  3. Reduce training data size for hyperopt (use 50-70%)
  4. Use faster GPU (RTX 4090 vs A4000)

Issue: "Results not reproducible"

Symptoms:

  • Different runs give different best parameters
  • High variance across runs

Solutions:

  1. Set random seed: optimizer.seed(42)
  2. Ensure deterministic data loading
  3. Disable non-deterministic CUDA operations
  4. Increase trials for more robust results (50+)

Issue: "Optimizer gets stuck in local minimum"

Symptoms:

  • Loss plateaus early
  • No improvement after initial trials

Solutions:

  1. Increase n_initial for better exploration
  2. Try different random seeds
  3. Widen parameter bounds
  4. Consider multi-start optimization

Best Practices

1. Start Small, Scale Up

Phase 1: Quick test (10 trials, 20 epochs)
  ↓ Verify system works
Phase 2: Standard run (30 trials, 50 epochs)
  ↓ Get baseline results
Phase 3: Production run (50+ trials, 100 epochs)
  ↓ Final optimization
Phase 4: Validation on holdout data

2. Monitor During Optimization

// Add progress tracking
for (i, trial) in result.trials.iter().enumerate() {
    println!("Trial {}/{}: Loss={:.6}",
             i+1, result.trials.len(), trial.objective);
}

3. Save Intermediate Results

// Checkpoint every N trials
if trial_num % 10 == 0 {
    save_checkpoint(&result)?;
}

4. Use Version Control

# Tag optimized parameters
git add hyperopt_results.json best_params.json
git commit -m "feat(ml): MAMBA-2 hyperopt results (loss: 0.00234)"
git tag v1.0-mamba2-optimized

Appendix: Complete Example

use anyhow::Result;
use ml::hyperopt::{ArgminOptimizer, HyperparameterOptimizable};
use ml::hyperopt::adapters::mamba2::Mamba2Trainer;
use tracing::{info, Level};

fn main() -> Result<()> {
    // Setup logging
    tracing_subscriber::fmt()
        .with_max_level(Level::INFO)
        .init();

    // Create trainer
    info!("Creating MAMBA-2 trainer...");
    let trainer = Mamba2Trainer::new(
        "test_data/ES_FUT_180d.parquet",
        50  // epochs per trial
    )?;

    // Configure optimizer
    info!("Configuring optimizer...");
    let optimizer = ArgminOptimizer::builder()
        .max_trials(30)
        .n_initial(5)
        .seed(42)
        .build();

    // Run optimization
    info!("Starting optimization...");
    let result = optimizer.optimize(trainer)?;

    // Display results
    info!("Optimization complete!");
    info!("Best hyperparameters:");
    info!("  Learning rate: {:.6}", result.best_params.learning_rate);
    info!("  Batch size: {}", result.best_params.batch_size);
    info!("  Dropout: {:.3}", result.best_params.dropout);
    info!("  Weight decay: {:.6}", result.best_params.weight_decay);
    info!("Best validation loss: {:.6}", result.best_objective);

    // Save results
    let json = serde_json::to_string_pretty(&result)?;
    std::fs::write("hyperopt_results.json", json)?;
    info!("Results saved to hyperopt_results.json");

    Ok(())
}

Summary

This guide covers:

  • Quick start examples for MAMBA-2
  • Complete system architecture
  • Parameter space customization
  • Cost estimation for different GPUs
  • Runtime calculations
  • Results interpretation
  • Production deployment workflow
  • Troubleshooting common issues

Next Steps:

  1. Run demo: cargo run -p ml --example hyperopt_mamba2_demo
  2. Validate results on holdout data
  3. Deploy optimized parameters to trading system
  4. Activate DQN/PPO/TFT adapters (when ready)

Support:

  • Issues: Create GitHub issue with "hyperopt" label
  • Questions: Contact ML team
  • Documentation: /home/jgrusewski/Work/foxhunt/HYPEROPT_DEPLOYMENT_GUIDE.md