Skip to content

Commit cd5f511

Browse files
committed
[CUDA] Add cuDNN-free ArgMax/ArgMin and fix LogSoftmax on plugin EP
Phase 2 of the CUDA plugin EP no-cuDNN work. - Add a custom arg_min_max_last_axis CUDA kernel and route single last-axis ArgMax/ArgMin through it in ReduceComputeCore, so these ops no longer require a cuDNN handle. select_last_index==1 is already rejected on CUDA, so keeping the first matching index is correct. - Add TryGetCudnnHandle (native and plugin adapter) that returns the cuDNN handle when available and nullptr otherwise, and use it in the reduction kernel so unsupported cuDNN paths degrade instead of throwing during handle acquisition. - Detect LogSoftmax from node.OpType() instead of the KernelDef op name so the plugin EP adapter classifies it correctly. - Enable and extend plugin EP tests: LogSoftmax and ArgMin coverage, drop the requires_cudnn gate from ArgMax/ReduceMean/ReduceSum, and reduce over the last axis to exercise the cuDNN-free paths.
1 parent bcc9d30 commit cd5f511

8 files changed

Lines changed: 133 additions & 12 deletions

File tree

docs/cuda_plugin_ep/QUICK_START.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ For local Linux CUDA 13 validation, use the no-cuDNN helper script. It keeps `CU
2727
bash .env/cuda_130_plugin_no_cudnn.sh --build --test_plugin
2828
```
2929

30-
The test mode sets `ORT_TEST_CUDA_PLUGIN_EP=1` and `ORT_TEST_CUDA_PLUGIN_NO_CUDNN=1`, which passes `enable_cudnn=0` to plugin sessions and skips plugin tests for operators that still require cuDNN, such as Conv, ConvTranspose, BatchNormalization, InstanceNormalization, LRN, ArgMax, reductions, Einsum, and cuDNN-backed pooling paths.
30+
The test mode sets `ORT_TEST_CUDA_PLUGIN_EP=1` and `ORT_TEST_CUDA_PLUGIN_NO_CUDNN=1`, which passes `enable_cudnn=0` to plugin sessions and skips plugin tests for operators that still require cuDNN, such as Conv, ConvTranspose, BatchNormalization, InstanceNormalization, LRN, Einsum, and cuDNN-backed pooling paths.
3131

3232
## Minimum ONNX Runtime Version
3333

onnxruntime/core/providers/cuda/cuda_kernel.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ class CudaKernel : public OpKernel {
133133
return RequireCudnnHandle(GetCudnnHandle(static_cast<CudaStream*>(ctx->GetComputeStream())));
134134
}
135135

136+
inline cudnnHandle_t TryGetCudnnHandle(OpKernelContext* ctx) const {
137+
return GetCudnnHandle(static_cast<CudaStream*>(ctx->GetComputeStream()));
138+
}
139+
136140
static inline cudnnHandle_t GetCudnnHandle(onnxruntime::CudaStream* stream) {
137141
return stream ? stream->cudnn_handle_ : nullptr;
138142
}

onnxruntime/core/providers/cuda/math/softmax.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Softmax final : public CudaKernel {
4747
}
4848
}
4949

50-
log_softmax_ = info.GetKernelDef().OpName() == "LogSoftmax";
50+
log_softmax_ = node.OpType() == "LogSoftmax";
5151
}
5252

5353
Status ComputeInternal(OpKernelContext* context) const override;

onnxruntime/core/providers/cuda/plugin/cuda_kernel_adapter.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,20 @@ class CudaKernel : public OpKernel {
10701070
return handle;
10711071
}
10721072

