Skip to content

Commit 580316f

Browse files
committed
fix(hesai): address review comments from WIP PR
- Copyright year 2024 -> 2026 for new files - Replace deprecated find_package(CUDA) with find_package(CUDAToolkit) - Remove --expt-relaxed-constexpr flag (not needed) - Remove unused per-packet kernel and launcher (dead code) - Batch launcher returns bool; caller logs via NEBULA_LOG_STREAM - Reorder CudaNebulaPoint fields for better memory packing - Remove redundant is_multi_frame member; use n_frames > 1 - Make HesaiCudaDecoder destructor virtual - Add int32_t range guarantee comment in angle corrector
1 parent e939e15 commit 580316f

6 files changed

Lines changed: 33 additions & 155 deletions

File tree

src/nebula_hesai/nebula_hesai/tests/hesai_cuda_decoder_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 TIER IV, Inc.
1+
// Copyright 2026 TIER IV, Inc.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

src/nebula_hesai/nebula_hesai_decoders/CMakeLists.txt

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,12 @@ option(BUILD_CUDA "Build with CUDA support" ON)
99

1010
# CUDA Configuration
1111
if(BUILD_CUDA)
12-
find_package(CUDA)
13-
if(NOT ${CUDA_FOUND})
14-
message(WARNING "CUDA not found, building without CUDA support")
12+
find_package(CUDAToolkit QUIET)
13+
if(NOT CUDAToolkit_FOUND)
14+
message(WARNING "CUDA Toolkit not found, building without CUDA support")
1515
set(BUILD_CUDA OFF)
1616
else()
17-
message(STATUS "CUDA found: ${CUDA_VERSION}")
18-
message(STATUS "CUDA include dirs: ${CUDA_INCLUDE_DIRS}")
19-
20-
# CUDA compiler flags
21-
list(APPEND CUDA_NVCC_FLAGS "--expt-relaxed-constexpr")
22-
list(APPEND CUDA_NVCC_FLAGS "-diag-suppress 20012")
17+
message(STATUS "CUDA Toolkit found: ${CUDAToolkit_VERSION}")
2318

2419
# GPU architectures — set via -DCMAKE_CUDA_ARCHITECTURES on the command line,
2520
# or fall back to a default set covering Turing through Ada + PTX for forward
@@ -28,14 +23,6 @@ if(BUILD_CUDA)
2823
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES STREQUAL "")
2924
set(CMAKE_CUDA_ARCHITECTURES "75;86;87;89" CACHE STRING "CUDA architectures" FORCE)
3025
endif()
31-
# Also emit PTX for the highest arch so the driver can JIT for newer GPUs
32-
list(APPEND CUDA_NVCC_FLAGS "-gencode arch=compute_89,code=compute_89")
33-
34-
# Extended lambda support
35-
list(APPEND CUDA_NVCC_FLAGS "--extended-lambda")
36-
37-
# Optimization flags
38-
list(APPEND CUDA_NVCC_FLAGS "-O3")
3926

4027
# Check if CUDA kernel source exists
4128
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/cuda/hesai_cuda_kernels.cu")
@@ -67,7 +54,7 @@ target_link_libraries(
6754
if(BUILD_CUDA AND CUDA_KERNELS_EXIST)
6855
enable_language(CUDA)
6956

70-
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O3 --expt-relaxed-constexpr")
57+
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O3 -diag-suppress 20012")
7158

7259
add_library(nebula_hesai_decoders_cuda SHARED
7360
src/cuda/hesai_cuda_kernels.cu
@@ -90,17 +77,20 @@ if(BUILD_CUDA AND CUDA_KERNELS_EXIST)
9077
$<INSTALL_INTERFACE:include>)
9178

9279
target_link_libraries(nebula_hesai_decoders_cuda
93-
PRIVATE cudart)
80+
PRIVATE CUDA::cudart_static)
9481

9582
target_compile_definitions(nebula_hesai_decoders_cuda PUBLIC NEBULA_CUDA_ENABLED)
9683
target_compile_definitions(nebula_hesai_decoders PUBLIC NEBULA_CUDA_ENABLED)
9784

9885
# Add CUDA include directories to main library for header resolution
9986
target_include_directories(nebula_hesai_decoders
100-
PUBLIC ${CUDA_INCLUDE_DIRS})
87+
PUBLIC ${CUDAToolkit_INCLUDE_DIRS})
10188

102-
# Link CUDA library to main library
103-
target_link_libraries(nebula_hesai_decoders PUBLIC nebula_hesai_decoders_cuda ${CUDA_LIBRARIES})
89+
# Link CUDA library to main library (CUDA::cudart is PRIVATE so it won't
90+
# propagate the imported target to downstream packages that lack CUDAToolkit)
91+
target_link_libraries(nebula_hesai_decoders
92+
PUBLIC nebula_hesai_decoders_cuda
93+
PRIVATE CUDA::cudart)
10494

