use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::thread::{self, JoinHandle};
#[cfg(not(miri))]
const SPIN_BEFORE_PARK: u32 = 100_000;
#[cfg(miri)]
const SPIN_BEFORE_PARK: u32 = 4;
#[cfg(not(miri))]
const DRAIN_SPIN_BEFORE_YIELD: u32 = 10_000;
#[cfg(miri)]
const DRAIN_SPIN_BEFORE_YIELD: u32 = 4;
const PIN_RETRY_BACKOFF: u32 = 1024;
#[derive(Clone, Copy)]
struct Job {
y_ptr: *mut f32,
n: usize,
chunk_rows: usize,
total_rows: usize,
active: usize,
closure: *const (),
run: unsafe fn(closure: *const (), y_ptr: *mut f32, n: usize, start: usize, end: usize),
}
unsafe fn trampoline<F: Fn(usize, &mut [f32]) + Sync>(
closure: *const (),
y_ptr: *mut f32,
n: usize,
start: usize,
end: usize,
) {
let f = unsafe { &*(closure as *const F) };
for row in start..end {
let slice = unsafe { std::slice::from_raw_parts_mut(y_ptr.add(row * n), n) };
f(row, slice);
}
}
struct Shared {
active_barrier: bool,
state: AtomicU64,
next_chunk: AtomicUsize,
pending: AtomicUsize,
shutdown: AtomicBool,
panicked: AtomicBool,
panic_payload: Mutex<Option<Box<dyn std::any::Any + Send>>>,
job: UnsafeCell<Option<Job>>,
}
const STEAL_CHUNKS_PER_WORKER: usize = 4;
const MIN_CHUNK_ROWS: usize = 16;
const GEMM_WORK_PER_WORKER_DEFAULT: usize = 48_000_000;
fn gemm_work_per_worker() -> usize {
static Q: OnceLock<usize> = OnceLock::new();
*Q.get_or_init(|| {
super::cpu_features::env_usize("CERA_GEMM_WORK_PER_WORKER")
.unwrap_or(GEMM_WORK_PER_WORKER_DEFAULT)
})
}
const ACTIVE_BITS: u64 = 16;
const ACTIVE_MASK: u64 = (1 << ACTIVE_BITS) - 1;
#[inline]
fn pack_state(epoch: u64, active: usize) -> u64 {
debug_assert!(
active as u64 <= ACTIVE_MASK,
"active {active} exceeds ACTIVE_MASK ({ACTIVE_MASK})"
);
(epoch << ACTIVE_BITS) | (active as u64 & ACTIVE_MASK)
}
#[inline]
fn state_epoch(state: u64) -> u64 {
state >> ACTIVE_BITS
}
#[inline]
fn state_active(state: u64) -> usize {
(state & ACTIVE_MASK) as usize
}
#[inline]
fn steal_and_run(shared: &Shared, job: &Job) {
loop {
let chunk = shared.next_chunk.fetch_add(1, Ordering::Relaxed);
let start = chunk * job.chunk_rows;
if start >= job.total_rows {
break;
}
let end = (start + job.chunk_rows).min(job.total_rows);
unsafe { (job.run)(job.closure, job.y_ptr, job.n, start, end) };
}
}
unsafe impl Sync for Shared {}
unsafe impl Send for Shared {}
pub struct RowPool {
shared: Arc<Shared>,
workers: Vec<JoinHandle<()>>,
dispatch_lock: Mutex<()>,
caller_pin: Option<usize>,
num_threads: usize,
}
pub(crate) fn pinning_enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| !super::cpu_features::env_disabled("CERA_PIN"))
}
impl RowPool {
pub fn prefill() -> &'static RowPool {
static POOL: OnceLock<RowPool> = OnceLock::new();
POOL.get_or_init(|| {
let topo = super::cpu_features::core_topology();
RowPool::build(topo.perf_core_count, &topo.pin_cores, true)
})
}
pub fn decode() -> &'static RowPool {
static POOL: OnceLock<RowPool> = OnceLock::new();
POOL.get_or_init(|| {
let topo = super::cpu_features::core_topology();
let n = super::calibrate::decode_thread_count(topo);
RowPool::build(n, &topo.pin_cores, false)
})
}
fn build(num_threads: usize, pin_cores: &[usize], active_barrier: bool) -> RowPool {
let num_threads = num_threads.max(1).min(ACTIVE_MASK as usize);
let spin_limit = std::env::var("CERA_SPIN")
.ok()
.and_then(|v| v.trim().parse::<u32>().ok())
.unwrap_or(SPIN_BEFORE_PARK);
let shared = Arc::new(Shared {
active_barrier,
state: AtomicU64::new(0),
next_chunk: AtomicUsize::new(0),
pending: AtomicUsize::new(0),
shutdown: AtomicBool::new(false),
panicked: AtomicBool::new(false),
panic_payload: Mutex::new(None),
job: UnsafeCell::new(None),
});
let pin_cores: &[usize] = if pinning_enabled() { pin_cores } else { &[] };
let mut workers = Vec::new();
for id in 1..num_threads {
let shared = Arc::clone(&shared);
let pin = pin_cores.get(id).copied();
match thread::Builder::new()
.name(format!("cera-rowpool-{id}"))
.spawn(move || worker_loop(shared, id, pin, spin_limit))
{
Ok(handle) => workers.push(handle),
Err(_) => break,
}
}
let num_threads = 1 + workers.len();
let caller_pin = pin_cores.first().copied();
RowPool {
shared,
workers,
dispatch_lock: Mutex::new(()),
caller_pin,
num_threads,
}
}
pub fn num_threads(&self) -> usize {
self.num_threads
}
fn pin_caller_once(&self) {
static CALLER_PIN_CLAIMED: AtomicBool = AtomicBool::new(false);
struct ClaimGuard;
impl Drop for ClaimGuard {
fn drop(&mut self) {
CALLER_PIN_CLAIMED.store(false, Ordering::Release);
}
}
struct CallerClaim {
guard: Option<ClaimGuard>,
retry_cooldown: u32,
}
thread_local! {
static CLAIM: std::cell::RefCell<CallerClaim> = const {
std::cell::RefCell::new(CallerClaim {
guard: None,
retry_cooldown: 0,
})
};
}
let Some(core) = self.caller_pin else {
return;
};
CLAIM.with(|c| {
let mut claim = c.borrow_mut();
if claim.retry_cooldown > 0 {
claim.retry_cooldown -= 1;
return;
}
if claim.guard.is_some() {
let _ = pin_current_thread_to_core(core);
claim.retry_cooldown = PIN_RETRY_BACKOFF;
return;
}
if !CALLER_PIN_CLAIMED.load(Ordering::Relaxed)
&& CALLER_PIN_CLAIMED
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
if pin_current_thread_to_core(core) {
claim.guard = Some(ClaimGuard);
claim.retry_cooldown = PIN_RETRY_BACKOFF;
} else {
CALLER_PIN_CLAIMED.store(false, Ordering::Release);
claim.retry_cooldown = PIN_RETRY_BACKOFF;
}
}
});
}
pub fn dispatch_rows<F>(&self, y: &mut [f32], n: usize, min_rows: usize, f: F)
where
F: Fn(usize, &mut [f32]) + Sync,
{
self.dispatch_rows_chunked(y, n, min_rows, MIN_CHUNK_ROWS, f);
}
pub fn dispatch_rows_chunked<F>(
&self,
y: &mut [f32],
n: usize,
min_rows: usize,
min_chunk_rows: usize,
f: F,
) where
F: Fn(usize, &mut [f32]) + Sync,
{
self.dispatch_inner(y, n, min_rows, min_chunk_rows, 0, f);
}
pub fn dispatch_rows_work<F>(
&self,
y: &mut [f32],
n: usize,
min_rows: usize,
depth: usize,
f: F,
) where
F: Fn(usize, &mut [f32]) + Sync,
{
self.dispatch_inner(y, n, min_rows, MIN_CHUNK_ROWS, depth, f);
}
fn dispatch_inner<F>(
&self,
y: &mut [f32],
n: usize,
min_rows: usize,
min_chunk_rows: usize,
depth: usize,
f: F,
) where
F: Fn(usize, &mut [f32]) + Sync,
{
debug_assert!(n >= 1, "dispatch_inner: n must be ≥ 1");
if n == 0 || y.is_empty() {
return;
}
self.pin_caller_once();
let total_rows = y.len() / n;
let (body, tail) = y.split_at_mut(total_rows * n);
self.dispatch_body(body, n, total_rows, min_rows, min_chunk_rows, depth, &f);
if !tail.is_empty() {
f(total_rows, tail);
}
}
#[allow(clippy::too_many_arguments)] fn dispatch_body<F>(
&self,
y: &mut [f32],
n: usize,
total_rows: usize,
min_rows: usize,
min_chunk_rows: usize,
depth: usize,
f: &F,
) where
F: Fn(usize, &mut [f32]) + Sync,
{
if total_rows == 0 {
return;
}
let min_rows = min_rows.max(1);
let rows_per_worker = total_rows.div_ceil(self.num_threads).max(min_rows);
let mut active = total_rows.div_ceil(rows_per_worker).min(self.num_threads);
if depth != 0 {
let total_macs = total_rows.saturating_mul(n).saturating_mul(depth);
let work_cap = (total_macs / gemm_work_per_worker()).clamp(1, self.num_threads);
active = active.min(work_cap);
}
let guard = if active > 1 {
match self.dispatch_lock.try_lock() {
Ok(g) => Some(g),
Err(std::sync::TryLockError::Poisoned(p)) => Some(p.into_inner()),
Err(std::sync::TryLockError::WouldBlock) => None,
}
} else {
None
};
let Some(_guard) = guard else {
for row in 0..total_rows {
f(row, &mut y[row * n..row * n + n]);
}
return;
};
let chunk_rows = total_rows
.div_ceil(active * STEAL_CHUNKS_PER_WORKER)
.max(min_chunk_rows.max(1));
let y_ptr = y.as_mut_ptr();
let closure_ptr = (f as *const F).cast::<()>();
let job = Job {
y_ptr,
n,
chunk_rows,
total_rows,
active,
closure: closure_ptr,
run: trampoline::<F>,
};
if self.shared.panicked.load(Ordering::Relaxed) {
drop(self.take_panic_payload());
}
unsafe {
*self.shared.job.get() = Some(job);
}
self.shared.next_chunk.store(0, Ordering::Relaxed);
if self.shared.active_barrier {
self.shared.pending.store(active - 1, Ordering::Release);
let next_epoch = state_epoch(self.shared.state.load(Ordering::Relaxed)) + 1;
self.shared
.state
.store(pack_state(next_epoch, active), Ordering::Release);
for h in self.workers.iter().take(active - 1) {
h.thread().unpark();
}
} else {
self.shared
.pending
.store(self.num_threads - 1, Ordering::Release);
self.shared.state.fetch_add(1, Ordering::Release);
for h in &self.workers {
h.thread().unpark();
}
}
{
let _drain = DrainGuard {
shared: &self.shared,
};
steal_and_run(&self.shared, &job);
}
if self.shared.panicked.load(Ordering::Relaxed) {
match self.take_panic_payload() {
Some(payload) => std::panic::resume_unwind(payload),
None => panic!("cera RowPool: a row closure panicked on a worker thread"),
}
}
}
fn take_panic_payload(&self) -> Option<Box<dyn std::any::Any + Send>> {
self.shared.panicked.store(false, Ordering::Relaxed);
self.shared
.panic_payload
.lock()
.unwrap_or_else(|p| p.into_inner())
.take()
}
}
struct DrainGuard<'a> {
shared: &'a Shared,
}
impl Drop for DrainGuard<'_> {
fn drop(&mut self) {
let mut spins = 0u32;
while self.shared.pending.load(Ordering::Acquire) != 0 {
spins = spins.saturating_add(1);
if spins < DRAIN_SPIN_BEFORE_YIELD {
std::hint::spin_loop();
} else {
thread::yield_now();
}
}
}
}
impl Drop for RowPool {
fn drop(&mut self) {
self.shared.shutdown.store(true, Ordering::Release);
self.shared
.state
.fetch_add(1 << ACTIVE_BITS, Ordering::Release);
for h in &self.workers {
h.thread().unpark();
}
for handle in self.workers.drain(..) {
let _ = handle.join();
}
}
}
fn worker_loop(shared: Arc<Shared>, worker_id: usize, pin_core: Option<usize>, spin_limit: u32) {
if let Some(core) = pin_core {
let _ = pin_current_thread_to_core(core);
}
let run_job = |shared: &Shared, job: &Job| {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
steal_and_run(shared, job);
}));
if let Err(payload) = result {
let mut slot = shared
.panic_payload
.lock()
.unwrap_or_else(|p| p.into_inner());
if slot.is_none() {
*slot = Some(payload);
drop(slot);
} else {
drop(slot);
std::mem::forget(payload);
}
shared.panicked.store(true, Ordering::Release);
}
};
let mut last_state = 0u64;
loop {
let mut spins = 0u32;
let mut parked = false;
let state;
loop {
if shared.shutdown.load(Ordering::Acquire) {
return;
}
let st = shared.state.load(Ordering::Acquire);
if st != last_state {
last_state = st;
state = st;
break;
}
spins = spins.saturating_add(1);
if spins < spin_limit {
std::hint::spin_loop();
} else {
thread::park();
parked = true;
}
}
if shared.shutdown.load(Ordering::Acquire) {
return;
}
if parked && let Some(core) = pin_core {
let _ = pin_current_thread_to_core(core);
}
if shared.active_barrier {
if worker_id >= state_active(state) {
continue;
}
if let Some(job) = unsafe { *shared.job.get() } {
run_job(&shared, &job);
}
shared.pending.fetch_sub(1, Ordering::Release);
} else {
let job = match unsafe { *shared.job.get() } {
Some(job) => job,
None => continue,
};
if worker_id < job.active {
run_job(&shared, &job);
}
shared.pending.fetch_sub(1, Ordering::Release);
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn pin_current_thread_to_core(core: usize) -> bool {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_SET(core, &mut set);
libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &set) == 0
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub(crate) fn pin_current_thread_to_core(_core: usize) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dispatch_matches_serial_across_shapes_and_threads() {
for &active_barrier in &[true, false] {
for &num_threads in &[1usize, 2, 4, 7] {
let pool = RowPool::build(num_threads, &[], active_barrier);
for &n in &[1usize, 3, 8] {
for &total_rows in &[0usize, 1, 5, 256, 1000] {
let len = total_rows * n;
let mut got = vec![0.0f32; len];
let mut want = vec![0.0f32; len];
let fill = |row: usize, slice: &mut [f32]| {
for (k, v) in slice.iter_mut().enumerate() {
*v = (row * 100 + k) as f32;
}
};
pool.dispatch_rows(&mut got, n, 64, fill);
for row in 0..total_rows {
fill(row, &mut want[row * n..row * n + n]);
}
assert_eq!(
got, want,
"mismatch: barrier={active_barrier} threads={num_threads} n={n} rows={total_rows}"
);
}
}
}
}
}
#[test]
fn every_row_written_exactly_once() {
let pool = RowPool::build(4, &[], true);
let total_rows = 10_000usize;
let mut counts = vec![0.0f32; total_rows];
pool.dispatch_rows(&mut counts, 1, 1, |_row, slice| {
slice[0] += 1.0;
});
assert!(counts.iter().all(|&c| c == 1.0));
}
#[test]
fn repeated_dispatches_reuse_workers() {
let pool = RowPool::build(4, &[], true);
let mut y = vec![0.0f32; 2048];
for iter in 0..50 {
pool.dispatch_rows(&mut y, 1, 1, |row, slice| {
slice[0] = (row + iter) as f32;
});
for (row, &v) in y.iter().enumerate() {
assert_eq!(v, (row + iter) as f32);
}
}
}
#[test]
fn varying_active_across_dispatches_is_correct() {
for &active_barrier in &[true, false] {
for &num_threads in &[2usize, 4, 8] {
let pool = RowPool::build(num_threads, &[], active_barrier);
for iter in 0..60usize {
let total_rows = if iter % 2 == 0 { 1 } else { 2048 };
let mut y = vec![0.0f32; total_rows];
pool.dispatch_rows(&mut y, 1, 1, |row, slice| slice[0] = (row + iter) as f32);
for (row, &v) in y.iter().enumerate() {
assert_eq!(
v,
(row + iter) as f32,
"barrier={active_barrier} threads={num_threads} iter={iter} row={row}"
);
}
}
}
}
}
#[test]
fn dispatch_rows_work_matches_serial() {
let pool = RowPool::build(8, &[], true);
for &depth in &[0usize, 1, 4096, 1_000_000] {
for &total_rows in &[0usize, 1, 3, 64, 500] {
let n = 4;
let mut got = vec![0.0f32; total_rows * n];
let mut want = vec![0.0f32; total_rows * n];
let fill = |row: usize, slice: &mut [f32]| {
for (k, v) in slice.iter_mut().enumerate() {
*v = (row * 7 + k) as f32;
}
};
pool.dispatch_rows_work(&mut got, n, 1, depth, fill);
for row in 0..total_rows {
fill(row, &mut want[row * n..row * n + n]);
}
assert_eq!(got, want, "depth={depth} rows={total_rows}");
}
}
}
#[test]
fn single_thread_pool_runs_serially() {
let pool = RowPool::build(1, &[], true);
assert_eq!(pool.num_threads(), 1);
let mut y = vec![0.0f32; 100];
pool.dispatch_rows(&mut y, 1, 1, |row, slice| slice[0] = row as f32);
assert!(y.iter().enumerate().all(|(i, &v)| v == i as f32));
}
#[test]
fn trailing_partial_row_matches_serial_chunks() {
let pool = RowPool::build(4, &[], true);
let n = 8usize;
let len = 8 * 300 + 5; let mut got = vec![0.0f32; len];
let fill = |row: usize, slice: &mut [f32]| {
for (k, v) in slice.iter_mut().enumerate() {
*v = (row * 1000 + k) as f32 + 1.0;
}
};
pool.dispatch_rows(&mut got, n, 1, fill);
let mut want = vec![0.0f32; len];
for (j, row) in want.chunks_mut(n).enumerate() {
fill(j, row);
}
assert_eq!(got, want);
}
#[test]
fn concurrent_dispatchers_are_safe() {
let pool = RowPool::build(4, &[], true);
for _ in 0..20 {
let mut a = vec![0.0f32; 4096];
let mut b = vec![0.0f32; 4096];
thread::scope(|s| {
let pool = &pool;
s.spawn(|| pool.dispatch_rows(&mut a, 1, 1, |row, s| s[0] = row as f32 + 1.0));
pool.dispatch_rows(&mut b, 1, 1, |row, s| s[0] = row as f32 + 2.0);
});
assert!(a.iter().enumerate().all(|(i, &v)| v == i as f32 + 1.0));
assert!(b.iter().enumerate().all(|(i, &v)| v == i as f32 + 2.0));
}
}
#[test]
fn worker_panic_propagates_and_pool_survives() {
let pool = RowPool::build(4, &[], true);
let mut y = vec![0.0f32; 4096];
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
pool.dispatch_rows(&mut y, 1, 1, |row, slice| {
if row == 2048 {
panic!("boom");
}
slice[0] = row as f32;
});
}));
let payload = result.expect_err("closure panic must propagate to the caller");
let msg: &str = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.expect("payload must be the original panic message");
assert_eq!(msg, "boom");
let mut z = vec![0.0f32; 4096];
pool.dispatch_rows(&mut z, 1, 1, |row, slice| slice[0] = row as f32);
assert!(z.iter().enumerate().all(|(i, &v)| v == i as f32));
}
}