Skip to main content

adele_ring/
dispatch.rs

1//! Base-aware ALU dispatcher.
2//!
3//! Before an operation, the dispatcher inspects the denominator signatures of
4//! the operands to decide *which channels are actually needed*. When adding
5//! `1/6 + 1/4` the natural base involves only the primes `{2, 3}`; the channels
6//! for 5, 7, 11, … are idle. On a GPU, idle channels consume no power — so the
7//! dispatcher is the hardware analog of lazy evaluation: work happens only where
8//! the number's arithmetic structure demands it.
9
10use crate::primes::lcm;
11use crate::rational::RnsRational;
12use crate::rns::Channels;
13
14/// A plan describing which channels an operation needs.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DispatchPlan {
17    /// Indices (into `channels`) of the channels that must be active.
18    pub active_channels: Vec<usize>,
19    /// LCM of the operands' natural bases.
20    pub natural_base: u64,
21    /// Bitmask of active channels (bit `i` set ⇒ channel `i` active).
22    pub channel_mask: u64,
23}
24
25/// Routes operations to the minimal set of RNS channels.
26pub struct Dispatcher {
27    channels: Channels,
28}
29
30impl Dispatcher {
31    pub fn new(channels: Channels) -> Self {
32        Dispatcher { channels }
33    }
34
35    /// The channels this dispatcher manages.
36    pub fn channels(&self) -> &Channels {
37        &self.channels
38    }
39
40    fn plan_for(&self, natural_base: u64) -> DispatchPlan {
41        let mut active_channels = Vec::new();
42        let mut channel_mask = 0u64;
43        for c in 0..self.channels.len() {
44            let prime = self.channels.modulus(c);
45            if natural_base % prime == 0 {
46                active_channels.push(c);
47                if c < 64 {
48                    channel_mask |= 1u64 << c;
49                }
50            }
51        }
52        DispatchPlan {
53            active_channels,
54            natural_base,
55            channel_mask,
56        }
57    }
58
59    /// Plan an addition: the natural base is the LCM of the operands' bases.
60    pub fn plan_add(&self, a: &RnsRational, b: &RnsRational) -> DispatchPlan {
61        self.plan_for(lcm(a.natural_base(), b.natural_base()))
62    }
63
64    /// Plan a multiplication.
65    pub fn plan_mul(&self, a: &RnsRational, b: &RnsRational) -> DispatchPlan {
66        self.plan_for(lcm(a.natural_base(), b.natural_base()))
67    }
68
69    /// Execute an addition (the result is exact regardless of the plan; the plan
70    /// is the hardware-scheduling hint).
71    pub fn execute_add(&self, a: &RnsRational, b: &RnsRational) -> RnsRational {
72        a.add(b)
73    }
74
75    /// Execute a multiplication.
76    pub fn execute_mul(&self, a: &RnsRational, b: &RnsRational) -> RnsRational {
77        a.mul(b)
78    }
79
80    /// Fraction of channels that are active for this plan (in `[0, 1]`).
81    pub fn channel_efficiency(&self, plan: &DispatchPlan) -> f64 {
82        plan.active_channels.len() as f64 / self.channels.len() as f64
83    }
84
85    /// Fraction of channels left idle for this plan.
86    pub fn channel_idle_fraction(&self, plan: &DispatchPlan) -> f64 {
87        1.0 - self.channel_efficiency(plan)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn sixth_plus_quarter_uses_two_channels() {
97        let channels = Channels::standard(16);
98        let dispatcher = Dispatcher::new(channels.clone());
99        let sixth = RnsRational::from_fraction(1, 6, channels.clone());
100        let quarter = RnsRational::from_fraction(1, 4, channels);
101        let plan = dispatcher.plan_add(&sixth, &quarter);
102        // Natural base involves only primes {2, 3}.
103        assert_eq!(plan.active_channels.len(), 2);
104        assert_eq!(plan.active_channels, vec![0, 1]); // channels for 2 and 3
105        assert!((dispatcher.channel_efficiency(&plan) - 2.0 / 16.0).abs() < 1e-12);
106    }
107
108    #[test]
109    fn integers_use_no_channels() {
110        let channels = Channels::standard(16);
111        let dispatcher = Dispatcher::new(channels.clone());
112        let a = RnsRational::from_int(3, channels.clone());
113        let b = RnsRational::from_int(5, channels);
114        let plan = dispatcher.plan_add(&a, &b);
115        assert!(plan.active_channels.is_empty());
116        assert_eq!(dispatcher.channel_efficiency(&plan), 0.0);
117    }
118
119    #[test]
120    fn execution_is_exact() {
121        let channels = Channels::standard(16);
122        let dispatcher = Dispatcher::new(channels.clone());
123        let sixth = RnsRational::from_fraction(1, 6, channels.clone());
124        let quarter = RnsRational::from_fraction(1, 4, channels.clone());
125        // 1/6 + 1/4 = 5/12
126        assert_eq!(
127            dispatcher.execute_add(&sixth, &quarter),
128            RnsRational::from_fraction(5, 12, channels)
129        );
130    }
131}