10595
install(TARGETS nebula_hesai_decoders_cuda EXPORT export_nebula_hesai_decoders_cuda)
10696
ament_export_targets(export_nebula_hesai_decoders_cuda)

src/nebula_hesai/nebula_hesai_decoders/include/nebula_hesai_decoders/cuda/hesai_cuda_decoder.hpp

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 TIER IV, Inc.
1+
// Copyright 2026 TIER IV, Inc.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -32,8 +32,8 @@ struct CudaNebulaPoint
3232
float elevation;
3333
float intensity;
3434
uint8_t return_type;
35-
uint16_t channel;
3635
uint8_t in_current_scan; // 1 = belongs to current scan, 0 = belongs to output/next scan
36+
uint16_t channel;
3737
uint32_t entry_id; // Block group ID for batched processing (used for sorting & filtering)
3838
};
3939

@@ -89,15 +89,14 @@ struct CudaDecoderConfig
8989
// Multi-frame support for sensors like AT128 (has 4 mirror frames)
9090
uint32_t n_frames; // Number of frames (1 for single-frame, 4 for AT128)
9191
CudaFrameAngleInfo frame_angles[MAX_CUDA_FRAMES]; // Per-frame angle boundaries
92-
bool is_multi_frame; // True if sensor has multiple frames (uses frame_angles)
9392
};
9493

