Skip to main content

ruprim/reduce/components/instructions/
topk.rs

1use ruda_kernel::dsl as kernel_dsl;
2use ruda_kernel::dsl::comptime;
3use ruda_kernel::dsl::ruda;
4use ruda_kernel::dsl::frontend::RudaIndexMutExpand;
5use ruda_kernel::dsl::prelude::*;
6
7use crate::reduce::components::instructions::AccumulatorFormat;
8use crate::reduce::components::instructions::plane_topk_insert;
9use crate::reduce::components::instructions::plane_topk_merge;
10use crate::reduce::components::instructions::{Accumulator, Item, Value};
11use crate::reduce::{
12    ReduceFamily, ReduceInstruction, ReducePrecision,
13    components::instructions::{ReduceRequirements, ReduceStep, SharedAccumulator},
14};
15use ruda_kernel::dsl::frontend::Numeric;
16
17#[derive(Debug, RudaType, Clone)]
18pub struct TopK {
19    #[ruda(comptime)]
20    pub k: usize,
21}
22
23impl ReduceFamily for TopK {
24    type Instruction<P: ReducePrecision> = Self;
25    type Config = usize;
26}
27
28#[derive(RudaType)]
29pub struct TopkAccumulator<E: Scalar, S: Size> {
30    pub elements: Array<Vector<E, S>>,
31    pub coordinates: Array<Vector<u32, S>>,
32}
33
34#[derive(RudaType)]
35pub struct TopKSharedAccumulator<P: ReducePrecision> {
36    elements: Sequence<SharedMemory<Vector<P::EA, P::SI>>>,
37    #[ruda(comptime)]
38    k: usize,
39}
40
41#[ruda]
42impl<P: ReducePrecision> SharedAccumulator<P, TopK> for TopKSharedAccumulator<P> {
43    fn allocate(#[comptime] length: usize, #[comptime] _coordinate: bool, inst: &TopK) -> Self {
44        let mut elements = Sequence::new();
45        for _ in 0..inst.k {
46            elements.push(SharedMemory::new(length));
47        }
48        TopKSharedAccumulator::<P> {
49            elements,
50            k: inst.k,
51        }
52    }
53
54    fn read(accumulator: &Self, index: usize) -> Accumulator<P> {
55        let mut values = Array::new(accumulator.k);
56        #[unroll]
57        for i in 0..accumulator.k {
58            values[i] = accumulator.elements[i][index];
59        }
60        Accumulator::<P> {
61            elements: Value::new_Multiple(values),
62            args: Value::new_None(),
63        }
64    }
65
66    fn write(accumulator: &mut Self, index: usize, item: Accumulator<P>) {
67        let values = item.elements.multiple();
68        #[unroll]
69        for i in 0..accumulator.k {
70            let acc = values[i];
71            let mut shared_acc = accumulator.elements[i];
72            shared_acc[index] = acc;
73        }
74    }
75}
76
77#[ruda]
78impl<P: ReducePrecision> ReduceInstruction<P> for TopK {
79    type SharedAccumulator = TopKSharedAccumulator<P>;
80    type Config = usize;
81
82    fn requirements(_this: &Self) -> super::ReduceRequirements {
83        ReduceRequirements { coordinates: false }
84    }
85
86    fn accumulator_format(this: &Self) -> comptime_type!(AccumulatorFormat) {
87        comptime!(AccumulatorFormat::Multiple(this.k))
88    }
89
90    fn from_config(#[comptime] config: Self::Config) -> Self {
91        TopK { k: config }
92    }
93
94    fn null_input(_this: &Self) -> Vector<P::EI, P::SI> {
95        Vector::empty().fill(P::EI::min_value())
96    }
97
98    fn null_accumulator(this: &Self) -> Accumulator<P> {
99        let mut elements = Array::new(comptime!(this.k));
100        #[unroll]
101        for i in 0..this.k {
102            elements[i] = Vector::new(P::EA::min_value());
103        }
104
105        Accumulator::<P> {
106            elements: Value::new_Multiple(elements),
107            args: Value::new_None(),
108        }
109    }
110
111    fn reduce(
112        this: &Self,
113        accumulator: &mut Accumulator<P>,
114        item: Item<P>,
115        #[comptime] reduce_step: ReduceStep,
116    ) {
117        let elements = accumulator.elements.multiple_mut();
118
119        match reduce_step {
120            ReduceStep::Plane => {
121                plane_topk_insert::<P::EA, P::SI>(
122                    elements,
123                    &mut accumulator.args,
124                    Vector::cast_from(item.elements),
125                    &item.args,
126                    this.k,
127                    false,
128                );
129            }
130            ReduceStep::Identity => {
131                let mut insert_item = Vector::cast_from(item.elements);
132
133                for j in 0..this.k {
134                    let acc_item = elements[j];
135                    let keep = acc_item.greater_than(insert_item);
136
137                    elements[j] = select_many(keep, acc_item, insert_item);
138                    insert_item = select_many(keep, insert_item, acc_item);
139                }
140            }
141        }
142    }
143
144    fn plane_reduce_inplace(this: &Self, accumulator: &mut Accumulator<P>) {
145        plane_topk_merge(
146            accumulator.elements.multiple_mut(),
147            &mut accumulator.args,
148            this.k,
149            false,
150        );
151    }
152
153    fn fuse_accumulators(this: &Self, accumulator: &mut Accumulator<P>, other: &Accumulator<P>) {
154        let acc_elements = accumulator.elements.multiple_mut();
155        let other_elements = other.elements.multiple();
156
157        for i in 0..this.k {
158            let mut item = other_elements[i];
159            for j in 0..this.k {
160                let current_item = acc_elements[j];
161                let keep = current_item.greater_than(item);
162
163                let new_top_item = select_many(keep, current_item, item);
164                let new_rest_item = select_many(keep, item, current_item);
165
166                acc_elements[j] = new_top_item;
167                item = new_rest_item;
168            }
169        }
170    }
171
172    fn to_output_parallel<Out: Numeric>(
173        this: &Self,
174        accumulator: Accumulator<P>,
175        _shape_axis_reduce: usize,
176    ) -> Value<Out> {
177        let accumulators = accumulator.elements.multiple();
178        let vector_size = accumulators[0].size().comptime();
179
180        let mut topk = Array::new(this.k);
181        #[unroll]
182        for slot in 0..this.k {
183            topk[slot] = Out::min_value();
184        }
185
186        #[unroll]
187        for i in 0..this.k {
188            #[unroll]
189            for j in 0..vector_size {
190                let mut element = Out::cast_from(accumulators[i][j]);
191
192                #[unroll]
193                for slot in 0..this.k {
194                    let current = topk[slot];
195
196                    let keep = current > element;
197
198                    topk[slot] = select(keep, current, element);
199                    element = select(keep, element, current);
200                }
201            }
202        }
203
204        Value::new_Multiple(topk)
205    }
206
207    fn to_output_perpendicular<Out: Numeric>(
208        this: &Self,
209        accumulator: Accumulator<P>,
210        _shape_axis_reduce: usize,
211    ) -> Value<Vector<Out, P::SI>> {
212        let acc_values = accumulator.elements.multiple();
213        let mut output = Array::new(this.k);
214
215        #[unroll]
216        for i in 0..this.k {
217            output[i] = Vector::cast_from(acc_values[i]);
218        }
219
220        Value::new_Multiple(output)
221    }
222}