tenflowers_core/ops/binary/
implementation.rs1use super::core::BinaryOp;
7use crate::shape_error_taxonomy::ShapeErrorUtils;
8use crate::{Device, Result, Tensor, TensorError};
9use scirs2_core::ndarray::{ArrayD, IxDyn};
10use scirs2_core::numeric::Zero;
11
12#[allow(unused_variables)]
14pub fn binary_op<T, Op>(a: &Tensor<T>, b: &Tensor<T>, op: Op) -> Result<Tensor<T>>
15where
16 T: Clone + Default + Zero + Send + Sync + 'static + bytemuck::Pod + bytemuck::Zeroable,
17 Op: BinaryOp<T>,
18{
19 if a.device() != b.device() {
27 return Err(TensorError::device_mismatch(
28 "binary_op",
29 &a.device().to_string(),
30 &b.device().to_string(),
31 ));
32 }
33
34 let broadcast_shape = a.shape().broadcast_shape(b.shape()).ok_or_else(|| {
36 ShapeErrorUtils::broadcast_incompatible("binary_op", a.shape(), b.shape())
37 })?;
38
39 match (a.device(), b.device()) {
41 (Device::Cpu, Device::Cpu) => {
42 cpu_binary_op(a, b, op, &broadcast_shape)
44 }
45 #[cfg(feature = "gpu")]
46 (Device::Gpu(_), Device::Gpu(_)) => {
47 gpu_binary_op(a, b, op, &broadcast_shape)
49 }
50 #[cfg(feature = "rocm")]
51 (Device::Rocm(_), Device::Rocm(_)) => {
52 eprintln!("Warning: ROCm binary operations using CPU fallback - native implementation pending");
55
56 cpu_binary_op(a, b, op, &broadcast_shape)
59 }
60 #[cfg(any(feature = "gpu", feature = "rocm"))]
61 _ => {
62 Err(TensorError::device_mismatch(
64 "binary_op",
65 &a.device().to_string(),
66 &b.device().to_string(),
67 ))
68 }
69 }
70}
71
72fn cpu_binary_op<T, Op>(
74 a: &Tensor<T>,
75 b: &Tensor<T>,
76 op: Op,
77 broadcast_shape: &crate::Shape,
78) -> Result<Tensor<T>>
79where
80 T: Clone + Default + Zero + Send + Sync + 'static + bytemuck::Pod + bytemuck::Zeroable,
81 Op: BinaryOp<T>,
82{
83 use crate::tensor::TensorStorage;
84
85 let (a_array, b_array) = match (&a.storage, &b.storage) {
87 (TensorStorage::Cpu(ref a_arr), TensorStorage::Cpu(ref b_arr)) => (a_arr, b_arr),
88 #[cfg(feature = "gpu")]
89 _ => {
90 return Err(TensorError::device_mismatch(
91 "cpu_binary_op",
92 "cpu",
93 "non-cpu",
94 ))
95 }
96 };
97
98 let output_dims = IxDyn(broadcast_shape.dims());
100 let mut output = ArrayD::zeros(output_dims);
101
102 if a.shape() == b.shape() && a.shape().dims() == broadcast_shape.dims() {
104 for ((a_val, b_val), out_val) in a_array.iter().zip(b_array.iter()).zip(output.iter_mut()) {
106 *out_val = op.apply(*a_val, *b_val);
107 }
108 } else {
109 broadcast_operation(
111 a_array,
112 b_array,
113 &mut output,
114 &op,
115 a.shape(),
116 b.shape(),
117 broadcast_shape,
118 )?;
119 }
120
121 Ok(Tensor::from_array(output))
123}
124
125fn broadcast_operation<T, Op>(
127 a_array: &ArrayD<T>,
128 b_array: &ArrayD<T>,
129 output: &mut ArrayD<T>,
130 op: &Op,
131 a_shape: &crate::Shape,
132 b_shape: &crate::Shape,
133 broadcast_shape: &crate::Shape,
134) -> Result<()>
135where
136 T: Clone + Default + Zero + Send + Sync + 'static + bytemuck::Pod + bytemuck::Zeroable,
137 Op: BinaryOp<T>,
138{
139 if a_shape.size() == 1 {
141 if let Some(a_scalar) = a_array.iter().next() {
143 for (b_val, out_val) in b_array.iter().zip(output.iter_mut()) {
144 *out_val = op.apply(*a_scalar, *b_val);
145 }
146 }
147 return Ok(());
148 }
149
150 if b_shape.size() == 1 {
151 if let Some(b_scalar) = b_array.iter().next() {
153 for (a_val, out_val) in a_array.iter().zip(output.iter_mut()) {
154 *out_val = op.apply(*a_val, *b_scalar);
155 }
156 }
157 return Ok(());
158 }
159
160 let output_shape = broadcast_shape.dims();
163 let a_dims = a_shape.dims();
164 let b_dims = b_shape.dims();
165
166 let mut a_strides = vec![0; output_shape.len()];
168 let mut b_strides = vec![0; output_shape.len()];
169
170 let a_offset = output_shape.len() - a_dims.len();
172 let b_offset = output_shape.len() - b_dims.len();
173
174 let mut a_stride_acc = 1;
176 for i in (0..a_dims.len()).rev() {
177 let out_idx = a_offset + i;
178 if a_dims[i] == 1 {
179 a_strides[out_idx] = 0; } else {
181 a_strides[out_idx] = a_stride_acc;
182 }
183 a_stride_acc *= a_dims[i];
184 }
185
186 let mut b_stride_acc = 1;
188 for i in (0..b_dims.len()).rev() {
189 let out_idx = b_offset + i;
190 if b_dims[i] == 1 {
191 b_strides[out_idx] = 0; } else {
193 b_strides[out_idx] = b_stride_acc;
194 }
195 b_stride_acc *= b_dims[i];
196 }
197
198 let total_elements: usize = output_shape.iter().product();
200 for linear_idx in 0..total_elements {
201 let mut coords = vec![0; output_shape.len()];
203 let mut remaining = linear_idx;
204 for i in (0..output_shape.len()).rev() {
205 coords[i] = remaining % output_shape[i];
206 remaining /= output_shape[i];
207 }
208
209 let mut a_idx = 0;
211 let mut b_idx = 0;
212 for i in 0..output_shape.len() {
213 a_idx += coords[i] * a_strides[i];
214 b_idx += coords[i] * b_strides[i];
215 }
216
217 let a_val = a_array
219 .as_slice()
220 .unwrap_or_else(|| panic!("Failed to get slice from a_array"))[a_idx];
221 let b_val = b_array
222 .as_slice()
223 .unwrap_or_else(|| panic!("Failed to get slice from b_array"))[b_idx];
224
225 let result_val = op.apply(a_val, b_val);
227 output
228 .as_slice_mut()
229 .unwrap_or_else(|| panic!("Failed to get mutable slice from output"))[linear_idx] =
230 result_val;
231 }
232
233 Ok(())
234}
235
236#[cfg(feature = "gpu")]
238fn gpu_binary_op<T, Op>(
239 a: &Tensor<T>,
240 b: &Tensor<T>,
241 op: Op,
242 broadcast_shape: &crate::Shape,
243) -> Result<Tensor<T>>
244where
245 T: Clone + Default + Zero + Send + Sync + 'static + bytemuck::Pod + bytemuck::Zeroable,
246 Op: BinaryOp<T>,
247{
248 use crate::gpu::binary_ops;
249 use crate::tensor::TensorStorage;
250
251 let (a_buffer, b_buffer) = match (&a.storage, &b.storage) {
253 (TensorStorage::Gpu(ref a_buf), TensorStorage::Gpu(ref b_buf)) => (a_buf, b_buf),
254 _ => {
255 return Err(TensorError::device_mismatch(
256 "gpu_binary_op",
257 "gpu",
258 "non-gpu",
259 ))
260 }
261 };
262
263 let gpu_op = match op.name() {
265 "Add" => binary_ops::BinaryOp::Add,
266 "Sub" => binary_ops::BinaryOp::Sub,
267 "Mul" => binary_ops::BinaryOp::Mul,
268 "Div" => binary_ops::BinaryOp::Div,
269 "Pow" => binary_ops::BinaryOp::Pow,
270 "Min" => binary_ops::BinaryOp::Min,
271 "Max" => binary_ops::BinaryOp::Max,
272 _ => {
273 return Err(TensorError::invalid_argument(format!(
274 "Unsupported GPU binary operation: {}",
275 op.name()
276 )))
277 }
278 };
279
280 let output_len = broadcast_shape.size();
282
283 let result_buffer = if a.shape() == b.shape() && a.shape().dims() == broadcast_shape.dims() {
285 binary_ops::execute_binary_op(a_buffer, b_buffer, gpu_op, output_len)?
287 } else {
288 binary_ops::execute_binary_op_with_broadcasting(
290 a_buffer,
291 b_buffer,
292 gpu_op,
293 a.shape().dims(),
294 b.shape().dims(),
295 broadcast_shape.dims(),
296 output_len,
297 )?
298 };
299
300 Ok(Tensor::from_gpu_buffer(
302 result_buffer,
303 broadcast_shape.clone(),
304 ))
305}