1use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::mpsc::{channel, Sender};
13use std::sync::{Arc, Condvar, Mutex};
14
15struct TaskPtr(*const (dyn Fn(usize, usize) + Sync));
19unsafe impl Send for TaskPtr {}
20
21struct Latch {
22 remaining: AtomicUsize,
23 lock: Mutex<()>,
24 cv: Condvar,
25}
26
27impl Latch {
28 fn new(n: usize) -> Arc<Self> {
29 Arc::new(Self {
30 remaining: AtomicUsize::new(n),
31 lock: Mutex::new(()),
32 cv: Condvar::new(),
33 })
34 }
35
36 fn count_down(&self) {
37 if self.remaining.fetch_sub(1, Ordering::AcqRel) == 1 {
38 let _g = self.lock.lock().unwrap();
39 self.cv.notify_all();
40 }
41 }
42
43 fn wait(&self) {
44 let mut g = self.lock.lock().unwrap();
45 while self.remaining.load(Ordering::Acquire) != 0 {
46 g = self.cv.wait(g).unwrap();
47 }
48 }
49}
50
51struct Job {
52 task: TaskPtr,
53 worker_idx: usize,
54 n_workers: usize,
55 latch: Arc<Latch>,
56}
57
58pub struct Pool {
60 txs: Vec<Sender<Job>>,
61}
62
63impl Pool {
64 pub fn new(n_workers: usize) -> Self {
65 let mut txs = Vec::with_capacity(n_workers);
66 for w in 0..n_workers {
67 let (tx, rx) = channel::<Job>();
68 std::thread::Builder::new()
69 .name(format!("cmf-pool-{w}"))
70 .spawn(move || {
71 while let Ok(job) = rx.recv() {
72 let f = unsafe { &*job.task.0 };
75 f(job.worker_idx, job.n_workers);
76 job.latch.count_down();
77 }
78 })
79 .expect("spawn pool worker");
80 txs.push(tx);
81 }
82 Self { txs }
83 }
84
85 pub fn from_env() -> Option<Arc<Self>> {
87 let n = match std::env::var("CMF_THREADS") {
88 Ok(v) => v.parse::<usize>().unwrap_or(0),
89 Err(_) => {
90 let avail = std::thread::available_parallelism()
91 .map(|n| n.get())
92 .unwrap_or(1);
93 avail.saturating_sub(1).min(8)
94 }
95 };
96 if n <= 1 {
97 None
98 } else {
99 Some(Arc::new(Self::new(n)))
100 }
101 }
102
103 pub fn n_workers(&self) -> usize {
104 self.txs.len()
105 }
106
107 pub fn run(&self, f: &(dyn Fn(usize, usize) + Sync)) {
110 let n = self.txs.len();
111 let latch = Latch::new(n);
112 let ptr: *const (dyn Fn(usize, usize) + Sync) = f;
115 let ptr: *const (dyn Fn(usize, usize) + Sync + 'static) =
116 unsafe { std::mem::transmute(ptr) };
117 for (i, tx) in self.txs.iter().enumerate() {
118 let job = Job {
119 task: TaskPtr(ptr),
120 worker_idx: i,
121 n_workers: n,
122 latch: latch.clone(),
123 };
124 tx.send(job).expect("pool worker died");
125 }
126 latch.wait();
127 }
128}
129
130pub fn matvec_rows(pool: Option<&Pool>, w: &[f32], x: &[f32], out: &mut [f32]) {
133 let in_dim = x.len();
134 let out_dim = out.len();
135 debug_assert!(w.len() >= out_dim * in_dim);
136
137 let row_dot = |o: usize| -> f32 {
138 let row = &w[o * in_dim..(o + 1) * in_dim];
139 let mut sum = 0.0f32;
140 for j in 0..in_dim {
141 sum += row[j] * x[j];
142 }
143 sum
144 };
145
146 match pool {
147 Some(pool) if out_dim >= 256 => {
149 let out_addr = SendMut(out.as_mut_ptr());
150 pool.run(&move |widx, n| {
151 let chunk = out_dim.div_ceil(n);
152 let start = widx * chunk;
153 let end = (start + chunk).min(out_dim);
154 for o in start..end {
155 unsafe { *out_addr.at(o) = row_dot(o) };
157 }
158 });
159 }
160 _ => {
161 for (o, dst) in out.iter_mut().enumerate() {
162 *dst = row_dot(o);
163 }
164 }
165 }
166}
167
168pub fn matvec_rows2(
174 pool: Option<&Pool>,
175 w: &[f32],
176 x1: &[f32],
177 x2: &[f32],
178 out1: &mut [f32],
179 out2: &mut [f32],
180) {
181 let in_dim = x1.len();
182 debug_assert_eq!(x2.len(), in_dim);
183 let out_dim = out1.len();
184 debug_assert_eq!(out2.len(), out_dim);
185 debug_assert!(w.len() >= out_dim * in_dim);
186
187 let row_dots = |o: usize| -> (f32, f32) {
188 let row = &w[o * in_dim..(o + 1) * in_dim];
189 let (mut s1, mut s2) = (0.0f32, 0.0f32);
190 for j in 0..in_dim {
191 s1 += row[j] * x1[j];
192 s2 += row[j] * x2[j];
193 }
194 (s1, s2)
195 };
196
197 match pool {
198 Some(pool) if out_dim >= 256 => {
199 let o1 = SendMut(out1.as_mut_ptr());
200 let o2 = SendMut(out2.as_mut_ptr());
201 pool.run(&move |widx, n| {
202 let chunk = out_dim.div_ceil(n);
203 let start = widx * chunk;
204 let end = (start + chunk).min(out_dim);
205 for o in start..end {
206 let (s1, s2) = row_dots(o);
207 unsafe {
209 *o1.at(o) = s1;
210 *o2.at(o) = s2;
211 }
212 }
213 });
214 }
215 _ => {
216 for o in 0..out_dim {
217 let (s1, s2) = row_dots(o);
218 out1[o] = s1;
219 out2[o] = s2;
220 }
221 }
222 }
223}
224
225#[derive(Clone, Copy)]
226struct SendMut(*mut f32);
227unsafe impl Send for SendMut {}
228unsafe impl Sync for SendMut {}
229
230impl SendMut {
231 #[inline]
234 fn at(self, i: usize) -> *mut f32 {
235 unsafe { self.0.add(i) }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn parallel_matvec_equals_serial_bitexact() {
245 let (out_dim, in_dim) = (512, 64);
246 let w: Vec<f32> = (0..out_dim * in_dim).map(|i| (i as f32 * 0.013).sin()).collect();
247 let x: Vec<f32> = (0..in_dim).map(|i| (i as f32 * 0.07).cos()).collect();
248
249 let mut serial = vec![0.0f32; out_dim];
250 matvec_rows(None, &w, &x, &mut serial);
251
252 let pool = Pool::new(4);
253 let mut parallel = vec![0.0f32; out_dim];
254 matvec_rows(Some(&pool), &w, &x, &mut parallel);
255
256 assert_eq!(serial, parallel, "row-parallel must be bit-identical");
257 }
258
259 #[test]
260 fn fused_pair_equals_two_singles_bitexact() {
261 let (out_dim, in_dim) = (300, 48);
262 let w: Vec<f32> = (0..out_dim * in_dim).map(|i| (i as f32 * 0.011).sin()).collect();
263 let x1: Vec<f32> = (0..in_dim).map(|i| (i as f32 * 0.03).cos()).collect();
264 let x2: Vec<f32> = (0..in_dim).map(|i| (i as f32 * 0.09).sin()).collect();
265
266 let mut a1 = vec![0.0f32; out_dim];
267 let mut a2 = vec![0.0f32; out_dim];
268 matvec_rows(None, &w, &x1, &mut a1);
269 matvec_rows(None, &w, &x2, &mut a2);
270
271 for pool in [None, Some(Pool::new(3))] {
272 let mut b1 = vec![0.0f32; out_dim];
273 let mut b2 = vec![0.0f32; out_dim];
274 matvec_rows2(pool.as_ref(), &w, &x1, &x2, &mut b1, &mut b2);
275 assert_eq!(a1, b1, "fused lane 1 must be bit-identical");
276 assert_eq!(a2, b2, "fused lane 2 must be bit-identical");
277 }
278 }
279
280 #[test]
281 fn pool_survives_many_runs() {
282 let pool = Pool::new(3);
283 let counter = AtomicUsize::new(0);
284 for _ in 0..100 {
285 pool.run(&|_, _| {
286 counter.fetch_add(1, Ordering::Relaxed);
287 });
288 }
289 assert_eq!(counter.load(Ordering::Relaxed), 300);
290 }
291}