|
32 | 32 |
|
33 | 33 | #pragma once |
34 | 34 |
|
| 35 | +#include <algorithm> |
| 36 | +#include <cmath> |
35 | 37 | #include <cstdint> |
36 | 38 | #include <cstdio> |
37 | 39 | #include <type_traits> |
38 | 40 | #include <numeric> |
| 41 | +#include <vector> |
39 | 42 |
|
40 | 43 | #include "matx/core/error.h" |
41 | 44 | #include "matx/core/nvtx.h" |
42 | 45 | #include "matx/core/tensor.h" |
| 46 | +#include "matx/executors/host.h" |
43 | 47 | #include "matx/kernels/channelize_poly.cuh" |
44 | 48 | #include "matx/operators/fft.h" |
45 | 49 | #include "matx/operators/slice.h" |
@@ -211,6 +215,119 @@ inline bool ShouldUseSmemTiled( |
211 | 215 | <= SmemTiledMaxBytes; |
212 | 216 | } |
213 | 217 |
|
| 218 | +template <typename AccumT, typename FilterT> |
| 219 | +__MATX_HOST__ __MATX_INLINE__ auto HostChannelizeCastFilter(FilterT v) |
| 220 | +{ |
| 221 | + if constexpr (is_complex_v<FilterT>) { |
| 222 | + return static_cast<AccumT>(v); |
| 223 | + } else if constexpr (is_complex_v<AccumT>) { |
| 224 | + using accum_scalar_t = typename inner_op_type_t<AccumT>::type; |
| 225 | + return static_cast<accum_scalar_t>(v); |
| 226 | + } else { |
| 227 | + return static_cast<AccumT>(v); |
| 228 | + } |
| 229 | +} |
| 230 | + |
| 231 | +template <typename AccumT, typename InputT> |
| 232 | +__MATX_HOST__ __MATX_INLINE__ auto HostChannelizeCastInput(InputT v) |
| 233 | +{ |
| 234 | + if constexpr (is_complex_v<InputT>) { |
| 235 | + return static_cast<AccumT>(v); |
| 236 | + } else if constexpr (is_complex_v<AccumT>) { |
| 237 | + using accum_scalar_t = typename inner_op_type_t<AccumT>::type; |
| 238 | + return static_cast<accum_scalar_t>(v); |
| 239 | + } else { |
| 240 | + return static_cast<AccumT>(v); |
| 241 | + } |
| 242 | +} |
| 243 | + |
| 244 | +template <typename AccumT, typename FilterValT, typename InputValT> |
| 245 | +__MATX_HOST__ __MATX_INLINE__ void HostChannelizeCmac( |
| 246 | + AccumT &accum, FilterValT hv, InputValT iv) |
| 247 | +{ |
| 248 | + if constexpr (is_complex_v<AccumT> && is_complex_v<FilterValT> && is_complex_v<InputValT>) { |
| 249 | + auto h_re = hv.real(), h_im = hv.imag(); |
| 250 | + auto i_re = iv.real(), i_im = iv.imag(); |
| 251 | + auto a_re = accum.real(), a_im = accum.imag(); |
| 252 | + a_re = h_re * i_re + a_re; |
| 253 | + a_re = -(h_im * i_im) + a_re; |
| 254 | + a_im = h_re * i_im + a_im; |
| 255 | + a_im = h_im * i_re + a_im; |
| 256 | + accum = {a_re, a_im}; |
| 257 | + } else if constexpr (is_complex_v<AccumT> && !is_complex_v<FilterValT> && is_complex_v<InputValT>) { |
| 258 | + auto a_re = accum.real(), a_im = accum.imag(); |
| 259 | + a_re = hv * iv.real() + a_re; |
| 260 | + a_im = hv * iv.imag() + a_im; |
| 261 | + accum = {a_re, a_im}; |
| 262 | + } else if constexpr (is_complex_v<AccumT> && is_complex_v<FilterValT> && !is_complex_v<InputValT>) { |
| 263 | + auto a_re = accum.real(), a_im = accum.imag(); |
| 264 | + a_re = hv.real() * iv + a_re; |
| 265 | + a_im = hv.imag() * iv + a_im; |
| 266 | + accum = {a_re, a_im}; |
| 267 | + } else { |
| 268 | + accum += hv * iv; |
| 269 | + } |
| 270 | +} |
| 271 | + |
| 272 | +template <typename Op, typename Arr, size_t... Is> |
| 273 | +__MATX_HOST__ __MATX_INLINE__ decltype(auto) HostReadSignalImpl( |
| 274 | + const Op &op, const Arr &batch_idx, index_t sample_idx, |
| 275 | + cuda::std::index_sequence<Is...>) |
| 276 | +{ |
| 277 | + return op(batch_idx[Is]..., sample_idx); |
| 278 | +} |
| 279 | + |
| 280 | +template <typename Op, typename Arr> |
| 281 | +__MATX_HOST__ __MATX_INLINE__ decltype(auto) HostReadSignal( |
| 282 | + const Op &op, const Arr &batch_idx, index_t sample_idx) |
| 283 | +{ |
| 284 | + return HostReadSignalImpl( |
| 285 | + op, batch_idx, sample_idx, |
| 286 | + cuda::std::make_index_sequence<static_cast<size_t>(Op::Rank() - 1)>{}); |
| 287 | +} |
| 288 | + |
| 289 | +template <typename OutType, typename Arr, typename ValueT, size_t... Is> |
| 290 | +__MATX_HOST__ __MATX_INLINE__ void HostWriteOutputImpl( |
| 291 | + OutType &out, const Arr &batch_idx, index_t output_idx, index_t channel, |
| 292 | + const ValueT &value, cuda::std::index_sequence<Is...>) |
| 293 | +{ |
| 294 | + out(batch_idx[Is]..., output_idx, channel) = |
| 295 | + static_cast<typename OutType::value_type>(value); |
| 296 | +} |
| 297 | + |
| 298 | +template <typename OutType, typename Arr, typename ValueT> |
| 299 | +__MATX_HOST__ __MATX_INLINE__ void HostWriteOutput( |
| 300 | + OutType &out, const Arr &batch_idx, index_t output_idx, index_t channel, |
| 301 | + const ValueT &value) |
| 302 | +{ |
| 303 | + HostWriteOutputImpl( |
| 304 | + out, batch_idx, output_idx, channel, value, |
| 305 | + cuda::std::make_index_sequence<static_cast<size_t>(OutType::Rank() - 2)>{}); |
| 306 | +} |
| 307 | + |
| 308 | +template <typename ComplexAccumT, typename ValueT> |
| 309 | +__MATX_HOST__ __MATX_INLINE__ ComplexAccumT HostAsComplex(ValueT v) |
| 310 | +{ |
| 311 | + using scalar_t = typename inner_op_type_t<ComplexAccumT>::type; |
| 312 | + if constexpr (is_complex_v<ValueT>) { |
| 313 | + return static_cast<ComplexAccumT>(v); |
| 314 | + } else { |
| 315 | + return ComplexAccumT{static_cast<scalar_t>(v), static_cast<scalar_t>(0)}; |
| 316 | + } |
| 317 | +} |
| 318 | + |
| 319 | +template <typename ComplexAccumT> |
| 320 | +__MATX_HOST__ __MATX_INLINE__ ComplexAccumT HostTwiddle(index_t channel, index_t branch, index_t num_channels) |
| 321 | +{ |
| 322 | + using scalar_t = typename inner_op_type_t<ComplexAccumT>::type; |
| 323 | + constexpr double pi = 3.141592653589793238462643383279502884; |
| 324 | + const double arg = 2.0 * pi * static_cast<double>(channel) * |
| 325 | + static_cast<double>(branch) / static_cast<double>(num_channels); |
| 326 | + return ComplexAccumT{ |
| 327 | + static_cast<scalar_t>(std::cos(arg)), |
| 328 | + static_cast<scalar_t>(std::sin(arg))}; |
| 329 | +} |
| 330 | + |
214 | 331 | template <int CTILE, typename OutType, typename InType, typename FilterType, typename AccumType> |
215 | 332 | inline void SmemTiledImpl( |
216 | 333 | OutType o, const InType &i, const FilterType &filter, |
@@ -730,4 +847,184 @@ inline void channelize_poly_impl(OutType out, const InType &in, const FilterType |
730 | 847 | } |
731 | 848 | } |
732 | 849 | } |
| 850 | + |
| 851 | +/** |
| 852 | + * @brief Host implementation of the 1D polyphase channelizer. |
| 853 | + * |
| 854 | + * This is a feature-parity implementation for CPU executors. It directly |
| 855 | + * computes the per-branch FIR values and then applies the unnormalized, |
| 856 | + * positive-sign DFT used by the CUDA channelizer. |
| 857 | + */ |
| 858 | +template <typename OutType, typename InType, typename FilterType, typename AccumType, ThreadsMode MODE> |
| 859 | +inline void channelize_poly_impl(OutType out, const InType &in, const FilterType &f, |
| 860 | + index_t num_channels, index_t decimation_factor, |
| 861 | + [[maybe_unused]] const HostExecutor<MODE> &exec) { |
| 862 | + MATX_NVTX_START("", matx::MATX_NVTX_LOG_API) |
| 863 | + using OutputOp = std::remove_cv_t<std::remove_reference_t<OutType>>; |
| 864 | + using InputOp = std::remove_cv_t<std::remove_reference_t<InType>>; |
| 865 | + using FilterOp = std::remove_cv_t<std::remove_reference_t<FilterType>>; |
| 866 | + using input_t = typename InputOp::value_type; |
| 867 | + using filter_t = typename FilterOp::value_type; |
| 868 | + using output_t = typename OutputOp::value_type; |
| 869 | + using filtering_accum_t = cuda::std::conditional_t< |
| 870 | + is_complex_v<input_t> || is_complex_v<filter_t>, |
| 871 | + typename detail::scalar_to_complex<AccumType>::ctype, |
| 872 | + AccumType>; |
| 873 | + using complex_accum_t = typename detail::scalar_to_complex<AccumType>::ctype; |
| 874 | + |
| 875 | + static_assert(!is_complex_v<AccumType>, |
| 876 | + "channelize_poly: accumulator type must be real; it will be treated as complex when necessary"); |
| 877 | + |
| 878 | + constexpr int IN_RANK = InputOp::Rank(); |
| 879 | + constexpr int OUT_RANK = OutputOp::Rank(); |
| 880 | + |
| 881 | + MATX_STATIC_ASSERT_STR(OUT_RANK == IN_RANK+1, matxInvalidDim, |
| 882 | + "channelize_poly: output rank should be 1 higher than input"); |
| 883 | + MATX_STATIC_ASSERT_STR(is_complex_v<output_t> || is_complex_half_v<output_t>, |
| 884 | + matxInvalidType, "channelize_poly: output type must be complex"); |
| 885 | + MATX_STATIC_ASSERT_STR(FilterType::Rank() == 1, matxInvalidDim, |
| 886 | + "channelize_poly: currently only support 1D filters"); |
| 887 | + |
| 888 | + MATX_ASSERT_STR(num_channels > 0, matxInvalidParameter, |
| 889 | + "channelize_poly: num_channels must be positive"); |
| 890 | + MATX_ASSERT_STR(decimation_factor > 0, matxInvalidParameter, |
| 891 | + "channelize_poly: decimation_factor must be positive"); |
| 892 | + MATX_ASSERT_STR(decimation_factor <= num_channels, matxInvalidParameter, |
| 893 | + "channelize_poly: decimation_factor must be <= num_channels"); |
| 894 | + |
| 895 | + for (int i = 0; i < IN_RANK-1; i++) { |
| 896 | + MATX_ASSERT_STR(out.Size(i) == in.Size(i), matxInvalidDim, |
| 897 | + "channelize_poly: input/output must have matched batch sizes"); |
| 898 | + } |
| 899 | + |
| 900 | + const index_t input_len = in.Size(IN_RANK-1); |
| 901 | + const index_t num_elem_per_channel = (input_len + decimation_factor - 1) / decimation_factor; |
| 902 | + MATX_ASSERT_STR(out.Size(OUT_RANK-1) == num_channels, matxInvalidDim, |
| 903 | + "channelize_poly: output size OUT_RANK-1 mismatch"); |
| 904 | + MATX_ASSERT_STR(out.Size(OUT_RANK-2) == num_elem_per_channel, matxInvalidDim, |
| 905 | + "channelize_poly: output size OUT_RANK-2 mismatch"); |
| 906 | + |
| 907 | + const index_t filter_full_len = f.Size(FilterOp::Rank()-1); |
| 908 | + const index_t filter_phase_len = (filter_full_len + num_channels - 1) / num_channels; |
| 909 | + index_t batch_count = 1; |
| 910 | + for (int i = 0; i < IN_RANK-1; i++) { |
| 911 | + batch_count *= in.Size(i); |
| 912 | + } |
| 913 | + |
| 914 | + std::vector<complex_accum_t> twiddles(static_cast<size_t>(num_channels * num_channels)); |
| 915 | + for (index_t channel = 0; channel < num_channels; channel++) { |
| 916 | + for (index_t branch = 0; branch < num_channels; branch++) { |
| 917 | + twiddles[static_cast<size_t>(channel * num_channels + branch)] = |
| 918 | + detail::cpoly::HostTwiddle<complex_accum_t>(channel, branch, num_channels); |
| 919 | + } |
| 920 | + } |
| 921 | + |
| 922 | + const index_t num_thread_buffers = std::max<index_t>(1, exec.GetNumThreads()); |
| 923 | + std::vector<filtering_accum_t> filtered_storage( |
| 924 | + static_cast<size_t>(num_thread_buffers * num_channels)); |
| 925 | + |
| 926 | + const auto compute_output = [&](index_t batch, index_t t) { |
| 927 | + const auto in_batch_idx = detail::BlockToIdx(in, batch, 1); |
| 928 | + const auto out_batch_idx = detail::BlockToIdx(out, batch, 2); |
| 929 | + index_t thread_index = 0; |
| 930 | +#ifdef MATX_EN_OMP |
| 931 | + if (num_thread_buffers > 1) { |
| 932 | + thread_index = static_cast<index_t>(omp_get_thread_num()); |
| 933 | + } |
| 934 | +#endif |
| 935 | + auto *filtered = filtered_storage.data() + |
| 936 | + static_cast<size_t>(thread_index * num_channels); |
| 937 | + |
| 938 | + for (index_t branch = 0; branch < num_channels; branch++) { |
| 939 | + filtering_accum_t accum{}; |
| 940 | + index_t h_ind = branch; |
| 941 | + index_t sample_idx = 0; |
| 942 | + index_t niter = 0; |
| 943 | + |
| 944 | + if (decimation_factor == num_channels) { |
| 945 | + const index_t s = num_channels - 1 - branch; |
| 946 | + sample_idx = s + t * num_channels; |
| 947 | + index_t h_skip = 0; |
| 948 | + if (sample_idx >= input_len) { |
| 949 | + h_skip = 1; |
| 950 | + sample_idx -= num_channels; |
| 951 | + } |
| 952 | + |
| 953 | + index_t available_taps = filter_phase_len; |
| 954 | + if (filter_phase_len > 0 && |
| 955 | + ((filter_phase_len - 1) * num_channels + branch) >= filter_full_len) { |
| 956 | + available_taps--; |
| 957 | + } |
| 958 | + |
| 959 | + if (available_taps > h_skip && (t + 1) > h_skip) { |
| 960 | + niter = std::min(available_taps - h_skip, t + 1 - h_skip); |
| 961 | + h_ind = branch + h_skip * num_channels; |
| 962 | + } |
| 963 | + } else { |
| 964 | + const index_t r_remapped = (branch + num_channels - decimation_factor) % num_channels; |
| 965 | + const index_t s = num_channels - 1 - r_remapped; |
| 966 | + const index_t last_arrived = t * decimation_factor + decimation_factor - 1; |
| 967 | + if (last_arrived >= s) { |
| 968 | + const index_t A = last_arrived - s; |
| 969 | + sample_idx = last_arrived - (A % num_channels); |
| 970 | + const index_t causal_count = A / num_channels + 1; |
| 971 | + const index_t phase = (branch + t * decimation_factor) % num_channels; |
| 972 | + index_t h_skip = 0; |
| 973 | + if (sample_idx >= input_len) { |
| 974 | + h_skip = 1; |
| 975 | + sample_idx -= num_channels; |
| 976 | + } |
| 977 | + |
| 978 | + index_t available_taps = filter_phase_len; |
| 979 | + if (filter_phase_len > 0 && |
| 980 | + ((filter_phase_len - 1) * num_channels + phase) >= filter_full_len) { |
| 981 | + available_taps--; |
| 982 | + } |
| 983 | + |
| 984 | + if (available_taps > h_skip && causal_count > h_skip) { |
| 985 | + niter = std::min(available_taps - h_skip, causal_count - h_skip); |
| 986 | + h_ind = phase + h_skip * num_channels; |
| 987 | + } |
| 988 | + } |
| 989 | + } |
| 990 | + |
| 991 | + for (index_t i = 0; i < niter; i++) { |
| 992 | + const input_t in_val = detail::cpoly::HostReadSignal(in, in_batch_idx, sample_idx); |
| 993 | + const filter_t h_val = f(h_ind); |
| 994 | + detail::cpoly::HostChannelizeCmac(accum, |
| 995 | + detail::cpoly::HostChannelizeCastFilter<filtering_accum_t>(h_val), |
| 996 | + detail::cpoly::HostChannelizeCastInput<filtering_accum_t>(in_val)); |
| 997 | + h_ind += num_channels; |
| 998 | + sample_idx -= num_channels; |
| 999 | + } |
| 1000 | + |
| 1001 | + filtered[static_cast<size_t>(branch)] = accum; |
| 1002 | + } |
| 1003 | + |
| 1004 | + for (index_t channel = 0; channel < num_channels; channel++) { |
| 1005 | + complex_accum_t dft{}; |
| 1006 | + for (index_t branch = 0; branch < num_channels; branch++) { |
| 1007 | + dft += detail::cpoly::HostAsComplex<complex_accum_t>( |
| 1008 | + filtered[static_cast<size_t>(branch)]) * |
| 1009 | + twiddles[static_cast<size_t>(channel * num_channels + branch)]; |
| 1010 | + } |
| 1011 | + detail::cpoly::HostWriteOutput(out, out_batch_idx, t, channel, dft); |
| 1012 | + } |
| 1013 | + }; |
| 1014 | + |
| 1015 | + const index_t total_outputs = batch_count * num_elem_per_channel; |
| 1016 | +#ifdef MATX_EN_OMP |
| 1017 | + if (exec.GetNumThreads() > 1) { |
| 1018 | + #pragma omp parallel for num_threads(exec.GetNumThreads()) |
| 1019 | + for (index_t i = 0; i < total_outputs; i++) { |
| 1020 | + compute_output(i / num_elem_per_channel, i % num_elem_per_channel); |
| 1021 | + } |
| 1022 | + } else |
| 1023 | +#endif |
| 1024 | + { |
| 1025 | + for (index_t i = 0; i < total_outputs; i++) { |
| 1026 | + compute_output(i / num_elem_per_channel, i % num_elem_per_channel); |
| 1027 | + } |
| 1028 | + } |
| 1029 | +} |
733 | 1030 | } // end namespace matx |
0 commit comments