1use super::{DType, DeviceTensor, TensorDesc};
2use crate::{
3 Error, Result,
4 inference::{Inference, ModelContext, PreparedModel},
5 loom::Specialization,
6 model::{Command, Dispatch, ModelFragment, ModelSession},
7 plan_cache::PlanCache,
8};
9use std::sync::Arc;
10
11pub struct TensorOps {
13 pub(super) context: ModelContext,
14 pub(super) linear: PlanCache<(TensorDesc, Vec<TensorDesc>, DType), PreparedModel>,
15 finite: PlanCache<TensorDesc, PreparedModel>,
16 gather: PlanCache<(TensorDesc, usize), PreparedModel>,
17}
18impl TensorOps {
19 pub fn gather_rows_fragment(&self, input: &TensorDesc, rows: usize) -> Result<ModelFragment> {
24 self.gather_fragment(input, rows, true)
25 }
26 fn gather_fragment(
27 &self,
28 input: &TensorDesc,
29 rows: usize,
30 mapped: bool,
31 ) -> Result<ModelFragment> {
32 if input.shape().len() != 2
33 || !input.is_contiguous()
34 || input.is_empty()
35 || input.bytes() > i32::MAX as usize
36 || rows == 0
37 {
38 return Err(Error::Message(
39 "gather requires nonempty contiguous rows".into(),
40 ));
41 }
42 let output = TensorDesc::new(input.dtype(), vec![rows, input.shape()[1]])?
43 .with_layout(input.layout())?;
44 if output.bytes() > i32::MAX as usize {
45 return Err(Error::Message(
46 "gather exceeds kernel indexing limits".into(),
47 ));
48 }
49 let ids_desc = TensorDesc::new(DType::U32, vec![rows])?;
50 if ids_desc.bytes() > i32::MAX as usize {
51 return Err(Error::Message(
52 "gather index storage exceeds kernel limits".into(),
53 ));
54 }
55 let mut model = ModelSession::in_context(&self.context)?;
56 let source = model.allocate(input.bytes())?;
57 let ids = model.allocate(ids_desc.bytes())?;
58 let destination = if mapped {
59 model.allocate_shared(output.bytes())?
60 } else {
61 model.allocate(output.bytes())?
62 };
63 let mut code = include_str!("gather.loom").to_owned();
64 for (name, value) in [
65 ("INPUT", input.bytes()),
66 ("LAST_INPUT", input.bytes() - 1),
67 ("ROWS", input.shape()[0]),
68 ("LAST_ROW", input.shape()[0] - 1),
69 ("ROW_BYTES", input.shape()[1] * input.dtype().bytes()),
70 ("BUCKET", rows),
71 ("LAST_INDEX", rows - 1),
72 ("OUTPUT", output.bytes()),
73 ("LAST_OUTPUT", output.bytes() - 1),
74 ("GRID", output.bytes().div_ceil(256)),
75 ] {
76 code = code.replace(&format!("@{name}@"), &value.to_string());
77 }
78 let kernel = unsafe { model.compile(&[(&code, Specialization::new("gather"))])? }[0];
80 unsafe {
81 model.freeze(&self.context)?.fragment(
82 &[Command::Dispatch(Dispatch::indices(
83 kernel,
84 [0],
85 [output.bytes().div_ceil(256) as u32, 1, 1],
86 vec![source.read(), ids.read(), destination.write()],
87 ))],
88 &[(source, input.clone()), (ids, ids_desc)],
89 &[(destination, output)],
90 )
91 }
92 }
93 pub fn new(context: &ModelContext, capacity: usize) -> Result<Self> {
95 Ok(Self {
96 context: context.clone(),
97 linear: PlanCache::new(capacity, PreparedModel::is_idle)?,
98 finite: PlanCache::new(capacity, PreparedModel::is_idle)?,
99 gather: PlanCache::new(capacity, PreparedModel::is_idle)?,
100 })
101 }
102 pub fn gather_rows(&self, input: &DeviceTensor, indices: &[usize]) -> Result<DeviceTensor> {
107 self.context.validate(input)?;
108 let desc = input.desc();
109 if desc.shape().len() != 2
110 || !desc.is_contiguous()
111 || desc.bytes() > i32::MAX as usize
112 || indices.iter().any(|&i| i >= desc.shape()[0])
113 {
114 return Err(Error::Message(
115 "gather requires contiguous rows and in-range indices".into(),
116 ));
117 }
118 let output = TensorDesc::new(desc.dtype(), vec![indices.len(), desc.shape()[1]])?
119 .with_layout(desc.layout())?;
120 if output.is_empty() {
121 input.completion().wait()?;
122 return self.context.allocate(output);
123 }
124 let bucket = indices
125 .len()
126 .checked_next_power_of_two()
127 .ok_or_else(|| Error::Message("gather size overflow".into()))?;
128 let plan = self.gather.get_or_prepare((desc.clone(), bucket), || {
129 self.gather_fragment(desc, bucket, false)?.prepare(3)
130 })?;
131 let bytes: Vec<_> = indices
132 .iter()
133 .copied()
134 .chain(std::iter::repeat(indices[0]))
135 .take(bucket)
136 .flat_map(|i| (i as u32).to_le_bytes())
137 .collect();
138 let ids = self
139 .context
140 .upload(TensorDesc::new(DType::U32, vec![bucket])?, &bytes)?;
141 let result = plan.submit(&[input.clone(), ids])?;
142 result.outputs()[0].view(0, output)
143 }
144 pub fn prepare_finite(&self, input: &TensorDesc) -> Result<Arc<PreparedModel>> {
148 self.finite
149 .get_or_prepare(input.clone(), || self.finite_fragment(input)?.prepare(3))
150 }
151 pub fn finite_fragment(&self, input: &TensorDesc) -> Result<ModelFragment> {
154 if input.dtype() != DType::F32
155 || !input.is_contiguous()
156 || input.is_empty()
157 || input.elements() > i32::MAX as usize
158 {
159 return Err(Error::Message(
160 "finite check requires nonempty contiguous F32".into(),
161 ));
162 }
163 {
164 let mut model = ModelSession::in_context(&self.context)?;
165 let src = model.allocate(input.bytes())?;
166 let mut current = src;
167 let mut count = input.elements();
168 let mut commands = Vec::new();
169 let mut first = true;
170 loop {
171 let out = count.div_ceil(256);
172 let dst = model.allocate(out * 4)?;
173 let ty = if first { "f32" } else { "i32" };
174 let bad = if first {
175 "%abs = scalar.absf %value : f32\n%max = scalar.constant 3.402823466e38 : f32\n%finite = scalar.cmpf ole, %abs, %max : f32\n%bad = scf.select %finite, %fzero, %fone : f32"
176 } else {
177 "%bad = scalar.uitofp %value : i32 to f32"
178 };
179 let mut source = include_str!("finite.loom")
180 .replace("@TYPE@", ty)
181 .replace("@CHECK@", bad);
182 for (name, value) in [
183 ("GRID", out.div_ceil(64)),
184 ("INPUT", count),
185 ("OUTPUT", out),
186 ("LAST_INPUT", count - 1),
187 ("LAST_OUTPUT", out - 1),
188 ] {
189 source = source.replace(&format!("@{name}@"), &value.to_string());
190 }
191 let kernel =
193 unsafe { model.compile(&[(&source, Specialization::new("finite"))])? }[0];
194 commands.push(Command::Dispatch(Dispatch::indices(
195 kernel,
196 [0],
197 [out.div_ceil(64) as u32, 1, 1],
198 vec![current.read(), dst.write()],
199 )));
200 current = dst;
201 if out == 1 {
202 break;
203 }
204 count = out;
205 first = false;
206 }
207 unsafe {
208 model.freeze(&self.context)?.fragment(
209 &commands,
210 &[(src, input.clone())],
211 &[(current, TensorDesc::new(DType::I32, vec![1])?)],
212 )
213 }
214 }
215 }
216 pub fn finite(&self, input: &DeviceTensor) -> Result<Inference> {
218 self.context.validate(input)?;
219 self.prepare_finite(input.desc())?
220 .submit(std::slice::from_ref(input))
221 }
222}