1073+
cudnnHandle_t TryGetCudnnHandle(OpKernelContext* ctx) const {
1074+
auto stream = Stream(ctx);
1075+
auto handle = GetCudnnHandle(stream);
1076+
if (handle != nullptr) {
1077+
return handle;
1078+
}
1079+
1080+
handle = DefaultCudnnHandle();
1081+
if (handle != nullptr && stream != nullptr) {
1082+
CUDNN_CALL_THROW(cudnnSetStream(handle, stream));
1083+
}
1084+
return handle;
1085+
}
1086+
10731087
static cublasHandle_t GetCublasHandle(cudaStream_t s) {
10741088
auto* sync = cuda_plugin::CudaSyncStream::FromCudaStream(s);
10751089
return sync ? sync->GetCublasHandle() : nullptr;

onnxruntime/core/providers/cuda/reduction/reduction_functions.cu

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,51 @@ INSTANTIATE_REDUCE_MATRIX_COLUMNS(double);
513513
INSTANTIATE_REDUCE_MATRIX_COLUMNS(BFloat16);
514514
#undef INSTANTIATE_REDUCE_MATRIX_COLUMNS
515515

516+
namespace detail {
517+
template <typename TIn, bool IsArgMax>
518+
__global__ void arg_min_max_last_axis_kernel(const TIn* input, int64_t* output, int m, int n) {
519+
const int row = blockIdx.x * blockDim.x + threadIdx.x;
520+
if (row >= m) return;
521+
522+
const int64_t row_offset = static_cast<int64_t>(row) * n;
523+
TIn best_value = input[row_offset];
524+
int64_t best_index = 0;
525+
for (int i = 1; i < n; ++i) {
526+
const TIn value = input[row_offset + i];
527+
if constexpr (IsArgMax) {
528+
if (value > best_value) {
529+
best_value = value;
530+
best_index = i;
531+
}
532+
} else {
533+
if (value < best_value) {
534+
best_value = value;
535+
best_index = i;
536+
}
537+
}
538+
}
539+
540+
output[row] = best_index;
541+
}
542+
} // namespace detail
543+
544+
template <typename TIn, bool IsArgMax>
545+
Status arg_min_max_last_axis(cudaStream_t stream, const TIn* input, int64_t* output, int m, int n) {
546+
if (m == 0) return Status::OK();
547+
constexpr int block_size = 256;
548+
const int grid_size = (m + block_size - 1) / block_size;
549+
detail::arg_min_max_last_axis_kernel<TIn, IsArgMax><<<grid_size, block_size, 0, stream>>>(input, output, m, n);
550+
return CUDA_CALL(cudaGetLastError());
551+
}
552+
553+
#define INSTANTIATE_ARG_MIN_MAX_LAST_AXIS(T) \
554+
template Status arg_min_max_last_axis<T, true>(cudaStream_t stream, const T* input, int64_t* output, int m, int n); \
555+
template Status arg_min_max_last_axis<T, false>(cudaStream_t stream, const T* input, int64_t* output, int m, int n)
556+
INSTANTIATE_ARG_MIN_MAX_LAST_AXIS(half);
557+
INSTANTIATE_ARG_MIN_MAX_LAST_AXIS(float);
558+
INSTANTIATE_ARG_MIN_MAX_LAST_AXIS(double);
559+
#undef INSTANTIATE_ARG_MIN_MAX_LAST_AXIS
560+
516561
} // namespace cuda
517562
} // namespace onnxruntime
518563

onnxruntime/core/providers/cuda/reduction/reduction_functions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ Status reduce_matrix_rows(cudaStream_t stream, const TIn* input, TOut* output, i
103103
template <typename TIn, typename TOut>
104104
Status reduce_matrix_columns(cudaStream_t stream, const TIn* input, TOut* output, int m, int n, void* buffer, size_t buffer_size);
105105

106+
/** Computes ArgMax/ArgMin indices over the last dimension in a row-major matrix. */
107+
template <typename TIn, bool IsArgMax>
108+
Status arg_min_max_last_axis(cudaStream_t stream, const TIn* input, int64_t* output, int m, int n);
109+
106110
/** Apply unary elementwise division. */
107111
template <typename T>
108112
void UnaryDiv(cudaStream_t stream, const T* input, T* output, T denominator, size_t count);

onnxruntime/core/providers/cuda/reduction/reduction_ops.cc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,29 @@ Status ReduceComputeCore(const AllocatorPtr& gpu_allocator, const CudaKernel* ke
481481
}
482482
}
483483

