Merge remote-tracking branch 'origin/multipath-testing-ci-physim' into integration_2026_w09 (!3910)
GPU Channel Simulation: nr_dlsim/nr_ulsim Integration + CI Test This MR is a follow-up to !3886 (merged) and extends CUDA-based channel simulation support to nr_dlsim and nr_ulsim, together with the corresponding CI tests. The MR incorporates: - Cherry-picked commits e0c204f7 and 5f344876 from !3588 - Integration of new CI physim tests using GPU-accelerated channel simulation in nr_dlsim and nr_ulsim tests, see commit c727b3bb New CI job: RAN-Channel-Simulation https://jenkins-oai.eurecom.fr/job/RAN-Channel-Simulation/
This commit is contained in:
@@ -132,6 +132,12 @@ This is the traditional CUDA programming model. The host and device have separat
|
||||
|
||||
To ensure the feature is modular, the integration into the OAI project was handled at both the build system and source code levels. All of the new CUDA source files (`channel_pipeline.cu`, `multipath_channel.cu`, `phase_noise.cu`) are compiled into a single static library named `oai_cuda_lib`. This is defined in the main `CMakeLists.txt` file and is controlled by the `CUDA_ENABLE` CMake option, which is disabled by default. When this option is activated (`-DCUDA_ENABLE=ON`), the build system compiles the CUDA library and also defines a global `ENABLE_CUDA` preprocessor macro that is visible to the rest of the project.
|
||||
|
||||
This `ENABLE_CUDA` macro is then used within the simulator files, such as `dlsim.c` and `ulsim.c`, to conditionally compile all CUDA-related code. This approach allows for the inclusion of the `oai_cuda.h` header, and the pre-allocation of GPU memory at startup, all without affecting the standard CPU-only build.
|
||||
|
||||
Inside the main processing loop, an `if (use_cuda)` statement acts as the primary runtime switch. To enable a direct comparison, timing measurement calls have been added to both the GPU and CPU execution paths. Also, both paths now source their data from the same common, pre-padded host buffer. The GPU path receives a pointer to the entire padded buffer and leverages its optimized kernel, while the CPU path uses a temporary array of offset pointers to work on the same data without being aware of the padding. This ensures both computations operate on the exact same source data, isolating the performance measurement to the channel processing itself. To execute the simulation on the GPU, the `-f cuda` flag must be provided at runtime; otherwise, the program defaults to this refined CPU implementation.
|
||||
|
||||
Finally, the build system links the `nr_dlsim` and `nr_ulsim` executables against the `oai_cuda_lib` only when the feature is enabled, creating a clean separation between the two codebases.
|
||||
|
||||
---
|
||||
|
||||
## Benchmark and Analysis Suite
|
||||
|
||||
@@ -77,6 +77,153 @@ __global__ void sum_outputs_kernel(const short2 *__restrict__ *__restrict__ indi
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void init_cuda_chsim_buffers(int use_cuda,
|
||||
int n_tx,
|
||||
int n_rx,
|
||||
void **d_tx_sig,
|
||||
void **d_intermediate_sig,
|
||||
void **d_final_output,
|
||||
void **d_curand_states,
|
||||
void **h_tx_sig_pinned,
|
||||
void **h_final_output_pinned,
|
||||
void **d_channel_coeffs_gpu)
|
||||
{
|
||||
if (use_cuda) {
|
||||
int num_samples_alloc = 153600;
|
||||
|
||||
#if defined(USE_UNIFIED_MEMORY)
|
||||
printf("Allocating CUDA Unified Memory...\n");
|
||||
const int max_padding_alloc = 256 - 1;
|
||||
size_t padded_tx_alloc_bytes = n_tx * (num_samples_alloc + max_padding_alloc) * 2 * sizeof(float);
|
||||
CHECK_CUDA(cudaMallocManaged(d_tx_sig, padded_tx_alloc_bytes, cudaMemAttachGlobal));
|
||||
CHECK_CUDA(cudaMallocManaged(d_intermediate_sig, n_rx * num_samples_alloc * sizeof(float) * 2, cudaMemAttachGlobal));
|
||||
CHECK_CUDA(cudaMallocManaged(d_final_output, n_rx * num_samples_alloc * sizeof(short) * 2, cudaMemAttachGlobal));
|
||||
*h_tx_sig_pinned = *d_tx_sig;
|
||||
*h_final_output_pinned = *d_final_output;
|
||||
int deviceId;
|
||||
CHECK_CUDA(cudaGetDevice(&deviceId));
|
||||
CHECK_CUDA(cudaMemAdvise(*d_tx_sig, padded_tx_alloc_bytes, cudaMemAdviseSetReadMostly, deviceId));
|
||||
CHECK_CUDA(cudaMemAdvise(*d_intermediate_sig,
|
||||
n_rx * num_samples_alloc * sizeof(float) * 2,
|
||||
cudaMemAdviseSetPreferredLocation,
|
||||
deviceId));
|
||||
CHECK_CUDA(
|
||||
cudaMemAdvise(*d_final_output, n_rx * num_samples_alloc * sizeof(short) * 2, cudaMemAdviseSetPreferredLocation, deviceId));
|
||||
CHECK_CUDA(
|
||||
cudaMemAdvise(*d_final_output, n_rx * num_samples_alloc * sizeof(short) * 2, cudaMemAdviseSetAccessedBy, cudaCpuDeviceId));
|
||||
|
||||
#elif defined(USE_ATS_MEMORY)
|
||||
printf("Allocating memory for ATS Hybrid path...\n");
|
||||
const int max_padding_alloc = 256 - 1;
|
||||
size_t padded_tx_alloc_bytes = n_tx * (num_samples_alloc + max_padding_alloc) * 2 * sizeof(float);
|
||||
*h_tx_sig_pinned = malloc(padded_tx_alloc_bytes);
|
||||
CHECK_CUDA(cudaMalloc(d_intermediate_sig, n_rx * num_samples_alloc * sizeof(float) * 2));
|
||||
CHECK_CUDA(cudaMalloc(d_final_output, n_rx * num_samples_alloc * sizeof(short) * 2));
|
||||
*h_final_output_pinned = malloc(n_rx * num_samples_alloc * sizeof(short) * 2);
|
||||
*d_tx_sig = NULL;
|
||||
#else // Explicit copy method
|
||||
printf("Pre-allocating GPU & Pinned memory for the channel pipeline...\n");
|
||||
const int max_padding_alloc = 256 - 1;
|
||||
size_t padded_tx_alloc_bytes = n_tx * (num_samples_alloc + max_padding_alloc) * 2 * sizeof(float);
|
||||
CHECK_CUDA(cudaMalloc(d_tx_sig, padded_tx_alloc_bytes));
|
||||
CHECK_CUDA(cudaMalloc(d_intermediate_sig, n_rx * num_samples_alloc * sizeof(float) * 2));
|
||||
CHECK_CUDA(cudaMalloc(d_final_output, n_rx * num_samples_alloc * sizeof(short) * 2));
|
||||
CHECK_CUDA(cudaMallocHost(h_tx_sig_pinned, padded_tx_alloc_bytes));
|
||||
CHECK_CUDA(cudaMallocHost(h_final_output_pinned, n_rx * num_samples_alloc * sizeof(short) * 2));
|
||||
#endif
|
||||
const int max_taps_alloc = 256; // Upper bound for channel length
|
||||
size_t channel_buffer_size = n_tx * n_rx * max_taps_alloc * sizeof(float) * 2;
|
||||
CHECK_CUDA(cudaMalloc(d_channel_coeffs_gpu, channel_buffer_size));
|
||||
int num_rand_elements = n_rx * num_samples_alloc;
|
||||
*d_curand_states = create_and_init_curand_states_cuda(num_rand_elements, time(NULL));
|
||||
}
|
||||
}
|
||||
|
||||
void free_cuda_chsim_buffers(int use_cuda,
|
||||
void **d_tx_sig_p,
|
||||
void **d_intermediate_sig_p,
|
||||
void **d_final_output_p,
|
||||
void **d_curand_states_p,
|
||||
void **h_tx_sig_pinned_p,
|
||||
void **h_final_output_pinned_p,
|
||||
float **h_channel_coeffs_p,
|
||||
void **d_channel_coeffs_gpu_p)
|
||||
{
|
||||
if (use_cuda) {
|
||||
printf("Freeing GPU and Pinned memory...\n");
|
||||
#if defined(USE_UNIFIED_MEMORY)
|
||||
if (*d_tx_sig_p) {
|
||||
cudaFree(*d_tx_sig_p);
|
||||
*d_tx_sig_p = NULL;
|
||||
*h_tx_sig_pinned_p = NULL;
|
||||
}
|
||||
if (*d_intermediate_sig_p) {
|
||||
cudaFree(*d_intermediate_sig_p);
|
||||
*d_intermediate_sig_p = NULL;
|
||||
}
|
||||
if (*d_final_output_p) {
|
||||
cudaFree(*d_final_output_p);
|
||||
*d_final_output_p = NULL;
|
||||
*h_final_output_pinned_p = NULL;
|
||||
}
|
||||
#elif defined(USE_ATS_MEMORY)
|
||||
if (*h_tx_sig_pinned_p) {
|
||||
free(*h_tx_sig_pinned_p);
|
||||
*h_tx_sig_pinned_p = NULL;
|
||||
}
|
||||
if (*d_intermediate_sig_p) {
|
||||
cudaFree(*d_intermediate_sig_p);
|
||||
*d_intermediate_sig_p = NULL;
|
||||
}
|
||||
if (*d_final_output_p) {
|
||||
cudaFree(*d_final_output_p);
|
||||
*d_final_output_p = NULL;
|
||||
}
|
||||
if (*h_final_output_pinned_p) {
|
||||
free(*h_final_output_pinned_p);
|
||||
*h_final_output_pinned_p = NULL;
|
||||
}
|
||||
#else
|
||||
if (*d_tx_sig_p) {
|
||||
cudaFree(*d_tx_sig_p);
|
||||
*d_tx_sig_p = NULL;
|
||||
}
|
||||
if (*d_intermediate_sig_p) {
|
||||
cudaFree(*d_intermediate_sig_p);
|
||||
*d_intermediate_sig_p = NULL;
|
||||
}
|
||||
if (*d_final_output_p) {
|
||||
cudaFree(*d_final_output_p);
|
||||
*d_final_output_p = NULL;
|
||||
}
|
||||
if (*h_tx_sig_pinned_p) {
|
||||
cudaFreeHost(*h_tx_sig_pinned_p);
|
||||
*h_tx_sig_pinned_p = NULL;
|
||||
}
|
||||
if (*h_final_output_pinned_p) {
|
||||
cudaFreeHost(*h_final_output_pinned_p);
|
||||
*h_final_output_pinned_p = NULL;
|
||||
}
|
||||
#endif
|
||||
if (*d_channel_coeffs_gpu_p) {
|
||||
cudaFree(*d_channel_coeffs_gpu_p);
|
||||
*d_channel_coeffs_gpu_p = NULL;
|
||||
}
|
||||
destroy_curand_states_cuda(*d_curand_states_p);
|
||||
*d_curand_states_p = NULL;
|
||||
if (*h_channel_coeffs_p) {
|
||||
free(*h_channel_coeffs_p);
|
||||
*h_channel_coeffs_p = NULL;
|
||||
}
|
||||
} else {
|
||||
if (*h_tx_sig_pinned_p) {
|
||||
free(*h_tx_sig_pinned_p);
|
||||
*h_tx_sig_pinned_p = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_channel_pipeline_cuda(c16_t **output_signal,
|
||||
int nb_tx,
|
||||
int nb_rx,
|
||||
|
||||
@@ -59,6 +59,27 @@ __global__ void add_noise_and_phase_noise_kernel(const float2 *__restrict__ r_si
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void init_cuda_chsim_buffers(int use_cuda,
|
||||
int n_tx,
|
||||
int n_rx,
|
||||
void **d_tx_sig_p,
|
||||
void **d_intermediate_sig_p,
|
||||
void **d_final_output_p,
|
||||
void **d_curand_states_p,
|
||||
void **h_tx_sig_pinned_p,
|
||||
void **h_final_output_pinned_p,
|
||||
void **d_channel_coeffs_gpu_p);
|
||||
|
||||
void free_cuda_chsim_buffers(int use_cuda,
|
||||
void **d_tx_sig_p,
|
||||
void **d_intermediate_sig_p,
|
||||
void **d_final_output_p,
|
||||
void **d_curand_states_p,
|
||||
void **h_tx_sig_pinned_p,
|
||||
void **h_final_output_pinned_p,
|
||||
float **h_channel_coeffs_p,
|
||||
void **d_channel_coeffs_gpu_p);
|
||||
|
||||
void multipath_channel_cuda(float **rx_sig_re,
|
||||
float **rx_sig_im,
|
||||
int nb_tx,
|
||||
|
||||
Reference in New Issue
Block a user