| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <ATen/cuda/CUDAContext.h> |
| #include <c10/cuda/CUDAGuard.h> |
| #include <torch/all.h> |
| #include <cfloat> |
| #include <climits> |
| #include <cstdint> |
|
|
| namespace { |
|
|
| constexpr int kThreads = 256; |
| constexpr int kNegBig = -(1 << 24); |
|
|
| __device__ __forceinline__ int imax3(int a, int b, int c) { |
| #if __CUDA_ARCH__ >= 800 |
| return __vimax3_s32(a, b, c); |
| #else |
| return max(a, max(b, c)); |
| #endif |
| } |
|
|
| __device__ __forceinline__ int imin3(int a, int b, int c) { |
| #if __CUDA_ARCH__ >= 800 |
| return __vimin3_s32(a, b, c); |
| #else |
| return min(a, min(b, c)); |
| #endif |
| } |
|
|
| __device__ int block_imax(int v) { |
| __shared__ int smem[kThreads]; |
| smem[threadIdx.x] = v; |
| __syncthreads(); |
| for (int s = kThreads / 2; s > 0; s >>= 1) { |
| if (threadIdx.x < s) smem[threadIdx.x] = max(smem[threadIdx.x], smem[threadIdx.x + s]); |
| __syncthreads(); |
| } |
| int r = smem[0]; |
| __syncthreads(); |
| return r; |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| __global__ void viterbi_i32_kernel(const int32_t* __restrict__ emit , |
| const int32_t* __restrict__ trans , |
| const int32_t* __restrict__ prior , int64_t B, |
| int64_t T, int64_t S, int32_t* __restrict__ bp , |
| int32_t* __restrict__ path , |
| int64_t* __restrict__ score , |
| int32_t* __restrict__ work ) { |
| const int64_t b = blockIdx.x; |
| int32_t* prev = work + b * 2 * S; |
| int32_t* curr = prev + S; |
| __shared__ int64_t norm_acc; |
| if (threadIdx.x == 0) norm_acc = 0; |
|
|
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) { |
| prev[j] = prior[j] + emit[(b * T + 0) * S + j]; |
| } |
| __syncthreads(); |
|
|
| for (int64_t t = 1; t < T; t++) { |
| |
| int mx = INT_MIN; |
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) mx = max(mx, prev[j]); |
| mx = block_imax(mx); |
| if (threadIdx.x == 0) norm_acc += mx; |
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) prev[j] -= mx; |
| __syncthreads(); |
|
|
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) { |
| |
| |
| int best = INT_MIN; |
| int64_t i = 0; |
| for (; i + 2 <= S; i += 2) { |
| const int s1 = max(prev[i] + trans[i * S + j], -32768); |
| const int s2 = max(prev[i + 1] + trans[(i + 1) * S + j], -32768); |
| const int p1 = (s1 << 16) | (int)i; |
| const int p2 = (s2 << 16) | (int)(i + 1); |
| best = imax3(best, p1, p2); |
| } |
| if (i < S) { |
| const int s1 = max(prev[i] + trans[i * S + j], -32768); |
| best = max(best, (s1 << 16) | (int)i); |
| } |
| curr[j] = (best >> 16) + emit[(b * T + t) * S + j]; |
| bp[(b * T + t) * S + j] = best & 0xFFFF; |
| } |
| __syncthreads(); |
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) prev[j] = curr[j]; |
| __syncthreads(); |
| } |
|
|
| if (threadIdx.x == 0) { |
| int best = INT_MIN; |
| int bj = 0; |
| for (int64_t j = 0; j < S; j++) { |
| if (prev[j] > best) { best = prev[j]; bj = (int)j; } |
| } |
| score[b] = (int64_t)best + norm_acc; |
| path[b * T + T - 1] = bj; |
| for (int64_t t = T - 1; t > 0; t--) { |
| bj = bp[(b * T + t) * S + bj]; |
| path[b * T + t - 1] = bj; |
| } |
| } |
| } |
|
|
| |
| __global__ void viterbi_f32_kernel(const float* __restrict__ emit, const float* __restrict__ trans, |
| const float* __restrict__ prior, int64_t B, int64_t T, |
| int64_t S, int32_t* __restrict__ bp, |
| int32_t* __restrict__ path, float* __restrict__ score, |
| float* __restrict__ work) { |
| const int64_t b = blockIdx.x; |
| float* prev = work + b * 2 * S; |
| float* curr = prev + S; |
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) { |
| prev[j] = prior[j] + emit[(b * T + 0) * S + j]; |
| } |
| __syncthreads(); |
| for (int64_t t = 1; t < T; t++) { |
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) { |
| float best = -FLT_MAX; |
| int bi = 0; |
| for (int64_t i = 0; i < S; i++) { |
| const float c = prev[i] + trans[i * S + j]; |
| if (c > best) { best = c; bi = (int)i; } |
| } |
| curr[j] = best + emit[(b * T + t) * S + j]; |
| bp[(b * T + t) * S + j] = bi; |
| } |
| __syncthreads(); |
| for (int64_t j = threadIdx.x; j < S; j += blockDim.x) prev[j] = curr[j]; |
| __syncthreads(); |
| } |
| if (threadIdx.x == 0) { |
| float best = -FLT_MAX; |
| int bj = 0; |
| for (int64_t j = 0; j < S; j++) { |
| if (prev[j] > best) { best = prev[j]; bj = (int)j; } |
| } |
| score[b] = best; |
| path[b * T + T - 1] = bj; |
| for (int64_t t = T - 1; t > 0; t--) { |
| bj = bp[(b * T + t) * S + bj]; |
| path[b * T + t - 1] = bj; |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
|
|
| template <typename T_, bool kIsInt> |
| __global__ void dtw_kernel(const T_* __restrict__ cost , int64_t B, int64_t N, |
| int64_t M, T_* __restrict__ D , |
| int32_t* __restrict__ path , |
| int32_t* __restrict__ path_len ) { |
| const int64_t b = blockIdx.x; |
| const T_* c = cost + b * N * M; |
| T_* d = D + b * N * M; |
| const T_ BIG = kIsInt ? (T_)(1 << 30) : (T_)FLT_MAX; |
|
|
| for (int64_t diag = 0; diag < N + M - 1; diag++) { |
| const int64_t i_lo = diag < M ? 0 : diag - M + 1; |
| const int64_t i_hi = diag < N ? diag : N - 1; |
| for (int64_t i = i_lo + threadIdx.x; i <= i_hi; i += blockDim.x) { |
| const int64_t j = diag - i; |
| T_ up = (i > 0) ? d[(i - 1) * M + j] : BIG; |
| T_ left = (j > 0) ? d[i * M + j - 1] : BIG; |
| T_ ul = (i > 0 && j > 0) ? d[(i - 1) * M + j - 1] : ((i == 0 && j == 0) ? (T_)0 : BIG); |
| T_ m; |
| if (kIsInt) { |
| m = (T_)imin3((int)up, (int)left, (int)ul); |
| } else { |
| m = fminf((float)up, fminf((float)left, (float)ul)); |
| } |
| d[i * M + j] = c[i * M + j] + m; |
| } |
| __syncthreads(); |
| } |
|
|
| if (threadIdx.x == 0) { |
| int64_t i = N - 1, j = M - 1, n = 0; |
| int32_t* p = path + b * (N + M) * 2; |
| while (true) { |
| p[n * 2] = (int32_t)i; |
| p[n * 2 + 1] = (int32_t)j; |
| n++; |
| if (i == 0 && j == 0) break; |
| const T_ up = (i > 0) ? d[(i - 1) * M + j] : BIG; |
| const T_ left = (j > 0) ? d[i * M + j - 1] : BIG; |
| const T_ ul = (i > 0 && j > 0) ? d[(i - 1) * M + j - 1] : BIG; |
| if (ul <= up && ul <= left) { i--; j--; } |
| else if (up <= left) { i--; } |
| else { j--; } |
| } |
| path_len[b] = (int32_t)n; |
| for (int64_t k = n; k < N + M; k++) { p[k * 2] = -1; p[k * 2 + 1] = -1; } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| template <typename T_, bool kIsInt> |
| __global__ void ctc_align_kernel(const T_* __restrict__ logp , |
| const int64_t* __restrict__ tgt , int64_t B, |
| int64_t T, int64_t C, int64_t L, int64_t blank, |
| uint8_t* __restrict__ bp , |
| int32_t* __restrict__ frames , |
| T_* __restrict__ score , |
| T_* __restrict__ work ) { |
| const int64_t b = blockIdx.x; |
| int64_t Lb = 0; |
| for (int64_t l = 0; l < L; l++) { |
| if (tgt[b * L + l] >= 0) Lb++; |
| } |
| const int64_t Sn = 2 * Lb + 1; |
| T_* prev = work + b * 2 * (2 * L + 1); |
| T_* curr = prev + (2 * L + 1); |
| const T_ NEG = kIsInt ? (T_)kNegBig : (T_)(-FLT_MAX / 2); |
|
|
| auto state_label = [&](int64_t s) -> int64_t { |
| return (s % 2 == 0) ? blank : tgt[b * L + s / 2]; |
| }; |
|
|
| for (int64_t s = threadIdx.x; s < Sn; s += blockDim.x) { |
| prev[s] = (s <= 1) ? logp[(b * T) * C + state_label(s)] : NEG; |
| } |
| __syncthreads(); |
|
|
| for (int64_t t = 1; t < T; t++) { |
| for (int64_t s = threadIdx.x; s < Sn; s += blockDim.x) { |
| const T_ stay = prev[s]; |
| const T_ from1 = (s >= 1) ? prev[s - 1] : NEG; |
| T_ from2 = NEG; |
| if (s >= 2 && s % 2 == 1 && state_label(s) != state_label(s - 2)) from2 = prev[s - 2]; |
| T_ best; |
| int choice; |
| if (kIsInt) { |
| best = (T_)imax3((int)stay, (int)from1, (int)from2); |
| } else { |
| best = fmaxf((float)stay, fmaxf((float)from1, (float)from2)); |
| } |
| choice = (best == stay) ? 0 : (best == from1 ? 1 : 2); |
| curr[s] = best + logp[(b * T + t) * C + state_label(s)]; |
| bp[(b * T + t) * (2 * L + 1) + s] = (uint8_t)choice; |
| } |
| __syncthreads(); |
| for (int64_t s = threadIdx.x; s < Sn; s += blockDim.x) prev[s] = curr[s]; |
| __syncthreads(); |
| } |
|
|
| if (threadIdx.x == 0) { |
| int64_t s = (Sn >= 2 && prev[Sn - 1] < prev[Sn - 2]) ? Sn - 2 : Sn - 1; |
| score[b] = prev[s]; |
| for (int64_t t = T - 1; t >= 0; t--) { |
| frames[b * T + t] = (int32_t)state_label(s); |
| if (t > 0) s -= bp[(b * T + t) * (2 * L + 1) + s]; |
| } |
| } |
| } |
|
|
| } |
|
|
| void viterbi(torch::Tensor& path, torch::Tensor& score, torch::Tensor& bp, |
| torch::Tensor const& emissions, torch::Tensor const& transitions, |
| torch::Tensor const& priors) { |
| TORCH_CHECK(emissions.is_cuda() && emissions.dim() == 3 && emissions.is_contiguous(), |
| "emissions must be contiguous [B, T, S] CUDA"); |
| const int64_t B = emissions.size(0), T = emissions.size(1), S = emissions.size(2); |
| TORCH_CHECK(S <= 32768, "S <= 32768 (packed argmax uses 16 bits)"); |
| TORCH_CHECK(transitions.sizes() == torch::IntArrayRef({S, S}) && transitions.is_contiguous(), |
| "transitions must be [S, S]"); |
| TORCH_CHECK(priors.numel() == S && priors.is_contiguous(), "priors must be [S]"); |
| TORCH_CHECK(path.dtype() == torch::kInt32 && path.sizes() == torch::IntArrayRef({B, T})); |
| TORCH_CHECK(bp.dtype() == torch::kInt32 && bp.sizes() == torch::IntArrayRef({B, T, S})); |
| const at::cuda::CUDAGuard guard(emissions.device()); |
| cudaStream_t stream = at::cuda::getCurrentCUDAStream(); |
|
|
| if (emissions.dtype() == torch::kInt32) { |
| TORCH_CHECK(transitions.dtype() == torch::kInt32 && priors.dtype() == torch::kInt32, |
| "dtype mismatch"); |
| TORCH_CHECK(score.dtype() == torch::kInt64 && score.numel() == B); |
| auto work = torch::empty({B, 2 * S}, emissions.options()); |
| viterbi_i32_kernel<<<B, kThreads, 0, stream>>>( |
| emissions.const_data_ptr<int32_t>(), transitions.const_data_ptr<int32_t>(), |
| priors.const_data_ptr<int32_t>(), B, T, S, bp.data_ptr<int32_t>(), |
| path.data_ptr<int32_t>(), score.data_ptr<int64_t>(), work.data_ptr<int32_t>()); |
| } else if (emissions.dtype() == torch::kFloat32) { |
| TORCH_CHECK(transitions.dtype() == torch::kFloat32 && priors.dtype() == torch::kFloat32, |
| "dtype mismatch"); |
| TORCH_CHECK(score.dtype() == torch::kFloat32 && score.numel() == B); |
| auto work = torch::empty({B, 2 * S}, emissions.options()); |
| viterbi_f32_kernel<<<B, kThreads, 0, stream>>>( |
| emissions.const_data_ptr<float>(), transitions.const_data_ptr<float>(), |
| priors.const_data_ptr<float>(), B, T, S, bp.data_ptr<int32_t>(), |
| path.data_ptr<int32_t>(), score.data_ptr<float>(), work.data_ptr<float>()); |
| } else { |
| TORCH_CHECK(false, "emissions must be int32 (DPX path) or float32"); |
| } |
| } |
|
|
| void dtw(torch::Tensor& path, torch::Tensor& path_len, torch::Tensor& D, |
| torch::Tensor const& cost) { |
| TORCH_CHECK(cost.is_cuda() && cost.dim() == 3 && cost.is_contiguous(), |
| "cost must be contiguous [B, N, M] CUDA"); |
| const int64_t B = cost.size(0), N = cost.size(1), M = cost.size(2); |
| TORCH_CHECK(D.sizes() == cost.sizes() && D.dtype() == cost.dtype()); |
| TORCH_CHECK(path.dtype() == torch::kInt32 && |
| path.sizes() == torch::IntArrayRef({B, N + M, 2})); |
| TORCH_CHECK(path_len.dtype() == torch::kInt32 && path_len.numel() == B); |
| const at::cuda::CUDAGuard guard(cost.device()); |
| cudaStream_t stream = at::cuda::getCurrentCUDAStream(); |
| if (cost.dtype() == torch::kInt32) { |
| dtw_kernel<int32_t, true><<<B, kThreads, 0, stream>>>( |
| cost.const_data_ptr<int32_t>(), B, N, M, D.data_ptr<int32_t>(), path.data_ptr<int32_t>(), |
| path_len.data_ptr<int32_t>()); |
| } else if (cost.dtype() == torch::kFloat32) { |
| dtw_kernel<float, false><<<B, kThreads, 0, stream>>>( |
| cost.const_data_ptr<float>(), B, N, M, D.data_ptr<float>(), path.data_ptr<int32_t>(), |
| path_len.data_ptr<int32_t>()); |
| } else { |
| TORCH_CHECK(false, "cost must be int32 (DPX path) or float32"); |
| } |
| } |
|
|
| void ctc_forced_align(torch::Tensor& frames, torch::Tensor& score, torch::Tensor const& log_probs, |
| torch::Tensor const& targets, int64_t blank) { |
| TORCH_CHECK(log_probs.is_cuda() && log_probs.dim() == 3 && log_probs.is_contiguous(), |
| "log_probs must be contiguous [B, T, C] CUDA"); |
| const int64_t B = log_probs.size(0), T = log_probs.size(1), C = log_probs.size(2); |
| TORCH_CHECK(targets.is_cuda() && targets.dtype() == torch::kInt64 && targets.dim() == 2 && |
| targets.size(0) == B && targets.is_contiguous(), |
| "targets must be contiguous int64 [B, L] (-1 padded)"); |
| const int64_t L = targets.size(1); |
| TORCH_CHECK(frames.dtype() == torch::kInt32 && frames.sizes() == torch::IntArrayRef({B, T})); |
| TORCH_CHECK(blank >= 0 && blank < C, "blank out of range"); |
| const at::cuda::CUDAGuard guard(log_probs.device()); |
| cudaStream_t stream = at::cuda::getCurrentCUDAStream(); |
| auto bp = torch::empty({B, T, 2 * L + 1}, log_probs.options().dtype(torch::kUInt8)); |
| if (log_probs.dtype() == torch::kInt32) { |
| TORCH_CHECK(score.dtype() == torch::kInt32 && score.numel() == B); |
| auto work = torch::empty({B, 2 * (2 * L + 1)}, log_probs.options()); |
| ctc_align_kernel<int32_t, true><<<B, kThreads, 0, stream>>>( |
| log_probs.const_data_ptr<int32_t>(), targets.const_data_ptr<int64_t>(), B, T, C, L, blank, |
| bp.data_ptr<uint8_t>(), frames.data_ptr<int32_t>(), score.data_ptr<int32_t>(), |
| work.data_ptr<int32_t>()); |
| } else if (log_probs.dtype() == torch::kFloat32) { |
| TORCH_CHECK(score.dtype() == torch::kFloat32 && score.numel() == B); |
| auto work = torch::empty({B, 2 * (2 * L + 1)}, log_probs.options()); |
| ctc_align_kernel<float, false><<<B, kThreads, 0, stream>>>( |
| log_probs.const_data_ptr<float>(), targets.const_data_ptr<int64_t>(), B, T, C, L, blank, |
| bp.data_ptr<uint8_t>(), frames.data_ptr<int32_t>(), score.data_ptr<float>(), |
| work.data_ptr<float>()); |
| } else { |
| TORCH_CHECK(false, "log_probs must be int32 (DPX path) or float32"); |
| } |
| } |
|
|