484+
if constexpr (ReduceTensorIndices == CUDNN_REDUCE_TENSOR_FLATTENED_INDICES) {
485+
if (axes.size() == 1) {
486+
const int64_t rank = input_shape.NumDimensions();
487+
const int64_t axis = HandleNegativeAxis(axes[0], rank);
488+
if (axis == rank - 1) {
489+
const int64_t m = input_shape.SizeToDimension(axis);
490+
const int64_t n = input_shape[axis];
491+
if (n > 0 && m <= std::numeric_limits<int>::max() && n <= std::numeric_limits<int>::max()) {
492+
if (cudnn_reduce_op == CUDNN_REDUCE_TENSOR_MAX) {
493+
return arg_min_max_last_axis<CudaT, true>(stream, reinterpret_cast<const CudaT*>(input.Data<T>()),
494+
output.MutableData<int64_t>(), gsl::narrow_cast<int>(m),
495+
gsl::narrow_cast<int>(n));
496+
}
497+
if (cudnn_reduce_op == CUDNN_REDUCE_TENSOR_MIN) {
498+
return arg_min_max_last_axis<CudaT, false>(stream, reinterpret_cast<const CudaT*>(input.Data<T>()),
499+
output.MutableData<int64_t>(), gsl::narrow_cast<int>(m),
500+
gsl::narrow_cast<int>(n));
501+
}
502+
}
503+
}
504+
}
505+
}
506+
484507
// This reduction keep adding values to this buffer. If a non-zero value, say 1000, is here, the sum will start with 1000.
485508
// Therefore zeroing out the memory is required
486509
CUDA_RETURN_IF_ERROR(cudaMemsetAsync(output.MutableDataRaw(), 0, output.SizeInBytes(), stream));
@@ -785,7 +808,7 @@ Status ReduceKernel<allow_multi_axes>::ComputeImpl(OpKernelContext* ctx, cudnnRe
785808
const bool fast_reduction = fast_reduction_ && !ctx->GetUseDeterministicCompute();
786809
return ReduceComputeCore<T, ReduceTensorIndices>(AllocatorPtr{}, this, *X, prepare_reduce_metadata, *Y, cudnn_reduce_op, axes,
787810
calculate_log_, calculate_sqt_, log_sum_exp_, fast_reduction,
788-
Stream(ctx), GetComputeStream(ctx), GetCudnnHandle(ctx));
811+
Stream(ctx), GetComputeStream(ctx), TryGetCudnnHandle(ctx));
789812
}
790813

791814
#define SPECIALIZED_REDUCEKERNEL_COMPUTEIMPL(T) \

onnxruntime/test/python/transformers/test_cuda_plugin_ep.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,25 @@ def expected(f):
18041804
result = _run_model_test(target_device, "Softmax", model, feed, expected)
18051805
self.assertEqual(result, TEST_PASS, "Softmax test failed")
18061806

