use ruda_kernel::dsl as kernel_dsl;
use ruda_kernel::dsl::{Runtime, prelude::*};
use ruda_kernel::tensor::{RudaTensor, allocation::empty_device_dtype, contiguous::into_contiguous};
use ruda_core::tensor::{Shape, TensorMetadata};
use super::common::{SHARED_ALPHA_CAPACITY, empty_input_nll, finalize_nll, l_prime_class, recurrence_step};
#[ruda(launch)]
fn ctc_loss_kernel<F: Float, I: Numeric>(
log_probs: &Tensor<F>, targets: &Tensor<I>, input_lengths: &Tensor<I>, target_lengths: &Tensor<I>, output: &mut Tensor<F>, blank: u32,
#[comptime] alpha_capacity: u32,
#[define(F, I)] _dtypes: [StorageType; 2],
) {
let n = RUDA_POS_X as usize;
let ruda_dim = RUDA_DIM_X as usize;
let alpha_cap = alpha_capacity as usize;
let blank_u = blank as usize;
let target_len = u32::cast_from(target_lengths[n]) as usize;
let input_len = u32::cast_from(input_lengths[n]) as usize;
let l_prime_len = 2 * target_len + 1;
if input_len == 0 {
if UNIT_POS_X == 0 {
output[n] = empty_input_nll::<F>(target_len);
}
terminate!();
}
let lp_t = log_probs.stride(0);
let lp_n = log_probs.stride(1);
let lp_c = log_probs.stride(2);
let tgt_n = targets.stride(0);
let tgt_s = targets.stride(1);
let mut alpha = SharedMemory::<F>::new(2 * alpha_cap);
let neg_inf = F::new(-6.0e4_f32);
let unreachable_threshold = F::new(-1.0e4_f32);
let one = F::new(1.0);
let mut s = UNIT_POS_X as usize;
while s < l_prime_len {
let mut init = neg_inf;
if s == 0 {
init = log_probs[n * lp_n + blank_u * lp_c];
} else if s == 1 {
let l1 = u32::cast_from(targets[n * tgt_n]) as usize;
init = log_probs[n * lp_n + l1 * lp_c];
}
alpha[s] = init;
s += ruda_dim;
}
sync_ruda();
for t in 1..input_len {
let mut s = UNIT_POS_X as usize;
while s < l_prime_len {
let l_class = l_prime_class::<I>(s, targets, n, tgt_n, tgt_s, blank_u);
let log_p = log_probs[t * lp_t + n * lp_n + l_class * lp_c];
let l_class_m2 = if s >= 2 {
l_prime_class::<I>(s - 2, targets, n, tgt_n, tgt_s, blank_u)
} else {
blank_u
};
let skip_allowed = s >= 2 && l_class != blank_u && l_class != l_class_m2;
let a_s = alpha[s];
let mut a_s_m1 = neg_inf;
if s >= 1 {
a_s_m1 = alpha[s - 1];
}
let mut a_s_m2 = neg_inf;
if s >= 2 {
a_s_m2 = alpha[s - 2];
}
alpha[alpha_cap + s] = recurrence_step::<F>(
a_s,
a_s_m1,
a_s_m2,
log_p,
skip_allowed,
unreachable_threshold,
one,
);
s += ruda_dim;
}
sync_ruda();
let mut s = UNIT_POS_X as usize;
while s < l_prime_len {
alpha[s] = alpha[alpha_cap + s];
s += ruda_dim;
}
sync_ruda();
}
if UNIT_POS_X == 0 {
let last_blank = alpha[2 * target_len];
let mut last_label = neg_inf;
if target_len > 0 {
last_label = alpha[2 * target_len - 1];
}
output[n] = finalize_nll::<F>(
last_blank,
last_label,
target_len,
unreachable_threshold,
one,
);
}
}
pub fn ctc_loss<R: Runtime>(
log_probs: RudaTensor<R>,
targets: RudaTensor<R>,
input_lengths: RudaTensor<R>,
target_lengths: RudaTensor<R>,
blank: usize,
) -> RudaTensor<R> {
let log_probs = into_contiguous(log_probs);
let targets = into_contiguous(targets);
let input_lengths = into_contiguous(input_lengths);
let target_lengths = into_contiguous(target_lengths);
let log_probs_shape = log_probs.shape();
let [_t, batch_size, _c] = log_probs_shape.dims::<3>();
let target_shape = targets.shape();
let max_target_len = target_shape.dims::<2>()[1];
let max_l_prime = 2 * max_target_len + 1;
assert!(
max_l_prime as u32 <= SHARED_ALPHA_CAPACITY,
"ctc_loss: 2 * max_target_len + 1 = {} exceeds the kernel's shared-memory \
alpha capacity ({}). Reduce target length or raise SHARED_ALPHA_CAPACITY.",
max_l_prime,
SHARED_ALPHA_CAPACITY,
);
let hw_max = log_probs.client.properties().hardware.max_ruda_dim.0;
let ruda_dim_x = (max_l_prime as u32).min(hw_max).min(256);
let client = log_probs.client.clone();
let device = log_probs.device.clone();
let f_dtype = log_probs.dtype;
let i_dtype = targets.dtype;
let output = empty_device_dtype::<R>(client.clone(), device, Shape::new([batch_size]), f_dtype);
let ruda_count = RudaCount::Static(batch_size as u32, 1, 1);
let ruda_dim = RudaDim::new_1d(ruda_dim_x);
ctc_loss_kernel::launch::<R>(
&client,
ruda_count,
ruda_dim,
log_probs.into_tensor_arg(),
targets.into_tensor_arg(),
input_lengths.into_tensor_arg(),
target_lengths.into_tensor_arg(),
output.clone().into_tensor_arg(),
blank as u32,
max_l_prime as u32,
[f_dtype.into(), i_dtype.into()],
);
output
}