use crate::{CpuStorage, Layout, Result, Shape, Tensor};
#[derive(Debug, Clone, Copy)]
struct HcSinkhorn {
iters: usize,
eps: f64,
}
fn row_norm(mat: &mut [f32], nh: usize, eps: f32) {
for r in 0..nh {
let mut s = eps;
for c in 0..nh {
s += mat[r * nh + c];
}
for c in 0..nh {
mat[r * nh + c] /= s;
}
}
}
fn col_norm(mat: &mut [f32], nh: usize, eps: f32) {
for c in 0..nh {
let mut s = eps;
for r in 0..nh {
s += mat[r * nh + c];
}
for r in 0..nh {
mat[r * nh + c] /= s;
}
}
}
fn sinkhorn_cpu_f32(data: &[f32], nmat: usize, nh: usize, iters: usize, eps: f32) -> Vec<f32> {
let mut out = vec![0f32; data.len()];
for m in 0..nmat {
let base = m * nh * nh;
let src = &data[base..base + nh * nh];
let mat = &mut out[base..base + nh * nh];
for r in 0..nh {
let row = &src[r * nh..r * nh + nh];
let mx = row.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0f32;
for c in 0..nh {
let e = (row[c] - mx).exp();
mat[r * nh + c] = e;
sum += e;
}
for c in 0..nh {
mat[r * nh + c] = mat[r * nh + c] / sum + eps;
}
}
col_norm(mat, nh, eps);
for _ in 1..iters {
row_norm(mat, nh, eps);
col_norm(mat, nh, eps);
}
}
out
}
impl crate::CustomOp1 for HcSinkhorn {
fn name(&self) -> &'static str {
"hc_sinkhorn"
}
fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)> {
let (o1, o2) = match layout.contiguous_offsets() {
Some(x) => x,
None => crate::bail!("hc_sinkhorn requires a contiguous input"),
};
let vs = match storage {
CpuStorage::F32(v) => &v[o1..o2],
_ => crate::bail!("hc_sinkhorn: f32 only"),
};
let dims = layout.shape().dims();
let nh = dims[dims.len() - 1];
let nmat = vs.len() / (nh * nh);
let out = sinkhorn_cpu_f32(vs, nmat, nh, self.iters, self.eps as f32);
Ok((CpuStorage::F32(out), layout.shape().clone()))
}
#[cfg(feature = "cuda")]
fn cuda_fwd(
&self,
storage: &crate::CudaStorage,
layout: &Layout,
) -> Result<(crate::CudaStorage, Shape)> {
use crate::cuda_backend::cudarc::driver::{LaunchConfig, PushKernelArg};
use crate::cuda_backend::{kernels, CudaStorageSlice as S, WrapErr};
let dev = &storage.device;
let (o1, o2) = match layout.contiguous_offsets() {
Some(x) => x,
None => crate::bail!("hc_sinkhorn requires a contiguous input"),
};
let src = storage.as_cuda_slice::<f32>()?.slice(o1..o2);
let dims = layout.shape().dims();
let nh = dims[dims.len() - 1];
let elem = layout.shape().elem_count();
let nmat = elem / (nh * nh);
let dst = unsafe { dev.alloc::<f32>(elem)? };
let func = dev.get_or_load_func("hc_sinkhorn_f32", &kernels::REDUCE)?;
let cfg = LaunchConfig {
grid_dim: (nmat as u32, 1, 1),
block_dim: (32, 1, 1),
shared_mem_bytes: (nh * nh * std::mem::size_of::<f32>()) as u32,
};
let stream = dev.cuda_stream();
let mut builder = stream.launch_builder(&func);
let nh_i = nh as i32;
let iters_i = self.iters as i32;
let eps = self.eps as f32;
builder
.arg(&src)
.arg(&dst)
.arg(&nh_i)
.arg(&iters_i)
.arg(&eps);
unsafe { builder.launch(cfg) }.w()?;
let out = crate::cuda_backend::CudaStorage {
slice: S::F32(dst),
device: dev.clone(),
};
Ok((out, layout.shape().clone()))
}
}
impl Tensor {
pub fn hc_sinkhorn(&self, iters: usize, eps: f64) -> Result<Tensor> {
let orig = self.dtype();
let x = self.to_dtype(crate::DType::F32)?.contiguous()?;
let r = x.apply_op1_no_bwd(&HcSinkhorn { iters, eps })?;
r.to_dtype(orig)
}
}