[ROCm] add Softmax Tunable Op (#14541)

### Description
Add Softmax Tunable Op, only include blockwise vec implementation and
composable kernel.
Related PR: https://github.com/microsoft/onnxruntime/pull/14475,
https://github.com/microsoft/onnxruntime/pull/14612

---------

Co-authored-by: peixuanzuo <peixuanzuo@linmif39a000004.zvflicr54joexhdgnhvmxrxygg.phxx.internal.cloudapp.net>
This commit is contained in:
PeixuanZuo 2023-02-13 15:56:50 +08:00 committed by GitHub
parent 12d91173c4
commit 326cf2f5e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 625 additions and 73 deletions

View file

@ -1460,6 +1460,7 @@ if (onnxruntime_USE_ROCM)
device_gemm_add_fastgelu_instance
device_gemm_fastgelu_instance
device_batched_gemm_instance
device_softmax_instance
)
target_compile_definitions(onnxruntime_providers_rocm PRIVATE USE_COMPOSABLE_KERNEL)
endif()

View file

@ -122,6 +122,7 @@ set(provider_excluded_files
"math/softmax_impl.cu"
"math/softmax_warpwise_impl.cuh"
"math/softmax_common.cc"
"math/softmax_common.h"
"math/softmax.cc"
"nn/conv.cc"
"nn/conv.h"

View file

@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include <string>
#include <utility>
#include <vector>
#ifdef USE_COMPOSABLE_KERNEL
#include "ck/ck.hpp"
#include "ck/library/tensor_operation_instance/gpu/softmax.hpp"
#include "ck/tensor_operation/gpu/device/device_softmax.hpp"
#include "ck/tensor_operation/gpu/device/tensor_layout.hpp"
#include "ck/tensor_operation/gpu/element/element_wise_operation.hpp"
#endif // USE_COMPOSABLE_KERNEL
#include "core/providers/rocm/math/softmax_common.h"
namespace onnxruntime {
namespace rocm {
#ifdef USE_COMPOSABLE_KERNEL
template <typename T>
struct DataTypeAdaptor {
using type = T;
};
template <>
struct DataTypeAdaptor<half> {
using type = ck::half_t;
};
template <>
struct DataTypeAdaptor<BFloat16> {
using type = ck::bhalf16_t;
};
using Nop = ck::tensor_operation::element_wise::PassThrough;
constexpr int Rank = 4;
constexpr int NumReduceDim = 1;
template <typename InputT, typename OutputT, typename AccT>
auto GetCKSoftmaxTypeStringAndOps() {
using InDataType = typename DataTypeAdaptor<InputT>::type;
using OutDataType = typename DataTypeAdaptor<OutputT>::type;
using AccDataType = typename DataTypeAdaptor<AccT>::type;
using DeviceSoftmax = ck::tensor_operation::device::
DeviceSoftmax<InDataType, AccDataType, OutDataType, Nop, Nop, Rank>;
using InstanceFactory = ck::tensor_operation::device::instance::DeviceOperationInstanceFactory<DeviceSoftmax>;
std::vector<std::pair<std::string, tunable::Op<SoftmaxParams<InputT, OutputT>>>> ret;
for (auto&& impl : InstanceFactory::GetInstances()) {
auto type_string = onnxruntime::MakeString(impl->GetTypeString());
auto invoker = impl->MakeInvokerPointer();
auto ck_softmax_op = [impl = std::move(impl), invoker = std::move(invoker)](const SoftmaxParams<InputT, OutputT>* params) -> Status {
AccDataType alpha{1.0f};
AccDataType beta{0.0f};
TUNABLE_OP_RETURN_UNSUPPORTED_ARGUMENT_IF(
params->is_log_softmax,
impl->GetTypeString(), " does not support log softmax");
TUNABLE_OP_RETURN_UNSUPPORTED_ARGUMENT_IF(
impl->GetRank() != Rank || impl->GetNumReduceDim() != NumReduceDim,
impl->GetTypeString(), " does not support current Rank or NumReduceDim ", params->Signature());
std::vector<ck::index_t> in_lengths{1, 1, params->batch_count, params->softmax_elements};
std::vector<ck::index_t> in_strides{params->batch_count * params->input_stride, params->batch_count * params->input_stride, params->input_stride, 1};
std::vector<ck::index_t> reduce_dims{3};
auto nop = Nop{};
auto arg = impl->MakeArgumentPointer(in_lengths, in_strides, reduce_dims, &alpha, &beta,
params->input, params->output, nop, nop);
TUNABLE_OP_RETURN_UNSUPPORTED_ARGUMENT_IF(!impl->IsSupportedArgument(arg.get()),
impl->GetTypeString(), " does not support ", params->Signature());
invoker->Run(arg.get(), StreamConfig{params->stream});
return Status::OK();
};
ret.emplace_back(std::make_pair(std::move(type_string), std::move(ck_softmax_op)));
}
return ret;
}
#endif // USE_COMPOSABLE_KERNEL
} // namespace rocm
} // namespace onnxruntime

View file

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/common/status.h"
#include "core/providers/rocm/miopen_common.h"
#include "core/providers/rocm/tunable/rocm_tunable.h"
namespace onnxruntime {
namespace rocm {
template <typename InputT, typename OutputT>
struct SoftmaxParams : tunable::OpParams {
SoftmaxParams(tunable::RocmTuningContext* tuning_ctx, hipStream_t stream, OutputT* output, const InputT* input,
int softmax_elements, int input_stride, int output_stride, int batch_count, bool is_log_softmax)
: OpParams(tuning_ctx, stream), output(output), input(input), softmax_elements(softmax_elements), input_stride(input_stride), output_stride(output_stride), batch_count(batch_count), is_log_softmax(is_log_softmax) {}
std::string Signature() const override {
std::string sig = std::to_string(batch_count) + "_" + std::to_string(softmax_elements);
return sig;
}
OutputT* output;
const InputT* input;
int softmax_elements;
int input_stride;
int output_stride;
int batch_count;
bool is_log_softmax;
};
Status SoftmaxForward(miopenHandle_t miopen_handle, const void* alpha, const miopenTensorDescriptor_t input_tensor,
const void* input_data, const void* beta, const miopenTensorDescriptor_t output_tensor,
void* output_data);
Status SoftmaxBackward(miopenHandle_t miopen_handle, bool is_log_softmax, const void* alpha,
const miopenTensorDescriptor_t input_tensor, const void* output_data,
const void* output_grad_data, const void* beta, const miopenTensorDescriptor_t output_tensor,
void* input_grad_data);
} // namespace rocm
} // namespace onnxruntime

View file

@ -1,18 +1,18 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright (c) 2016-present, Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Modifications Copyright (c) Microsoft. */
@ -29,8 +29,8 @@
namespace onnxruntime {
namespace rocm {
template <typename input_t, typename output_t, typename acc_t, bool is_log_softmax>
Status dispatch_warpwise_softmax_forward(hipStream_t stream, output_t* dst, const input_t* src, int softmax_elements, int softmax_elements_stride, int batch_count) {
template <typename InputT, typename OutputT, typename AccT, bool IsLogSoftmax>
Status dispatch_warpwise_softmax_forward(hipStream_t stream, OutputT* dst, const InputT* src, int softmax_elements, int softmax_elements_stride, int batch_count) {
if (softmax_elements == 0) {
return Status::OK();
} else {
@ -51,39 +51,23 @@ Status dispatch_warpwise_softmax_forward(hipStream_t stream, output_t* dst, cons
dim3 threads(warp_size, warps_per_block, 1);
// Launch code would be more elegant if C++ supported FOR CONSTEXPR
switch (log2_elements) {
case 0: // 1
softmax_warp_forward<input_t, output_t, acc_t, 0, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 1: // 2
softmax_warp_forward<input_t, output_t, acc_t, 1, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 2: // 4
softmax_warp_forward<input_t, output_t, acc_t, 2, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 3: // 8
softmax_warp_forward<input_t, output_t, acc_t, 3, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 4: // 16
softmax_warp_forward<input_t, output_t, acc_t, 4, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 5: // 32
softmax_warp_forward<input_t, output_t, acc_t, 5, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 6: // 64
softmax_warp_forward<input_t, output_t, acc_t, 6, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 7: // 128
softmax_warp_forward<input_t, output_t, acc_t, 7, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 8: // 256
softmax_warp_forward<input_t, output_t, acc_t, 8, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 9: // 512
softmax_warp_forward<input_t, output_t, acc_t, 9, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
case 10: // 1024
softmax_warp_forward<input_t, output_t, acc_t, 10, is_log_softmax><<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, softmax_elements_stride, softmax_elements);
break;
#define LAUNCH_SOFTMAX_WARP_FORWARD(L2E) \
case L2E: \
softmax_warp_forward<InputT, OutputT, AccT, L2E, IsLogSoftmax> \
<<<dim3(blocks), dim3(threads), 0, stream>>>(dst, src, batch_count, \
softmax_elements_stride, softmax_elements); \
break;
LAUNCH_SOFTMAX_WARP_FORWARD(0); // 1
LAUNCH_SOFTMAX_WARP_FORWARD(1); // 2
LAUNCH_SOFTMAX_WARP_FORWARD(2); // 4
LAUNCH_SOFTMAX_WARP_FORWARD(3); // 8
LAUNCH_SOFTMAX_WARP_FORWARD(4); // 16
LAUNCH_SOFTMAX_WARP_FORWARD(5); // 32
LAUNCH_SOFTMAX_WARP_FORWARD(6); // 64
LAUNCH_SOFTMAX_WARP_FORWARD(7); // 128
LAUNCH_SOFTMAX_WARP_FORWARD(8); // 256
LAUNCH_SOFTMAX_WARP_FORWARD(9); // 512
LAUNCH_SOFTMAX_WARP_FORWARD(10); // 1024
default:
break;
}
@ -91,39 +75,43 @@ Status dispatch_warpwise_softmax_forward(hipStream_t stream, output_t* dst, cons
return HIP_CALL(hipGetLastError());
}
#define SPECIALIZED_SOFTMAX_IMPL(input_t, output_t, acc_t) \
template Status dispatch_warpwise_softmax_forward<input_t, output_t, acc_t, false>(hipStream_t stream, output_t * dst, const input_t* src, int softmax_elements, int softmax_elements_stride, int batch_count); \
template Status dispatch_warpwise_softmax_forward<input_t, output_t, acc_t, true>(hipStream_t stream, output_t * dst, const input_t* src, int softmax_elements, int softmax_elements_stride, int batch_count);
#define SPECIALIZED_SOFTMAX_IMPL(InputT, OutputT, AccT) \
template Status dispatch_warpwise_softmax_forward<InputT, OutputT, AccT, false>( \
hipStream_t stream, OutputT * dst, const InputT* src, int softmax_elements, \
int softmax_elements_stride, int batch_count); \
template Status dispatch_warpwise_softmax_forward<InputT, OutputT, AccT, true>( \
hipStream_t stream, OutputT * dst, const InputT* src, int softmax_elements, \
int softmax_elements_stride, int batch_count);
SPECIALIZED_SOFTMAX_IMPL(float, float, float)
SPECIALIZED_SOFTMAX_IMPL(half, half, float)
SPECIALIZED_SOFTMAX_IMPL(double, double, double)
SPECIALIZED_SOFTMAX_IMPL(BFloat16, BFloat16, float)
template <typename input_t, typename output_t, typename acc_t, bool is_log_softmax>
Status dispatch_blockwise_softmax_forward(hipStream_t stream, output_t* output, const input_t* input, int softmax_elements,
int input_stride, int output_stride, int batch_count) {
template <typename InputT, typename OutputT, typename AccT, bool IsLogSoftmax>
Status dispatch_blockwise_softmax_forward(hipStream_t stream, OutputT* output, const InputT* input, int softmax_elements,
int input_stride, int output_stride, int batch_count) {
dim3 grid(batch_count);
constexpr int ILP = sizeof(float4) / sizeof(input_t);
constexpr int ILP = sizeof(float4) / sizeof(InputT);
dim3 block = SoftMax_getBlockSize(ILP, softmax_elements);
if (is_log_softmax) {
softmax_block_forward<ILP, input_t, acc_t, output_t, LogSoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(acc_t), stream>>>(output, const_cast<input_t*>(input),
softmax_elements, input_stride, output_stride);
if (IsLogSoftmax) {
softmax_block_forward<ILP, InputT, AccT, OutputT, LogSoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(AccT), stream>>>(output, const_cast<InputT*>(input),
softmax_elements, input_stride, output_stride);
} else {
softmax_block_forward<ILP, input_t, acc_t, output_t, SoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(acc_t), stream>>>(output, const_cast<input_t*>(input),
softmax_elements, input_stride, output_stride);
softmax_block_forward<ILP, InputT, AccT, OutputT, SoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(AccT), stream>>>(output, const_cast<InputT*>(input),
softmax_elements, input_stride, output_stride);
}
return HIP_CALL(hipGetLastError());
return HIP_CALL(hipGetLastError());
}
#define SPECIALIZED_BLOCKWISE_SOFTMAX_IMPL(input_t, output_t, acc_t) \
template Status dispatch_blockwise_softmax_forward<input_t, output_t, acc_t, false>( \
hipStream_t stream, output_t * output, const input_t* src, int softmax_elements, \
int input_stride, int output_stride, int batch_count); \
template Status dispatch_blockwise_softmax_forward<input_t, output_t, acc_t, true>( \
hipStream_t stream, output_t * output, const input_t* src, int softmax_elements, \
#define SPECIALIZED_BLOCKWISE_SOFTMAX_IMPL(InputT, OutputT, AccT) \
template Status dispatch_blockwise_softmax_forward<InputT, OutputT, AccT, false>( \
hipStream_t stream, OutputT * output, const InputT* input, int softmax_elements, \
int input_stride, int output_stride, int batch_count); \
template Status dispatch_blockwise_softmax_forward<InputT, OutputT, AccT, true>( \
hipStream_t stream, OutputT * output, const InputT* input, int softmax_elements, \
int input_stride, int output_stride, int batch_count);
SPECIALIZED_BLOCKWISE_SOFTMAX_IMPL(float, float, float)
@ -135,5 +123,5 @@ SPECIALIZED_BLOCKWISE_SOFTMAX_IMPL(BFloat16, BFloat16, float)
SPECIALIZED_BLOCKWISE_SOFTMAX_IMPL(half, float, float) // used by BeamSearch op
#endif
}
}
} // namespace rocm
} // namespace onnxruntime

View file

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include <hip/hip_runtime.h>
#include "core/providers/rocm/cu_inc/common.cuh"
#include "core/providers/rocm/math/softmax_ck.cuh"
#include "core/providers/rocm/math/softmax_common.h"
#include "core/providers/rocm/math/softmax_warpwise_impl.cuh"
#include "core/providers/rocm/math/softmax_blockwise_impl.cuh"
#include "core/providers/rocm/tunable/rocm_tunable.h"
namespace onnxruntime {
namespace rocm {
template <typename InputT, typename OutputT, typename AccT, int VecSize>
Status SoftmaxBlockwiseOp(const SoftmaxParams<InputT, OutputT>* params) {
dim3 grid(params->batch_count);
dim3 block = SoftMax_getBlockSize(VecSize, params->softmax_elements);
if (params->is_log_softmax) {
softmax_block_forward<VecSize, InputT, AccT, OutputT, LogSoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(AccT), params->stream>>>(params->output, const_cast<InputT*>(params->input),
params->softmax_elements, params->input_stride,
params->output_stride);
} else {
softmax_block_forward<VecSize, InputT, AccT, OutputT, SoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(AccT), params->stream>>>(params->output, const_cast<InputT*>(params->input),
params->softmax_elements, params->input_stride,
params->output_stride);
}
return HIP_CALL(hipGetLastError());
}
template <typename InputT, typename OutputT, typename AccT>
Status SoftmaxBlockwiseStaticSelection(const SoftmaxParams<InputT, OutputT>* params) {
dim3 grid(params->batch_count);
constexpr int ILP = sizeof(float4) / sizeof(InputT);
dim3 block = SoftMax_getBlockSize(ILP, params->softmax_elements);
if (params->is_log_softmax) {
softmax_block_forward<ILP, InputT, AccT, OutputT, LogSoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(AccT), params->stream>>>(params->output, const_cast<InputT*>(params->input),
params->softmax_elements, params->input_stride,
params->output_stride);
} else {
softmax_block_forward<ILP, InputT, AccT, OutputT, SoftMaxForwardEpilogue>
<<<grid, block, block.x * sizeof(AccT), params->stream>>>(params->output, const_cast<InputT*>(params->input),
params->softmax_elements, params->input_stride,
params->output_stride);
}
return HIP_CALL(hipGetLastError());
}
template <typename InputT, typename OutputT, typename AccT>
class SoftmaxTunableOp : public onnxruntime::rocm::tunable::TunableOp<SoftmaxParams<InputT, OutputT>> {
public:
SoftmaxTunableOp() {
this->RegisterOp(SoftmaxBlockwiseStaticSelection<InputT, OutputT, AccT>);
this->RegisterOp(SoftmaxBlockwiseOp<InputT, OutputT, AccT, 1>);
this->RegisterOp(SoftmaxBlockwiseOp<InputT, OutputT, AccT, 2>);
this->RegisterOp(SoftmaxBlockwiseOp<InputT, OutputT, AccT, 4>);
this->RegisterOp(SoftmaxBlockwiseOp<InputT, OutputT, AccT, 8>);
this->RegisterOp(SoftmaxBlockwiseOp<InputT, OutputT, AccT, 16>);
#ifdef USE_COMPOSABLE_KERNEL
for (auto&& [_, op] : GetCKSoftmaxTypeStringAndOps<InputT, OutputT, AccT>()) {
ORT_UNUSED_PARAMETER(_);
this->RegisterOp(std::move(op));
}
#endif // USE_COMPOSABLE_KERNEL
}
};
} // namespace rocm
} // namespace onnxruntime

View file

@ -7,8 +7,9 @@
#include "python/tools/kernel_explorer/kernels/vector_add.h"
#include "python/tools/kernel_explorer/kernels/rocm/fast_gelu.h"
#include "python/tools/kernel_explorer/kernels/rocm/gemm.h"
#include "python/tools/kernel_explorer/kernels/rocm/skip_layer_norm.h"
#include "python/tools/kernel_explorer/kernels/rocm/gemm_fast_gelu.h"
#include "python/tools/kernel_explorer/kernels/rocm/skip_layer_norm.h"
#include "python/tools/kernel_explorer/kernels/rocm/softmax.h"
namespace py = pybind11;
@ -24,6 +25,7 @@ PYBIND11_MODULE(_kernel_explorer, m) {
InitGemm(m);
InitSkipLayerNorm(m);
InitGemmFastGelu(m);
InitSoftmax(m);
#endif
m.def("is_composable_kernel_available", []() {

View file

@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "python/tools/kernel_explorer/kernels/rocm/softmax.h"
#include <hip/hip_fp16.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <string>
#include <utility>
#include <vector>
#include "core/providers/rocm/math/softmax_ck.cuh"
#include "core/providers/rocm/math/softmax_tunable_op.cuh"
#include "core/providers/rocm/shared_inc/accumulation_type.h"
#include "python/tools/kernel_explorer/device_array.h"
#include "python/tools/kernel_explorer/kernel_explorer_interface.h"
namespace py = pybind11;
namespace onnxruntime {
template <typename T, int VecSize>
class SoftmaxBlockwise : public IKernelExplorer {
public:
SoftmaxBlockwise(DeviceArray& output, DeviceArray& input, int softmax_elements,
int input_stride, int output_stride, int batch_count, bool is_log_softmax)
: params_(TuningContext(), Stream(), static_cast<T*>(output.ptr()), static_cast<T*>(input.ptr()),
softmax_elements, input_stride, output_stride, batch_count, is_log_softmax) {
type_string_ = "SoftmaxBlockwise_" + std::to_string(VecSize);
}
void Run() override {
ORT_THROW_IF_ERROR((rocm::SoftmaxBlockwiseOp<T, T, rocm::AccumulationType_t<T>, VecSize>(&params_)));
}
std::vector<std::string> ListOps() const {
return {type_string_};
}
bool SelectOp(const std::string& name) {
Status status = rocm::SoftmaxBlockwiseOp<T, T, rocm::AccumulationType_t<T>, VecSize>(&params_);
return status.IsOK() && name == type_string_;
}
private:
using ParamsT = rocm::SoftmaxParams<T, T>;
ParamsT params_{};
std::string type_string_{};
};
template <typename T>
class SoftmaxBlockwiseStaticSelection : public IKernelExplorer {
public:
SoftmaxBlockwiseStaticSelection(DeviceArray& output, DeviceArray& input, int softmax_elements,
int input_stride, int output_stride, int batch_count, bool is_log_softmax)
: params_(TuningContext(), Stream(), static_cast<T*>(output.ptr()), static_cast<T*>(input.ptr()),
softmax_elements, input_stride, output_stride, batch_count, is_log_softmax) {}
void Run() override {
ORT_THROW_IF_ERROR((rocm::SoftmaxBlockwiseStaticSelection<T, T, rocm::AccumulationType_t<T>>(&params_)));
}
std::vector<std::string> ListOps() const {
return {"SoftmaxBlockwiseStaticSelection"};
}
bool SelectOp(const std::string& name) {
return name == "SoftmaxBlockwiseStaticSelection";
}
private:
using ParamsT = rocm::SoftmaxParams<T, T>;
ParamsT params_{};
};
template <typename T>
class SoftmaxTunable : public IKernelExplorer {
public:
SoftmaxTunable(DeviceArray& output, DeviceArray& input, int softmax_elements,
int input_stride, int output_stride, int batch_count, bool is_log_softmax)
: params_(TuningContext(), Stream(), static_cast<T*>(output.ptr()), static_cast<T*>(input.ptr()),
softmax_elements, input_stride, output_stride, batch_count, is_log_softmax) {
params_.TuningContext()->EnableTunableOp();
}
void Run() override {
ORT_THROW_IF_ERROR(op_(&params_));
}
std::vector<std::string> ListOps() const {
return {"SoftmaxTunable"};
}
bool SelectOp(const std::string& name) {
return name == "SoftmaxTunable";
}
private:
using ParamsT = rocm::SoftmaxParams<T, T>;
ParamsT params_{};
rocm::SoftmaxTunableOp<T, T, rocm::AccumulationType_t<T>> op_{};
};
#ifdef USE_COMPOSABLE_KERNEL
template <typename T>
class CKSoftmax : public IKernelExplorer {
public:
CKSoftmax(DeviceArray& output, DeviceArray& input, int softmax_elements,
int input_stride, int output_stride, int batch_count, bool is_log_softmax)
: params_(TuningContext(), Stream(), static_cast<T*>(output.ptr()), static_cast<T*>(input.ptr()),
softmax_elements, input_stride, output_stride, batch_count, is_log_softmax) {
for (auto&& [type_string, op] : rocm::GetCKSoftmaxTypeStringAndOps<T, T, rocm::AccumulationType_t<T>>()) {
type_strings_.emplace_back(std::move(type_string));
ops_.emplace_back(std::move(op));
}
}
void Run() override {
ORT_THROW_IF_ERROR(ops_[selected_op_](&params_));
}
std::vector<std::string> ListOps() const {
return type_strings_;
}
bool SelectOp(const std::string& name) {
for (size_t i = 0; i < ops_.size(); i++) {
if (type_strings_[i] == name) {
selected_op_ = i;
Status status = ops_[i](&params_);
return status.IsOK();
}
}
ORT_THROW("Cannot find implementation ", name);
}
private:
using ParamsT = rocm::SoftmaxParams<T, T>;
using OpT = rocm::tunable::Op<ParamsT>;
ParamsT params_{};
std::vector<OpT> ops_;
std::vector<std::string> type_strings_;
size_t selected_op_{};
};
#endif // USE_COMPOSABLE_KERNEL
#define REGISTER_OP(name, type, vec_size) \
py::class_<name<type, vec_size>>(m, #name "_" #type "_" #vec_size) \
.def(py::init<DeviceArray&, DeviceArray&, int, int, int, int, bool>()) \
.def("SetRepeats", &name<type, vec_size>::SetRepeats) \
.def("Profile", &name<type, vec_size>::Profile) \
.def("Run", &name<type, vec_size>::Run) \
.def("ListOps", &name<type, vec_size>::ListOps) \
.def("SelectOp", &name<type, vec_size>::SelectOp);
#define REGISTER_OP_FOR_ALL_VEC_SIZE(name, type) \
REGISTER_OP(name, type, 1) \
REGISTER_OP(name, type, 2) \
REGISTER_OP(name, type, 4) \
REGISTER_OP(name, type, 8) \
REGISTER_OP(name, type, 16)
#define REGISTER_OP_TYPED(name, type) \
py::class_<name<type>>(m, #name "_" #type) \
.def(py::init<DeviceArray&, DeviceArray&, int, int, int, int, bool>()) \
.def("SetRepeats", &name<type>::SetRepeats) \
.def("Profile", &name<type>::Profile) \
.def("Run", &name<type>::Run) \
.def("ListOps", &name<type>::ListOps) \
.def("SelectOp", &name<type>::SelectOp);
void InitSoftmax(py::module m) {
REGISTER_OP_FOR_ALL_VEC_SIZE(SoftmaxBlockwise, half);
REGISTER_OP_FOR_ALL_VEC_SIZE(SoftmaxBlockwise, float);
REGISTER_OP_TYPED(SoftmaxBlockwiseStaticSelection, half);
REGISTER_OP_TYPED(SoftmaxBlockwiseStaticSelection, float);
REGISTER_OP_TYPED(SoftmaxTunable, half);
REGISTER_OP_TYPED(SoftmaxTunable, float);
#ifdef USE_COMPOSABLE_KERNEL
REGISTER_OP_TYPED(CKSoftmax, half);
REGISTER_OP_TYPED(CKSoftmax, float);
#endif // USE_COMPOSABLE_KERNEL
}
} // namespace onnxruntime

View file

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
namespace onnxruntime {
void InitSoftmax(py::module m);
}

View file

@ -0,0 +1,148 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# --------------------------------------------------------------------------
import re
import sys
from dataclasses import dataclass
from itertools import product
import kernel_explorer as ke
import numpy as np
import pytest
from utils import dtype_to_bytes, dtype_to_suffix
def get_test_sizes():
batch_count = [1, 8]
softmax_elements = [1, 2, 3, 4, 5, 7, 8, 9, 11, 16, 31, 32, 33, 64, 65, 127, 128, 1024, 1025, 2048, 4096]
is_log_softmax = [True, False]
return product(batch_count, softmax_elements, is_log_softmax)
def dtype_to_funcs(dtype):
type_map = {
"float16": list(filter(lambda x: re.match("Softmax.*_half.*", x), dir(ke))),
"float32": list(filter(lambda x: re.match("Softmax.*_float.*", x), dir(ke))),
}
return type_map[dtype]
def softmax(x, is_log_softmax):
x = x - np.max(x, axis=-1, keepdims=1)
if is_log_softmax:
return x - np.log(np.sum(np.exp(x), axis=-1, keepdims=1))
return (np.exp(x)) / np.sum(np.exp(x), axis=-1, keepdims=1)
def _test_softmax(batch_count, softmax_elements, is_log_softmax, dtype, func):
np.random.seed(0)
x = np.random.rand(batch_count, softmax_elements).astype(dtype)
y = np.random.rand(batch_count, softmax_elements).astype(dtype)
x_d = ke.DeviceArray(x)
y_d = ke.DeviceArray(y)
y_ref = softmax(x, is_log_softmax)
softmax_func = getattr(ke, func)
softmax_op = softmax_func(
y_d, x_d, softmax_elements, softmax_elements, softmax_elements, batch_count, is_log_softmax
)
for impl in softmax_op.ListOps():
if not softmax_op.SelectOp(impl):
continue
softmax_op.Run()
y_d.UpdateHostNumpyArray()
np.testing.assert_allclose(y_ref, y, rtol=1e-02)
dtypes = ["float16", "float32"]
@pytest.mark.parametrize("batch_count, softmax_elements, is_log_softmax", get_test_sizes())
@pytest.mark.parametrize("dtype", dtypes)
def test_softmax(batch_count, softmax_elements, is_log_softmax, dtype):
for f in dtype_to_funcs(dtype):
_test_softmax(batch_count, softmax_elements, is_log_softmax, dtype, f)
@pytest.mark.parametrize("batch_count, softmax_elements, is_log_softmax", get_test_sizes())
@pytest.mark.parametrize("dtype", dtypes)
def test_ck_softmax(batch_count, softmax_elements, is_log_softmax, dtype):
ck_f_name = "CKSoftmax" + "_" + dtype_to_suffix(dtype)
_test_softmax(batch_count, softmax_elements, is_log_softmax, dtype, ck_f_name)
@dataclass
class SoftmaxMetric(ke.BandwidthMetric):
batch_count: int
softmax_elements: int
is_log_softmax: bool
def report(self):
prefix = f"{self.name:<110} {self.dtype} batch_count={self.batch_count:<4} softmax_elements={self.softmax_elements:<4} is_log_softmax={self.is_log_softmax:<4}"
if self.duration > 0:
return prefix + f"{self.duration:.2f} us, {self.gbps:.2f} GB/s"
return prefix + "not supported"
def profile_softmax_func(batch_count, softmax_elements, is_log_softmax, dtype, func):
np.random.seed(0)
x = np.random.rand(batch_count, softmax_elements).astype(dtype)
y = np.random.rand(batch_count, softmax_elements).astype(dtype)
x_d = ke.DeviceArray(x)
y_d = ke.DeviceArray(y)
softmax_func = getattr(ke, func)
softmax_op = softmax_func(
y_d, x_d, softmax_elements, softmax_elements, softmax_elements, batch_count, is_log_softmax
)
for impl in softmax_op.ListOps():
duration_ms = -1
if softmax_op.SelectOp(impl):
duration_ms = softmax_op.Profile()
total_bytes = 2 * batch_count * softmax_elements * dtype_to_bytes(dtype)
ke.report(SoftmaxMetric(impl, dtype, duration_ms, total_bytes, batch_count, softmax_elements, is_log_softmax))
def profile_with_args(batch_count, softmax_elements, is_log_softmax, dtype, sort):
with ke.benchmark(sort):
for func in dtype_to_funcs(dtype):
profile_softmax_func(batch_count, softmax_elements, is_log_softmax, dtype, func)
# ck function
ck_f_name = "CKSoftmax" + "_" + dtype_to_suffix(dtype)
profile_softmax_func(batch_count, softmax_elements, is_log_softmax, dtype, ck_f_name)
profile_size = [(1, 2048), (8, 2048), (65536, 4096)]
def profile():
for dtype in dtypes:
for batch_count, softmax_elements in profile_size:
profile_with_args(batch_count, softmax_elements, False, dtype, True)
print()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
group = parser.add_argument_group("profile with args")
group.add_argument("batch_count", type=int)
group.add_argument("softmax_elements", type=int)
group.add_argument("is_log_softmax", type=int)
group.add_argument("dtype", choices=dtypes)
group.add_argument("--sort", action="store_true")
if len(sys.argv) == 1:
profile()
else:
args = parser.parse_args()
profile_with_args(args.batch_count, args.softmax_elements, args.is_log_softmax, args.dtype, args.sort)