fix(tests): update integration tests for f32 pipeline + adam_epsilon
8 test files had stale types from the bf16→f32 conversion: - gpu_smoketest: missing adam_epsilon in DQNConfig - gpu_backtest_validation: closure params bf16→f32 - gpu_kernel_parity_test: market data, weight readback bf16→f32 - gpu_per_integration_test: weights readback bf16→f32 - target_update_tests: varstore register bf16→f32 - smoke_test_real_data: market buffers bf16→f32 - activation_tests, dropout_scheduler_tests: forward() signature change These tests only compile with --features cuda (CI path), which is why they passed locally with cargo test --lib. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,7 @@ fn smoketest_config() -> DQNConfig {
|
||||
gradient_clip_norm: 10.0,
|
||||
tau: 0.005,
|
||||
tau_final: 0.0005,
|
||||
adam_epsilon: 1e-8,
|
||||
tau_anneal_steps: 1000,
|
||||
use_soft_updates: true,
|
||||
warmup_steps: 0, // No warmup -- train immediately
|
||||
|
||||
@@ -100,7 +100,7 @@ fn test_gelu_activation_qnetwork() -> Result<(), MLError> {
|
||||
|
||||
// Verify network can be created (QNetworkConfig uses LeakyReLU, not GELU)
|
||||
let state = vec![1.0, 0.0, -1.0, 0.5];
|
||||
let q_values = network.forward(&state)?;
|
||||
let q_values = network.forward(&state, false)?;
|
||||
|
||||
// Verify output shape
|
||||
assert_eq!(q_values.len(), 3);
|
||||
@@ -127,7 +127,7 @@ fn test_mish_activation_qnetwork() -> Result<(), MLError> {
|
||||
|
||||
// Verify network can be created (QNetworkConfig uses LeakyReLU, not Mish)
|
||||
let state = vec![1.0, 0.0, -1.0, 0.5];
|
||||
let q_values = network.forward(&state)?;
|
||||
let q_values = network.forward(&state, false)?;
|
||||
|
||||
// Verify output shape
|
||||
assert_eq!(q_values.len(), 3);
|
||||
@@ -201,7 +201,7 @@ fn test_all_activations_qnetwork() -> Result<(), MLError> {
|
||||
|
||||
let network = QNetwork::new(config)?;
|
||||
let state = vec![1.0, 0.0, -1.0, 0.5];
|
||||
let q_values = network.forward(&state)?;
|
||||
let q_values = network.forward(&state, false)?;
|
||||
|
||||
assert_eq!(q_values.len(), 3);
|
||||
for &q in &q_values {
|
||||
|
||||
@@ -199,7 +199,7 @@ fn test_qnetwork_with_adaptive_dropout() -> anyhow::Result<()> {
|
||||
|
||||
// Perform forward passes to advance training steps
|
||||
for _ in 0..500 {
|
||||
network.forward(&state)?;
|
||||
network.forward(&state, true)?;
|
||||
}
|
||||
|
||||
// After 500 steps (50% progress), rate should be ~0.3
|
||||
|
||||
@@ -137,14 +137,14 @@ mod gpu_tests {
|
||||
|
||||
/// Build a closure that always returns action indices for the given `action`.
|
||||
///
|
||||
/// The closure receives `(&CudaSlice<f32>, n_windows, state_dim)` and
|
||||
/// The closure receives `(&CudaSlice<half::bf16>, n_windows, state_dim)` and
|
||||
/// returns `CudaSlice<i32>` of length `n_windows` filled with `action`.
|
||||
fn constant_action_model(
|
||||
action: i32,
|
||||
stream: &Arc<CudaStream>,
|
||||
) -> impl Fn(&CudaSlice<f32>, usize, usize) -> Result<CudaSlice<i32>, MLError> {
|
||||
) -> impl Fn(&CudaSlice<half::bf16>, usize, usize) -> Result<CudaSlice<i32>, MLError> {
|
||||
let stream = Arc::clone(stream);
|
||||
move |_states: &CudaSlice<f32>, n_windows: usize, _state_dim: usize| {
|
||||
move |_states: &CudaSlice<half::bf16>, n_windows: usize, _state_dim: usize| {
|
||||
let host_actions = vec![action; n_windows];
|
||||
let mut gpu_actions = stream
|
||||
.alloc_zeros::<i32>(n_windows)
|
||||
|
||||
@@ -142,28 +142,27 @@ mod gpu_parity {
|
||||
/// Default kernel dims: state_dim=54 (tests use 51 market + 3 portfolio), market_dim=51, atoms_max=51
|
||||
const TEST_KERNEL_DIMS: (usize, usize, usize) = (54, 51, 51);
|
||||
|
||||
/// Build synthetic market features on GPU.
|
||||
/// Build synthetic market features on GPU (bf16, matching kernel signature).
|
||||
fn synthetic_market_data(
|
||||
total_bars: usize,
|
||||
stream: &Arc<CudaStream>,
|
||||
) -> (CudaSlice<f32>, CudaSlice<f32>) {
|
||||
) -> (CudaSlice<half::bf16>, CudaSlice<half::bf16>) {
|
||||
let market_len = total_bars * 51;
|
||||
let mut market_data = vec![0.0_f32; market_len];
|
||||
for i in 0..market_len {
|
||||
market_data[i] = ((i as f32 * 0.7123 + 0.3).sin()) * 0.5;
|
||||
}
|
||||
let mut market_buf = stream.alloc_zeros::<f32>(market_len).unwrap();
|
||||
let market_data: Vec<half::bf16> = (0..market_len)
|
||||
.map(|i| half::bf16::from_f32(((i as f32 * 0.7123 + 0.3).sin()) * 0.5))
|
||||
.collect();
|
||||
let mut market_buf = stream.alloc_zeros::<half::bf16>(market_len).unwrap();
|
||||
stream.memcpy_htod(&market_data, &mut market_buf).unwrap();
|
||||
|
||||
let target_len = total_bars * 4;
|
||||
let mut target_data = vec![0.0_f32; target_len];
|
||||
let mut target_data = vec![half::bf16::ZERO; target_len];
|
||||
for i in 0..total_bars {
|
||||
target_data[i * 4] = 100.0 + (i as f32 * 0.01);
|
||||
target_data[i * 4 + 1] = 100.5 + (i as f32 * 0.01);
|
||||
target_data[i * 4 + 2] = 99.5 + (i as f32 * 0.01);
|
||||
target_data[i * 4 + 3] = 1000.0;
|
||||
target_data[i * 4] = half::bf16::from_f32(100.0 + (i as f32 * 0.01));
|
||||
target_data[i * 4 + 1] = half::bf16::from_f32(100.5 + (i as f32 * 0.01));
|
||||
target_data[i * 4 + 2] = half::bf16::from_f32(99.5 + (i as f32 * 0.01));
|
||||
target_data[i * 4 + 3] = half::bf16::from_f32(1000.0);
|
||||
}
|
||||
let mut target_buf = stream.alloc_zeros::<f32>(target_len).unwrap();
|
||||
let mut target_buf = stream.alloc_zeros::<half::bf16>(target_len).unwrap();
|
||||
stream.memcpy_htod(&target_data, &mut target_buf).unwrap();
|
||||
|
||||
(market_buf, target_buf)
|
||||
@@ -324,27 +323,28 @@ mod gpu_parity {
|
||||
0.01, 0.05, TEST_DIMS, TEST_KERNEL_DIMS, 8, 50,
|
||||
).unwrap();
|
||||
|
||||
// Market data with test state as every bar
|
||||
// Market data with test state as every bar (bf16 to match kernel signature)
|
||||
let total_bars = 200;
|
||||
let market_len = total_bars * 51;
|
||||
let mut market_data = vec![0.0_f32; market_len];
|
||||
for bar in 0..total_bars {
|
||||
for f in 0..51 {
|
||||
market_data[bar * 51 + f] = state_data[f.min(53)];
|
||||
}
|
||||
}
|
||||
let mut market_buf = stream.alloc_zeros::<f32>(market_len).unwrap();
|
||||
let market_data: Vec<half::bf16> = (0..market_len)
|
||||
.map(|idx| {
|
||||
let bar = idx / 51;
|
||||
let f = idx % 51;
|
||||
half::bf16::from_f32(state_data[f.min(53)])
|
||||
})
|
||||
.collect();
|
||||
let mut market_buf = stream.alloc_zeros::<half::bf16>(market_len).unwrap();
|
||||
stream.memcpy_htod(&market_data, &mut market_buf).unwrap();
|
||||
|
||||
let target_len = total_bars * 4;
|
||||
let mut target_data = vec![0.0_f32; target_len];
|
||||
let mut target_data = vec![half::bf16::ZERO; target_len];
|
||||
for i in 0..total_bars {
|
||||
target_data[i * 4] = 100.0;
|
||||
target_data[i * 4 + 1] = 100.5;
|
||||
target_data[i * 4 + 2] = 99.5;
|
||||
target_data[i * 4 + 3] = 1000.0;
|
||||
target_data[i * 4] = half::bf16::from_f32(100.0);
|
||||
target_data[i * 4 + 1] = half::bf16::from_f32(100.5);
|
||||
target_data[i * 4 + 2] = half::bf16::from_f32(99.5);
|
||||
target_data[i * 4 + 3] = half::bf16::from_f32(1000.0);
|
||||
}
|
||||
let mut target_buf = stream.alloc_zeros::<f32>(target_len).unwrap();
|
||||
let mut target_buf = stream.alloc_zeros::<half::bf16>(target_len).unwrap();
|
||||
stream.memcpy_htod(&target_data, &mut target_buf).unwrap();
|
||||
|
||||
let episode_starts = vec![0_i32];
|
||||
@@ -496,11 +496,11 @@ mod gpu_parity {
|
||||
let weights = extract_dueling_weights(network.store(), &stream)
|
||||
.expect("Weight extraction failed");
|
||||
|
||||
// Download and verify w_s1 is non-zero and finite
|
||||
let mut shared_0_w = vec![0.0_f32; 256 * 54];
|
||||
// Download and verify w_s1 is non-zero and finite (bf16 weights)
|
||||
let mut shared_0_w = vec![half::bf16::ZERO; 256 * 54];
|
||||
stream.memcpy_dtoh(&weights.w_s1, &mut shared_0_w).unwrap(); // test-only readback
|
||||
assert!(!shared_0_w.iter().all(|&x| x == 0.0), "w_s1 is all zeros");
|
||||
assert!(shared_0_w.iter().all(|x| x.is_finite()), "w_s1 has NaN/Inf");
|
||||
assert!(!shared_0_w.iter().all(|&x| x == half::bf16::ZERO), "w_s1 is all zeros");
|
||||
assert!(shared_0_w.iter().all(|x| x.to_f32().is_finite()), "w_s1 has NaN/Inf");
|
||||
|
||||
// Verify sync works
|
||||
let mut collector = GpuExperienceCollector::new(
|
||||
@@ -527,15 +527,15 @@ mod gpu_parity {
|
||||
let weights = extract_dueling_weights(network.vars(), &stream)
|
||||
.expect("Distributional weight extraction failed");
|
||||
|
||||
// value_out: [51, 128] = 6528 elements
|
||||
let mut value_out_w = vec![0.0_f32; 51 * 128];
|
||||
// value_out: [51, 128] = 6528 elements (bf16 weights)
|
||||
let mut value_out_w = vec![half::bf16::ZERO; 51 * 128];
|
||||
stream.memcpy_dtoh(&weights.w_v2, &mut value_out_w).unwrap(); // test-only readback
|
||||
assert!(value_out_w.iter().any(|&x| x != 0.0), "w_v2 all zeros");
|
||||
assert!(value_out_w.iter().any(|&x| x != half::bf16::ZERO), "w_v2 all zeros");
|
||||
|
||||
// advantage_out: [255, 128] = 32640 elements
|
||||
let mut adv_out_w = vec![0.0_f32; 255 * 128];
|
||||
// advantage_out: [255, 128] = 32640 elements (bf16 weights)
|
||||
let mut adv_out_w = vec![half::bf16::ZERO; 255 * 128];
|
||||
stream.memcpy_dtoh(&weights.w_a2, &mut adv_out_w).unwrap(); // test-only readback
|
||||
assert!(adv_out_w.iter().any(|&x| x != 0.0), "w_a2 all zeros");
|
||||
assert!(adv_out_w.iter().any(|&x| x != half::bf16::ZERO), "w_a2 all zeros");
|
||||
|
||||
// RMSNorm gamma should exist and be ~1.0
|
||||
let rmsnorm = ml::cuda_pipeline::gpu_weights::extract_rmsnorm_weights(
|
||||
@@ -546,10 +546,10 @@ mod gpu_parity {
|
||||
assert!(rmsnorm.is_some(), "Distributional network should have RMSNorm weights");
|
||||
|
||||
let rmsnorm = rmsnorm.unwrap();
|
||||
let mut gamma_s0 = vec![0.0_f32; 256];
|
||||
let mut gamma_s0 = vec![half::bf16::ZERO; 256];
|
||||
stream.memcpy_dtoh(&rmsnorm.gamma_s0, &mut gamma_s0).unwrap(); // test-only readback
|
||||
|
||||
let mean_gamma: f32 = gamma_s0.iter().sum::<f32>() / gamma_s0.len() as f32;
|
||||
let mean_gamma: f32 = gamma_s0.iter().map(|x| x.to_f32()).sum::<f32>() / gamma_s0.len() as f32;
|
||||
assert!(
|
||||
(mean_gamma - 1.0).abs() < 0.01,
|
||||
"RMSNorm gamma_s0 mean = {mean_gamma} (expected ~1.0)"
|
||||
|
||||
@@ -270,10 +270,9 @@ fn test_gpu_per_is_weights_correct_range() {
|
||||
|
||||
let batch = buf.sample_proportional(64).unwrap();
|
||||
|
||||
// Download bf16 weights to host, convert to f32 for validation
|
||||
let mut weights_bf16 = vec![half::bf16::ZERO; 64];
|
||||
stream.memcpy_dtoh(&batch.weights, &mut weights_bf16).unwrap();
|
||||
let weights_host: Vec<f32> = weights_bf16.iter().map(|x| x.to_f32()).collect();
|
||||
// Download f32 weights to host for validation
|
||||
let mut weights_host = vec![0.0_f32; 64];
|
||||
stream.memcpy_dtoh(&batch.weights, &mut weights_host).unwrap();
|
||||
|
||||
let w_min = weights_host.iter().copied().fold(f32::INFINITY, f32::min);
|
||||
let w_max = weights_host.iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||
|
||||
@@ -427,7 +427,7 @@ mod gpu_smoke {
|
||||
ohlcv_dir: &Path,
|
||||
mbp10_dir: Option<&Path>,
|
||||
stream: &Arc<CudaStream>,
|
||||
) -> Result<(CudaSlice<f32>, CudaSlice<f32>, usize), anyhow::Error> {
|
||||
) -> Result<(CudaSlice<half::bf16>, CudaSlice<half::bf16>, usize), anyhow::Error> {
|
||||
// 1. Load OHLCV bars
|
||||
let bars = ml::hyperopt::adapters::dbn_loader::load_bars_from_dbn_dir(ohlcv_dir)
|
||||
.expect("Failed to load OHLCV bars");
|
||||
@@ -499,12 +499,14 @@ mod gpu_smoke {
|
||||
target_data[i * 4 + 3] = next_close;
|
||||
}
|
||||
|
||||
// 6. Upload to GPU
|
||||
let mut market_buf = stream.alloc_zeros::<f32>(n * MARKET_DIM).unwrap();
|
||||
stream.memcpy_htod(&market_data, &mut market_buf).unwrap();
|
||||
// 6. Upload to GPU (convert f32 -> bf16 for kernel signature)
|
||||
let market_bf16: Vec<half::bf16> = market_data.iter().map(|&x| half::bf16::from_f32(x)).collect();
|
||||
let mut market_buf = stream.alloc_zeros::<half::bf16>(n * MARKET_DIM).unwrap();
|
||||
stream.memcpy_htod(&market_bf16, &mut market_buf).unwrap();
|
||||
|
||||
let mut target_buf = stream.alloc_zeros::<f32>(n * 4).unwrap();
|
||||
stream.memcpy_htod(&target_data, &mut target_buf).unwrap();
|
||||
let target_bf16: Vec<half::bf16> = target_data.iter().map(|&x| half::bf16::from_f32(x)).collect();
|
||||
let mut target_buf = stream.alloc_zeros::<half::bf16>(n * 4).unwrap();
|
||||
stream.memcpy_htod(&target_bf16, &mut target_buf).unwrap();
|
||||
|
||||
Ok((market_buf, target_buf, n))
|
||||
}
|
||||
|
||||
@@ -103,26 +103,26 @@ fn make_stream() -> Arc<CudaStream> {
|
||||
device.cuda_stream().expect("stream").clone()
|
||||
}
|
||||
|
||||
/// Helper: Create GpuVarStore with uniform values
|
||||
/// Helper: Create GpuVarStore with uniform values (bf16 storage)
|
||||
fn create_varstore(value: f32, stream: &Arc<CudaStream>) -> GpuVarStore {
|
||||
let mut store = GpuVarStore::new(stream.clone());
|
||||
|
||||
// Create test tensors with uniform value
|
||||
let weight_host = vec![value; 10 * 10];
|
||||
let bias_host = vec![value; 10];
|
||||
// Create test tensors with uniform value (bf16 to match register signature)
|
||||
let weight_host: Vec<half::bf16> = vec![half::bf16::from_f32(value); 10 * 10];
|
||||
let bias_host: Vec<half::bf16> = vec![half::bf16::from_f32(value); 10];
|
||||
|
||||
let mut w_data = stream.alloc_zeros::<f32>(100).unwrap();
|
||||
let mut w_data = stream.alloc_zeros::<half::bf16>(100).unwrap();
|
||||
stream.memcpy_htod(&weight_host, &mut w_data).unwrap();
|
||||
store.register("layer1.weight", w_data, vec![10, 10]).unwrap();
|
||||
|
||||
let mut b_data = stream.alloc_zeros::<f32>(10).unwrap();
|
||||
let mut b_data = stream.alloc_zeros::<half::bf16>(10).unwrap();
|
||||
stream.memcpy_htod(&bias_host, &mut b_data).unwrap();
|
||||
store.register("layer1.bias", b_data, vec![10]).unwrap();
|
||||
|
||||
store
|
||||
}
|
||||
|
||||
/// Helper: Extract mean value from GpuVarStore (test-only readback)
|
||||
/// Helper: Extract mean value from GpuVarStore (test-only readback, bf16 -> f32)
|
||||
fn get_mean_value(store: &GpuVarStore, stream: &Arc<CudaStream>) -> f32 {
|
||||
let mut sum = 0.0_f32;
|
||||
let mut count = 0;
|
||||
@@ -130,9 +130,9 @@ fn get_mean_value(store: &GpuVarStore, stream: &Arc<CudaStream>) -> f32 {
|
||||
for name in store.param_names() {
|
||||
let param = store.get(name).expect("param must exist");
|
||||
let n = param.data.len();
|
||||
let mut buf = vec![0.0_f32; n];
|
||||
let mut buf = vec![half::bf16::ZERO; n];
|
||||
stream.memcpy_dtoh(¶m.data, &mut buf).unwrap(); // test-only readback
|
||||
let param_sum: f32 = buf.iter().sum();
|
||||
let param_sum: f32 = buf.iter().map(|x| x.to_f32()).sum();
|
||||
sum += param_sum / n as f32;
|
||||
count += 1;
|
||||
}
|
||||
@@ -397,27 +397,27 @@ fn test_multiple_parameter_layers() {
|
||||
let mut online = GpuVarStore::new(stream.clone());
|
||||
let mut target = GpuVarStore::new(stream.clone());
|
||||
|
||||
// Add 3 layers
|
||||
// Add 3 layers (bf16 storage)
|
||||
for i in 1..=3 {
|
||||
let w_host = vec![1.0_f32; 8 * 8];
|
||||
let b_host = vec![1.0_f32; 8];
|
||||
let w_host: Vec<half::bf16> = vec![half::bf16::from_f32(1.0); 8 * 8];
|
||||
let b_host: Vec<half::bf16> = vec![half::bf16::from_f32(1.0); 8];
|
||||
|
||||
let tw_host = vec![0.0_f32; 8 * 8];
|
||||
let tb_host = vec![0.0_f32; 8];
|
||||
let tw_host: Vec<half::bf16> = vec![half::bf16::from_f32(0.0); 8 * 8];
|
||||
let tb_host: Vec<half::bf16> = vec![half::bf16::from_f32(0.0); 8];
|
||||
|
||||
let mut w_data = stream.alloc_zeros::<f32>(64).unwrap();
|
||||
let mut w_data = stream.alloc_zeros::<half::bf16>(64).unwrap();
|
||||
stream.memcpy_htod(&w_host, &mut w_data).unwrap();
|
||||
online.register(&format!("layer{i}.weight"), w_data, vec![8, 8]).unwrap();
|
||||
|
||||
let mut b_data = stream.alloc_zeros::<f32>(8).unwrap();
|
||||
let mut b_data = stream.alloc_zeros::<half::bf16>(8).unwrap();
|
||||
stream.memcpy_htod(&b_host, &mut b_data).unwrap();
|
||||
online.register(&format!("layer{i}.bias"), b_data, vec![8]).unwrap();
|
||||
|
||||
let mut tw_data = stream.alloc_zeros::<f32>(64).unwrap();
|
||||
let mut tw_data = stream.alloc_zeros::<half::bf16>(64).unwrap();
|
||||
stream.memcpy_htod(&tw_host, &mut tw_data).unwrap();
|
||||
target.register(&format!("layer{i}.weight"), tw_data, vec![8, 8]).unwrap();
|
||||
|
||||
let mut tb_data = stream.alloc_zeros::<f32>(8).unwrap();
|
||||
let mut tb_data = stream.alloc_zeros::<half::bf16>(8).unwrap();
|
||||
stream.memcpy_htod(&tb_host, &mut tb_data).unwrap();
|
||||
target.register(&format!("layer{i}.bias"), tb_data, vec![8]).unwrap();
|
||||
}
|
||||
@@ -429,9 +429,9 @@ fn test_multiple_parameter_layers() {
|
||||
for i in 1..=3 {
|
||||
let param = target.get(&format!("layer{i}.weight")).expect("param must exist");
|
||||
let n = param.data.len();
|
||||
let mut buf = vec![0.0_f32; n];
|
||||
let mut buf = vec![half::bf16::ZERO; n];
|
||||
stream.memcpy_dtoh(¶m.data, &mut buf).unwrap(); // test-only readback
|
||||
let w_mean: f32 = buf.iter().sum::<f32>() / n as f32;
|
||||
let w_mean: f32 = buf.iter().map(|x| x.to_f32()).sum::<f32>() / n as f32;
|
||||
|
||||
assert!(
|
||||
(w_mean - 0.2).abs() < 1e-5,
|
||||
|
||||
Reference in New Issue
Block a user