1807+
def test_op_log_softmax(self):
1808+
target_device = get_cuda_plugin_device()
1809+
model = _make_simple_model(
1810+
"LogSoftmax",
1811+
[("X", TensorProto.FLOAT, [2, 5])],
1812+
[("Y", TensorProto.FLOAT, [2, 5])],
1813+
attrs={"axis": 1},
1814+
opset=13,
1815+
)
1816+
feed = {"X": np.random.rand(2, 5).astype(np.float32)}
1817+
1818+
def expected(f):
1819+
x = f["X"]
1820+
shifted = x - np.max(x, axis=1, keepdims=True)
1821+
return shifted - np.log(np.sum(np.exp(shifted), axis=1, keepdims=True))
1822+
1823+
result = _run_model_test(target_device, "LogSoftmax", model, feed, expected)
1824+
self.assertEqual(result, TEST_PASS, "LogSoftmax test failed")
1825+
18071826
def test_op_relu(self):
18081827
target_device = get_cuda_plugin_device()
18091828
model = _make_simple_model(
@@ -1900,7 +1919,6 @@ def test_op_flatten(self):
19001919
result = _run_model_test(target_device, "Flatten", model, feed, lambda f: f["X"].reshape(2, 12))
19011920
self.assertEqual(result, TEST_PASS, "Flatten test failed")
19021921

1903-
@requires_cudnn
19041922
def test_op_argmax(self):
19051923
target_device = get_cuda_plugin_device()
19061924
model = _make_simple_model(
@@ -1916,6 +1934,21 @@ def test_op_argmax(self):
19161934
)
19171935
self.assertEqual(result, TEST_PASS, "ArgMax test failed")
19181936

1937+
def test_op_argmin(self):
1938+
target_device = get_cuda_plugin_device()
1939+
model = _make_simple_model(
1940+
"ArgMin",
1941+
[("X", TensorProto.FLOAT, [3, 5])],
1942+
[("Y", TensorProto.INT64, [3, 1])],
1943+
attrs={"axis": 1, "keepdims": 1},
1944+
opset=13,
1945+
)
1946+
feed = {"X": np.random.rand(3, 5).astype(np.float32)}
1947+
result = _run_model_test(
1948+
target_device, "ArgMin", model, feed, lambda f: np.argmin(f["X"], axis=1).reshape(3, 1)
1949+
)
1950+
self.assertEqual(result, TEST_PASS, "ArgMin test failed")
1951+
19191952
def test_op_topk(self):
19201953
target_device = get_cuda_plugin_device()
19211954
model = _make_simple_model(
@@ -2030,37 +2063,35 @@ def expected(f):
20302063
result = _run_model_test(target_device, "ConvTranspose", model, feed, expected)
20312064
self.assertEqual(result, TEST_PASS, "ConvTranspose test failed")
20322065

2033-
@requires_cudnn
20342066
def test_op_reduce_mean(self):
20352067
target_device = get_cuda_plugin_device()
20362068
model = _make_simple_model(
20372069
"ReduceMean",
20382070
[("X", TensorProto.FLOAT, [3, 4, 5])],
2039-
[("Y", TensorProto.FLOAT, [3, 1, 5])],
2040-
attrs={"axes": [1], "keepdims": 1},
2071+
[("Y", TensorProto.FLOAT, [3, 4, 1])],
2072+
attrs={"axes": [2], "keepdims": 1},
20412073
opset=13,
20422074
)
20432075
feed = {"X": np.random.rand(3, 4, 5).astype(np.float32)}
20442076
result = _run_model_test(
2045-
target_device, "ReduceMean", model, feed, lambda f: np.mean(f["X"], axis=1, keepdims=True)
2077+
target_device, "ReduceMean", model, feed, lambda f: np.mean(f["X"], axis=2, keepdims=True)
20462078
)
20472079
self.assertEqual(result, TEST_PASS, "ReduceMean test failed")
20482080

2049-
@requires_cudnn
20502081
def test_op_reduce_sum(self):
20512082
target_device = get_cuda_plugin_device()
20522083
model = _make_simple_model(
20532084
"ReduceSum",
20542085
[("X", TensorProto.FLOAT, [3, 4, 5]), ("axes", TensorProto.INT64, [1])],
2055-
[("Y", TensorProto.FLOAT, [3, 1, 5])],
2086+
[("Y", TensorProto.FLOAT, [3, 4, 1])],
20562087
attrs={"keepdims": 1},
20572088
opset=13,
20582089
)
2059-
axes_init = helper.make_tensor("axes", TensorProto.INT64, [1], [1])
2090+
axes_init = helper.make_tensor("axes", TensorProto.INT64, [1], [2])
20602091
model.graph.initializer.append(axes_init)
20612092
feed = {"X": np.random.rand(3, 4, 5).astype(np.float32)}
20622093
result = _run_model_test(
2063-
target_device, "ReduceSum", model, feed, lambda f: np.sum(f["X"], axis=1, keepdims=True)
2094+
target_device, "ReduceSum", model, feed, lambda f: np.sum(f["X"], axis=2, keepdims=True)
20642095
)
20652096
self.assertEqual(result, TEST_PASS, "ReduceSum test failed")
20662097

0 commit comments

Comments
 (0)