use std::sync::Arc;
use super::in_process::{InProcessTransport, DEFAULT_WAIT_TIMEOUT};
use super::topology::{
bind_current_thread_memory, build_pool, pin_current_thread, PartitionConfig, PartitionSlot,
TopologyError,
};
use super::PLACEMENT_LOG_TARGET as LOG_TARGET;
pub struct PartitionRuntime {
slots: Vec<PartitionSlot>,
pools: Vec<rayon::ThreadPool>,
bind_memory: bool,
wait_timeout: std::time::Duration,
}
impl PartitionRuntime {
pub fn new(config: &PartitionConfig) -> Result<Arc<Self>, TopologyError> {
let slots = config.resolve()?;
let mut pools = Vec::with_capacity(slots.len());
for (rank, slot) in slots.iter().enumerate() {
pools.push(build_pool(slot, config.bind_memory, &format!("ps-part{rank}"))?);
}
#[cfg(feature = "cuda")]
let wait_timeout = if slots.iter().any(|s| s.device.is_some()) {
crate::engine::gpu::wire_timeout()
} else {
DEFAULT_WAIT_TIMEOUT
};
#[cfg(not(feature = "cuda"))]
let wait_timeout = DEFAULT_WAIT_TIMEOUT;
Ok(Arc::new(Self { slots, pools, bind_memory: config.bind_memory, wait_timeout }))
}
pub fn num_partitions(&self) -> usize {
self.slots.len()
}
#[cfg(feature = "cuda")]
pub(crate) fn slots(&self) -> &[PartitionSlot] {
&self.slots
}
pub(crate) fn partition_bits(&self) -> u8 {
debug_assert!(self.slots.len().is_power_of_two());
self.slots.len().trailing_zeros() as u8
}
pub(crate) fn placement_summary(&self) -> String {
let mut summary = String::new();
for (rank, slot) in self.slots.iter().enumerate() {
if rank > 0 {
summary.push_str(", ");
}
match (&slot.cpus, slot.device) {
(_, Some(device)) => {
summary.push_str(&format!("{rank}:gpu{device} x{}", slot.threads))
}
(Some(cpus), None) => summary.push_str(&format!("{rank}:{cpus}x{}", slot.threads)),
(None, None) => summary.push_str(&format!("{rank}:unpinned x{}", slot.threads)),
}
}
summary
}
pub(crate) fn install<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
self.pools[0].install(f)
}
pub(crate) fn map_partitions<I, O, F>(&self, items: Vec<I>, f: F) -> Vec<O>
where
I: Send,
O: Send,
F: Fn(usize, I, &InProcessTransport) -> O + Send + Sync,
{
let size = self.num_partitions();
assert_eq!(items.len(), size, "map_partitions: one item per partition");
let transports = InProcessTransport::group_with_timeout(size as u32, self.wait_timeout);
let f = &f;
let mut items = items.into_iter();
let mut transports = transports.into_iter();
let item0 = items.next().expect("at least one partition");
let transport0 = transports.next().expect("at least one transport");
std::thread::scope(|scope| {
let handles: Vec<_> = items
.zip(transports)
.enumerate()
.map(|(i, (item, transport))| {
let rank = i + 1;
let slot = &self.slots[rank];
let pool = &self.pools[rank];
let bind_memory = self.bind_memory;
scope.spawn(move || {
place_current_thread(slot, bind_memory);
pool.install(move || f(rank, item, &transport))
})
})
.collect();
let mut out = Vec::with_capacity(size);
out.push(self.pools[0].install(|| f(0, item0, &transport0)));
for handle in handles {
match handle.join() {
Ok(o) => out.push(o),
Err(payload) => std::panic::resume_unwind(payload),
}
}
out
})
}
}
fn place_current_thread(slot: &PartitionSlot, bind_memory: bool) {
#[cfg(feature = "cuda")]
if let Some(device) = slot.device {
crate::engine::cuda_context::bind_device_context(device);
}
if let Some(cpus) = &slot.cpus {
if let Err(err) = pin_current_thread(cpus) {
log::warn!(target: LOG_TARGET, "failed to pin partition driver to {cpus}: {err}");
}
}
if bind_memory {
if let Err(err) = bind_current_thread_memory(slot.node) {
log::warn!(
target: LOG_TARGET,
"failed to bind partition driver memory to {:?}: {err}",
slot.node,
);
}
}
}
#[cfg(test)]
mod tests;