#[cfg(feature = "gpu")]
mod adapter_probe;
#[cfg(feature = "gpu")]
mod backend;
#[cfg(feature = "gpu")]
pub(crate) mod gpu_shader;
mod policy;
pub use policy::*;
mod self_test;
pub use self_test::*;
#[cfg(feature = "gpu")]
pub(crate) use adapter_probe::{
gpu_adapter_device_identity, gpu_adapter_probe, is_software_adapter,
};
static MOE_FEATURE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static MOE_SCORE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "ml")]
fn ml_split_prof_enabled() -> bool {
crate::scan_profile::enabled()
}
pub(crate) fn ml_split_profile_dump() {
use std::sync::atomic::Ordering::Relaxed;
let f = MOE_FEATURE_NS.swap(0, Relaxed) as f64 / 1e6;
let s = MOE_SCORE_NS.swap(0, Relaxed) as f64 / 1e6;
if f == 0.0 && s == 0.0 {
return;
}
eprintln!(
"=== ML split: feature_extract={f:.1}ms moe_score={s:.1}ms (score = {:.1}% of ML compute; \
only this fraction is GPU-offloadable) ===",
100.0 * s / (f + s).max(1e-9),
);
}
pub(crate) fn ml_split_profile_reset() {
use std::sync::atomic::Ordering::Relaxed;
MOE_FEATURE_NS.store(0, Relaxed);
MOE_SCORE_NS.store(0, Relaxed);
}
#[cfg(all(test, feature = "ml", feature = "multiline"))]
pub(crate) fn batch_ml_inference<T: crate::ml_scorer::MlScoreInput>(
candidates: &[T],
config: &crate::types::ScannerConfig,
) -> Vec<f64> {
batch_ml_inference_with_timeout(
candidates,
config,
std::time::Duration::from_millis(
crate::scanner_config::ScannerTuningConfig::GPU_MOE_TIMEOUT_MS_DEFAULT,
),
)
}
#[cfg(feature = "ml")]
pub(crate) fn batch_ml_inference_with_timeout<T: crate::ml_scorer::MlScoreInput>(
candidates: &[T],
config: &crate::types::ScannerConfig,
gpu_moe_timeout: std::time::Duration,
) -> Vec<f64> {
if candidates.is_empty() {
return Vec::new();
}
#[cfg(feature = "ml")]
{
use rayon::prelude::*;
#[cfg(not(feature = "gpu"))]
let _ = gpu_moe_timeout; let prof = ml_split_prof_enabled();
if candidates.len() < crate::ml_scorer::GPU_BATCH_THRESHOLD {
let t = prof.then(std::time::Instant::now);
let scores = crate::ml_scorer::score_input_batch_serial(candidates, config);
if let Some(t) = t {
MOE_SCORE_NS.fetch_add(
t.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
return scores;
}
let t_feat = prof.then(std::time::Instant::now);
let features: Vec<[f32; crate::ml_scorer::NUM_FEATURES]> = candidates
.par_iter()
.map(|candidate| candidate.ml_features(config))
.collect();
if let Some(t) = t_feat {
MOE_FEATURE_NS.fetch_add(
t.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
let t_score = prof.then(std::time::Instant::now);
let score_features_on_cpu =
|| crate::ml_scorer::score_precomputed_batch_on_cpu(candidates, &features);
let scores = {
#[cfg(feature = "gpu")]
{
match backend::batch_score_features(&features, gpu_moe_timeout) {
Some(mut scores) if scores.len() == candidates.len() => {
crate::confidence::policy::apply_empty_candidate_score_policy(
candidates.iter().map(|candidate| candidate.ml_text()),
&mut scores,
);
scores
}
Some(scores) => {
debug_assert_eq!(
scores.len(),
candidates.len(),
"backend::batch_score_features must return one score per input"
);
backend::moe_runtime_degrade(&format!(
"caller-side score count mismatch: backend returned {} scores for {} candidates",
scores.len(),
candidates.len()
));
score_features_on_cpu()
}
None => score_features_on_cpu(),
}
}
#[cfg(not(feature = "gpu"))]
{
score_features_on_cpu()
}
};
if let Some(t) = t_score {
MOE_SCORE_NS.fetch_add(
t.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
scores
}
#[cfg(not(feature = "ml"))]
{
let _ = candidates; let _ = config; let _ = gpu_moe_timeout; Vec::new()
}
}
pub fn gpu_available() -> bool {
gpu_probe().available
}