Files
rustytorch/crates/production/rtx-inference/tests/cache_test.rs
T
Omar SobhandClaude Sonnet 4.6 0e1d6a74b6
CI / Format Check (push) Failing after 6s
GPU Tests / Check GPU Availability (push) Successful in 0s
CI / Clippy Check (push) Failing after 8s
Documentation / Build User Guide (push) Successful in 8s
GPU Tests / CUDA Tests (11.8) (push) Has been skipped
GPU Tests / CUDA Tests (12.1) (push) Has been skipped
Documentation / Build API Documentation (push) Failing after 19s
CI / Build (ubuntu-latest) (push) Failing after 53s
CI / Build CPU-Only (Explicit) (push) Failing after 1m6s
Performance Benchmarks / Run Benchmarks (push) Successful in 7m57s
CI / Build (macos-latest) (push) Failing after 28s
CI / Test (macos-latest) (push) Has been skipped
CI / Test (ubuntu-latest) (push) Has been skipped
CI / Python Bindings (maturin) (macos-latest) (push) Has been skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Has been skipped
CI / WASM Build + Size Check (push) Has been skipped
CI / Distributed Training Tests (push) Has been skipped
CI / CI Success (push) Failing after 0s
GPU Tests / Metal Tests (push) Has been skipped
feat(batch6): windowed acceptance metrics, KV INT8 quant, col/row-parallel linear
Windowed acceptance rate (rtx-inference/speculative):
- WindowedAcceptanceTracker: O(1) VecDeque sliding window, p50/p95/min/max
- AcceptanceTrend enum (Rising/Falling/Stable, ±0.05 threshold)
- AcceptanceDashboard aggregator; wired into PerformanceMetrics::update()
  and dashboard(); 13 tests

KV cache INT8 quantization (rtx-inference/cache):
- KvCacheQuantMode { None, Int8 { scale_per_token }, Fp8E4M3 } enum
- KvQuantizer::encode/decode: symmetric per-block INT8 (scale=max_abs/127)
  gives 4× compression vs f32; Fp8E4M3 CPU proxy, GPU path reserved
- QuantizedKvBlock carries data+scale+mode; KvCacheConfig::quant_mode
  defaulting to None; 14 tests

ColParallel + RowParallel linear (rtx-distributed):
- ColParallelLinear: shards weight rows across TP ranks, forward_cpu()
  batch matmul + per-shard bias; no AllReduce (output shards concatenated)
- RowParallelLinear: shards weight cols across TP ranks, forward_cpu()
  partial sum + bias on rank 0 only; async forward() calls ProcessGroup
  AllReduce for real NCCL path; CPU sim is no-op
- TensorParallel::matmul() replaced zeros stub with ColParallelLinear(tp=1)
- col→row roundtrip verified within 1e-3; 9 tests

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-27 03:54:07 +00:00

201 lines
6.2 KiB
Rust

