ワンクリックで
port-cusignal-module
How to port a cusignal Python/CUDA module to ins::signal C++ using Insight7 primitives and HAL
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
How to port a cusignal Python/CUDA module to ins::signal C++ using Insight7 primitives and HAL
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Add device information query functions through HAL → ins:: API → language bindings
Add --device all/--timer/--info CLI flags to demos across all languages for competition scoring
Add composite signal operators using existing primitives (no dedicated backend kernels needed)
Profile and optimize binding language (Lua/Julia/Python) demo performance to match or exceed C++ baseline
busted loads _insight.so from ~/.luarocks/lib first, not build/ — must copy .so after rebuild
Julia C API bindings must check for nullptr return — silent failure otherwise
| name | port-cusignal-module |
| description | How to port a cusignal Python/CUDA module to ins::signal C++ using Insight7 primitives and HAL |
| source | auto-skill |
| extracted_at | 2026-05-29T23:24:25.262Z |
This skill captures the end-to-end workflow for porting a cuSignal Python/CUDA module to Insight7's ins::signal namespace. cuSignal is scipy.signal's GPU-accelerated twin; Insight7 replaces CuPy with a HAL-based C++ framework supporting CPU, CUDA, and future accelerators.
~/plum/cusignal/ (read-only reference)~/plum/Insight7/add-api, add-cpu-op, add-cuda-op, test-opins::signalAll cuSignal functions move from cusignal.xxx to ins::signal::xxx. No sub-namespaces — flat namespace like scipy.signal.
include/insight/ops/signal.h # Master include (all sub-headers)
include/insight/ops/signal/<module>.h # Per-module declarations
src/ops/signal/<module>.cpp # Per-module implementations
backends/cpu/kernels/signal/<op>.cpp # CPU kernels (only if composite won't do)
backends/cuda/kernels/signal/<op>.cu # CUDA kernels (only if composite won't do)
tests/cpu/test_signal_<module>.cpp # CPU tests
tests/cuda/test_signal_<module>.cpp # CUDA tests
Most cuSignal functions combine existing Insight7 primitives (FFT, math, linalg). All 14 signal
submodules now have dedicated backend kernels registered with the signal_ prefix convention.
The general approach is:
sosfilt).cu file for the operationSignal kernel conventions (2026-06-01):
signal_ prefix: signal_<op>_kernel_cpu, signal_<op>_kernel_gpunumel > 1000Insight7 primitives available for composition:
| Category | Functions |
|---|---|
| FFT | fft::rfft, fft::irfft, fft::fft, fft::ifft, fft::fftfreq, fft::fftshift, fft::next_fast_len |
| Math | sin, cos, exp, log, sqrt, abs, square, negative |
| Compare | greater_than, less_than, greater_equal, less_equal, equal |
| Logical | logical_and, logical_or, logical_not |
| Binary | add, sub, mul, div, pow, maximum, minimum |
| Reduction | sum, mean, max, min, cumsum, prod, argmax, argmin |
| Creation | zeros, ones, full, arange, linspace, zeros_like, full_like |
| Manipulation | concat, stack, pad, flip, roll, reshape, squeeze, unsqueeze |
| Indexing | slice, take, where, scatter, gather |
| Linalg | matmul, solve, inv, svd, norm |
| Existing signal | convolve, unwrap, sinc |
Python's duck typing and optional args map to C++ patterns:
# Python: string enum parameter
get_window('hann', 256)
chirp(t, f0, t1, f1, method='linear')
// C++: std::string parameter or enum class
Array get_window(const std::string &window, int64_t Nx, bool fftbins = true);
enum class ChirpMethod { Linear, Quadratic, Logarithmic, Hyperbolic };
Array chirp(const Array &t, double f0, double t1, double f1,
ChirpMethod method = ChirpMethod::Linear);
# Python: str or tuple parameter
get_window(('kaiser', 5.0), 256)
// C++: overload with extra param
Array get_window(const std::string &window, double param, int64_t Nx,
bool fftbins = true);
Port in this order to avoid circular dependencies:
Layer 0 (no deps): windows, waveforms, bsplines
Layer 1 (deps on L0): filter_design (→windows), convolution (uses fft)
Layer 2 (deps on L1): filtering (→convolution,filter_design,windows),
spectral_analysis (→windows,filtering for detrend)
Layer 3 (deps on L2): wavelets (→convolve), resample (→filter_design,windows),
acoustics (→fft), demod, peak_finding
Layer 4 (deps on L2+3): estimation (standalone algo), radartools (→windows,convolve),
io (standalone)
# Find the Python implementation
grep -rn "def function_name" ~/plum/cusignal/python/cusignal/<module>/
# Check for CUDA kernel
ls ~/plum/cusignal/cpp/src/<module>/
Key files to read:
python/cusignal/<module>/<module>.py — Python API + logicpython/cusignal/<module>/__init__.py — exportspython/cusignal/<module>/_<module>_cuda.py — CUDA kernel wrappers (if exists)cpp/src/<module>/_<module>.cu — raw CUDA kernels (if exists)In include/insight/ops/signal/<module>.h:
#pragma once
#include "insight/core/array.h"
#include <string>
namespace ins {
namespace signal {
/**
* @brief Brief description matching scipy.signal docs.
*
* @param x Input array
* @param param Parameter description
* @return Output array description
*/
Array function_name(const Array &x, double param = 1.0);
} // namespace signal
} // namespace ins
In src/ops/signal/<module>.cpp:
#include "insight/ops/signal/<module>.h"
#include "insight/ops/elementwise.h"
#include "insight/ops/fft.h"
// ... other needed includes
namespace ins {
namespace signal {
Array function_name(const Array &x, double param) {
INS_CHECK(x.defined(), "function_name: input is undefined");
INS_CHECK(is_floating_point(x.dtype()),
"function_name: only floating point types supported");
// Compose using existing primitives
Array result = /* ... */;
return result;
}
} // namespace signal
} // namespace ins
In include/insight/ops/signal.h, add:
#include "insight/ops/signal/<module>.h"
In tests/cpu/test_signal_<module>.cpp:
#include "insight/insight.h"
#include <cmath>
#include <gtest/gtest.h>
using namespace ins;
using namespace ins::signal;
class Signal<Module>TestCPU : public ::testing::Test {
protected:
static void SetUpTestSuite() {
ins::init();
set_device(CPUPlace());
}
};
TEST_F(Signal<Module>TestCPU, FunctionBasic) {
// Test against known values from scipy.signal
std::vector<float> input = {1.0f, 2.0f, 3.0f};
Array x = to_array(input);
Array result = signal::function_name(x);
Array cpu_result = result.to(CPUPlace());
const float *data = cpu_result.data<float>();
EXPECT_NEAR(data[0], expected_val, 1e-5);
}
CUDA tests mirror CPU tests with GPUPlace(0) and _gpu helpers.
Only when composite won't work. Follow existing skills:
add-cpu-op skilladd-cuda-op skill.cu files: change CuPy ElementwiseKernel to raw CUDA __global__ kernels, replace cp.RawModule with Insight7's kernel registrationcuSignal kernels use CuPy's ElementwiseKernel or RawKernel. To port:
# cuSignal Python (CuPy ElementwiseKernel)
_cupy_correlate_1d = cp.ElementwiseKernel(
'T sig, T filt', 'T out',
'out = sig * filt', # or more complex logic
'_cupy_correlate_1d')
// Insight7 CUDA kernel
template<typename T>
__global__ void correlate_1d_kernel(const T* sig, const T* filt, T* out,
int64_t numel) {
int64_t i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= numel) return;
out[i] = sig[i] * filt[i]; // or more complex logic
}
extern "C" C_Status correlate_1d_kernel_gpu(void** inputs, void** outputs) {
// ... standard wrapper pattern
}
REGISTER_GPU_KERNEL(correlate_1d, INSIGHT_DTYPE_F32, correlate_1d_kernel_gpu);
For cuSignal's .cu fatbin files (e.g., _convolution.cu), the kernel bodies can be extracted and wrapped with Insight7's registration macros.
cuSignal tests compare GPU vs CPU. Our tests should compare against scipy.signal reference values:
# Generate reference values (run once, hardcode in C++ tests)
import scipy.signal as sps
import numpy as np
result = sps.hann(256)
print(result.tolist())
Then use those values in C++ EXPECT_NEAR assertions.
next_fast_len for FFT padding — Insight7 has fft::next_fast_len, use itstd::complex<float/double> on CPU and cuFloatComplex/cuDoubleComplex on CUDAsym=True = scipy fftbins=False (periodic). The conventions are swapped vs scipy's default.Shape uses .dim(i) not [i]: The Shape class has no operator[]. Always use shape().dim(i).linalg:: is not a namespace: Functions like matmul, inv, solve are in namespace ins directly. From namespace ins::signal, call them as just matmul(a, b).dtype_sizeof: Use insight_dtype_size(static_cast<int32_t>(dtype)) from insight/c_api/dtype.h.SetUpTestSuite() with ins::init({"cpu"}); set_device(CPUPlace());. Without this, get_device() throws "Device interface not available".to_array needs #include "insight/ops/creation.h": The to_array and zeros functions are in this header._dtype_code helper: For Julia bindings that need dtype codes, check if the helper exists in Insight.jl before creating new ones.firwin cutoff must be Sequence: The Python binding for firwin expects cutoff as a Sequence, not a scalar float. Pass [0.3] not 0.3.When rewriting signal functions that use data<T>() raw pointer access in the frontend:
| Raw pointer pattern | Composite replacement |
|---|---|
| Manual matmul triple loop | matmul(a, b) |
| Manual transpose double loop | permute(x, {0, 2, 1}) or transpose(x) |
| Gauss-Jordan inverse (30+ lines) | inv(m) or simple_inv(m) (see below) |
| Manual element-wise add/sub | add(a, b), sub(a, b) |
| Manual diff along axis | diff(x, 1, axis) |
Manual phase extraction std::arg(z) | atan(im/re) with quadrant correction via where() |
| Manual convolution loop | convolve(x, h, "same") or fft→mul→ifft |
| Manual FFT multiply-accumulate | rfft→mul→irfft |
| Manual cumsum-based windowing | cumsum + slice |
| Manual neighbor comparison | take(flat, gather_indices) for neighbor lookup |
Scalar read data<double>()[0] | slice(arr, {0}, {0}, {1}).item<double>() |
| Complex sort by magnitude | real(), imag(), square(), argsort() on mag_sq |
| Setting single element | put(arr, idx_arr, val_arr) — returns NEW array |
slice() takes Array& not const Array&: Must use mutable local variable:
Array data_mutable = data; // Required for slice()
Array center = slice(data_mutable, {axis}, {start}, {end});
put() returns new array, doesn't modify in-place:
result = put(result, idx_arr, val_arr); // Must capture return value
item() only works on scalar arrays (numel == 1):
// WRONG: a.item<double>() if a has multiple elements
// RIGHT:
Array a0 = slice(a, {0}, {0}, {1}); // Extract first element
double val = a0.item<double>();
argsort doesn't support complex dtype: Use magnitude-squared approach:
Array re = real(p), im = imag(p);
Array mag_sq = add(square(re), square(im));
Array idx = argsort(mag_sq, 0, false);
Array sorted = take(p, idx);
inv()/solve() need OpenBLAS: Use simple_inv() fallback for small matrices:
// Gauss-Jordan inverse — no LAPACK dependency
Array simple_inv(const Array &mat) {
int n = mat.shape().dim(0);
// Copy to vector, Gauss-Jordan elimination, return result
}
FFT in loops — avoid temporary array issues: Use to_array() for fresh arrays:
// WRONG: slice() → reshape() → rfft() (view may be invalidated)
// RIGHT: extract to vector, create fresh array
std::vector<double> pulse_vec(samples);
// ... fill pulse_vec ...
Array pulse = to_array(pulse_vec, dtype, cpu);
Array fft_pulse = fft::rfft(pulse, nfft);
nonzero() returns Array (flat indices), not vector<Array>: Convert flat to multi-dim:
Array flat_idx = nonzero(mask);
Array axis_idx = mod(div(flat_idx, stride_arr), dim_arr);
When splitting signal bindings into multi-file submodule:
bindings/python/insight/signal/
├── __init__.py # Re-exports all sub-modules
├── windows.py # Wrapper functions with docstrings
├── waveforms.py
├── bsplines.py
├── filter_design.py
├── convolution.py
├── filtering.py
├── spectral.py
├── wavelets.py
├── acoustics.py
├── demod.py
├── peak_finding.py
├── radar.py
├── estimation.py
└── io.py
Each file pattern:
"""Module docstring."""
from insight._insight import signal as _signal
__all__ = ["func1", "func2"]
def func1(args):
"""Google-style docstring."""
return _signal.func1(args)
Note: square → square_wf in native binding (avoids conflict with unary square).
Main __init__.py imports from .signal submodule, result types from ._insight.signal.
[points, dim_x, dim_x] for matrices, [points, dim_x, 1] for vectorsfix-batched-matmul-3d skill)matmul for FP, HP, etc.pulse_compression: FFT-based matched filter — iterate over pulses, rfft→multiply→irfftpulse_doppler: FFT along pulse dimension (axis 0)ca_cfar: 2D cumsum for O(1) cell averagingmvdr: R = X*X^T/N, w = R^{-1}sv / (sv^TR^{-1}*sv)boolrelextrema internal helper: for each element, compare with order neighbors on each sidestd::vector<Array> (one index array per dimension)unwrap(angle(x)) then diffangle(x) = atan2(imag(x), real(x)) computed manually for complex arraysunwrap and diff are existing Insight7 primitivespack_bin/unpack_bin: byte-level endianness conversionread_sigmf: parse .sigmf-meta JSON for dtype/endianness, then read+unpack .sigmf-datacd ~/plum/Insight7/build
cmake .. -DINSIGHT_WITH_CUDA=ON
cmake --build . -j$(nproc)
ctest -R "Signal" --output-on-failure
All 12 categories from the competition appendix are now covered:
| Category | Functions | Status |
|---|---|---|
| bsplines | cubic, gauss_spline, quadratic | ✅ All implemented |
| correlate | correlate, correlate2d | ✅ All implemented |
| demod | fm_demod | ✅ Implemented |
| estimation | kalmanfilter | ✅ KalmanFilter class |
| filter_design | firwin, firwin2 | ✅ All implemented |
| filtering | channelize_poly, detrend, firfilter, freq_shift, hilbert, hilbert2, lfilter_zi, sosfilt, wiener, decimate, resample, resample_poly, upfirdn | ✅ All implemented |
| peak_finding | argrelextrema | ✅ Implemented |
| radartools | ambgfun, ca_cfar, cfar_alpha, pulse_compression, pulse_doppler | ✅ All implemented |
| spectral | csd, istft, lombscargle, spectrogram, stft, vectorstrength | ✅ All implemented |
| waveforms | chirp, gausspulse, sawtooth, square, unit_impulse | ✅ All implemented |
| wavelets | cwt, morlet, morlet2 | ✅ All implemented |
| windows | qmf, ricker, chebwin, general_cosine, general_gaussian, hamming, kaiser, parzen, taylor, triang | ✅ All implemented |
Note: firfilter2 and channelize_poly are not in the original scipy.signal API but are cuSignal-specific extensions.
These operators were missing and added as composite implementations:
lombscargle: Frontend only (backend kernels existed)istft: Overlap-add synthesissosfilt: Cascade biquad (Direct Form II transposed)upfirdn: Upsample + FIR filter + downsamplechannelize_poly: Polyphase channelizerqmf: Quadrature mirror filter pairAll are composite implementations using existing primitives (FFT, convolution, elementwise ops).