use cudf::{
DeviceId, QueryMemoryDomainUsage,
runtime::{
DeviceFaultKind, DeviceFaultNativeStatus, DeviceHealthSnapshot, DeviceHealthStatus, device_health_snapshot,
},
};
use crate::{
observability::{
AttemptDeviceHealth, AttemptDeviceHealthStatus, AttemptFaultIsolation, AttemptNativeFault,
AttemptNativeFaultEvidence, AttemptNativeFaultKind, AttemptNativeStatus, AttemptTeardownHealth,
},
runtime::{attempt_domain::AttemptAllocationDomain, service_stream::StreamCleanupOutcome},
};
#[derive(Debug)]
pub(crate) struct CapacityCommitDecision {
pub(crate) disposition: CapacityCommitDisposition,
pub(crate) usage: Option<QueryMemoryDomainUsage>,
pub(crate) native_fault_evidence: Option<AttemptNativeFaultEvidence>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CapacityCommitDisposition {
HealthyReuse,
FrozenResident { resident_bytes: u64 },
DeviceQuarantine { reason: String },
BackendUnhealthy { reason: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CleanupEvidence {
Completed,
StreamSynchronizationFailed,
CallbackAbandoned,
}
impl From<StreamCleanupOutcome> for CleanupEvidence {
fn from(outcome: StreamCleanupOutcome) -> Self {
match outcome {
StreamCleanupOutcome::Completed => Self::Completed,
StreamCleanupOutcome::StreamSynchronizationFailed => Self::StreamSynchronizationFailed,
StreamCleanupOutcome::CallbackAbandoned => Self::CallbackAbandoned,
}
}
}
#[cfg(test)]
pub(crate) fn decide_capacity_disposition(
device_ordinal: usize,
domain: Option<&AttemptAllocationDomain>,
cleanup_evidence: CleanupEvidence,
terminal_usage: Option<QueryMemoryDomainUsage>,
) -> (CapacityCommitDisposition, Option<QueryMemoryDomainUsage>) {
let decision = decide_capacity_commit(device_ordinal, domain, cleanup_evidence, terminal_usage);
(decision.disposition, decision.usage)
}
pub(crate) fn decide_capacity_commit(
device_ordinal: usize,
domain: Option<&AttemptAllocationDomain>,
cleanup_evidence: CleanupEvidence,
terminal_usage: Option<QueryMemoryDomainUsage>,
) -> CapacityCommitDecision {
if domain.is_none() {
return CapacityCommitDecision {
disposition: CapacityCommitDisposition::HealthyReuse,
usage: None,
native_fault_evidence: None,
};
}
if cleanup_evidence == CleanupEvidence::CallbackAbandoned {
return CapacityCommitDecision {
disposition: CapacityCommitDisposition::BackendUnhealthy {
reason: "cleanup callback abandoned before completion; fault isolation unknown".to_string(),
},
usage: None,
native_fault_evidence: Some(unknown_isolation_native_evidence(cleanup_evidence)),
};
}
let Ok(device) = DeviceId::new(device_ordinal) else {
return CapacityCommitDecision {
disposition: CapacityCommitDisposition::BackendUnhealthy {
reason: format!("invalid device ordinal {device_ordinal} at release commit"),
},
usage: None,
native_fault_evidence: Some(unknown_isolation_native_evidence(cleanup_evidence)),
};
};
let health_before_sample = device_health_snapshot(device);
if health_before_sample.status() == DeviceHealthStatus::Unhealthy {
return device_quarantine_decision(device_ordinal, cleanup_evidence, health_before_sample, None);
}
if cleanup_evidence == CleanupEvidence::StreamSynchronizationFailed {
return CapacityCommitDecision {
disposition: CapacityCommitDisposition::DeviceQuarantine {
reason: format!("stream synchronization failed during cleanup on device {device_ordinal}"),
},
usage: None,
native_fault_evidence: Some(device_local_native_evidence(cleanup_evidence, health_before_sample)),
};
}
let usage = match terminal_usage.map_or_else(|| domain.expect("domain presence checked above").usage(), Ok) {
Ok(usage) => usage,
Err(error) => {
let health_after_sample = device_health_snapshot(device);
if health_after_sample.status() == DeviceHealthStatus::Unhealthy {
return device_quarantine_decision(device_ordinal, cleanup_evidence, health_after_sample, None);
} else {
return CapacityCommitDecision {
disposition: CapacityCommitDisposition::BackendUnhealthy {
reason: format!("attempt domain usage sample failed after cleanup: {error}"),
},
usage: None,
native_fault_evidence: Some(unknown_isolation_native_evidence(cleanup_evidence)),
};
}
},
};
let health_after_sample = device_health_snapshot(device);
if health_after_sample.status() == DeviceHealthStatus::Unhealthy {
return device_quarantine_decision(device_ordinal, cleanup_evidence, health_after_sample, Some(usage));
}
if usage.active_members != 0
|| usage.current_allocations != 0
|| usage.current_bytes != 0
|| usage.current_aligned_bytes != 0
{
return CapacityCommitDecision {
disposition: CapacityCommitDisposition::DeviceQuarantine {
reason: format!(
"residual attempt domain usage after terminal cleanup on device {device_ordinal} \
(domain_id={}, active_members={}, current_allocations={}, current_bytes={}, \
current_aligned_bytes={})",
usage.domain_id,
usage.active_members,
usage.current_allocations,
usage.current_bytes,
usage.current_aligned_bytes
),
},
usage: Some(usage),
native_fault_evidence: Some(device_local_native_evidence(cleanup_evidence, health_after_sample)),
};
}
CapacityCommitDecision {
disposition: CapacityCommitDisposition::HealthyReuse,
usage: Some(usage),
native_fault_evidence: Some(device_local_native_evidence(cleanup_evidence, health_after_sample)),
}
}
fn device_quarantine_decision(
device_ordinal: usize,
cleanup_evidence: CleanupEvidence,
health: DeviceHealthSnapshot,
usage: Option<QueryMemoryDomainUsage>,
) -> CapacityCommitDecision {
CapacityCommitDecision {
disposition: CapacityCommitDisposition::DeviceQuarantine {
reason: format!(
"persistent CUDA fault on device {device_ordinal} (fault_count={}, first_fault={:?})",
health.fault_count(),
health.first_fault()
),
},
usage,
native_fault_evidence: Some(device_local_native_evidence(cleanup_evidence, health)),
}
}
fn teardown_health(cleanup_evidence: CleanupEvidence) -> AttemptTeardownHealth {
match cleanup_evidence {
CleanupEvidence::Completed => AttemptTeardownHealth::Completed,
CleanupEvidence::StreamSynchronizationFailed => AttemptTeardownHealth::StreamSynchronizationFailed,
CleanupEvidence::CallbackAbandoned => AttemptTeardownHealth::CallbackAbandoned,
}
}
fn unknown_isolation_native_evidence(cleanup_evidence: CleanupEvidence) -> AttemptNativeFaultEvidence {
AttemptNativeFaultEvidence {
isolation: AttemptFaultIsolation::Unknown,
teardown_health: teardown_health(cleanup_evidence),
device_health: None,
first_fault: None,
}
}
fn device_local_native_evidence(
cleanup_evidence: CleanupEvidence,
health: DeviceHealthSnapshot,
) -> AttemptNativeFaultEvidence {
let first_fault = health.first_fault().map(|fault| AttemptNativeFault {
kind: match fault.kind() {
DeviceFaultKind::CurrentDeviceQueryFailed => AttemptNativeFaultKind::CurrentDeviceQueryFailed,
DeviceFaultKind::OwnerDeviceEntryFailed => AttemptNativeFaultKind::OwnerDeviceEntryFailed,
DeviceFaultKind::AmbientDeviceRestoreFailed => AttemptNativeFaultKind::AmbientDeviceRestoreFailed,
DeviceFaultKind::FatalDeviceOperation => AttemptNativeFaultKind::FatalDeviceOperation,
DeviceFaultKind::TeardownQuarantined => AttemptNativeFaultKind::TeardownQuarantined,
_ => AttemptNativeFaultKind::Unknown,
},
status: fault.native_status().and_then(|status| match status {
DeviceFaultNativeStatus::Cudf(status) => Some(AttemptNativeStatus::Cudf(status)),
DeviceFaultNativeStatus::Cuda(status) => Some(AttemptNativeStatus::Cuda(status)),
_ => None,
}),
});
AttemptNativeFaultEvidence {
isolation: AttemptFaultIsolation::DeviceLocal,
teardown_health: teardown_health(cleanup_evidence),
device_health: Some(AttemptDeviceHealth {
status: match health.status() {
DeviceHealthStatus::Healthy => AttemptDeviceHealthStatus::Healthy,
DeviceHealthStatus::Unhealthy => AttemptDeviceHealthStatus::Unhealthy,
},
fault_count: health.fault_count(),
rejected_operation_count: health.rejected_operation_count(),
quarantined_teardown_count: health.quarantined_teardown_count(),
}),
first_fault,
}
}