use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use onnx_runtime_cuda_memory::virtual_memory::PhysicalHandlePool;
use onnx_runtime_cuda_memory::vmm_allocator::CudaVmmAllocator;
use onnx_runtime_ep_api::{
EpError, ExpertWeightGroup, LazyWeightBoundary, ResidencyPlan, Result,
StaticProfileResidencyPolicy, expert_weight_groups, plan_residency,
};
use onnx_runtime_ir::{Graph, NodeId, ValueId};
use onnx_runtime_loader::WeightRegionCatalog;
use crate::coarse_residency::{BoundaryApplicationOutcome, coarse_residency_profile_enabled};
use crate::kernels::expert_route_telemetry::{
RouteDecision, TelemetrySnapshot, consume_and_validate,
};
use crate::weight_paging::{CudaWeightResidency, RouteReservationHealth};
#[derive(Debug)]
pub enum RouteWindowConsumeOutcome {
Disabled,
RejectedNotSafeBoundary { reason: &'static str },
WholeBank { reason: String },
Applied {
routed_experts: Vec<usize>,
epoch: u32,
count: u32,
outcome: Box<BoundaryApplicationOutcome>,
},
}
enum Prepared {
Early(RouteWindowConsumeOutcome),
Ready {
plan: ResidencyPlan,
routed_experts: Vec<usize>,
epoch: u32,
count: u32,
},
}
#[allow(clippy::too_many_arguments)]
fn prepare_route_window(
residency: &CudaWeightResidency,
snapshot: &TelemetrySnapshot,
expected_epoch: u32,
expected_request: u32,
expected_device: u32,
bank_values: &[ValueId],
boundary: LazyWeightBoundary,
catalogs: &HashMap<ValueId, WeightRegionCatalog>,
device_count: usize,
) -> Prepared {
if let Some(reason) = residency.resize_safe_point(device_count).blocking_reason() {
return Prepared::Early(RouteWindowConsumeOutcome::RejectedNotSafeBoundary { reason });
}
match consume_and_validate(
&snapshot.header,
&snapshot.bitmap,
expected_epoch,
expected_request,
expected_device,
snapshot.num_experts,
usize::try_from(snapshot.routes_per_row)
.expect("u32 routes-per-row telemetry contract fits usize"),
) {
RouteDecision::WholeBank(reason) => {
return Prepared::Early(RouteWindowConsumeOutcome::WholeBank { reason });
}
RouteDecision::HotSet(_) => {}
}
let routed_experts = snapshot.routed_experts();
if routed_experts.is_empty() {
return Prepared::Early(RouteWindowConsumeOutcome::WholeBank {
reason: "route window recorded no in-range experts; nothing to keep resident".into(),
});
}
let profile: HashMap<ValueId, Vec<usize>> = bank_values
.iter()
.filter(|value| catalogs.contains_key(value))
.map(|value| (*value, routed_experts.clone()))
.collect();
let policy = StaticProfileResidencyPolicy::new(profile);
let candidates: Vec<(ValueId, LazyWeightBoundary, &WeightRegionCatalog)> = bank_values
.iter()
.filter_map(|value| {
catalogs
.get(value)
.map(|catalog| (*value, boundary, catalog))
})
.collect();
let plan = plan_residency(candidates, &policy, None);
Prepared::Ready {
plan,
routed_experts,
epoch: snapshot.epoch(),
count: snapshot.count(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn consume_route_window_at_boundary(
residency: &CudaWeightResidency,
snapshot: &TelemetrySnapshot,
expected_epoch: u32,
expected_request: u32,
expected_device: u32,
bank_values: &[ValueId],
boundary: LazyWeightBoundary,
catalogs: &HashMap<ValueId, WeightRegionCatalog>,
allocators: &HashMap<ValueId, Arc<CudaVmmAllocator>>,
device_pool: &Arc<PhysicalHandlePool>,
host_pool: &Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expert_groups: &[ExpertWeightGroup],
) -> RouteWindowConsumeOutcome {
if !coarse_residency_profile_enabled() {
return RouteWindowConsumeOutcome::Disabled;
}
consume_resolved_route_window_at_boundary(
residency,
snapshot,
expected_epoch,
expected_request,
expected_device,
bank_values,
boundary,
catalogs,
allocators,
device_pool,
host_pool,
device_count,
device_ordinal,
expert_groups,
)
}
#[allow(clippy::too_many_arguments)]
fn consume_resolved_route_window_at_boundary(
residency: &CudaWeightResidency,
snapshot: &TelemetrySnapshot,
expected_epoch: u32,
expected_request: u32,
expected_device: u32,
bank_values: &[ValueId],
boundary: LazyWeightBoundary,
catalogs: &HashMap<ValueId, WeightRegionCatalog>,
allocators: &HashMap<ValueId, Arc<CudaVmmAllocator>>,
device_pool: &Arc<PhysicalHandlePool>,
host_pool: &Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expert_groups: &[ExpertWeightGroup],
) -> RouteWindowConsumeOutcome {
match prepare_route_window(
residency,
snapshot,
expected_epoch,
expected_request,
expected_device,
bank_values,
boundary,
catalogs,
device_count,
) {
Prepared::Early(outcome) => outcome,
Prepared::Ready {
plan,
routed_experts,
epoch,
count,
} => {
let outcome = residency.apply_resolved_coarse_residency_plan(
&plan,
catalogs,
allocators,
device_pool,
host_pool,
device_count,
device_ordinal,
expert_groups,
);
RouteWindowConsumeOutcome::Applied {
routed_experts,
epoch,
count,
outcome: Box::new(outcome),
}
}
}
}
#[cfg(any(test, feature = "gpu-tests"))]
#[allow(clippy::too_many_arguments)]
pub fn consume_route_window_at_boundary_with_phase8_faults(
runtime: &Arc<crate::runtime::CudaRuntime>,
residency: &CudaWeightResidency,
snapshot: &TelemetrySnapshot,
expected_epoch: u32,
expected_request: u32,
expected_device: u32,
bank_values: &[ValueId],
boundary: LazyWeightBoundary,
catalogs: &HashMap<ValueId, WeightRegionCatalog>,
allocators: &HashMap<ValueId, Arc<CudaVmmAllocator>>,
device_pool: &Arc<PhysicalHandlePool>,
host_pool: &Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expert_groups: &[ExpertWeightGroup],
phase8_faults: HashMap<ValueId, Arc<onnx_runtime_cuda_memory::release::DriverFaultPlan>>,
) -> RouteWindowConsumeOutcome {
consume_route_window_at_boundary_with_phase8_faults_inner(
runtime,
residency,
snapshot,
expected_epoch,
expected_request,
expected_device,
bank_values,
boundary,
catalogs,
allocators,
device_pool,
host_pool,
device_count,
device_ordinal,
expert_groups,
phase8_faults,
None,
)
}
#[cfg(any(test, feature = "gpu-tests"))]
#[allow(clippy::too_many_arguments)]
fn consume_route_window_at_boundary_with_phase8_faults_inner(
runtime: &Arc<crate::runtime::CudaRuntime>,
residency: &CudaWeightResidency,
snapshot: &TelemetrySnapshot,
expected_epoch: u32,
expected_request: u32,
expected_device: u32,
bank_values: &[ValueId],
boundary: LazyWeightBoundary,
catalogs: &HashMap<ValueId, WeightRegionCatalog>,
allocators: &HashMap<ValueId, Arc<CudaVmmAllocator>>,
device_pool: &Arc<PhysicalHandlePool>,
host_pool: &Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expert_groups: &[ExpertWeightGroup],
phase8_faults: HashMap<ValueId, Arc<onnx_runtime_cuda_memory::release::DriverFaultPlan>>,
rollback_interlock: Option<Arc<crate::coarse_residency::RollbackSafePointInterlock>>,
) -> RouteWindowConsumeOutcome {
match prepare_route_window(
residency,
snapshot,
expected_epoch,
expected_request,
expected_device,
bank_values,
boundary,
catalogs,
device_count,
) {
Prepared::Early(outcome) => outcome,
Prepared::Ready {
plan,
routed_experts,
epoch,
count,
} => {
let outcome = match rollback_interlock {
Some(interlock) => {
crate::coarse_residency::apply_residency_plan_at_boundary_with_rollback_interlock(
runtime,
residency,
&plan,
catalogs,
allocators,
device_pool,
host_pool,
device_count,
device_ordinal,
expert_groups,
phase8_faults,
interlock,
)
}
None => crate::coarse_residency::apply_residency_plan_at_boundary_with_phase8_faults(
runtime,
residency,
&plan,
catalogs,
allocators,
device_pool,
host_pool,
device_count,
device_ordinal,
expert_groups,
phase8_faults,
),
};
RouteWindowConsumeOutcome::Applied {
routed_experts,
epoch,
count,
outcome: Box::new(outcome),
}
}
}
}
pub trait RouteTelemetrySource: Send + Sync {
fn route_telemetry_snapshot(&self) -> Result<Option<TelemetrySnapshot>>;
fn reset_route_telemetry_boundary(&self) -> Result<()>;
}
pub struct RouteResidencyBoundary {
source: Arc<dyn RouteTelemetrySource>,
residency: Arc<CudaWeightResidency>,
bank_values: Vec<ValueId>,
boundary: LazyWeightBoundary,
catalogs: HashMap<ValueId, WeightRegionCatalog>,
allocators: HashMap<ValueId, Arc<CudaVmmAllocator>>,
device_pool: Arc<PhysicalHandlePool>,
host_pool: Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expected_request: u32,
expected_device: u32,
expected_epoch: AtomicU32,
expert_groups: Vec<ExpertWeightGroup>,
reservation_health: Arc<RouteReservationHealth>,
transition_state: Mutex<StableTransitionState>,
}
#[derive(Default)]
struct StableTransitionState {
installed: bool,
host_ranges: HashMap<ValueId, Vec<(usize, usize)>>,
poisoned: Option<String>,
}
impl RouteResidencyBoundary {
#[allow(clippy::too_many_arguments)]
pub fn new(
source: Arc<dyn RouteTelemetrySource>,
residency: Arc<CudaWeightResidency>,
bank_values: Vec<ValueId>,
boundary: LazyWeightBoundary,
catalogs: HashMap<ValueId, WeightRegionCatalog>,
allocators: HashMap<ValueId, Arc<CudaVmmAllocator>>,
device_pool: Arc<PhysicalHandlePool>,
host_pool: Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expected_request: u32,
expected_device: u32,
initial_epoch: u32,
expert_groups: Vec<ExpertWeightGroup>,
reservation_health: Arc<RouteReservationHealth>,
) -> Self {
Self {
source,
residency,
bank_values,
boundary,
catalogs,
allocators,
device_pool,
host_pool,
device_count,
device_ordinal,
expected_request,
expected_device,
expected_epoch: AtomicU32::new(initial_epoch),
expert_groups,
reservation_health,
transition_state: Mutex::new(StableTransitionState::default()),
}
}
fn expected_epoch(&self) -> u32 {
self.expected_epoch.load(Ordering::Relaxed)
}
pub fn bank_value_count(&self) -> usize {
self.bank_values.len()
}
fn advance_epoch(&self) {
self.expected_epoch.fetch_add(1, Ordering::Relaxed);
}
fn record_host_ranges(
&self,
state: &mut StableTransitionState,
outcome: &RouteWindowConsumeOutcome,
) {
let RouteWindowConsumeOutcome::Applied { outcome, .. } = outcome else {
return;
};
for range in &outcome.host_resident_ranges {
state
.host_ranges
.entry(range.value)
.or_default()
.push((range.offset, range.len));
}
state.installed = !state.host_ranges.is_empty();
}
fn poison_after_incomplete_group(
&self,
state: &mut StableTransitionState,
outcome: &RouteWindowConsumeOutcome,
) -> Option<String> {
let RouteWindowConsumeOutcome::Applied { outcome, .. } = outcome else {
return None;
};
let quarantined_blocks = outcome
.quarantined
.iter()
.map(|(_, blocks)| blocks.len())
.sum::<usize>();
let reason = if quarantined_blocks > 0 {
Some(format!(
"{quarantined_blocks} route-bank physical mapping(s) are quarantined"
))
} else if !outcome.rollback_failures.is_empty() {
Some(format!(
"{} route-bank rollback(s) failed to restore device residency",
outcome.rollback_failures.len()
))
} else if !outcome.host_resident_ranges.is_empty()
&& (outcome.failure_count > 0 || !outcome.fatal_progress.is_empty())
{
Some(format!(
"logical expert group left {} HOST_NUMA range(s) across {} value(s) after {} \
member transition failure(s)",
outcome.host_resident_ranges.len(),
outcome.values_touched,
outcome.failure_count + outcome.fatal_progress.len()
))
} else {
None
};
if let Some(reason) = reason {
state.poisoned = Some(reason.clone());
state.installed = false;
Some(reason)
} else {
None
}
}
}
fn observe_route_window_without_transition(
binding: &RouteResidencyBoundary,
snapshot: &TelemetrySnapshot,
) -> RouteWindowConsumeOutcome {
match prepare_route_window(
&binding.residency,
snapshot,
binding.expected_epoch(),
binding.expected_request,
binding.expected_device,
&binding.bank_values,
binding.boundary,
&binding.catalogs,
binding.device_count,
) {
Prepared::Early(outcome) => outcome,
Prepared::Ready {
routed_experts,
epoch,
count,
..
} => RouteWindowConsumeOutcome::Applied {
routed_experts,
epoch,
count,
outcome: Box::default(),
},
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RouteResidencyBindingReject {
NoExpertGroups,
NoTelemetrySource {
node: NodeId,
},
TelemetryProducerUnsupported {
node: NodeId,
boundary: LazyWeightBoundary,
},
MissingCatalog {
value: ValueId,
},
MissingAllocator {
value: ValueId,
},
UnsupportedBoundary {
node: NodeId,
boundary: LazyWeightBoundary,
},
RequestIdentityOutOfRange {
executor: u64,
},
Reservation(crate::weight_paging::RouteBankReservationReject),
TelemetryUnsupported {
node: NodeId,
reason: String,
},
}
impl RouteResidencyBindingReject {
pub fn reason(&self) -> String {
match self {
RouteResidencyBindingReject::NoExpertGroups => {
"no routed expert group discovered".to_string()
}
RouteResidencyBindingReject::NoTelemetrySource { node } => {
format!("expert group node {node:?} has no armed telemetry source")
}
RouteResidencyBindingReject::TelemetryProducerUnsupported { node, boundary } => {
format!(
"expert group node {node:?} at {boundary:?} has no supported executor-scoped \
route-telemetry producer"
)
}
RouteResidencyBindingReject::MissingCatalog { value } => {
format!("bank value {value:?} has no region catalog")
}
RouteResidencyBindingReject::MissingAllocator { value } => {
format!("bank value {value:?} has no VMM allocator")
}
RouteResidencyBindingReject::UnsupportedBoundary { node, boundary } => {
format!("expert group node {node:?} uses unsupported boundary {boundary:?}")
}
RouteResidencyBindingReject::RequestIdentityOutOfRange { executor } => {
format!("executor identity {executor} does not fit telemetry request_id")
}
RouteResidencyBindingReject::Reservation(reject) => {
format!("executor-scoped bank reservation unavailable: {reject}")
}
RouteResidencyBindingReject::TelemetryUnsupported { node, reason } => {
format!("expert group node {node:?} telemetry unsupported: {reason}")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RouteResidencyInstallOutcome {
GateDisabled,
OffloadDisabled,
Rejected(RouteResidencyBindingReject),
Installed { banks: usize },
}
pub(crate) fn validate_route_residency_bindings(
graph: &Graph,
has_source: impl Fn(NodeId) -> bool,
has_catalog: impl Fn(ValueId) -> bool,
has_allocator: impl Fn(ValueId) -> bool,
) -> std::result::Result<Vec<ExpertWeightGroup>, RouteResidencyBindingReject> {
let groups = expert_weight_groups(graph);
if groups.is_empty() {
return Err(RouteResidencyBindingReject::NoExpertGroups);
}
for group in &groups {
if !has_source(group.node) {
return Err(
if group
.boundary
.route_telemetry_producer_may_appear_after_compilation()
{
RouteResidencyBindingReject::NoTelemetrySource { node: group.node }
} else {
RouteResidencyBindingReject::TelemetryProducerUnsupported {
node: group.node,
boundary: group.boundary,
}
},
);
}
for member in &group.members {
if !has_catalog(*member) {
return Err(RouteResidencyBindingReject::MissingCatalog { value: *member });
}
if !has_allocator(*member) {
return Err(RouteResidencyBindingReject::MissingAllocator { value: *member });
}
}
}
Ok(groups)
}
#[allow(clippy::too_many_arguments)]
pub fn build_route_residency_boundaries(
graph: &Graph,
residency: Arc<CudaWeightResidency>,
sources: &HashMap<NodeId, Arc<dyn RouteTelemetrySource>>,
catalogs: &HashMap<ValueId, WeightRegionCatalog>,
allocators: &HashMap<ValueId, Arc<CudaVmmAllocator>>,
reservation_health: Arc<RouteReservationHealth>,
device_pool: Arc<PhysicalHandlePool>,
host_pool: Arc<PhysicalHandlePool>,
device_count: usize,
device_ordinal: i32,
expected_request: u32,
expected_device: u32,
initial_epoch: u32,
) -> std::result::Result<Vec<RouteResidencyBoundary>, RouteResidencyBindingReject> {
let groups = validate_route_residency_bindings(
graph,
|node| sources.contains_key(&node),
|value| catalogs.contains_key(&value),
|value| allocators.contains_key(&value),
)?;
Ok(groups
.into_iter()
.map(|group| {
let group_catalogs = group
.members
.iter()
.map(|value| (*value, catalogs[value].clone()))
.collect();
let group_allocators = group
.members
.iter()
.map(|value| (*value, Arc::clone(&allocators[value])))
.collect();
RouteResidencyBoundary::new(
Arc::clone(&sources[&group.node]),
Arc::clone(&residency),
group.members.clone(),
group.boundary,
group_catalogs,
group_allocators,
Arc::clone(&device_pool),
Arc::clone(&host_pool),
device_count,
device_ordinal,
expected_request,
expected_device,
initial_epoch,
vec![group],
Arc::clone(&reservation_health),
)
})
.collect())
}
#[derive(Debug, Default)]
pub struct RouteResidencyDiagnostics {
boundaries: AtomicU64,
applied: AtomicU64,
route_count: AtomicU64,
values_touched: AtomicU64,
device_bytes_released: AtomicU64,
host_bytes_committed: AtomicU64,
transition_time_ns: AtomicU64,
rollback_count: AtomicU64,
quarantined_blocks: AtomicU64,
fatal_values: AtomicU64,
boundary_host_time_ns: AtomicU64,
boundary_host_time_max_ns: AtomicU64,
rejected: AtomicU64,
whole_bank: AtomicU64,
empty: AtomicU64,
last_reason: Mutex<Option<String>>,
installs: AtomicU64,
declines: AtomicU64,
last_install_reason: Mutex<Option<String>>,
}
impl RouteResidencyDiagnostics {
pub fn boundaries(&self) -> u64 {
self.boundaries.load(Ordering::Relaxed)
}
pub fn applied(&self) -> u64 {
self.applied.load(Ordering::Relaxed)
}
pub fn route_count(&self) -> u64 {
self.route_count.load(Ordering::Relaxed)
}
pub fn values_touched(&self) -> u64 {
self.values_touched.load(Ordering::Relaxed)
}
pub fn device_bytes_released(&self) -> u64 {
self.device_bytes_released.load(Ordering::Relaxed)
}
pub fn host_bytes_committed(&self) -> u64 {
self.host_bytes_committed.load(Ordering::Relaxed)
}
pub fn transition_time_ns(&self) -> u64 {
self.transition_time_ns.load(Ordering::Relaxed)
}
pub fn rollback_count(&self) -> u64 {
self.rollback_count.load(Ordering::Relaxed)
}
pub fn quarantined_blocks(&self) -> u64 {
self.quarantined_blocks.load(Ordering::Relaxed)
}
pub fn fatal_values(&self) -> u64 {
self.fatal_values.load(Ordering::Relaxed)
}
pub fn boundary_host_time_ns(&self) -> u64 {
self.boundary_host_time_ns.load(Ordering::Relaxed)
}
pub fn boundary_host_time_max_ns(&self) -> u64 {
self.boundary_host_time_max_ns.load(Ordering::Relaxed)
}
pub fn rejected(&self) -> u64 {
self.rejected.load(Ordering::Relaxed)
}
pub fn whole_bank(&self) -> u64 {
self.whole_bank.load(Ordering::Relaxed)
}
pub fn empty(&self) -> u64 {
self.empty.load(Ordering::Relaxed)
}
pub fn last_reason(&self) -> Option<String> {
self.last_reason.lock().unwrap().clone()
}
pub fn installs(&self) -> u64 {
self.installs.load(Ordering::Relaxed)
}
pub fn declines(&self) -> u64 {
self.declines.load(Ordering::Relaxed)
}
pub fn last_install_reason(&self) -> Option<String> {
self.last_install_reason.lock().unwrap().clone()
}
fn set_install_reason(&self, reason: String) {
*self.last_install_reason.lock().unwrap() = Some(reason);
}
pub(crate) fn record_install(&self, banks: usize) {
self.installs.fetch_add(1, Ordering::Relaxed);
self.set_install_reason(format!("installed binding over {banks} bank value(s)"));
}
pub(crate) fn record_decline(&self, reason: &str) {
self.declines.fetch_add(1, Ordering::Relaxed);
self.set_install_reason(format!("declined: {reason}"));
}
fn set_reason(&self, reason: String) {
*self.last_reason.lock().unwrap() = Some(reason);
}
fn record_rejected(&self, reason: &str) {
self.rejected.fetch_add(1, Ordering::Relaxed);
self.set_reason(format!("rejected: {reason}"));
}
fn record_empty(&self, reason: &str) {
self.empty.fetch_add(1, Ordering::Relaxed);
self.set_reason(format!("empty: {reason}"));
}
fn record_boundary_host_time(&self, elapsed: Duration) {
let nanos = elapsed.as_nanos().min(u128::from(u64::MAX)) as u64;
self.boundary_host_time_ns
.fetch_add(nanos, Ordering::Relaxed);
self.boundary_host_time_max_ns
.fetch_max(nanos, Ordering::Relaxed);
}
fn record_outcome(&self, outcome: &RouteWindowConsumeOutcome) {
match outcome {
RouteWindowConsumeOutcome::Disabled => {
self.set_reason("disabled".into());
}
RouteWindowConsumeOutcome::RejectedNotSafeBoundary { reason } => {
self.rejected.fetch_add(1, Ordering::Relaxed);
self.set_reason(format!("rejected: {reason}"));
}
RouteWindowConsumeOutcome::WholeBank { reason } => {
self.whole_bank.fetch_add(1, Ordering::Relaxed);
self.set_reason(format!("whole-bank: {reason}"));
}
RouteWindowConsumeOutcome::Applied {
routed_experts,
epoch,
count,
outcome,
} => {
self.applied.fetch_add(1, Ordering::Relaxed);
self.route_count
.fetch_add(u64::from(*count), Ordering::Relaxed);
self.values_touched
.fetch_add(outcome.values_touched as u64, Ordering::Relaxed);
self.device_bytes_released
.fetch_add(outcome.device_bytes_released, Ordering::Relaxed);
self.host_bytes_committed
.fetch_add(outcome.host_bytes_committed, Ordering::Relaxed);
let transition_ns =
(outcome.transition_time_ms * 1_000_000.0).clamp(0.0, u64::MAX as f64) as u64;
self.transition_time_ns
.fetch_add(transition_ns, Ordering::Relaxed);
self.rollback_count
.fetch_add(outcome.rollback_count as u64, Ordering::Relaxed);
self.quarantined_blocks.fetch_add(
outcome
.quarantined
.iter()
.map(|(_, blocks)| blocks.len() as u64)
.sum::<u64>(),
Ordering::Relaxed,
);
self.fatal_values
.fetch_add(outcome.fatal_progress.len() as u64, Ordering::Relaxed);
self.set_reason(format!(
"applied hot-set of {} experts at epoch {epoch} (count {count})",
routed_experts.len()
));
}
}
}
}
fn window_was_consumed(outcome: &RouteWindowConsumeOutcome) -> bool {
matches!(
outcome,
RouteWindowConsumeOutcome::Applied { .. } | RouteWindowConsumeOutcome::WholeBank { .. }
)
}
pub fn run_route_residency_boundary(
binding: &RouteResidencyBoundary,
diag: &RouteResidencyDiagnostics,
) -> Result<()> {
let started = Instant::now();
diag.boundaries.fetch_add(1, Ordering::Relaxed);
if let Some(reason) = binding
.residency
.resize_safe_point(binding.device_count)
.blocking_reason()
{
diag.record_rejected(reason);
diag.record_boundary_host_time(started.elapsed());
return Ok(());
}
let Some(snapshot) = binding.source.route_telemetry_snapshot()? else {
diag.record_empty("route telemetry disarmed; no window to consume");
diag.record_boundary_host_time(started.elapsed());
return Ok(());
};
let mut transition_state = binding
.transition_state
.lock()
.expect("route-residency transition state poisoned");
if let Some(reason) = &transition_state.poisoned {
return Err(EpError::KernelFailed(format!(
"route-residency boundary is unusable after an earlier atomic transition failure: \
{reason}; tear down and rebuild the executor"
)));
}
let mut transition_guard = None;
let outcome = if transition_state.installed {
observe_route_window_without_transition(binding, &snapshot)
} else {
transition_guard = Some(binding.reservation_health.begin_transition().map_err(
|reason| {
EpError::KernelFailed(format!(
"route-residency could not linearize the executor reservation transition: \
{reason}"
))
},
)?);
consume_route_window_at_boundary(
&binding.residency,
&snapshot,
binding.expected_epoch(),
binding.expected_request,
binding.expected_device,
&binding.bank_values,
binding.boundary,
&binding.catalogs,
&binding.allocators,
&binding.device_pool,
&binding.host_pool,
binding.device_count,
binding.device_ordinal,
&binding.expert_groups,
)
};
binding.record_host_ranges(&mut transition_state, &outcome);
if let Some(reason) = binding.poison_after_incomplete_group(&mut transition_state, &outcome) {
if let Some(guard) = transition_guard.take() {
guard.poison(reason.clone());
} else {
binding.reservation_health.mark_unusable(reason.clone());
}
diag.record_outcome(&outcome);
diag.record_boundary_host_time(started.elapsed());
return Err(EpError::KernelFailed(format!(
"route-residency invalidated the executor-scoped bank reservation: {reason}; no \
dispatch, capture, or replay may use this executor until it is rebuilt"
)));
}
if window_was_consumed(&outcome) {
binding.source.reset_route_telemetry_boundary()?;
binding.advance_epoch();
}
diag.record_outcome(&outcome);
diag.record_boundary_host_time(started.elapsed());
if let Some(guard) = transition_guard {
guard.complete();
}
Ok(())
}
#[cfg(any(test, feature = "gpu-tests"))]
pub fn run_route_residency_boundary_with_phase8_faults(
runtime: &Arc<crate::runtime::CudaRuntime>,
binding: &RouteResidencyBoundary,
diag: &RouteResidencyDiagnostics,
phase8_faults: HashMap<ValueId, Arc<onnx_runtime_cuda_memory::release::DriverFaultPlan>>,
) -> Result<()> {
run_route_residency_boundary_with_phase8_faults_inner(
runtime,
binding,
diag,
phase8_faults,
None,
)
}
#[cfg(any(test, feature = "gpu-tests"))]
pub fn run_route_residency_boundary_with_rollback_interlock(
runtime: &Arc<crate::runtime::CudaRuntime>,
binding: &RouteResidencyBoundary,
diag: &RouteResidencyDiagnostics,
phase8_faults: HashMap<ValueId, Arc<onnx_runtime_cuda_memory::release::DriverFaultPlan>>,
rollback_interlock: Arc<crate::coarse_residency::RollbackSafePointInterlock>,
) -> Result<()> {
run_route_residency_boundary_with_phase8_faults_inner(
runtime,
binding,
diag,
phase8_faults,
Some(rollback_interlock),
)
}
#[cfg(any(test, feature = "gpu-tests"))]
fn run_route_residency_boundary_with_phase8_faults_inner(
runtime: &Arc<crate::runtime::CudaRuntime>,
binding: &RouteResidencyBoundary,
diag: &RouteResidencyDiagnostics,
phase8_faults: HashMap<ValueId, Arc<onnx_runtime_cuda_memory::release::DriverFaultPlan>>,
rollback_interlock: Option<Arc<crate::coarse_residency::RollbackSafePointInterlock>>,
) -> Result<()> {
let started = Instant::now();
diag.boundaries.fetch_add(1, Ordering::Relaxed);
if let Some(reason) = binding
.residency
.resize_safe_point(binding.device_count)
.blocking_reason()
{
diag.record_rejected(reason);
diag.record_boundary_host_time(started.elapsed());
return Ok(());
}
let Some(snapshot) = binding.source.route_telemetry_snapshot()? else {
diag.record_empty("route telemetry disarmed; no window to consume");
diag.record_boundary_host_time(started.elapsed());
return Ok(());
};
let mut transition_state = binding
.transition_state
.lock()
.expect("route-residency transition state poisoned");
if let Some(reason) = &transition_state.poisoned {
return Err(EpError::KernelFailed(format!(
"route-residency boundary is unusable after an earlier atomic transition failure: \
{reason}; tear down and rebuild the executor"
)));
}
let mut transition_guard = None;
let outcome = if transition_state.installed {
observe_route_window_without_transition(binding, &snapshot)
} else {
transition_guard = Some(binding.reservation_health.begin_transition().map_err(
|reason| {
EpError::KernelFailed(format!(
"route-residency could not linearize the executor reservation transition: \
{reason}"
))
},
)?);
consume_route_window_at_boundary_with_phase8_faults_inner(
runtime,
&binding.residency,
&snapshot,
binding.expected_epoch(),
binding.expected_request,
binding.expected_device,
&binding.bank_values,
binding.boundary,
&binding.catalogs,
&binding.allocators,
&binding.device_pool,
&binding.host_pool,
binding.device_count,
binding.device_ordinal,
&binding.expert_groups,
phase8_faults,
rollback_interlock,
)
};
binding.record_host_ranges(&mut transition_state, &outcome);
if let Some(reason) = binding.poison_after_incomplete_group(&mut transition_state, &outcome) {
if let Some(guard) = transition_guard.take() {
guard.poison(reason.clone());
} else {
binding.reservation_health.mark_unusable(reason.clone());
}
diag.record_outcome(&outcome);
diag.record_boundary_host_time(started.elapsed());
return Err(EpError::KernelFailed(format!(
"route-residency invalidated the executor-scoped bank reservation: {reason}; no \
dispatch, capture, or replay may use this executor until it is rebuilt"
)));
}
if window_was_consumed(&outcome) {
binding.source.reset_route_telemetry_boundary()?;
binding.advance_epoch();
}
diag.record_outcome(&outcome);
diag.record_boundary_host_time(started.elapsed());
if let Some(guard) = transition_guard {
guard.complete();
}
Ok(())
}
#[allow(dead_code)]
fn _assert_qmoe_is_route_telemetry_source() {
fn is_source<T: RouteTelemetrySource>() {}
is_source::<crate::kernels::qmoe::QMoEKernel>();
}
#[cfg(test)]
mod binding_tests {
use std::collections::HashSet;
use onnx_runtime_ep_api::LazyWeightBoundary;
use onnx_runtime_ir::{DataType, Graph, NodeId, TensorData, ValueId, WeightRef, static_shape};
use super::{RouteResidencyBindingReject, validate_route_residency_bindings};
fn shape1(n: usize) -> onnx_runtime_ir::Shape {
static_shape([n])
}
fn inline_initializer(graph: &mut Graph, name: &str) -> ValueId {
let value = graph.create_named_value(name, DataType::Uint8, shape1(4));
graph.set_initializer(
value,
WeightRef::Inline(TensorData::from_raw(DataType::Uint8, vec![4], vec![0u8; 4])),
);
value
}
fn qmoe_node(graph: &mut Graph) -> (NodeId, Vec<ValueId>) {
let input = graph.create_named_value("input", DataType::Float32, shape1(4));
let router = graph.create_named_value("router_probs", DataType::Float32, shape1(4));
let fc1_w = inline_initializer(graph, "fc1_experts_weights");
let fc1_s = inline_initializer(graph, "fc1_scales");
let fc1_b = inline_initializer(graph, "fc1_experts_bias");
let fc2_w = inline_initializer(graph, "fc2_experts_weights");
let fc2_s = inline_initializer(graph, "fc2_scales");
let fc3_w = inline_initializer(graph, "fc3_experts_weights");
let fc3_s = inline_initializer(graph, "fc3_scales");
let output = graph.create_named_value("output", DataType::Float32, shape1(4));
let mut node = onnx_runtime_ir::Node::new(
NodeId(0),
"QMoE",
vec![
Some(input),
Some(router),
Some(fc1_w),
Some(fc1_s),
Some(fc1_b),
Some(fc2_w),
Some(fc2_s),
None,
Some(fc3_w),
Some(fc3_s),
],
vec![output],
);
node.domain = "com.microsoft".to_string();
let node_id = graph.insert_node(node);
(
node_id,
vec![fc1_w, fc1_s, fc1_b, fc2_w, fc2_s, fc3_w, fc3_s],
)
}
fn block_quantized_moe_node(graph: &mut Graph) -> (NodeId, ValueId) {
let input = graph.create_named_value("input", DataType::Float32, shape1(4));
let weight = inline_initializer(graph, "experts");
let output = graph.create_named_value("output", DataType::Float32, shape1(4));
let mut node = onnx_runtime_ir::Node::new(
NodeId(0),
"BlockQuantizedMoE",
vec![Some(input), Some(weight)],
vec![output],
);
node.domain = "pkg.nxrt".to_string();
(graph.insert_node(node), weight)
}
fn always(_: NodeId) -> bool {
true
}
fn always_v(_: ValueId) -> bool {
true
}
#[test]
fn binds_single_qmoe_bank_with_all_artifacts_present() {
let mut graph = Graph::new();
let (node, members) = qmoe_node(&mut graph);
let groups = validate_route_residency_bindings(&graph, always, always_v, always_v)
.expect("bindable bank");
assert_eq!(groups.len(), 1);
let group = &groups[0];
assert_eq!(group.node, node);
assert_eq!(group.boundary, LazyWeightBoundary::QMoe);
assert_eq!(group.members, members, "exact fc1/fc2/fc3 membership bound");
}
#[test]
fn rejects_graph_with_no_expert_group() {
let mut graph = Graph::new();
let w = inline_initializer(&mut graph, "dense_weight");
let x = graph.create_named_value("x", DataType::Float32, shape1(4));
let y = graph.create_named_value("y", DataType::Float32, shape1(4));
graph.insert_node(onnx_runtime_ir::Node::new(
NodeId(0),
"MatMul",
vec![Some(x), Some(w)],
vec![y],
));
assert_eq!(
validate_route_residency_bindings(&graph, always, always_v, always_v),
Err(RouteResidencyBindingReject::NoExpertGroups)
);
}
#[test]
fn plural_binding_accepts_multiple_property_discovered_banks() {
let mut graph = Graph::new();
let (first, _) = qmoe_node(&mut graph);
let (second, _) = qmoe_node(&mut graph);
let groups = validate_route_residency_bindings(&graph, always, always_v, always_v)
.expect("plural binding");
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].node, first);
assert_eq!(groups[1].node, second);
}
#[test]
fn rejects_when_group_node_has_no_telemetry_source() {
let mut graph = Graph::new();
let (node, _) = qmoe_node(&mut graph);
let err = validate_route_residency_bindings(&graph, |_| false, always_v, always_v)
.expect_err("no source");
assert_eq!(err, RouteResidencyBindingReject::NoTelemetrySource { node });
}
#[test]
fn missing_block_quantized_moe_producer_is_terminally_unsupported() {
let mut graph = Graph::new();
let (node, _) = block_quantized_moe_node(&mut graph);
let err = validate_route_residency_bindings(&graph, |_| false, always_v, always_v)
.expect_err("BlockQuantizedMoE has no deferred producer");
assert_eq!(
err,
RouteResidencyBindingReject::TelemetryProducerUnsupported {
node,
boundary: LazyWeightBoundary::BlockQuantizedMoe,
}
);
let reason = err.reason();
assert!(
reason.contains("BlockQuantizedMoe") && reason.contains("no supported"),
"terminal reason names the unsupported boundary capability: {reason}"
);
}
#[test]
fn rejects_when_a_bank_member_has_no_catalog() {
let mut graph = Graph::new();
let (_, members) = qmoe_node(&mut graph);
let with_catalog: HashSet<ValueId> = members[1..].iter().copied().collect();
let err = validate_route_residency_bindings(
&graph,
always,
|v| with_catalog.contains(&v),
always_v,
)
.expect_err("missing catalog");
assert_eq!(
err,
RouteResidencyBindingReject::MissingCatalog { value: members[0] }
);
}
#[test]
fn rejects_when_a_bank_member_has_no_allocator() {
let mut graph = Graph::new();
let (_, members) = qmoe_node(&mut graph);
let with_alloc: HashSet<ValueId> = members[1..].iter().copied().collect();
let err = validate_route_residency_bindings(&graph, always, always_v, |v| {
with_alloc.contains(&v)
})
.expect_err("missing allocator");
assert_eq!(
err,
RouteResidencyBindingReject::MissingAllocator { value: members[0] }
);
}
#[test]
fn reject_reasons_are_non_empty_and_carry_context() {
assert!(
!RouteResidencyBindingReject::NoExpertGroups
.reason()
.is_empty()
);
let r = RouteResidencyBindingReject::NoTelemetrySource { node: NodeId(3) }.reason();
assert!(r.contains('3'), "reason carries the node identity: {r}");
}
#[test]
fn reservation_unavailable_reason_carries_typed_detail() {
let r = RouteResidencyBindingReject::Reservation(
crate::weight_paging::RouteBankReservationReject::UnalignedExpertRange {
value: ValueId(7),
expert: 0,
offset: 1,
len: 2,
granularity: 4,
},
)
.reason();
assert!(r.contains("ValueId(7)"), "reason names the bank value: {r}");
assert!(
r.contains("not aligned") && r.contains("reservation"),
"reason preserves the typed reservation failure: {r}"
);
}
}