1use crate::matrix::rand_util::{collect_f32_seeded, entropy_seed};
2use crate::matrix::traits::*;
3use candle_core::{CpuStorage, DType, Device, InplaceOp2, Layout, Tensor};
4use rand_distr::{Gamma, StandardNormal, Uniform};
5use rayon::prelude::*;
6
7impl SampleOps for Tensor {
8 type Mat = Self;
9 type Scalar = f32;
10
11 fn runif(nrow: usize, ncol: usize) -> Self::Mat {
12 Self::runif_seeded(nrow, ncol, entropy_seed())
13 }
14
15 fn rnorm(nrow: usize, ncol: usize) -> Self::Mat {
16 Self::rnorm_seeded(nrow, ncol, entropy_seed())
17 }
18
19 fn rgamma(nrow: usize, ncol: usize, param: (f32, f32)) -> Self::Mat {
20 Self::rgamma_seeded(nrow, ncol, param, entropy_seed())
21 }
22
23 fn runif_seeded(nrow: usize, ncol: usize, seed: u64) -> Self::Mat {
24 let u01 = Uniform::new(0_f32, 1_f32).expect("failed to create uniform distribution");
25 let data = collect_f32_seeded(nrow * ncol, u01, seed);
26 Tensor::from_vec(data, (nrow, ncol), &Device::Cpu)
27 .expect("failed to create Tensor runif_seeded")
28 }
29
30 fn rnorm_seeded(nrow: usize, ncol: usize, seed: u64) -> Self::Mat {
31 let data = collect_f32_seeded(nrow * ncol, StandardNormal, seed);
36 Tensor::from_vec(data, (nrow, ncol), &Device::Cpu)
37 .expect("failed to create Tensor rnorm_seeded")
38 }
39
40 fn rgamma_seeded(nrow: usize, ncol: usize, param: (f32, f32), seed: u64) -> Self::Mat {
41 let (shape, scale) = param;
42 let pdf = Gamma::new(shape, scale).unwrap();
43 let data = collect_f32_seeded(nrow * ncol, pdf, seed);
44 Tensor::from_vec(data, (nrow, ncol), &Device::Cpu)
45 .expect("failed to create Tensor rgamma_seeded")
46 }
47}
48
49impl MatTriplets for Tensor {
50 type Mat = Self;
51 type Scalar = f32;
52
53 fn from_nonzero_triplets<I>(
54 nrow: usize,
55 ncol: usize,
56 triplets: &[(I, I, Self::Scalar)],
57 ) -> anyhow::Result<Self::Mat>
58 where
59 I: TryInto<usize> + Copy,
60 <I as TryInto<usize>>::Error: std::fmt::Debug,
61 {
62 let mut data = vec![0_f32; ncol * nrow];
63 for &(ii, jj, x_ij) in triplets {
64 let ii: usize = ii.try_into().expect("failed to convert index ii");
65 let jj: usize = jj.try_into().expect("failed to convert index jj");
66 data[ii * ncol + jj] = x_ij;
67 }
68 Ok(Tensor::from_vec(data, (nrow, ncol), &Device::Cpu)?)
69 }
70
71 fn to_nonzero_triplets(&self) -> anyhow::Result<NRowNColTriplets<Self::Scalar>> {
72 if let Ok((nrow, ncol)) = self.dims2() {
73 let eps = 1e-6;
74 let mut ret = vec![];
75 let xx: Vec<Vec<Self::Scalar>> = self.to_vec2()?;
76 for (i, x_i) in xx.iter().enumerate() {
77 for (j, &x_ij) in x_i.iter().enumerate() {
78 if x_ij.abs() > eps {
79 ret.push((i, j, x_ij));
80 }
81 }
82 }
83
84 Ok(NRowNColTriplets {
85 nrow,
86 ncol,
87 triplets: ret,
88 })
89 } else {
90 anyhow::bail!("not a 2D Tensor");
91 }
92 }
93}
94
95impl FusedTensorOps for Tensor {
111 fn clamped_exp_add_inplace(self, offset: &Tensor, ceiling: f64) -> anyhow::Result<Self> {
112 let broadcast = match (self.dims(), offset.dims()) {
116 (&[n, f], &[n_off, f_off]) => Broadcast::of(n, f, n_off, f_off),
117 _ => None,
118 };
119 let fused = matches!(self.device(), Device::Cpu)
120 && self.dtype() == DType::F32
121 && offset.dtype() == DType::F32
122 && self.is_contiguous()
123 && offset.is_contiguous();
124
125 let (Some(broadcast), true) = (broadcast, fused) else {
126 return Ok(self.broadcast_add(offset)?.minimum(ceiling)?.exp()?);
127 };
128
129 self.inplace_op2(
130 offset,
131 &ClampedExpAdd {
132 broadcast,
133 ceiling: ceiling as f32,
137 },
138 )?;
139 Ok(self)
140 }
141}
142
143#[derive(Clone, Copy)]
146enum Broadcast {
147 Element,
149 Column,
151 Row,
153}
154
155impl Broadcast {
156 fn of(n: usize, f: usize, n_off: usize, f_off: usize) -> Option<Self> {
157 match (n_off, f_off) {
158 _ if n_off == n && f_off == f => Some(Self::Element),
159 (1, _) if f_off == f => Some(Self::Column),
160 (_, 1) if n_off == n => Some(Self::Row),
161 _ => None,
162 }
163 }
164}
165
166struct ClampedExpAdd {
170 broadcast: Broadcast,
171 ceiling: f32,
172}
173
174const MIN_FUSED_TASK_ELEMS: usize = 4096;
178
179impl InplaceOp2 for ClampedExpAdd {
180 fn name(&self) -> &'static str {
181 "clamped-exp-add"
182 }
183
184 fn cpu_fwd(
185 &self,
186 s1: &mut CpuStorage,
187 l1: &Layout,
188 s2: &CpuStorage,
189 l2: &Layout,
190 ) -> candle_core::Result<()> {
191 let (CpuStorage::F32(lhs), CpuStorage::F32(rhs)) = (s1, s2) else {
192 candle_core::bail!("clamped-exp-add: expected f32 storage on both operands");
193 };
194 let (n, f) = l1.shape().dims2()?;
195
196 let lhs = &mut lhs[l1.start_offset()..l1.start_offset() + n * f];
199 let rhs = &rhs[l2.start_offset()..l2.start_offset() + l2.shape().elem_count()];
200
201 let (ceiling, broadcast) = (self.ceiling, self.broadcast);
202 let rows_per_task = MIN_FUSED_TASK_ELEMS.div_ceil(f.max(1)).max(1);
203 lhs.par_chunks_mut(rows_per_task * f)
204 .enumerate()
205 .for_each(|(t, block)| {
206 for (r, row) in block.chunks_mut(f).enumerate() {
207 let i = t * rows_per_task + r;
208 match broadcast {
209 Broadcast::Element => apply(row, &rhs[i * f..(i + 1) * f], ceiling),
210 Broadcast::Column => apply(row, rhs, ceiling),
211 Broadcast::Row => {
212 let b = rhs[i];
213 for x in row.iter_mut() {
214 *x = (*x + b).min(ceiling).exp();
215 }
216 }
217 }
218 }
219 });
220 Ok(())
221 }
222}
223
224#[inline]
226fn apply(row: &mut [f32], off: &[f32], ceiling: f32) {
227 for (x, &b) in row.iter_mut().zip(off) {
228 *x = (*x + b).min(ceiling).exp();
229 }
230}
231
232#[cfg(test)]
233#[path = "tensor_util_tests.rs"]
234mod tests;