//! Comprehensive tests for paged KV cache functionality
use rtx_inference::{
InferenceError,
cache::{EvictionPolicy, KvCacheConfig, MemoryTier, PagedKvCache},
};
use rtx_tensor::{Device, Tensor};
use std::time::Duration;
#[cfg(test)]
mod cache_tests {
use super::*;
fn create_test_config() -> KvCacheConfig {
KvCacheConfig {
page_size: 4,
max_pages_gpu: 10,
max_pages_cpu: 20,
max_pages_nvme: 50,
eviction_policy: EvictionPolicy::LRU,
prefetch_enabled: false,
prefetch_distance: 4,
compression_enabled: false,
compression_ratio_threshold: 2.0,
memory_pressure_threshold: 0.8,
model_isolation: true,
persistence_enabled: false,
persistence_path: "/tmp/rtx_cache".to_string(),
..Default::default()
}
}
fn create_test_tensor(device: &Device) -> Tensor {
Tensor::from_data(vec![1.0, 2.0, 3.0, 4.0], vec![4, 1], device).unwrap()
}
#[tokio::test]
async fn test_cache_creation() {
let config = create_test_config();
let device = Device::cpu();
let cache = PagedKvCache::new(config, device).await;
assert!(cache.is_ok());
}
#[tokio::test]
async fn test_page_allocation() {
let config = create_test_config();
let device = Device::cpu();
let mut cache = PagedKvCache::new(config, device).await.unwrap();
let pages = cache.allocate_pages("test_seq", 10).await.unwrap();
assert_eq!(pages.len(), 3); // 10 tokens / 4 tokens per page = 3 pages
}
#[tokio::test]
async fn test_store_and_retrieve_kv_data() {
let config = create_test_config();
let device = Device::cpu();
let mut cache = PagedKvCache::new(config, device.clone()).await.unwrap();
// Allocate pages
let _pages = cache.allocate_pages("test_seq", 8).await.unwrap();
// Create KV tensors
let key_data = create_test_tensor(&device);
let value_data = create_test_tensor(&device);
// Store data
let result = cache
.store_kv_data("test_seq", 0, &key_data, &value_data)
.await;
assert!(result.is_ok());
// Retrieve data
let (retrieved_keys, retrieved_values) = cache.get_kv_data("test_seq", 0, 4).await.unwrap();
assert_eq!(retrieved_keys.shape(), key_data.shape());
assert_eq!(retrieved_values.shape(), value_data.shape());
}
#[tokio::test]
#[ignore = "Pre-existing tier migration assertion failure"]
async fn test_tier_migration() {
let config = create_test_config();
let device = Device::cpu();
let mut cache = PagedKvCache::new(config, device.clone()).await.unwrap();
// Allocate in GPU tier
let pages = cache
.allocate_pages_in_tier("test_seq", 4, MemoryTier::GPU)
.await
.unwrap();
assert_eq!(pages.len(), 1);
let page_info = cache.get_page_info(pages[0]).await.unwrap();
assert_eq!(page_info.tier, MemoryTier::GPU);
// Migrate to CPU
cache
.migrate_to_tier("test_seq", MemoryTier::CPU)
.await
.unwrap();
let page_info_after = cache.get_page_info(pages[0]).await.unwrap();
assert_eq!(page_info_after.tier, MemoryTier::CPU);
}
#[tokio::test]
async fn test_cache_stats() {
let config = create_test_config();
let device = Device::cpu();
let mut cache = PagedKvCache::new(config, device).await.unwrap();
// Allocate some pages
cache.allocate_pages("seq1", 8).await.unwrap();
cache.allocate_pages("seq2", 4).await.unwrap();
let stats = cache.get_stats().await;
assert!(stats.total_pages_allocated >= 3);
assert!(stats.gpu_pages_used >= 3);
}
#[tokio::test]
async fn test_eviction_policies() {
for policy in [
EvictionPolicy::LRU,
EvictionPolicy::LFU,
EvictionPolicy::FIFO,
EvictionPolicy::Random,
] {
let config = KvCacheConfig {
eviction_policy: policy,
..create_test_config()
};
let device = Device::cpu();
let cache = PagedKvCache::new(config, device).await;
assert!(
cache.is_ok(),
"Failed to create cache with policy {:?}",
policy
);
}
}
#[tokio::test]
async fn test_compression_config() {
let config = KvCacheConfig {
compression_enabled: true,
compression_ratio_threshold: 2.0,
..create_test_config()
};
let device = Device::cpu();
let mut cache = PagedKvCache::new(config, device.clone()).await.unwrap();
// Allocate in CPU tier (where compression applies)
cache
.allocate_pages_in_tier("test_seq", 4, MemoryTier::CPU)
.await
.unwrap();
let key_data = create_test_tensor(&device);
let value_data = create_test_tensor(&device);
cache
.store_kv_data("test_seq", 0, &key_data, &value_data)
.await
.unwrap();
// Compression stats should be updated
let stats = cache.get_stats().await;
assert!(stats.compression_ratio > 0.0);
}
#[tokio::test]
async fn test_persistence_config() {
let config = KvCacheConfig {
persistence_enabled: true,
persistence_path: "/tmp/test_cache".to_string(),
..create_test_config()
};
let device = Device::cpu();
let cache = PagedKvCache::new(config, device).await.unwrap();
let result = cache.persist_to_disk().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_cache_recovery() {
let config = KvCacheConfig {
persistence_enabled: true,
persistence_path: "/tmp/test_cache_recovery".to_string(),
..create_test_config()
};
let device = Device::cpu();
let mut cache = PagedKvCache::new(config, device).await.unwrap();
let result = cache.recover_from_disk().await;
assert!(result.is_ok());
}
}