#[cfg(test)]
use std::sync::{Mutex, MutexGuard};
use std::time::Duration;
use datafusion::execution::{SessionState, SessionStateBuilder};
use query_planning::error::QueryError;
#[cfg(all(test, feature = "cugraph"))]
use query_planning::error::Result as QueryEngineResult;
#[cfg(feature = "qualification")]
pub use query_runtime::runtime::DeviceQuarantineInjection;
#[cfg(all(test, feature = "cugraph"))]
use query_runtime::runtime::QueryHandle;
pub use query_runtime::runtime::{
DeviceCapabilities, GpuDeviceCapacityProbe, GpuDeviceMemoryLimit, GpuMemoryOwnership,
};
use query_runtime::runtime::{
GpuBackendConfig, GpuBackendCore, GpuBackendSession, QueryExecutionConfig, QueueBounds, ValidatedGpuBackendConfig,
probe_device_capacity,
};
pub use query_runtime::{
runtime::GpuBackendDeviceConfig as AlgeonGpuDeviceConfig,
runtime::GpuBackendDeviceInstallationBounds as AlgeonGpuDeviceInstallationBounds,
runtime::GpuBackendDeviceProfile as AlgeonGpuDeviceProfile,
};
#[cfg(feature = "cugraph")]
use crate::cugraph_sql::GraphBfsSingleflightSnapshot;
use crate::{
build_info::algeon_version_table_function,
error::{Error, Result},
native_exec::{AlgeonNativeExecutionConfig, QueryAdmissionSource, SourceChunking},
observability::{
BackendDiagnostics, BackendMetricsSnapshot, DeviceTelemetrySnapshot, GpuBackendCloseSnapshot,
GpuBackendDeviceStartupRecord, KvikioProcessConfig,
},
planner::{AlgeonGpuExecutionMode, AlgeonNativeOptimizerConfig},
s3_storage::install_s3_storage,
session::{AlgeonSessionStateBuilderExt, installed_algeon_native_optimizer_config},
};
#[derive(Debug, Clone)]
pub struct AlgeonGpuBackend {
core: GpuBackendCore,
session: GpuBackendSession,
optimizer_config: AlgeonNativeOptimizerConfig,
devices: Vec<AlgeonGpuDeviceConfig>,
queue_policy: AlgeonGpuQueuePolicy,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct AlgeonGpuQueuePolicy {
max_queued_attempts: usize,
max_queue_wait: Duration,
}
impl AlgeonGpuQueuePolicy {
pub fn max_queued_attempts(&self) -> usize {
self.max_queued_attempts
}
pub fn max_queue_wait(&self) -> Duration {
self.max_queue_wait
}
pub fn with_max_queued_attempts(mut self, max_queued_attempts: usize) -> Self {
self.max_queued_attempts = max_queued_attempts;
self
}
pub fn with_max_queue_wait(mut self, max_queue_wait: Duration) -> Self {
self.max_queue_wait = max_queue_wait;
self
}
}
impl Default for AlgeonGpuQueuePolicy {
fn default() -> Self {
let bounds = QueueBounds::default();
Self { max_queued_attempts: bounds.max_waiters, max_queue_wait: bounds.max_wait }
}
}
#[derive(Debug, Clone, Default)]
pub struct AlgeonGpuBackendBuilder {
optimizer_config: AlgeonNativeOptimizerConfig,
devices: Vec<AlgeonGpuDeviceConfig>,
queue_policy: AlgeonGpuQueuePolicy,
attempt_completion_queue_capacity: Option<usize>,
}
impl AlgeonGpuBackend {
pub fn builder() -> AlgeonGpuBackendBuilder {
AlgeonGpuBackendBuilder::default()
}
pub fn install_on(&self, builder: SessionStateBuilder) -> Result<SessionStateBuilder> {
install_backend_optimizer_config(builder, &self.optimizer_config)
}
#[cfg(feature = "qualification")]
pub fn quarantine_device_for_qualification(&self, device_ordinal: usize) -> Result<DeviceQuarantineInjection> {
self.core.fault_injector().quarantine_device(device_ordinal).map_err(|error| {
Error::from_native(
"algeon_datafusion.backend.quarantine_device_for_qualification",
"controlled device quarantine failed",
error,
)
})
}
pub fn device_startup_records(&self) -> &[GpuBackendDeviceStartupRecord] {
self.core.device_startup_records()
}
pub fn kvikio_process_config(&self) -> Option<KvikioProcessConfig> {
self.core.kvikio_process_config()
}
pub fn probe_device_capacity(
device_ordinal: usize,
memory_ownership: GpuMemoryOwnership,
backend_reserve_bytes: u64,
) -> Result<GpuDeviceCapacityProbe> {
probe_device_capacity(device_ordinal, memory_ownership, backend_reserve_bytes).map_err(|error| {
Error::from_native_with_blocking_diagnostics(
"algeon_datafusion.backend.probe_device_capacity",
"failed to probe declarable GPU device capacity",
error,
)
})
}
pub fn close(&self) -> bool {
self.core.close()
}
pub fn close_snapshot(&self) -> GpuBackendCloseSnapshot {
self.core.close_snapshot()
}
pub fn close_and_wait(&self, timeout: Duration) -> Result<GpuBackendCloseSnapshot> {
self.core.close_and_wait(timeout).map_err(|error| {
Error::from_native("algeon_datafusion.backend.close", "failed to close GPU backend before timeout", error)
})
}
pub async fn close_and_wait_async(&self, timeout: Duration) -> Result<GpuBackendCloseSnapshot> {
self.core.close_and_wait_async(timeout).await.map_err(|error| {
Error::from_native("algeon_datafusion.backend.close", "failed to close GPU backend before timeout", error)
})
}
pub fn close_and_wait_until(&self, deadline: std::time::Instant) -> Result<GpuBackendCloseSnapshot> {
self.core.close_and_wait_until(deadline).map_err(|error| {
Error::from_native("algeon_datafusion.backend.close", "failed to close GPU backend before deadline", error)
})
}
pub fn backend_metrics_snapshot(&self) -> BackendMetricsSnapshot {
self.session.backend_metrics_snapshot()
}
pub fn diagnostics(&self) -> Result<BackendDiagnostics> {
self.session.diagnostics().map_err(|error| {
Error::from_native("algeon_datafusion.backend.diagnostics", "failed to read backend diagnostics", error)
})
}
pub fn queue_policy(&self) -> &AlgeonGpuQueuePolicy {
&self.queue_policy
}
pub fn device_telemetry_snapshots(&self) -> Result<Vec<DeviceTelemetrySnapshot>> {
self.session.device_telemetry_snapshots().map_err(|error| {
Error::from_native(
"algeon_datafusion.backend.device_telemetry_snapshots",
"failed to read device telemetry snapshots",
error,
)
})
}
pub fn same_backend(&self, other: &Self) -> bool {
self.core.identity() == other.core.identity()
}
pub(crate) fn session_capability(&self) -> &GpuBackendSession {
&self.session
}
pub fn is_session_state_bound(&self, state: &SessionState) -> bool {
installed_algeon_native_optimizer_config(state)
.is_some_and(|optimizer_config| self.is_execution_config_bound(optimizer_config.native_execution_config()))
}
pub(crate) fn is_execution_config_bound(&self, config: &AlgeonNativeExecutionConfig) -> bool {
config.query_admission_source_matches(&self.session)
}
pub fn optimizer_config(&self) -> &AlgeonNativeOptimizerConfig {
&self.optimizer_config
}
#[cfg(feature = "cugraph")]
pub fn cugraph_bfs_singleflight_snapshot(&self) -> GraphBfsSingleflightSnapshot {
self.optimizer_config.native_execution_config().bfs_singleflight().snapshot()
}
pub fn devices(&self) -> &[AlgeonGpuDeviceConfig] {
&self.devices
}
fn build_bounded(
optimizer_config: AlgeonNativeOptimizerConfig,
configured_devices: Vec<AlgeonGpuDeviceConfig>,
queue_policy: AlgeonGpuQueuePolicy,
attempt_completion_queue_capacity: Option<usize>,
) -> Result<Self> {
let validated = validated_backend_construction(
optimizer_config,
configured_devices,
queue_policy,
attempt_completion_queue_capacity,
)?;
let devices = validated.core_config.devices().cloned().collect();
let core = GpuBackendCore::new(validated.core_config).map_err(|error| {
backend_core_build_error("failed to initialize ownership-enforcing GPU backend core", error)
})?;
let session = core.session_capability();
let max_device_total_memory_bytes = core
.device_startup_records()
.iter()
.map(|record| record.facts.total_bytes)
.max()
.expect("a published backend has a startup record for each validated device");
let execution_config = validated
.optimizer_config
.native_execution_config()
.clone()
.with_query_admission_source(QueryAdmissionSource::new(session.clone(), max_device_total_memory_bytes));
let optimizer_config = validated.optimizer_config.with_native_execution_config(execution_config);
Ok(AlgeonGpuBackend { core, session, optimizer_config, devices, queue_policy: validated.queue_policy })
}
}
fn backend_core_build_error(message: &'static str, source: QueryError) -> Error {
Error::from_native_with_blocking_diagnostics("algeon_datafusion.backend.gpu_core", message, source)
}
pub(crate) fn install_backend_optimizer_config(
builder: SessionStateBuilder,
optimizer_config: &AlgeonNativeOptimizerConfig,
) -> Result<SessionStateBuilder> {
let builder = match optimizer_config.native_execution_config().s3_storage() {
Some(storage) => install_s3_storage(builder, storage)?,
None => builder,
};
let mut builder = builder.with_algeon_native(optimizer_config.clone());
let version = algeon_version_table_function(optimizer_config.execution_mode());
builder.table_functions().get_or_insert_with(Default::default).insert(version.name().to_string(), version);
Ok(builder)
}
#[derive(Debug)]
struct ValidatedBackendConstruction {
optimizer_config: AlgeonNativeOptimizerConfig,
queue_policy: AlgeonGpuQueuePolicy,
core_config: ValidatedGpuBackendConfig,
}
fn validated_backend_construction(
optimizer_config: AlgeonNativeOptimizerConfig,
configured_devices: Vec<AlgeonGpuDeviceConfig>,
queue_policy: AlgeonGpuQueuePolicy,
attempt_completion_queue_capacity: Option<usize>,
) -> Result<ValidatedBackendConstruction> {
let service_config = backend_query_execution_config(optimizer_config.native_execution_config());
let mut core_config =
GpuBackendConfig::new(configured_devices, service_config, backend_queue_bounds(&queue_policy));
if let Some(capacity) = attempt_completion_queue_capacity {
core_config = core_config.with_attempt_completion_queue_capacity(capacity);
}
let core_config = core_config.validate().map_err(|error| {
backend_core_build_error("invalid ownership-enforcing GPU backend core configuration", error)
})?;
Ok(ValidatedBackendConstruction { optimizer_config, queue_policy, core_config })
}
#[cfg(test)]
pub(crate) struct InstalledTestGpuBackend {
backend: Option<AlgeonGpuBackend>,
_serial: MutexGuard<'static, ()>,
}
#[cfg(test)]
impl InstalledTestGpuBackend {
pub(crate) fn new() -> Self {
Self::with_config(
AlgeonNativeExecutionConfig::new(
SourceChunking::try_new(64 * 1024 * 1024, 2, 3, 1).expect("valid source chunking"),
),
AlgeonGpuDeviceProfile::new(0)
.with_memory_limit(GpuDeviceMemoryLimit::Explicit { limit_bytes: 256 * 1024 * 1024 }),
AlgeonGpuQueuePolicy::default(),
)
}
pub(crate) fn with_config(
native_execution_config: AlgeonNativeExecutionConfig,
device_profile: AlgeonGpuDeviceProfile,
queue_policy: AlgeonGpuQueuePolicy,
) -> Self {
Self::with_config_and_execution_mode(
native_execution_config,
device_profile,
queue_policy,
AlgeonGpuExecutionMode::NativePreferred,
)
}
pub(crate) fn with_config_and_execution_mode(
native_execution_config: AlgeonNativeExecutionConfig,
device_profile: AlgeonGpuDeviceProfile,
queue_policy: AlgeonGpuQueuePolicy,
execution_mode: AlgeonGpuExecutionMode,
) -> Self {
static SERIAL: Mutex<()> = Mutex::new(());
let serial = SERIAL.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let device = AlgeonGpuDeviceConfig::new(device_profile, GpuMemoryOwnership::WholeDeviceExclusive);
let backend = AlgeonGpuBackend::builder()
.with_native_execution_config(native_execution_config)
.with_execution_mode(execution_mode)
.devices([device])
.queue_policy(queue_policy)
.with_attempt_completion_queue_capacity(64)
.build()
.expect("test backend configuration must be valid");
Self { backend: Some(backend), _serial: serial }
}
pub(crate) fn backend(&self) -> &AlgeonGpuBackend {
self.backend.as_ref().expect("test backend remains live until fixture drop")
}
pub(crate) fn optimizer_config(&self) -> &AlgeonNativeOptimizerConfig {
self.backend().optimizer_config()
}
#[cfg(feature = "cugraph")]
pub(crate) fn admit_query(&self) -> QueryEngineResult<QueryHandle> {
self.backend().session_capability().admit_query()
}
pub(crate) fn install_on(&self, builder: SessionStateBuilder) -> Result<SessionStateBuilder> {
self.backend().install_on(builder)
}
}
#[cfg(test)]
impl Default for InstalledTestGpuBackend {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl Drop for InstalledTestGpuBackend {
fn drop(&mut self) {
let Some(backend) = self.backend.take() else {
return;
};
let close = backend.close_and_wait(Duration::from_secs(10));
drop(backend);
if let Err(error) = close
&& !std::thread::panicking()
{
panic!("root GPU unit test backend failed to close: {error}");
}
}
}
impl AlgeonGpuBackendBuilder {
pub fn with_optimizer_config(mut self, optimizer_config: AlgeonNativeOptimizerConfig) -> Self {
self.optimizer_config = optimizer_config;
self
}
pub fn with_attempt_completion_queue_capacity(mut self, capacity: usize) -> Self {
self.attempt_completion_queue_capacity = Some(capacity);
self
}
pub fn with_execution_mode(mut self, mode: AlgeonGpuExecutionMode) -> Self {
self.optimizer_config = self.optimizer_config.with_execution_mode(mode);
self
}
pub fn with_native_execution_config(mut self, native_execution_config: AlgeonNativeExecutionConfig) -> Self {
self.optimizer_config = self.optimizer_config.with_native_execution_config(native_execution_config);
self
}
pub fn devices(mut self, devices: impl IntoIterator<Item = AlgeonGpuDeviceConfig>) -> Self {
self.devices = devices.into_iter().collect();
self
}
pub fn queue_policy(mut self, queue_policy: AlgeonGpuQueuePolicy) -> Self {
self.queue_policy = queue_policy;
self
}
pub fn build(self) -> Result<AlgeonGpuBackend> {
AlgeonGpuBackend::build_bounded(
self.optimizer_config,
self.devices,
self.queue_policy,
self.attempt_completion_queue_capacity,
)
}
pub fn validate(self) -> Result<()> {
validated_backend_construction(
self.optimizer_config,
self.devices,
self.queue_policy,
self.attempt_completion_queue_capacity,
)?;
Ok(())
}
}
fn backend_query_execution_config(execution_config: &AlgeonNativeExecutionConfig) -> QueryExecutionConfig {
let SourceChunking {
max_source_chunk_bytes,
max_row_groups_per_chunk,
max_retry_attempts,
min_retry_chunk_row_groups,
} = execution_config.source_chunking();
QueryExecutionConfig {
lakehouse_cache_fill: execution_config.lakehouse_cache_fill_config().clone(),
source_storage_profiles: execution_config.source_storage_profiles().to_vec(),
source_scheduler_policy: execution_config.source_scheduler_policy(),
source_chunk_planning_mode: execution_config.source_chunk_planning_mode(),
statistical_aggregate_execution_mode: execution_config.statistical_aggregate_execution_mode(),
planning_policy: execution_config.planning_policy(),
aggregate_strategy_execution_options: execution_config.aggregate_strategy_execution_options(),
source_chunk_planning_options: execution_config.source_chunk_planning_options(),
max_source_chunk_bytes: max_source_chunk_bytes.get(),
max_row_groups_per_chunk: max_row_groups_per_chunk.get(),
max_retry_attempts: max_retry_attempts.get(),
min_retry_chunk_row_groups: min_retry_chunk_row_groups.get(),
}
}
fn backend_queue_bounds(queue_policy: &AlgeonGpuQueuePolicy) -> QueueBounds {
QueueBounds { max_waiters: queue_policy.max_queued_attempts, max_wait: queue_policy.max_queue_wait }
}
#[cfg(test)]
mod tests;