drbh commited on
Commit ·
6c273f0
1
Parent(s): e86cb2b
feat: implement mel-spectrogram with bindings
Browse files- .gitignore +1 -0
- build.toml +16 -0
- ext-torch/__init__.py +15 -0
- ext-torch/registration.h +27 -0
- ext-torch/torch_binding.cpp +12 -0
- ext-torch/torch_binding.h +10 -0
- mel_spectrogram/mel_spectrogram.cu +163 -0
.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
py_example
|
build.toml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[general]
|
| 2 |
+
version = "0.0.1"
|
| 3 |
+
|
| 4 |
+
[torch]
|
| 5 |
+
name = "mel_spectrogram"
|
| 6 |
+
src = [
|
| 7 |
+
"ext-torch/registration.h",
|
| 8 |
+
"ext-torch/torch_binding.cpp",
|
| 9 |
+
"ext-torch/torch_binding.h",
|
| 10 |
+
]
|
| 11 |
+
pysrc = ["ext-torch/__init__.py"]
|
| 12 |
+
|
| 13 |
+
[kernel.mel_spectrogram]
|
| 14 |
+
capabilities = ["7.0", "7.2", "7.5", "8.0", "8.6", "8.7", "8.9", "9.0"]
|
| 15 |
+
src = ["mel_spectrogram/mel_spectrogram.cu"]
|
| 16 |
+
depends = ["torch"]
|
ext-torch/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
try:
|
| 4 |
+
from ._ops import ops
|
| 5 |
+
except ImportError as e:
|
| 6 |
+
# Fallback for local development.
|
| 7 |
+
try:
|
| 8 |
+
import _mel_spectrogram
|
| 9 |
+
ops = torch.ops._mel_spectrogram
|
| 10 |
+
except ImportError:
|
| 11 |
+
raise e
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def mel_spectrogram(out: torch.Tensor, samples: torch.Tensor, filters: torch.Tensor, fft_size: int, fft_step: int) -> torch.Tensor:
|
| 15 |
+
return ops.mel_spectrogram(out, samples, filters, fft_size, fft_step)
|
ext-torch/registration.h
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#include <Python.h>
|
| 4 |
+
|
| 5 |
+
#define _CONCAT(A, B) A##B
|
| 6 |
+
#define CONCAT(A, B) _CONCAT(A, B)
|
| 7 |
+
|
| 8 |
+
#define _STRINGIFY(A) #A
|
| 9 |
+
#define STRINGIFY(A) _STRINGIFY(A)
|
| 10 |
+
|
| 11 |
+
// A version of the TORCH_LIBRARY macro that expands the NAME, i.e. so NAME
|
| 12 |
+
// could be a macro instead of a literal token.
|
| 13 |
+
#define TORCH_LIBRARY_EXPAND(NAME, MODULE) TORCH_LIBRARY(NAME, MODULE)
|
| 14 |
+
|
| 15 |
+
// A version of the TORCH_LIBRARY_IMPL macro that expands the NAME, i.e. so NAME
|
| 16 |
+
// could be a macro instead of a literal token.
|
| 17 |
+
#define TORCH_LIBRARY_IMPL_EXPAND(NAME, DEVICE, MODULE) \
|
| 18 |
+
TORCH_LIBRARY_IMPL(NAME, DEVICE, MODULE)
|
| 19 |
+
|
| 20 |
+
// REGISTER_EXTENSION allows the shared library to be loaded and initialized
|
| 21 |
+
// via python's import statement.
|
| 22 |
+
#define REGISTER_EXTENSION(NAME) \
|
| 23 |
+
PyMODINIT_FUNC CONCAT(PyInit_, NAME)() { \
|
| 24 |
+
static struct PyModuleDef module = {PyModuleDef_HEAD_INIT, \
|
| 25 |
+
STRINGIFY(NAME), nullptr, 0, nullptr}; \
|
| 26 |
+
return PyModule_Create(&module); \
|
| 27 |
+
}
|
ext-torch/torch_binding.cpp
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <torch/library.h>
|
| 2 |
+
|
| 3 |
+
#include "registration.h"
|
| 4 |
+
#include "torch_binding.h"
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
|
| 8 |
+
ops.def("mel_spectrogram(Tensor out, Tensor samples, Tensor filters, int64_t fft_size, int64_t fft_step) -> Tensor", &mel_spectrogram);
|
| 9 |
+
ops.impl("mel_spectrogram", torch::kCUDA, &mel_spectrogram);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
REGISTER_EXTENSION(TORCH_EXTENSION_NAME)
|
ext-torch/torch_binding.h
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#include <torch/torch.h>
|
| 4 |
+
|
| 5 |
+
void mel_spectrogram(
|
| 6 |
+
torch::Tensor out,
|
| 7 |
+
torch::Tensor samples,
|
| 8 |
+
torch::Tensor filters,
|
| 9 |
+
int64_t fft_size,
|
| 10 |
+
int64_t fft_step);
|
mel_spectrogram/mel_spectrogram.cu
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <ATen/cuda/CUDAContext.h>
|
| 2 |
+
#include <torch/all.h>
|
| 3 |
+
#include <c10/cuda/CUDAGuard.h>
|
| 4 |
+
#include <cufft.h>
|
| 5 |
+
#include <cuda_runtime.h>
|
| 6 |
+
#include <device_launch_parameters.h>
|
| 7 |
+
|
| 8 |
+
const float PI = 3.14159265358979323846f;
|
| 9 |
+
const float EPS = 1e-10f;
|
| 10 |
+
|
| 11 |
+
// small helper function to perform atomic max on floats
|
| 12 |
+
__device__ __forceinline__ float atomicMaxFloat(float* address, float val) {
|
| 13 |
+
unsigned int* address_as_uint = (unsigned int*)address;
|
| 14 |
+
unsigned int old = *address_as_uint;
|
| 15 |
+
unsigned int assumed;
|
| 16 |
+
do {
|
| 17 |
+
assumed = old;
|
| 18 |
+
old = atomicCAS(address_as_uint, assumed,
|
| 19 |
+
__float_as_uint(fmaxf(val, __uint_as_float(assumed))));
|
| 20 |
+
} while (assumed != old);
|
| 21 |
+
return __uint_as_float(old);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
__global__ void apply_hanning_window_kernel(
|
| 26 |
+
float* __restrict__ windowed_samples,
|
| 27 |
+
const float* __restrict__ samples,
|
| 28 |
+
const int fft_size,
|
| 29 |
+
const int n_samples,
|
| 30 |
+
const int fft_step) {
|
| 31 |
+
|
| 32 |
+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
| 33 |
+
int stride = blockDim.x * gridDim.x;
|
| 34 |
+
|
| 35 |
+
// max FFT size of 1024
|
| 36 |
+
__shared__ float hann[1024];
|
| 37 |
+
if (threadIdx.x < fft_size) {
|
| 38 |
+
hann[threadIdx.x] = 0.5f * (1.0f - cosf((2.0f * PI * threadIdx.x) / (float)fft_size));
|
| 39 |
+
}
|
| 40 |
+
__syncthreads();
|
| 41 |
+
|
| 42 |
+
for (int i = idx; i < n_samples; i += stride) {
|
| 43 |
+
int segment = i / fft_step;
|
| 44 |
+
int offset = i % fft_size;
|
| 45 |
+
if (offset < fft_size && (segment * fft_step + offset) < n_samples) {
|
| 46 |
+
windowed_samples[segment * fft_size + offset] =
|
| 47 |
+
samples[segment * fft_step + offset] * hann[offset];
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
__global__ void compute_mel_energies_kernel(
|
| 53 |
+
float* __restrict__ mel_spec,
|
| 54 |
+
const float* __restrict__ fft_magnitudes,
|
| 55 |
+
const float* __restrict__ mel_filters,
|
| 56 |
+
const int n_mel,
|
| 57 |
+
const int n_fft,
|
| 58 |
+
const int n_frames) {
|
| 59 |
+
|
| 60 |
+
int mel_idx = blockIdx.y;
|
| 61 |
+
int frame_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
| 62 |
+
|
| 63 |
+
if (mel_idx < n_mel && frame_idx < n_frames) {
|
| 64 |
+
float sum = 0.0f;
|
| 65 |
+
|
| 66 |
+
// sum up the FFT magnitudes for each mel filter
|
| 67 |
+
for (int k = 0; k < n_fft; k++) {
|
| 68 |
+
sum += fft_magnitudes[frame_idx * n_fft + k] *
|
| 69 |
+
mel_filters[mel_idx * n_fft + k];
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
sum = log10f(fmaxf(sum, EPS));
|
| 73 |
+
mel_spec[mel_idx * n_frames + frame_idx] = sum;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
__global__ void normalize_mel_spec_kernel(
|
| 78 |
+
float* __restrict__ mel_spec,
|
| 79 |
+
const int n_mel,
|
| 80 |
+
const int n_frames) {
|
| 81 |
+
|
| 82 |
+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
| 83 |
+
int stride = blockDim.x * gridDim.x;
|
| 84 |
+
|
| 85 |
+
__shared__ float block_max;
|
| 86 |
+
if (threadIdx.x == 0) {
|
| 87 |
+
block_max = -INFINITY;
|
| 88 |
+
}
|
| 89 |
+
__syncthreads();
|
| 90 |
+
|
| 91 |
+
for (int i = idx; i < n_mel * n_frames; i += stride) {
|
| 92 |
+
atomicMaxFloat(&block_max, mel_spec[i]);
|
| 93 |
+
}
|
| 94 |
+
__syncthreads();
|
| 95 |
+
|
| 96 |
+
float mmax = block_max - 8.0f;
|
| 97 |
+
for (int i = idx; i < n_mel * n_frames; i += stride) {
|
| 98 |
+
mel_spec[i] = fmaxf(mel_spec[i], mmax) / 4.0f + 1.0f;
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
void mel_spectrogram(
|
| 103 |
+
torch::Tensor& out, // [n_mel, n_frames]
|
| 104 |
+
torch::Tensor& samples, // [n_samples]
|
| 105 |
+
torch::Tensor& filters, // [n_mel, n_fft]
|
| 106 |
+
const int fft_size,
|
| 107 |
+
const int fft_step) {
|
| 108 |
+
|
| 109 |
+
TORCH_CHECK(samples.is_cuda(), "Input samples must be a CUDA tensor");
|
| 110 |
+
TORCH_CHECK(filters.is_cuda(), "Mel filters must be a CUDA tensor");
|
| 111 |
+
TORCH_CHECK(samples.scalar_type() == torch::kFloat32, "Input samples must be float32");
|
| 112 |
+
TORCH_CHECK(filters.scalar_type() == torch::kFloat32, "Mel filters must be float32");
|
| 113 |
+
|
| 114 |
+
const int n_samples = samples.size(0);
|
| 115 |
+
const int n_frames = n_samples / fft_step;
|
| 116 |
+
const int n_mel = filters.size(0);
|
| 117 |
+
const int n_fft = 1 + fft_size / 2;
|
| 118 |
+
|
| 119 |
+
// set up tensors
|
| 120 |
+
auto windowed = torch::empty({n_frames, fft_size},
|
| 121 |
+
torch::dtype(torch::kFloat32).device(samples.device()));
|
| 122 |
+
auto fft_out = torch::empty({n_frames, n_fft},
|
| 123 |
+
torch::dtype(torch::kFloat32).device(samples.device()));
|
| 124 |
+
|
| 125 |
+
// set up kernel parameters
|
| 126 |
+
const int threads = 256;
|
| 127 |
+
dim3 block(threads);
|
| 128 |
+
dim3 grid((n_samples + threads - 1) / threads);
|
| 129 |
+
dim3 mel_grid(n_frames / threads + 1, n_mel);
|
| 130 |
+
|
| 131 |
+
const at::cuda::OptionalCUDAGuard device_guard(device_of(samples));
|
| 132 |
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
| 133 |
+
|
| 134 |
+
// apply window function
|
| 135 |
+
apply_hanning_window_kernel<<<grid, block, 0, stream>>>(
|
| 136 |
+
windowed.data_ptr<float>(),
|
| 137 |
+
samples.data_ptr<float>(),
|
| 138 |
+
fft_size,
|
| 139 |
+
n_samples,
|
| 140 |
+
fft_step);
|
| 141 |
+
|
| 142 |
+
cufftHandle plan;
|
| 143 |
+
cufftPlan1d(&plan, fft_size, CUFFT_R2C, n_frames);
|
| 144 |
+
cufftSetStream(plan, stream);
|
| 145 |
+
cufftExecR2C(plan, windowed.data_ptr<float>(), (cufftComplex*)fft_out.data_ptr<float>());
|
| 146 |
+
|
| 147 |
+
// compute mel energies
|
| 148 |
+
compute_mel_energies_kernel<<<mel_grid, block, 0, stream>>>(
|
| 149 |
+
out.data_ptr<float>(),
|
| 150 |
+
fft_out.data_ptr<float>(),
|
| 151 |
+
filters.data_ptr<float>(),
|
| 152 |
+
n_mel,
|
| 153 |
+
n_fft,
|
| 154 |
+
n_frames);
|
| 155 |
+
|
| 156 |
+
// normalize
|
| 157 |
+
normalize_mel_spec_kernel<<<grid, block, 0, stream>>>(
|
| 158 |
+
out.data_ptr<float>(),
|
| 159 |
+
n_mel,
|
| 160 |
+
n_frames);
|
| 161 |
+
|
| 162 |
+
cufftDestroy(plan);
|
| 163 |
+
}
|