Skip to content

Commit 63fcfa7

Browse files
authored
Add host direct conv1d and conv2d (#1186)
Implement CPU direct convolution paths for conv1d and conv2d across host executors, matching existing CUDA cropping behavior for full, same, and valid modes. Add focused host executor tests for 1D and 2D convolution modes and update the executor compatibility documentation. Tested in localmatx:13.2-arm64: cmake --build build-codex-localmatx --target test_00_transform_ConvCorr -j20; ./test_00_transform_ConvCorr --gtest_filter="CorrelationConvolutionHostDirectTest*"
1 parent 580b8cc commit 63fcfa7

6 files changed

Lines changed: 404 additions & 49 deletions

File tree

docs_input/executor_compatibility.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ fused JIT expression; non-JIT CUDA execution through cudaExecutor remains availa
7676
"clone", "|yes|", "|yes|", "|yes|", "View expression."
7777
"concat", "|yes|", "|yes|", "|yes|", "View/expression composition."
7878
"conj", "|yes|", "|yes|", "|yes|", "Element-wise expression."
79-
"conv1d", "|partial|", "|yes|", "|no|", "Direct CUDA convolution is CUDA-only; FFT convolution can use the CPU FFT backend when enabled."
80-
"conv2d", "|no|", "|yes|", "|no|", "CUDA-only convolution transform."
79+
"conv1d", "|yes|", "|yes|", "|no|", "Direct convolution supports host and CUDA executors. FFT convolution can use the CPU FFT backend when enabled."
80+
"conv2d", "|yes|", "|yes|", "|no|", "Direct convolution supports host and CUDA executors."
8181
"copy", "|yes|", "|yes|", "|no|", "Executor-dispatched assignment/copy transform; CUDAJITExecutor fuses expression evaluation instead of using this transform directly."
82-
"corr", "|partial|", "|yes|", "|no|", "Correlation transform; host FFT backend may be required depending on mode and input."
82+
"corr", "|yes|", "|yes|", "|no|", "Direct correlation supports host and CUDA executors. FFT correlation can use the CPU FFT backend when enabled."
8383
"cos", "|yes|", "|yes|", "|yes|", "Element-wise expression."
8484
"cosh", "|yes|", "|yes|", "|yes|", "Element-wise expression."
8585
"cov", "|no|", "|yes|", "|no|", "CUDA-only covariance transform."

include/matx/executors/support.h

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,7 @@ constexpr bool CheckFFTSupport() {
8383

8484
template <typename Exec>
8585
constexpr bool CheckDirect1DConvSupport() {
86-
if constexpr (is_host_executor_v<Exec>) {
87-
return false;
88-
}
89-
else {
90-
return true;
91-
}
86+
return true;
9287
}
9388

9489
template <typename Exec, typename T>
@@ -103,12 +98,7 @@ constexpr bool CheckFFT1DConvSupport() {
10398

10499
template <typename Exec>
105100
constexpr bool Check2DConvSupport() {
106-
if constexpr (is_host_executor_v<Exec>) {
107-
return false;
108-
}
109-
else {
110-
return true;
111-
}
101+
return true;
112102
}
113103

114104
template <typename Exec, typename T>

include/matx/operators/conv.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ namespace matx
177177

178178
template <typename Out, typename Executor>
179179
void Exec(Out &&out, Executor &&ex) const {
180-
MATX_ASSERT_STR(!(is_host_executor_v<Executor> && method_ == MATX_C_METHOD_DIRECT), matxNotSupported, "direct conv1d() only supports the CUDA executor currently");
181180
MATX_STATIC_ASSERT_STR((Rank() == cuda::std::tuple_element_t<0, remove_cvref_t<Out>>::Rank()),
182181
matxInvalidParameter, "conv1d: inputs and outputs must have same rank to use conv1d with axis parameter");
183182
if constexpr (!std::is_same_v<PermDims, no_permute_t>) {
@@ -405,13 +404,11 @@ namespace detail {
405404

406405
template <typename Out, typename Executor>
407406
void Exec(Out &&out, Executor &&ex) const {
408-
static_assert(is_cuda_executor_v<Executor>, "conv2d() only supports the CUDA executor currently");
409-
410407
if constexpr (!std::is_same_v<PermDims, no_permute_t>) {
411-
conv2d_impl(permute(cuda::std::get<0>(out), perm_), a_, b_, mode_, ex.getStream());
408+
conv2d_impl(permute(cuda::std::get<0>(out), perm_), a_, b_, mode_, ex);
412409
}
413410
else {
414-
conv2d_impl(cuda::std::get<0>(out), a_, b_, mode_, ex.getStream());
411+
conv2d_impl(cuda::std::get<0>(out), a_, b_, mode_, ex);
415412
}
416413
}
417414

include/matx/operators/corr.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ namespace matx
158158

159159
template <typename Out, typename Executor>
160160
void Exec(Out &&out, Executor &&ex) const {
161-
MATX_ASSERT_STR(!(is_host_executor_v<Executor> && method_ == MATX_C_METHOD_DIRECT), matxNotSupported, "direct corr() only supports the CUDA executor currently");
162161
MATX_STATIC_ASSERT_STR((Rank() == cuda::std::tuple_element_t<0, remove_cvref_t<Out>>::Rank()),
163162
matxInvalidParameter, "corr: inputs and outputs must have same rank to use corr with axis parameter");
164163
if constexpr (!std::is_same_v<PermDims, no_permute_t>) {
@@ -253,4 +252,4 @@ __MATX_INLINE__ auto corr(const In1Type &i1, const In2Type &i2,
253252
return detail::CorrOp(in1, in2, mode, method, perm);
254253
}
255254

256-
}
255+
}

include/matx/transforms/conv.h

Lines changed: 180 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,71 @@ inline void matxDirectConv1DInternal(OutputType &o, const InType &i,
223223
#endif
224224
}
225225

226+
template <typename OutputType, typename InType, typename FilterType, typename Executor>
227+
requires is_host_executor_v<Executor>
228+
inline void matxDirectConv1DInternal(OutputType &o, const InType &i,
229+
const FilterType &filter, matxConvCorrMode_t mode,
230+
[[maybe_unused]] const Executor &exec)
231+
{
232+
MATX_NVTX_START("", matx::MATX_NVTX_LOG_INTERNAL)
233+
234+
MATX_STATIC_ASSERT(OutputType::Rank() == InType::Rank(), matxInvalidDim);
235+
MATX_STATIC_ASSERT(OutputType::Rank() == FilterType::Rank(), matxInvalidDim);
236+
237+
constexpr int Rank = OutputType::Rank();
238+
const index_t signal_len = i.Size(Rank - 1);
239+
const index_t filter_len = filter.Size(Rank - 1);
240+
[[maybe_unused]] const index_t full_len = signal_len + filter_len - 1;
241+
242+
MATX_ASSERT_STR(mode != MATX_C_MODE_FULL || o.Size(Rank - 1) == full_len,
243+
matxInvalidSize, "Output size for FULL convolution incorrect");
244+
MATX_ASSERT_STR(mode != MATX_C_MODE_SAME || o.Size(Rank - 1) == signal_len,
245+
matxInvalidSize, "Output size for SAME convolution incorrect");
246+
MATX_ASSERT_STR(mode != MATX_C_MODE_VALID || o.Size(Rank - 1) == signal_len - filter_len + 1,
247+
matxInvalidSize, "Output size for VALID convolution incorrect");
248+
249+
index_t start = 0;
250+
if (mode == MATX_C_MODE_SAME) {
251+
start = (filter_len & 1) ? (filter_len - 1) / 2 : filter_len / 2 - 1;
252+
}
253+
else if (mode == MATX_C_MODE_VALID) {
254+
start = filter_len - 1;
255+
}
256+
257+
const index_t total = static_cast<index_t>(TotalSize(o));
258+
259+
#ifdef MATX_EN_OMP
260+
#pragma omp parallel for num_threads(exec.GetNumThreads()) if(exec.GetNumThreads() > 1)
261+
#endif
262+
for (index_t abs = 0; abs < total; abs++) {
263+
auto odims = GetIdxFromAbs(o, abs);
264+
const index_t full_idx = odims[Rank - 1] + start;
265+
266+
typename OutputType::value_type sum{0};
267+
for (index_t f = 0; f < filter_len; f++) {
268+
const index_t sig_idx = full_idx - f;
269+
if (sig_idx >= 0 && sig_idx < signal_len) {
270+
auto idims = odims;
271+
auto fdims = odims;
272+
idims[Rank - 1] = sig_idx;
273+
fdims[Rank - 1] = f;
274+
275+
const auto ival = cuda::std::apply([&](auto &&...args) {
276+
return i.operator()(args...);
277+
}, idims);
278+
const auto fval = cuda::std::apply([&](auto &&...args) {
279+
return filter.operator()(args...);
280+
}, fdims);
281+
sum = detail::madd(ival, fval, sum);
282+
}
283+
}
284+
285+
cuda::std::apply([&](auto &&...args) {
286+
o.operator()(args...) = sum;
287+
}, odims);
288+
}
289+
}
290+
226291

227292
template <typename OutputType, typename In1Type, typename In2Type>
228293
void matxDirectConv2DInternal(OutputType &o, In1Type &in1,
@@ -256,6 +321,82 @@ void matxDirectConv2DInternal(OutputType &o, In1Type &in1,
256321
Conv2D<OutputType, In1Type, In2Type, BLOCK_X, BLOCK_Y, FILTER_SHARED_X, FILTER_SHARED_Y, FILTER_REG_X, FILTER_REG_Y, ILPY><<<blocks, threads, 0, stream>>>(o, in1, in2, mode, num_batch);
257322
#endif
258323
}
324+
325+
template <typename OutputType, typename In1Type, typename In2Type, typename Executor>
326+
requires is_host_executor_v<Executor>
327+
void matxDirectConv2DInternal(OutputType &o, const In1Type &in1,
328+
const In2Type &in2, matxConvCorrMode_t mode,
329+
[[maybe_unused]] const Executor &exec)
330+
{
331+
MATX_NVTX_START("", matx::MATX_NVTX_LOG_INTERNAL)
332+
333+
MATX_STATIC_ASSERT(OutputType::Rank() == In1Type::Rank(), matxInvalidDim);
334+
MATX_STATIC_ASSERT(OutputType::Rank() == In2Type::Rank(), matxInvalidDim);
335+
MATX_STATIC_ASSERT(OutputType::Rank() >= 2, matxInvalidDim);
336+
337+
constexpr int Rank = OutputType::Rank();
338+
339+
const index_t i1N = in1.Size(Rank - 2);
340+
const index_t i1M = in1.Size(Rank - 1);
341+
const index_t i2N = in2.Size(Rank - 2);
342+
const index_t i2M = in2.Size(Rank - 1);
343+
344+
index_t dy = 0;
345+
index_t dx = 0;
346+
if (mode == MATX_C_MODE_SAME) {
347+
dy = i2N / 2;
348+
dx = i2M / 2;
349+
}
350+
else if (mode == MATX_C_MODE_FULL) {
351+
dy = i2N - 1;
352+
dx = i2M - 1;
353+
}
354+
355+
const index_t total = static_cast<index_t>(TotalSize(o));
356+
357+
#ifdef MATX_EN_OMP
358+
#pragma omp parallel for num_threads(exec.GetNumThreads()) if(exec.GetNumThreads() > 1)
359+
#endif
360+
for (index_t abs = 0; abs < total; abs++) {
361+
auto odims = GetIdxFromAbs(o, abs);
362+
const index_t oy = odims[Rank - 2];
363+
const index_t ox = odims[Rank - 1];
364+
365+
typename OutputType::value_type sum{0};
366+
for (index_t fy = 0; fy < i2N; fy++) {
367+
const index_t iy = oy + fy - dy;
368+
if (iy < 0 || iy >= i1N) {
369+
continue;
370+
}
371+
372+
for (index_t fx = 0; fx < i2M; fx++) {
373+
const index_t ix = ox + fx - dx;
374+
if (ix < 0 || ix >= i1M) {
375+
continue;
376+
}
377+
378+
auto i1dims = odims;
379+
auto i2dims = odims;
380+
i1dims[Rank - 2] = iy;
381+
i1dims[Rank - 1] = ix;
382+
i2dims[Rank - 2] = i2N - 1 - fy;
383+
i2dims[Rank - 1] = i2M - 1 - fx;
384+
385+
const auto i1val = cuda::std::apply([&](auto &&...args) {
386+
return in1.operator()(args...);
387+
}, i1dims);
388+
const auto i2val = cuda::std::apply([&](auto &&...args) {
389+
return in2.operator()(args...);
390+
}, i2dims);
391+
sum = detail::madd(i1val, i2val, sum);
392+
}
393+
}
394+
395+
cuda::std::apply([&](auto &&...args) {
396+
o.operator()(args...) = sum;
397+
}, odims);
398+
}
399+
}
259400
} // end namespace detail
260401

261402
template <typename OutputType, typename In1Type, typename In2Type, typename Executor>
@@ -401,42 +542,55 @@ inline void conv1d_impl(OutputType o, const In1Type &i1, const In2Type &i2,
401542
* @param in1 First input operator
402543
* @param in2 Second input operator
403544
* @param mode Convolution mode
404-
* @param stream CUDA stream
545+
* @param exec Executor
405546
*/
406-
template <typename OutputType, typename In1Type, typename In2Type>
547+
template <typename OutputType, typename In1Type, typename In2Type, typename Executor>
407548
inline void conv2d_impl(OutputType o, const In1Type in1, const In2Type in2,
408-
matxConvCorrMode_t mode, cudaStream_t stream = 0)
549+
matxConvCorrMode_t mode, const Executor &exec)
409550
{
410551
MATX_NVTX_START("", matx::MATX_NVTX_LOG_API)
411552
constexpr int Rank1 = In1Type::Rank();
412553
constexpr int Rank2 = In2Type::Rank();
413554

414555
if constexpr (In1Type::Rank() == In2Type::Rank()) {
415-
index_t size1 = in1.Size(Rank1-1) * in1.Size(Rank1-2);
416-
index_t size2 = in2.Size(Rank2-1) * in2.Size(Rank2-2);
417-
// smaller size is the filter, set it as second input
418-
if(size1 >= size2) {
419-
detail::matxDirectConv2DInternal(o, in1, in2, mode, stream);
420-
} else { // swap in1/in2
421-
detail::matxDirectConv2DInternal(o, in2, in1, mode, stream);
422-
}
423-
// These branches clone the inputs to match in rank
424-
} else if constexpr (In1Type::Rank() <In2Type::Rank()) {
425-
// in1 is smaller so clone it to match in2
426-
auto shape = in2.Shape();
427-
int d = Rank2 - Rank1;
428-
for(int i = 0; i < Rank1; i++) {
429-
shape[i+d] = matxKeepDim;
556+
auto run_direct = [&](const auto &signal, const auto &filter) {
557+
if constexpr (is_cuda_executor_v<Executor>) {
558+
detail::matxDirectConv2DInternal(o, signal, filter, mode, exec.getStream());
430559
}
431-
conv2d_impl(o, clone<Rank2>(in1, shape), in2, mode, stream);
432-
} else {
433-
// in1 is smaller so clone it to match in2
434-
auto shape = in1.Shape();
435-
int d = Rank1 - Rank2;
436-
for(int i = 0; i < Rank2; i++) {
437-
shape[i+d] = matxKeepDim;
560+
else if constexpr (is_host_executor_v<Executor>) {
561+
detail::matxDirectConv2DInternal(o, signal, filter, mode, exec);
562+
}
563+
else {
564+
static_assert(is_cuda_executor_v<Executor> || is_host_executor_v<Executor>,
565+
"conv2d() only supports CUDA and host executors currently");
438566
}
439-
conv2d_impl(o, in1, clone<Rank1>(in2, shape), mode, stream);
567+
};
568+
569+
index_t size1 = in1.Size(Rank1-1) * in1.Size(Rank1-2);
570+
index_t size2 = in2.Size(Rank2-1) * in2.Size(Rank2-2);
571+
// smaller size is the filter, set it as second input
572+
if(size1 >= size2) {
573+
run_direct(in1, in2);
574+
} else { // swap in1/in2
575+
run_direct(in2, in1);
576+
}
577+
// These branches clone the inputs to match in rank
578+
} else if constexpr (In1Type::Rank() < In2Type::Rank()) {
579+
// in1 is smaller so clone it to match in2
580+
auto shape = in2.Shape();
581+
int d = Rank2 - Rank1;
582+
for(int i = 0; i < Rank1; i++) {
583+
shape[i+d] = matxKeepDim;
584+
}
585+
conv2d_impl(o, clone<Rank2>(in1, shape), in2, mode, exec);
586+
} else {
587+
// in1 is smaller so clone it to match in2
588+
auto shape = in1.Shape();
589+
int d = Rank1 - Rank2;
590+
for(int i = 0; i < Rank2; i++) {
591+
shape[i+d] = matxKeepDim;
592+
}
593+
conv2d_impl(o, in1, clone<Rank1>(in2, shape), mode, exec);
440594
}
441595
}
442596

0 commit comments

Comments
 (0)