drbh commited on
Commit
6c273f0
·
1 Parent(s): e86cb2b

feat: implement mel-spectrogram with bindings

Browse files
.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
+ }