9594
/// @brief Main CUDA decoder class for Hesai LiDAR
9695
class HesaiCudaDecoder
9796
{
9897
public:
9998
HesaiCudaDecoder();
100-
~HesaiCudaDecoder();
99+
virtual ~HesaiCudaDecoder();
101100

102101
/// @brief Initialize decoder with maximum points and channels
103102
bool initialize(size_t max_points, uint32_t n_channels);
@@ -126,20 +125,9 @@ class HesaiCudaDecoder
126125

127126
extern "C" {
128127

129-
/// @brief Launch kernel to decode a single Hesai packet
130-
void launch_decode_hesai_packet(
131-
const uint16_t* d_distances,
132-
const uint8_t* d_reflectivities,
133-
const nebula::drivers::cuda::CudaAngleCorrectionData* d_angle_lut,
134-
const nebula::drivers::cuda::CudaDecoderConfig& config,
135-
nebula::drivers::cuda::CudaNebulaPoint* d_points,
136-
uint32_t* d_count,
137-
uint32_t n_azimuths,
138-
uint32_t raw_azimuth,
139-
cudaStream_t stream);
140-
141128
/// @brief Launch batched kernel to decode entire scan
142-
void launch_decode_hesai_scan_batch(
129+
/// @return true on success, false on CUDA error
130+
bool launch_decode_hesai_scan_batch(
143131
const uint16_t* d_distances_batch,
144132
const uint8_t* d_reflectivities_batch,
145133
const uint32_t* d_raw_azimuths,

src/nebula_hesai/nebula_hesai_decoders/include/nebula_hesai_decoders/decoders/angle_corrector_calibration_based.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ class AngleCorrectorCalibrationBased
155155
[[nodiscard]] std::tuple<uint32_t, uint32_t, uint32_t, uint32_t>
156156
get_cuda_raw_angles(double fov_start_deg, double fov_end_deg, double cut_angle_deg) const
157157
{
158-
// Find min/max azimuth offset in raw units
158+
// Find min/max azimuth offset in raw units.
159+
// All values are guaranteed to fit in int32_t: angles are in [0, 360) degrees and
160+
// AngleUnit is at most 100 (0.01-degree resolution), so raw values stay within ±36000.
159161
auto round_away_from_zero = [](float value) -> int32_t {
160162
return (value < 0) ? static_cast<int32_t>(std::floor(value))
161163
: static_cast<int32_t>(std::ceil(value));

src/nebula_hesai/nebula_hesai_decoders/include/nebula_hesai_decoders/decoders/hesai_decoder.hpp

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,8 @@
2626
#ifdef NEBULA_CUDA_ENABLED
2727
#include "nebula_hesai_decoders/cuda/hesai_cuda_decoder.hpp"
2828

29-
// C-linkage kernel launcher declarations
30-
extern "C" void launch_decode_hesai_packet(
31-
const uint16_t * d_distances,
32-
const uint8_t * d_reflectivities,
33-
const nebula::drivers::cuda::CudaAngleCorrectionData * d_angle_lut,
34-
const nebula::drivers::cuda::CudaDecoderConfig & config,
35-
nebula::drivers::cuda::CudaNebulaPoint * d_points,
36-
uint32_t * d_count,
37-
uint32_t n_azimuths,
38-
uint32_t raw_azimuth,
39-
cudaStream_t stream);
40-
41-
extern "C" void launch_decode_hesai_scan_batch(
29+
// C-linkage kernel launcher declaration (defined in hesai_cuda_kernels.cu)
30+
extern "C" bool launch_decode_hesai_scan_batch(
4231
const uint16_t * d_distances_batch,
4332
const uint8_t * d_reflectivities_batch,
4433
const uint32_t * d_raw_azimuths,
@@ -328,7 +317,7 @@ class HesaiDecoder : public HesaiScanDecoder
328317
config.max_output_points = n_entries * n_channels * max_returns;
329318

330319
if constexpr (SensorT::uses_calibration_based_angles) {
331-
config.is_multi_frame = false;
320+
// Single-frame sensor (calibration-based)
332321
config.n_frames = 1;
333322
config.timestamp_reset_angle_raw = cuda_timestamp_reset_angle_raw_;
334323
config.emit_angle_raw = cuda_emit_angle_raw_;
@@ -337,7 +326,7 @@ class HesaiDecoder : public HesaiScanDecoder
337326
config.frame_angles[0].timestamp_reset = cuda_timestamp_reset_angle_raw_;
338327
config.frame_angles[0].scan_emit = cuda_emit_angle_raw_;
339328
} else {
340-
config.is_multi_frame = true;
329+
// Multi-frame sensor (correction-based, e.g. AT128)
341330
config.n_frames = static_cast<uint32_t>(angle_corrector_.get_n_frames());
342331
config.timestamp_reset_angle_raw = 0;
343332
config.emit_angle_raw = 0;
@@ -465,11 +454,14 @@ class HesaiDecoder : public HesaiScanDecoder
465454
d_points_, 0, sparse_buffer_size * sizeof(cuda::CudaNebulaPoint), cuda_stream_);
466455

467456
// Launch batched kernel
468-
launch_decode_hesai_scan_batch(
457+
bool kernel_ok = launch_decode_hesai_scan_batch(
469458
gpu_scan_buffer_.d_distances_batch, gpu_scan_buffer_.d_reflectivities_batch,
470459
gpu_scan_buffer_.d_raw_azimuths, gpu_scan_buffer_.d_n_returns,
471460
gpu_scan_buffer_.d_last_azimuths, cuda_decoder_->get_angle_lut(), config, d_points_,
472461
d_count_, cuda_n_azimuths_, n_entries, cuda_stream_);
462+
if (!kernel_ok) {
463+
NEBULA_LOG_STREAM(logger_->error, "CUDA batched kernel launch failed");
464+
}
473465

474466
cudaStreamSynchronize(cuda_stream_);
475467

src/nebula_hesai/nebula_hesai_decoders/src/cuda/hesai_cuda_kernels.cu

Lines changed: 4 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 TIER IV, Inc.
1+
// Copyright 2026 TIER IV, Inc.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -71,68 +71,6 @@ __device__ __forceinline__ bool cuda_is_inside_overlap_multiframe(
7171
return false;
7272
}
7373

74-
// CUDA kernel for decoding a single Hesai LiDAR packet
75-
__global__ void decode_hesai_packet_kernel(
76-
const uint16_t * __restrict__ distances,
77-
const uint8_t * __restrict__ reflectivities,
78-
const CudaAngleCorrectionData * __restrict__ angle_lut,
79-
const CudaDecoderConfig config,
80-
CudaNebulaPoint * __restrict__ output_points,
81-
uint32_t * __restrict__ output_count,
82-
uint32_t n_azimuths,
83-
uint32_t raw_azimuth)
84-
{
85-
const uint32_t channel_id = blockIdx.x * blockDim.x + threadIdx.x;
86-
const uint32_t block_id = blockIdx.y;
87-
88-
if (channel_id >= config.n_channels || block_id >= config.n_blocks) {
89-
return;
90-
}
91-
92-
const uint32_t data_stride = config.data_stride > 0 ? config.data_stride : config.n_blocks;
93-
const uint32_t data_idx = channel_id * data_stride + block_id;
94-
95-
const uint16_t raw_distance = distances[data_idx];
96-
const uint8_t reflectivity = reflectivities[data_idx];
97-
98-
if (raw_distance == 0) {
99-
return;
100-
}
101-
102-
const float distance = static_cast<float>(raw_distance) * config.dis_unit;
103-
104-
if (distance < config.min_range || distance > config.max_range) {
105-
return;
106-
}
107-
108-
if (distance < config.sensor_min_range || distance > config.sensor_max_range) {
109-
return;
110-
}
111-
112-
const uint32_t azimuth_idx = (raw_azimuth / config.azimuth_scale) % n_azimuths;
113-
const uint32_t lut_idx = azimuth_idx * config.n_channels + channel_id;
114-
const CudaAngleCorrectionData angle_data = angle_lut[lut_idx];
115-
116-
const float xy_distance = distance * angle_data.cos_elevation;
117-
const float x = xy_distance * angle_data.sin_azimuth;
118-
const float y = xy_distance * angle_data.cos_azimuth;
119-
const float z = distance * angle_data.sin_elevation;
120-
121-
const uint32_t output_idx = atomicAdd(output_count, 1);
122-
123-
CudaNebulaPoint & out_pt = output_points[output_idx];
124-
out_pt.x = x;
125-
out_pt.y = y;
126-
out_pt.z = z;
127-
out_pt.distance = distance;
128-
out_pt.azimuth = angle_data.azimuth_rad;
129-
out_pt.elevation = angle_data.elevation_rad;
130-
out_pt.intensity = static_cast<float>(reflectivity);
131-
out_pt.return_type = static_cast<uint8_t>(block_id);
132-
out_pt.channel = static_cast<uint16_t>(channel_id);
133-
out_pt.entry_id = config.entry_id;
134-
}
135-
13674
/// @brief Batched kernel for processing an entire scan in one launch
13775
__global__ void decode_hesai_scan_batch_kernel(
13876
const uint16_t * __restrict__ d_distances_batch,
@@ -186,7 +124,7 @@ __global__ void decode_hesai_scan_batch_kernel(
186124
uint8_t in_current_scan = 1;
187125

188126
bool is_in_overlap = false;
189-
if (config.is_multi_frame) {
127+
if (config.n_frames > 1) {
190128
is_in_overlap = cuda_is_inside_overlap_multiframe(
191129
last_azimuth, raw_azimuth, config.frame_angles, config.n_frames, config.n_azimuths_raw);
192130
} else {
@@ -336,37 +274,8 @@ bool HesaiCudaDecoder::upload_angle_corrections(
336274

337275
} // namespace nebula::drivers::cuda
338276

339-
// C-linkage wrapper for per-packet kernel
340-
extern "C" void launch_decode_hesai_packet(
341-
const uint16_t * d_distances,
342-
const uint8_t * d_reflectivities,
343-
const nebula::drivers::cuda::CudaAngleCorrectionData * d_angle_lut,
344-
const nebula::drivers::cuda::CudaDecoderConfig & config,
345-
nebula::drivers::cuda::CudaNebulaPoint * d_points,
346-
uint32_t * d_count,
347-
uint32_t n_azimuths,
348-
uint32_t raw_azimuth,
349-
cudaStream_t stream)
350-
{
351-
const uint32_t threads_per_block = 128;
352-
const uint32_t n_blocks_x = (config.n_channels + threads_per_block - 1) / threads_per_block;
353-
const uint32_t n_blocks_y = config.n_blocks;
354-
355-
dim3 grid(n_blocks_x, n_blocks_y);
356-
dim3 block(threads_per_block);
357-
358-
nebula::drivers::cuda::decode_hesai_packet_kernel<<<grid, block, 0, stream>>>(
359-
d_distances, d_reflectivities, d_angle_lut, config,
360-
d_points, d_count, n_azimuths, raw_azimuth);
361-
362-
cudaError_t err = cudaGetLastError();
363-
if (err != cudaSuccess) {
364-
fprintf(stderr, "CUDA kernel launch failed: %s\n", cudaGetErrorString(err));
365-
}
366-
}
367-
368277
// C-linkage wrapper for batched kernel
369-
extern "C" void launch_decode_hesai_scan_batch(
278+
extern "C" bool launch_decode_hesai_scan_batch(
370279
const uint16_t * d_distances_batch,
371280
const uint8_t * d_reflectivities_batch,
372281
const uint32_t * d_raw_azimuths,
@@ -391,8 +300,5 @@ extern "C" void launch_decode_hesai_scan_batch(
391300
d_distances_batch, d_reflectivities_batch, d_raw_azimuths, d_n_returns, d_last_azimuths,
392301
d_angle_lut, config, d_points, d_count, n_azimuths, n_packets);
393302

394-
cudaError_t err = cudaGetLastError();
395-
if (err != cudaSuccess) {
396-
fprintf(stderr, "CUDA batched kernel launch failed: %s\n", cudaGetErrorString(err));
397-
}
303+
return cudaGetLastError() == cudaSuccess;
398304
}

0 commit comments

Comments
 (0)