CUTLASS 2.0 (#62)

CUTLASS 2.0

Substantially refactored for

- Better performance, particularly for native Turing Tensor Cores
- Robust and durable templates spanning the design space
- Encapsulated functionality embodying modern C++11 programming techniques
- Optimized containers and data types for efficient, generic, portable device code

Updates to:
- Quick start guide
- Documentation
- Utilities
- CUTLASS Profiler

Native Turing Tensor Cores
- Efficient GEMM kernels targeting Turing Tensor Cores
- Mixed-precision floating point, 8-bit integer, 4-bit integer, and binarized operands

Coverage of existing CUTLASS functionality:
- GEMM kernels targeting CUDA and Tensor Cores in NVIDIA GPUs
- Volta Tensor Cores through native mma.sync and through WMMA API
- Optimizations such as parallel reductions, threadblock rasterization, and intra-threadblock reductions
- Batched GEMM operations
- Complex-valued GEMMs

Note: this commit and all that follow require a host compiler supporting C++11 or greater.
This commit is contained in:
Andrew Kerr
2019-11-19 16:55:34 -08:00
committed by GitHub
parent b5cab177a9
commit fb335f6a5f
5434 changed files with 599799 additions and 250176 deletions
+156
View File
@@ -0,0 +1,156 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Helper functions for mapping CUTLASS concepts to cuBLAS.
*/
#include <stdexcept>
#if CUTLASS_ENABLE_CUBLAS
#include "cublas_helpers.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Converts a cuBLAS status to cutlass::Status
Status get_cutlass_status(cublasStatus_t cublas) {
if (cublas == CUBLAS_STATUS_SUCCESS) {
return Status::kSuccess;
}
else if (cublas == CUBLAS_STATUS_INVALID_VALUE) {
return Status::kErrorInvalidProblem;
}
if (cublas == CUBLAS_STATUS_NOT_SUPPORTED) {
return Status::kErrorNotSupported;
}
return Status::kErrorInternal;
}
/// Maps a CUTLASS tensor layout to a cuBLAS transpose operation
cublasOperation_t get_cublas_transpose_operation(library::LayoutTypeID layout) {
switch (layout) {
case library::LayoutTypeID::kColumnMajor:
return CUBLAS_OP_N;
case library::LayoutTypeID::kRowMajor:
return CUBLAS_OP_T;
default: break;
}
throw std::runtime_error("CUTLASS layout type does not correspond to cublas type");
}
/// Maps a CUTLASS numeric type to a cuBLAS data type enumeration
bool get_cublas_datatype(cublasDataType_t &data_type, library::NumericTypeID element_type) {
switch (element_type) {
case library::NumericTypeID::kF16:
data_type = CUDA_R_16F;
return true;
case library::NumericTypeID::kF32:
data_type = CUDA_R_32F;
return true;
case library::NumericTypeID::kF64:
data_type = CUDA_R_64F;
return true;
case library::NumericTypeID::kS4:
break;
case library::NumericTypeID::kS8:
data_type = CUDA_R_8I;
return true;
case library::NumericTypeID::kS16:
break;
case library::NumericTypeID::kS32:
data_type = CUDA_R_32I;
return true;
case library::NumericTypeID::kS64:
break;
case library::NumericTypeID::kU4:
break;
case library::NumericTypeID::kU8:
data_type = CUDA_R_8U;
return true;
case library::NumericTypeID::kU16:
break;
case library::NumericTypeID::kU32:
data_type = CUDA_R_32U;
return true;
case library::NumericTypeID::kU64:
break;
case library::NumericTypeID::kB1:
break;
case library::NumericTypeID::kInvalid:
default:
break;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Gets the cublas algorithm given threadblock tile dimensions and math opcode class
cublasGemmAlgo_t get_cublas_gemm_algo(int cta_m, int cta_n, int cta_k, library::OpcodeClassID opcode_class) {
// TODO
return (opcode_class == library::OpcodeClassID::kSimt ?
CUBLAS_GEMM_DEFAULT : CUBLAS_GEMM_DEFAULT_TENSOR_OP);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns a status if cuBLAS can satisfy a particular GEMM description
Status cublas_satisfies(library::GemmDescription const &desc) {
auto const &math_instruction = desc.tile_description.math_instruction;
if (math_instruction.element_accumulator == library::NumericTypeID::kS32 &&
math_instruction.opcode_class == library::OpcodeClassID::kTensorOp) {
return Status::kErrorNotSupported;
}
return Status::kSuccess;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
#endif // #if CUTLASS_ENABLE_CUBLAS
+93
View File
@@ -0,0 +1,93 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Helper functions for mapping CUTLASS concepts to cuBLAS.
*/
#pragma once
#if CUTLASS_ENABLE_CUBLAS
#include <cublas_v2.h>
#include "cutlass/cutlass.h"
#include "cutlass/library/library.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Converts a cuBLAS status to cutlass::Status
Status get_cutlass_status(cublasStatus_t cublas);
/// Maps a CUTLASS tensor layout to a cuBLAS transpose operation
cublasOperation_t get_cublas_transpose_operation(library::LayoutTypeID layout);
/// Maps a CUTLASS numeric type to a cuBLAS data type enumeration
bool get_cublas_datatype(cublasDataType_t &data_type, library::NumericTypeID element_type);
/// Gets the cublas algorithm given threadblock tile dimensions and math opcode class
cublasGemmAlgo_t get_cublas_gemm_algo(
int cta_m,
int cta_n,
int cta_k,
library::OpcodeClassID opcode_class);
/// Returns a status if cuBLAS can satisfy a particular GEMM description
Status cublas_satisfies(library::GemmDescription const &desc);
/// This is a helper class to create cublasHandle_t automatically on CublasCreate object creation and
/// to destroy cublasHandle_t on CublasCreate object destruction.
/// Additionaly, it provides implicit cast from CublasCreate's object to cublasHandle_t's object
class CublasCreate {
private:
cublasHandle_t handle;
cublasStatus_t status;
public:
CublasCreate() {
status = cublasCreate(&handle);
}
~CublasCreate() {
cublasDestroy(handle);
}
/// Implicit cast CublasCreate object to cublasHandle_t
operator cublasHandle_t() const { return handle; }
/// returns cublasStatus_t for handle creation
cublasStatus_t get_cublas_create_status() { return status; }
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
#endif // #if CUTLASS_ENABLE_CUBLAS
+194
View File
@@ -0,0 +1,194 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Execution environment
*/
#include <iostream>
#include <stdexcept>
// CUTLASS Library includes
#include "cutlass/library/library.h"
#include "cutlass/library/manifest.h"
// Profiler includes
#include "cutlass_profiler.h"
#include "gemm_operation_profiler.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
CutlassProfiler::CutlassProfiler(
Options const &options
):
options_(options) {
operation_profilers_.emplace_back(new GemmOperationProfiler);
}
CutlassProfiler::~CutlassProfiler() {
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Execute the program
int CutlassProfiler::operator()() {
if (options_.about.help) {
if (options_.operation_kind == library::OperationKind::kInvalid) {
print_usage_(std::cout);
}
else {
for (auto & profiler : operation_profilers_) {
if (profiler->kind() == options_.operation_kind) {
profiler->print_usage(std::cout);
profiler->print_examples(std::cout);
return 0;
}
}
}
return 0;
}
else if (options_.about.version) {
options_.about.print_version(std::cout);
std::cout << std::endl;
return 0;
}
else if (options_.about.device_info) {
options_.device.print_device_info(std::cout);
return 0;
}
if (options_.execution_mode == ExecutionMode::kProfile ||
options_.execution_mode == ExecutionMode::kDryRun ||
options_.execution_mode == ExecutionMode::kTrace) {
// Profiles all operations
profile_();
}
else if (options_.execution_mode == ExecutionMode::kEnumerate) {
// Enumerates all operations
enumerate_();
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Enumerates all operations
void CutlassProfiler::enumerate_() {
}
/// Profiles all operations
int CutlassProfiler::profile_() {
library::Manifest manifest;
Status status = manifest.initialize();
if (status != Status::kSuccess) {
return -1;
}
int result = 0;
DeviceContext device_context;
// For all profilers
for (auto & profiler : operation_profilers_) {
if (options_.operation_kind == library::OperationKind::kInvalid ||
options_.operation_kind == profiler->kind()) {
result = profiler->profile_all(options_, manifest, device_context);
if (result) {
return result;
}
}
}
return result;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Prints all options
void CutlassProfiler::print_usage_(std::ostream &out) {
options_.print_usage(out);
out << "\nOperations:\n\n";
// For all profilers
for (auto & profiler : operation_profilers_) {
std::string kind_str = library::to_string(profiler->kind());
size_t kAlignment = 40;
size_t columns = 0;
if (kind_str.size() < kAlignment) {
columns = kAlignment - kind_str.size();
}
out << " " << kind_str << std::string(columns, ' ') << profiler->description() << "\n";
}
out << "\n\nFor details about a particular function, specify the function name with --help.\n\nExample:\n\n"
<< " $ cutlass_profiler --operation=Gemm --help\n\n";
}
/// Prints usage
void CutlassProfiler::print_options_(std::ostream &out) {
options_.print_options(out);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Initializes the CUDA device
void CutlassProfiler::initialize_device_() {
cudaError_t result = cudaSetDevice(options_.device.device);
if (result != cudaSuccess) {
std::cerr << "Failed to set device.";
throw std::runtime_error("Failed to set device");
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+86
View File
@@ -0,0 +1,86 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Execution environment
*/
#pragma once
#include "options.h"
#include "operation_profiler.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// CUTLASS Profiler application
class CutlassProfiler {
private:
//
// Data members
//
/// Performance testbench options
Options options_;
/// Entry points for each operation
OperationProfilerVector operation_profilers_;
private:
/// Prints usage
void print_usage_(std::ostream &);
/// Prints usage
void print_options_(std::ostream &);
/// Initializes the device
void initialize_device_();
/// Enumerates all operations
void enumerate_();
/// Profiles all operations
int profile_();
public:
CutlassProfiler(Options const &options);
~CutlassProfiler();
/// Invokes profiling operations
int operator()();
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+50
View File
@@ -0,0 +1,50 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief
*/
#pragma once
#include <iostream>
#define report(x) { std::cout << "\033[31m" << __FILE__ << ":" << __LINE__ << " " << x << "\033[0m" << std::endl; }
//#define report(x) {}
// Enable/Disble Profiler debug prints
#define DEBUG_PROFILER
//RED 31m // profiler prints debug messages in red
//YELLOW 33m // ir prints debug messages in yellow
#ifndef DEBUG_PROFILER
#define debugprof(...)
#else
#define debugprof(...) do { \
printf("\033[31m[DEBUG PROF] %s:%d | ", __FILE__, __LINE__); \
printf(__VA_ARGS__); \
printf("\033[0m\n"); \
} while (0)
#endif
+922
View File
@@ -0,0 +1,922 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Execution environment
*/
#include <cstring>
#include "cutlass/numeric_types.h"
#include "cutlass/layout/matrix.h"
#include "cutlass/layout/tensor.h"
#include "cutlass/util/reference/device/tensor_compare.h"
#include "cutlass/util/reference/device/tensor_fill.h"
#include "cutlass/util/reference/host/tensor_fill.h"
#include "cutlass/util/host_tensor.h"
#include "cutlass/util/tensor_view_io.h"
#include "device_allocation.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
size_t DeviceAllocation::bytes(library::NumericTypeID type, size_t capacity) {
return size_t(cutlass::library::sizeof_bits(type)) * capacity / 8;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Layout>
static std::vector<int> get_packed_layout_stride(std::vector<int> const &extent) {
typename Layout::TensorCoord extent_coord;
typename Layout::Stride stride_coord;
if (extent.size() != size_t(Layout::kRank)) {
throw std::runtime_error("Layout does not have same rank as extent vector.");
}
for (int i = 0; i < Layout::kRank; ++i) {
extent_coord[i] = extent.at(i);
}
std::vector<int> stride;
stride.resize(Layout::kStrideRank, 0);
Layout layout = Layout::packed(extent_coord);
stride_coord = layout.stride();
for (int i = 0; i < Layout::kStrideRank; ++i) {
stride.at(i) = stride_coord[i];
}
return stride;
}
/// Returns the stride of a packed layout
std::vector<int> DeviceAllocation::get_packed_layout(
library::LayoutTypeID layout_id,
std::vector<int> const &extent) {
std::vector<int> stride;
switch (layout_id) {
case library::LayoutTypeID::kColumnMajor:
stride = get_packed_layout_stride<cutlass::layout::ColumnMajor>(extent);
break;
case library::LayoutTypeID::kRowMajor:
stride = get_packed_layout_stride<cutlass::layout::RowMajor>(extent);
break;
case library::LayoutTypeID::kColumnMajorInterleavedK4:
stride = get_packed_layout_stride<cutlass::layout::ColumnMajorInterleaved<4>>(extent);
break;
case library::LayoutTypeID::kRowMajorInterleavedK4:
stride = get_packed_layout_stride<cutlass::layout::RowMajorInterleaved<4>>(extent);
break;
case library::LayoutTypeID::kColumnMajorInterleavedK16:
stride = get_packed_layout_stride<cutlass::layout::ColumnMajorInterleaved<16>>(extent);
break;
case library::LayoutTypeID::kRowMajorInterleavedK16:
stride = get_packed_layout_stride<cutlass::layout::RowMajorInterleaved<16>>(extent);
break;
case library::LayoutTypeID::kTensorNCHW:
stride = get_packed_layout_stride<cutlass::layout::TensorNCHW>(extent);
break;
case library::LayoutTypeID::kTensorNHWC:
stride = get_packed_layout_stride<cutlass::layout::TensorNHWC>(extent);
break;
default: break;
}
return stride;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Template to use CUTLASS Layout functions to
template <typename Layout>
static size_t construct_layout_(
void *bytes,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> &stride) {
if (extent.size() != Layout::kRank) {
throw std::runtime_error(
"Layout must have same rank as extent vector.");
}
if (Layout::kStrideRank && stride.empty()) {
stride = get_packed_layout_stride<Layout>(extent);
return construct_layout_<Layout>(
bytes,
layout_id,
extent,
stride);
}
else if (Layout::kStrideRank && stride.size() != Layout::kStrideRank) {
throw std::runtime_error(
"Layout requires either empty stride or stride vector matching Layout::kStrideRank");
}
typename Layout::Stride stride_coord;
for (int i = 0; i < Layout::kStrideRank; ++i) {
stride_coord[i] = stride.at(i);
}
typename Layout::TensorCoord extent_coord;
for (int i = 0; i < Layout::kRank; ++i) {
extent_coord[i] = extent.at(i);
}
// Construct the CUTLASS layout object from the stride object
Layout layout(stride_coord);
// Pack it into bytes
if (bytes) {
*reinterpret_cast<Layout *>(bytes) = layout;
}
// Return capacity
size_t capacity_ = layout.capacity(extent_coord);
return capacity_;
}
/// returns the capacity needed
size_t DeviceAllocation::construct_layout(
void *bytes,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> &stride) {
switch (layout_id) {
case library::LayoutTypeID::kColumnMajor:
return construct_layout_<cutlass::layout::ColumnMajor>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kRowMajor:
return construct_layout_<cutlass::layout::RowMajor>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kColumnMajorInterleavedK4:
return construct_layout_<cutlass::layout::ColumnMajorInterleaved<4>>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kRowMajorInterleavedK4:
return construct_layout_<cutlass::layout::RowMajorInterleaved<4>>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kColumnMajorInterleavedK16:
return construct_layout_<cutlass::layout::ColumnMajorInterleaved<16>>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kRowMajorInterleavedK16:
return construct_layout_<cutlass::layout::RowMajorInterleaved<16>>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kTensorNCHW:
return construct_layout_<cutlass::layout::TensorNHWC>(bytes, layout_id, extent, stride);
case library::LayoutTypeID::kTensorNHWC:
return construct_layout_<cutlass::layout::TensorNHWC>(bytes, layout_id, extent, stride);
default: break;
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
DeviceAllocation::DeviceAllocation():
type_(library::NumericTypeID::kInvalid),
capacity_(0),
pointer_(nullptr),
layout_(library::LayoutTypeID::kUnknown) {
}
DeviceAllocation::DeviceAllocation(
library::NumericTypeID type,
size_t capacity
):
type_(type), capacity_(capacity), pointer_(nullptr), layout_(library::LayoutTypeID::kUnknown) {
cudaError_t result = cudaMalloc((void **)&pointer_, bytes(type, capacity));
if (result != cudaSuccess) {
type_ = library::NumericTypeID::kInvalid;
capacity_ = 0;
pointer_ = nullptr;
throw std::bad_alloc();
}
}
DeviceAllocation::DeviceAllocation(
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride
):
type_(type), capacity_(size_t(0)), pointer_(nullptr) {
reset(type, layout_id, extent, stride);
}
DeviceAllocation::~DeviceAllocation() {
if (pointer_) {
cudaFree(pointer_);
}
}
DeviceAllocation &DeviceAllocation::reset() {
if (pointer_) {
cudaFree(pointer_);
}
type_ = library::NumericTypeID::kInvalid;
capacity_ = 0;
pointer_ = nullptr;
layout_ = library::LayoutTypeID::kUnknown;
stride_.clear();
extent_.clear();
tensor_ref_buffer_.clear();
return *this;
}
DeviceAllocation &DeviceAllocation::reset(library::NumericTypeID type, size_t capacity) {
reset();
cudaError_t result = cudaMalloc((void **)&pointer_, bytes(type, capacity));
if (result != cudaSuccess) {
throw std::bad_alloc();
}
type_ = type;
capacity_ = capacity;
layout_ = library::LayoutTypeID::kUnknown;
stride_.clear();
extent_.clear();
tensor_ref_buffer_.resize(sizeof(pointer_), 0);
std::memcpy(tensor_ref_buffer_.data(), &pointer_, sizeof(pointer_));
return *this;
}
/// Allocates memory for a given layout and tensor
DeviceAllocation &DeviceAllocation::reset(
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride) {
reset();
tensor_ref_buffer_.resize(sizeof(pointer_) + (sizeof(int) * library::get_layout_stride_rank(layout_id)), 0);
type_ = type;
layout_ = layout_id;
stride_ = stride;
extent_ = extent;
capacity_ = construct_layout(
tensor_ref_buffer_.data() + sizeof(pointer_),
layout_id,
extent,
stride_);
cudaError_t result = cudaMalloc((void **)&pointer_, bytes(type, capacity_));
if (result != cudaSuccess) {
throw std::bad_alloc();
}
std::memcpy(tensor_ref_buffer_.data(), &pointer_, sizeof(pointer_));
return *this;
}
bool DeviceAllocation::good() const {
return (capacity_ && pointer_);
}
library::NumericTypeID DeviceAllocation::type() const {
return type_;
}
void *DeviceAllocation::data() const {
return pointer_;
}
library::LayoutTypeID DeviceAllocation::layout() const {
return layout_;
}
std::vector<int> const & DeviceAllocation::stride() const {
return stride_;
}
/// Gets the extent vector
std::vector<int> const & DeviceAllocation::extent() const {
return extent_;
}
size_t DeviceAllocation::capacity() const {
return capacity_;
}
size_t DeviceAllocation::bytes() const {
return bytes(type_, capacity_);
}
/// Copies from an equivalent-sized tensor in device memory
void DeviceAllocation::copy_from_device(void const *ptr) {
cudaError_t result = cudaMemcpy(data(), ptr, bytes(), cudaMemcpyDeviceToDevice);
if (result != cudaSuccess) {
throw std::runtime_error("Failed device-to-device copy");
}
}
/// Copies from an equivalent-sized tensor in device memory
void DeviceAllocation::copy_from_host(void const *ptr) {
cudaError_t result = cudaMemcpy(data(), ptr, bytes(), cudaMemcpyHostToDevice);
if (result != cudaSuccess) {
throw std::runtime_error("Failed device-to-device copy");
}
}
/// Copies from an equivalent-sized tensor in device memory
void DeviceAllocation::copy_to_host(void *ptr) {
cudaError_t result = cudaMemcpy(ptr, data(), bytes(), cudaMemcpyDeviceToHost);
if (result != cudaSuccess) {
throw std::runtime_error("Failed device-to-device copy");
}
}
void DeviceAllocation::initialize_random_device(int seed, Distribution dist) {
if (!good()) {
throw std::runtime_error("Attempting to initialize invalid allocation.");
}
// Instantiate calls to CURAND here. This file takes a long time to compile for
// this reason.
switch (type_) {
case library::NumericTypeID::kF16:
cutlass::reference::device::BlockFillRandom<cutlass::half_t>(
reinterpret_cast<cutlass::half_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kF32:
cutlass::reference::device::BlockFillRandom<float>(
reinterpret_cast<float *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kF64:
cutlass::reference::device::BlockFillRandom<double>(
reinterpret_cast<double *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS8:
cutlass::reference::device::BlockFillRandom<int8_t>(
reinterpret_cast<int8_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS16:
cutlass::reference::device::BlockFillRandom<int16_t>(
reinterpret_cast<int16_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS32:
cutlass::reference::device::BlockFillRandom<int32_t>(
reinterpret_cast<int32_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS64:
cutlass::reference::device::BlockFillRandom<int64_t>(
reinterpret_cast<int64_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU8:
cutlass::reference::device::BlockFillRandom<uint8_t>(
reinterpret_cast<uint8_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU16:
cutlass::reference::device::BlockFillRandom<uint16_t>(
reinterpret_cast<uint16_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU32:
cutlass::reference::device::BlockFillRandom<uint32_t>(
reinterpret_cast<uint32_t *>(pointer_),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU64:
cutlass::reference::device::BlockFillRandom<uint64_t>(
reinterpret_cast<uint64_t *>(pointer_),
capacity_,
seed,
dist
);
break;
default: break;
}
}
void DeviceAllocation::initialize_random_host(int seed, Distribution dist) {
if (!good()) {
throw std::runtime_error("Attempting to initialize invalid allocation.");
}
std::vector<uint8_t> host_data(bytes());
switch (type_) {
case library::NumericTypeID::kF16:
cutlass::reference::host::BlockFillRandom<cutlass::half_t>(
reinterpret_cast<cutlass::half_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kF32:
cutlass::reference::host::BlockFillRandom<float>(
reinterpret_cast<float *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kF64:
cutlass::reference::host::BlockFillRandom<double>(
reinterpret_cast<double *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS8:
cutlass::reference::host::BlockFillRandom<int8_t>(
reinterpret_cast<int8_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS16:
cutlass::reference::host::BlockFillRandom<int16_t>(
reinterpret_cast<int16_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS32:
cutlass::reference::host::BlockFillRandom<int32_t>(
reinterpret_cast<int32_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kS64:
cutlass::reference::host::BlockFillRandom<int64_t>(
reinterpret_cast<int64_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU8:
cutlass::reference::host::BlockFillRandom<uint8_t>(
reinterpret_cast<uint8_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU16:
cutlass::reference::host::BlockFillRandom<uint16_t>(
reinterpret_cast<uint16_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU32:
cutlass::reference::host::BlockFillRandom<uint32_t>(
reinterpret_cast<uint32_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
case library::NumericTypeID::kU64:
cutlass::reference::host::BlockFillRandom<uint64_t>(
reinterpret_cast<uint64_t *>(host_data.data()),
capacity_,
seed,
dist
);
break;
default: break;
}
copy_from_host(host_data.data());
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns true if two blocks have exactly the same value
bool DeviceAllocation::block_compare_equal(
library::NumericTypeID numeric_type,
void const *ptr_A,
void const *ptr_B,
size_t capacity) {
switch (numeric_type) {
case library::NumericTypeID::kF16:
return reference::device::BlockCompareEqual<half_t>(
reinterpret_cast<half_t const *>(ptr_A),
reinterpret_cast<half_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kF32:
return reference::device::BlockCompareEqual<float>(
reinterpret_cast<float const *>(ptr_A),
reinterpret_cast<float const *>(ptr_B),
capacity);
case library::NumericTypeID::kF64:
return reference::device::BlockCompareEqual<double>(
reinterpret_cast<double const *>(ptr_A),
reinterpret_cast<double const *>(ptr_B),
capacity);
case library::NumericTypeID::kS8:
return reference::device::BlockCompareEqual<int8_t>(
reinterpret_cast<int8_t const *>(ptr_A),
reinterpret_cast<int8_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kS16:
return reference::device::BlockCompareEqual<int16_t>(
reinterpret_cast<int16_t const *>(ptr_A),
reinterpret_cast<int16_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kS32:
return reference::device::BlockCompareEqual<int32_t>(
reinterpret_cast<int32_t const *>(ptr_A),
reinterpret_cast<int32_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kS64:
return reference::device::BlockCompareEqual<int64_t>(
reinterpret_cast<int64_t const *>(ptr_A),
reinterpret_cast<int64_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kU8:
return reference::device::BlockCompareEqual<uint8_t>(
reinterpret_cast<uint8_t const *>(ptr_A),
reinterpret_cast<uint8_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kU16:
return reference::device::BlockCompareEqual<uint16_t>(
reinterpret_cast<uint16_t const *>(ptr_A),
reinterpret_cast<uint16_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kU32:
return reference::device::BlockCompareEqual<uint32_t>(
reinterpret_cast<uint32_t const *>(ptr_A),
reinterpret_cast<uint32_t const *>(ptr_B),
capacity);
case library::NumericTypeID::kU64:
return reference::device::BlockCompareEqual<uint64_t>(
reinterpret_cast<uint64_t const *>(ptr_A),
reinterpret_cast<uint64_t const *>(ptr_B),
capacity);
default:
throw std::runtime_error("Unsupported numeric type");
}
}
/// Returns true if two blocks have approximately the same value
bool DeviceAllocation::block_compare_relatively_equal(
library::NumericTypeID numeric_type,
void const *ptr_A,
void const *ptr_B,
size_t capacity,
double epsilon,
double nonzero_floor) {
switch (numeric_type) {
case library::NumericTypeID::kF16:
return reference::device::BlockCompareRelativelyEqual<half_t>(
reinterpret_cast<half_t const *>(ptr_A),
reinterpret_cast<half_t const *>(ptr_B),
capacity,
static_cast<half_t>(epsilon),
static_cast<half_t>(nonzero_floor));
case library::NumericTypeID::kF32:
return reference::device::BlockCompareRelativelyEqual<float>(
reinterpret_cast<float const *>(ptr_A),
reinterpret_cast<float const *>(ptr_B),
capacity,
static_cast<float>(epsilon),
static_cast<float>(nonzero_floor));
case library::NumericTypeID::kF64:
return reference::device::BlockCompareRelativelyEqual<double>(
reinterpret_cast<double const *>(ptr_A),
reinterpret_cast<double const *>(ptr_B),
capacity,
static_cast<double>(epsilon),
static_cast<double>(nonzero_floor));
case library::NumericTypeID::kS8:
return reference::device::BlockCompareRelativelyEqual<int8_t>(
reinterpret_cast<int8_t const *>(ptr_A),
reinterpret_cast<int8_t const *>(ptr_B),
capacity,
static_cast<int8_t>(epsilon),
static_cast<int8_t>(nonzero_floor));
case library::NumericTypeID::kS16:
return reference::device::BlockCompareRelativelyEqual<int16_t>(
reinterpret_cast<int16_t const *>(ptr_A),
reinterpret_cast<int16_t const *>(ptr_B),
capacity,
static_cast<int16_t>(epsilon),
static_cast<int16_t>(nonzero_floor));
case library::NumericTypeID::kS32:
return reference::device::BlockCompareRelativelyEqual<int32_t>(
reinterpret_cast<int32_t const *>(ptr_A),
reinterpret_cast<int32_t const *>(ptr_B),
capacity,
static_cast<int32_t>(epsilon),
static_cast<int32_t>(nonzero_floor));
case library::NumericTypeID::kS64:
return reference::device::BlockCompareRelativelyEqual<int64_t>(
reinterpret_cast<int64_t const *>(ptr_A),
reinterpret_cast<int64_t const *>(ptr_B),
capacity,
static_cast<int64_t>(epsilon),
static_cast<int64_t>(nonzero_floor));
case library::NumericTypeID::kU8:
return reference::device::BlockCompareRelativelyEqual<uint8_t>(
reinterpret_cast<uint8_t const *>(ptr_A),
reinterpret_cast<uint8_t const *>(ptr_B),
capacity,
static_cast<uint8_t>(epsilon),
static_cast<uint8_t>(nonzero_floor));
case library::NumericTypeID::kU16:
return reference::device::BlockCompareRelativelyEqual<uint16_t>(
reinterpret_cast<uint16_t const *>(ptr_A),
reinterpret_cast<uint16_t const *>(ptr_B),
capacity,
static_cast<uint16_t>(epsilon),
static_cast<uint16_t>(nonzero_floor));
case library::NumericTypeID::kU32:
return reference::device::BlockCompareRelativelyEqual<uint32_t>(
reinterpret_cast<uint32_t const *>(ptr_A),
reinterpret_cast<uint32_t const *>(ptr_B),
capacity,
static_cast<uint32_t>(epsilon),
static_cast<uint32_t>(nonzero_floor));
case library::NumericTypeID::kU64:
return reference::device::BlockCompareRelativelyEqual<uint64_t>(
reinterpret_cast<uint64_t const *>(ptr_A),
reinterpret_cast<uint64_t const *>(ptr_B),
capacity,
static_cast<uint64_t>(epsilon),
static_cast<uint64_t>(nonzero_floor));
default:
throw std::runtime_error("Unsupported numeric type");
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Permits copying dynamic vectors into static-length vectors
template <typename TensorCoord, int Rank>
struct vector_to_coord {
vector_to_coord(TensorCoord &coord, std::vector<int> const &vec) {
coord[Rank - 1] = vec.at(Rank - 1);
if (Rank > 1) {
vector_to_coord<TensorCoord, Rank - 1>(coord, vec);
}
}
};
/// Permits copying dynamic vectors into static-length vectors
template <typename TensorCoord>
struct vector_to_coord<TensorCoord, 1> {
vector_to_coord(TensorCoord &coord, std::vector<int> const &vec) {
coord[0] = vec.at(0);
}
};
/// Permits copying dynamic vectors into static-length vectors
template <typename TensorCoord>
struct vector_to_coord<TensorCoord, 0> {
vector_to_coord(TensorCoord &coord, std::vector<int> const &vec) {
}
};
/////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Element, typename Layout>
static void write_tensor_csv_static_tensor_view(
std::ostream &out,
DeviceAllocation &allocation) {
Coord<Layout::kRank> extent;
Coord<Layout::kStrideRank> stride;
if (allocation.extent().size() != Layout::kRank) {
throw std::runtime_error("Allocation extent has invalid rank");
}
if (allocation.stride().size() != Layout::kStrideRank) {
throw std::runtime_error("Allocation stride has invalid rank");
}
vector_to_coord<Coord<Layout::kRank>, Layout::kRank>(extent, allocation.extent());
vector_to_coord<Coord<Layout::kStrideRank>, Layout::kStrideRank>(stride, allocation.stride());
Layout layout(stride);
HostTensor<Element, Layout> host_tensor(extent, layout, false);
if (host_tensor.capacity() != allocation.capacity()) {
throw std::runtime_error("Unexpected capacity to equal.");
}
host_tensor.copy_in_device_to_host(static_cast<Element const *>(allocation.data()), host_tensor.capacity());
TensorViewWrite(out, host_tensor.host_view());
}
/////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
static void write_tensor_csv_static_type(
std::ostream &out,
DeviceAllocation &allocation) {
switch (allocation.layout()) {
case library::LayoutTypeID::kRowMajor:
write_tensor_csv_static_tensor_view<T, layout::RowMajor>(out, allocation);
break;
case library::LayoutTypeID::kColumnMajor:
write_tensor_csv_static_tensor_view<T, layout::ColumnMajor>(out, allocation);
break;
case library::LayoutTypeID::kTensorNHWC:
write_tensor_csv_static_tensor_view<T, layout::TensorNHWC>(out, allocation);
break;
default:
throw std::runtime_error("Unhandled layout");
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Writes a tensor to csv
void DeviceAllocation::write_tensor_csv(
std::ostream &out) {
switch (this->type()) {
case library::NumericTypeID::kF16:
write_tensor_csv_static_type<half_t>(out, *this);
break;
case library::NumericTypeID::kF32:
write_tensor_csv_static_type<float>(out, *this);
break;
case library::NumericTypeID::kF64:
write_tensor_csv_static_type<double>(out, *this);
break;
case library::NumericTypeID::kS8:
write_tensor_csv_static_type<int8_t>(out, *this);
break;
case library::NumericTypeID::kS16:
write_tensor_csv_static_type<int16_t>(out, *this);
break;
case library::NumericTypeID::kS32:
write_tensor_csv_static_type<int32_t>(out, *this);
break;
case library::NumericTypeID::kS64:
write_tensor_csv_static_type<int64_t>(out, *this);
break;
case library::NumericTypeID::kU8:
write_tensor_csv_static_type<uint8_t>(out, *this);
break;
case library::NumericTypeID::kU16:
write_tensor_csv_static_type<uint16_t>(out, *this);
break;
case library::NumericTypeID::kU32:
write_tensor_csv_static_type<uint32_t>(out, *this);
break;
case library::NumericTypeID::kU64:
write_tensor_csv_static_type<uint64_t>(out, *this);
break;
default:
throw std::runtime_error("Unsupported numeric type");
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+191
View File
@@ -0,0 +1,191 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Execution environment
*/
#pragma once
#include <stdexcept>
#include <list>
#include <vector>
#include "cutlass/library/library.h"
#include "cutlass/util/distribution.h"
#include "enumerated_types.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Device memory allocation
class DeviceAllocation {
private:
/// Data type of contained elements
library::NumericTypeID type_;
/// Capacity in elements of device allocation
size_t capacity_;
/// Pointer to device memory
void *pointer_;
/// Layout type ID
library::LayoutTypeID layout_;
/// Stride vector
std::vector<int> stride_;
/// Extent vector
std::vector<int> extent_;
/// Buffer holding TensorRef instance to recently allocated memory
std::vector<uint8_t> tensor_ref_buffer_;
public:
//
// Static member functions
//
/// Determines the number of bytes needed to represent this numeric type
static size_t bytes(library::NumericTypeID type, size_t capacity);
/// Returns the stride of a packed layout
static std::vector<int> get_packed_layout(
library::LayoutTypeID layout_id,
std::vector<int> const &extent);
/// returns the capacity needed
static size_t construct_layout(
void *bytes,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> &stride);
/// Returns true if two blocks have exactly the same value
static bool block_compare_equal(
library::NumericTypeID numeric_type,
void const *ptr_A,
void const *ptr_B,
size_t capacity);
/// Returns true if two blocks have approximately the same value
static bool block_compare_relatively_equal(
library::NumericTypeID numeric_type,
void const *ptr_A,
void const *ptr_B,
size_t capacity,
double epsilon,
double nonzero_floor);
public:
//
// Methods
//
DeviceAllocation();
DeviceAllocation(library::NumericTypeID type, size_t capacity);
DeviceAllocation(
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride = std::vector<int>());
~DeviceAllocation();
DeviceAllocation &reset();
/// Allocates device memory of a given type and capacity
DeviceAllocation &reset(library::NumericTypeID type, size_t capacity);
/// Allocates memory for a given layout and tensor
DeviceAllocation &reset(
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride = std::vector<int>());
/// Returns a buffer owning the tensor reference
std::vector<uint8_t> &tensor_ref() {
return tensor_ref_buffer_;
}
bool good() const;
/// Data type of contained elements
library::NumericTypeID type() const;
/// Pointer to device memory
void *data() const;
/// Gets the layout type
library::LayoutTypeID layout() const;
/// Gets the stride vector
std::vector<int> const & stride() const;
/// Gets the extent vector
std::vector<int> const & extent() const;
/// Capacity of allocation in number of elements
size_t capacity() const;
/// Capacity of allocation in bytes
size_t bytes() const;
/// Initializes a device allocation to a random distribution using cuRAND
void initialize_random_device(int seed, Distribution dist);
/// Initializes a device allocation to a random distribution using cuRAND
void initialize_random_host(int seed, Distribution dist);
/// Copies from an equivalent-sized tensor in device memory
void copy_from_device(void const *ptr);
/// Copies from an equivalent-sized tensor in device memory
void copy_from_host(void const *ptr);
/// Copies from an equivalent-sized tensor in device memory
void copy_to_host(void *ptr);
/// Writes a tensor to csv
void write_tensor_csv(std::ostream &out);
};
using DeviceAllocationList = std::list<DeviceAllocation>;
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+124
View File
@@ -0,0 +1,124 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief
*/
#include "device_context.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Allocates memory of a given type, capacity (elements), and name
DeviceAllocation *DeviceContext::allocate_block(
std::string const &name,
library::NumericTypeID type,
size_t capacity) {
device_memory_.emplace_back(type, capacity);
DeviceAllocation *allocation = &device_memory_.back();
allocations_[name] = allocation;
return allocation;
}
/// Allocates memory of a given type, capacity (elements), and name
DeviceAllocation *DeviceContext::allocate_tensor(
std::string const &name,
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride) {
device_memory_.emplace_back(type, layout_id, extent, stride);
DeviceAllocation *allocation = &device_memory_.back();
allocations_[name] = allocation;
return allocation;
}
/// Allocates memory of a given type, capacity (elements), and name
DeviceAllocation *DeviceContext::allocate_tensor(
Options const &options,
std::string const &name,
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride) {
DeviceAllocation *allocation =
allocate_tensor(name, type, layout_id, extent, stride);
if (options.initialization.enabled) {
if (options.initialization.provider == Provider::kReferenceDevice) {
allocation->initialize_random_device(
options.initialization.seed,
options.initialization.data_distribution);
}
else if (options.initialization.provider == Provider::kReferenceHost) {
allocation->initialize_random_host(
options.initialization.seed,
options.initialization.data_distribution);
}
}
return allocation;
}
/// Clears named allocations (but does not necessarily free memory)
void DeviceContext::clear() {
allocations_.clear();
}
/// Frees all device memory allocations
void DeviceContext::free() {
allocations_.clear();
device_memory_.clear();
}
/// Gets the allocation by name
DeviceAllocation &DeviceContext::at(std::string const &name) {
return *allocations_.at(name);
}
size_t DeviceContext::size() const {
return allocations_.size();
}
DeviceContext::AllocationMap::iterator DeviceContext::begin() {
return allocations_.begin();
}
DeviceContext::AllocationMap::iterator DeviceContext::end() {
return allocations_.end();
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+108
View File
@@ -0,0 +1,108 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief
*/
#pragma once
#include <map>
#include <string>
#include "cutlass/library/library.h"
#include "options.h"
#include "device_allocation.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Collection of allocations on the device
class DeviceContext {
public:
//
// Type definitions
//
using AllocationMap = std::map<std::string, DeviceAllocation *>;
private:
//
// Data members
//
/// Memory allocations that exist (owning)
DeviceAllocationList device_memory_;
/// Non-owning set of named allocations
AllocationMap allocations_;
public:
/// Allocates memory of a given type, capacity (elements), and name
DeviceAllocation *allocate_block(
std::string const &name,
library::NumericTypeID type,
size_t capacity);
/// Allocates memory of a given type, capacity (elements), and name
DeviceAllocation *allocate_tensor(
std::string const &name,
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride = std::vector<int>());
/// Allocates memory of a given type, capacity (elements), and name
DeviceAllocation *allocate_tensor(
Options const &options,
std::string const &name,
library::NumericTypeID type,
library::LayoutTypeID layout_id,
std::vector<int> const &extent,
std::vector<int> const &stride = std::vector<int>());
/// Clears named allocations (but does not necessarily free memory)
void clear();
/// Frees all device memory allocations
void free();
/// Gets the allocation by name
DeviceAllocation &at(std::string const &name);
size_t size() const;
AllocationMap::iterator begin();
AllocationMap::iterator end();
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+315
View File
@@ -0,0 +1,315 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Provides several functions for filling tensors with data.
*/
#include "enumerated_types.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
static struct {
char const *text;
char const *pretty;
ExecutionMode enumerant;
}
ExecutionMode_enumerants[] = {
{"profile", "Profile", ExecutionMode::kProfile},
{"dry_run", "Dry run", ExecutionMode::kDryRun},
{"dry", "dry run", ExecutionMode::kDryRun},
{"trace", "Trace", ExecutionMode::kTrace},
{"enumerate", "Enumerate", ExecutionMode::kEnumerate}
};
/// Converts a ExecutionMode enumerant to a string
char const *to_string(ExecutionMode mode, bool pretty) {
for (auto const & possible : ExecutionMode_enumerants) {
if (mode == possible.enumerant) {
if (pretty) {
return possible.pretty;
}
else {
return possible.text;
}
}
}
return pretty ? "Invalid" : "invalid";
}
/// Parses a ExecutionMode enumerant from a string
template <>
ExecutionMode from_string<ExecutionMode>(std::string const &str) {
for (auto const & possible : ExecutionMode_enumerants) {
if ((str.compare(possible.text) == 0) ||
(str.compare(possible.pretty) == 0)) {
return possible.enumerant;
}
}
return ExecutionMode::kInvalid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
static struct {
char const *text;
char const *pretty;
AlgorithmMode enumerant;
}
AlgorithmMode_enumerants[] = {
{"matching", "Matching", AlgorithmMode::kMatching},
{"best", "Best", AlgorithmMode::kBest},
{"default", "Default", AlgorithmMode::kDefault}
};
/// Converts a ExecutionMode enumerant to a string
char const *to_string(AlgorithmMode mode, bool pretty) {
for (auto const & possible : AlgorithmMode_enumerants) {
if (mode == possible.enumerant) {
if (pretty) {
return possible.pretty;
}
else {
return possible.text;
}
}
}
return pretty ? "Invalid" : "invalid";
}
/// Parses a ExecutionMode enumerant from a string
template <>
AlgorithmMode from_string<AlgorithmMode>(std::string const &str) {
for (auto const & possible : AlgorithmMode_enumerants) {
if ((str.compare(possible.text) == 0) ||
(str.compare(possible.pretty) == 0)) {
return possible.enumerant;
}
}
return AlgorithmMode::kInvalid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
static struct {
char const *text;
char const *pretty;
Provider enumerant;
}
Provider_enumerants[] = {
{"cutlass", "CUTLASS", Provider::kCUTLASS},
{"host", "reference_host", Provider::kReferenceHost},
{"device", "reference_device", Provider::kReferenceDevice},
{"cublas", "cuBLAS", Provider::kCUBLAS},
};
/// Converts a Provider enumerant to a string
char const *to_string(Provider provider, bool pretty) {
for (auto const & possible : Provider_enumerants) {
if (provider == possible.enumerant) {
if (pretty) {
return possible.pretty;
}
else {
return possible.text;
}
}
}
return pretty ? "Invalid" : "invalid";
}
/// Parses a Provider enumerant from a string
template <>
Provider from_string<Provider>(std::string const &str) {
for (auto const & possible : Provider_enumerants) {
if ((str.compare(possible.text) == 0) ||
(str.compare(possible.pretty) == 0)) {
return possible.enumerant;
}
}
return Provider::kInvalid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
static struct {
char const *text;
char const *pretty;
Disposition enumerant;
}
Disposition_enumerants[] = {
{"passed", "Passed", Disposition::kPassed},
{"failed", "Failed", Disposition::kFailed},
{"not_run", "Not run", Disposition::kNotRun},
{"not_verified", "Not verified", Disposition::kNotVerified},
{"not_supported", "Not supported", Disposition::kNotSupported},
{"incorrect", "Incorrect", Disposition::kIncorrect}
};
/// Converts a Disposition enumerant to a string
char const *to_string(Disposition disposition, bool pretty) {
for (auto const & possible : Disposition_enumerants) {
if (disposition == possible.enumerant) {
if (pretty) {
return possible.pretty;
}
else {
return possible.text;
}
}
}
return pretty ? "Invalid" : "invalid";
}
/// Parses a Disposition enumerant from a string
template <>
Disposition from_string<Disposition>(std::string const &str) {
for (auto const & possible : Disposition_enumerants) {
if ((str.compare(possible.text) == 0) ||
(str.compare(possible.pretty) == 0)) {
return possible.enumerant;
}
}
return Disposition::kInvalid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
static struct {
char const *text;
char const *pretty;
SaveWorkspace enumerant;
}
SaveWorkspace_enumerants[] = {
{"never", "Never", SaveWorkspace::kNever},
{"incorrect", "Incorrect", SaveWorkspace::kIncorrect},
{"always", "Always", SaveWorkspace::kAlways}
};
/// Converts a SaveWorkspace enumerant to a string
char const *to_string(SaveWorkspace save_option, bool pretty) {
for (auto const & possible : SaveWorkspace_enumerants) {
if (save_option == possible.enumerant) {
if (pretty) {
return possible.pretty;
}
else {
return possible.text;
}
}
}
return pretty ? "Invalid" : "invalid";
}
/// Parses a SaveWorkspace enumerant from a string
template <>
SaveWorkspace from_string<SaveWorkspace>(std::string const &str) {
for (auto const & possible : SaveWorkspace_enumerants) {
if ((str.compare(possible.text) == 0) ||
(str.compare(possible.pretty) == 0)) {
return possible.enumerant;
}
}
return SaveWorkspace::kInvalid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
static struct {
char const *text;
char const *pretty;
ArgumentTypeID enumerant;
}
ArgumentTypeID_enumerants[] = {
{"scalar", "Scalar", ArgumentTypeID::kScalar},
{"int", "Integer", ArgumentTypeID::kInteger},
{"tensor", "Tensor", ArgumentTypeID::kTensor},
{"batched_tensor", "BatchedTensor", ArgumentTypeID::kBatchedTensor},
{"struct", "Struct", ArgumentTypeID::kStructure},
{"enum", "Enumerated type", ArgumentTypeID::kEnumerated}
};
/// Converts a ArgumentTypeID enumerant to a string
char const *to_string(ArgumentTypeID type, bool pretty) {
for (auto const & possible : ArgumentTypeID_enumerants) {
if (type == possible.enumerant) {
if (pretty) {
return possible.pretty;
}
else {
return possible.text;
}
}
}
return pretty ? "Invalid" : "invalid";
}
/// Parses a ArgumentTypeID enumerant from a string
template <>
ArgumentTypeID from_string<ArgumentTypeID>(std::string const &str) {
for (auto const & possible : ArgumentTypeID_enumerants) {
if ((str.compare(possible.text) == 0) ||
(str.compare(possible.pretty) == 0)) {
return possible.enumerant;
}
}
return ArgumentTypeID::kInvalid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+165
View File
@@ -0,0 +1,165 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Provides several functions for filling tensors with data.
*/
#pragma once
#include <string>
#include <vector>
#include <iostream>
#define TRACE(x) { std::cout << __FILE__ << ":" << __LINE__ << " " << x << std::endl; }
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
T from_string(std::string const &);
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Enumerated type describing how the performance testbench evaluates kernels.
enum class ExecutionMode {
kProfile, ///< regular verification and profiling
kDryRun, ///< no kernels are launched or workspaces allocated; used to assess what operators might be launched
kEnumerate, ///< no kernels launched or workspaces allocated; lists all function types and functions
kTrace, ///< executes a single device-side computation with no other kernel launches
kInvalid
};
/// Converts a ExecutionMode enumerant to a string
char const *to_string(ExecutionMode mode, bool pretty = false);
/// Parses a ExecutionMode enumerant from a string
template <>
ExecutionMode from_string<ExecutionMode>(std::string const &str);
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Library algorithm mode
enum class AlgorithmMode {
kMatching, ///< compare against best matching algorithm
kBest, ///< evaluate all library algorithms and report best
kDefault, ///< use the library's default algorithm option
kInvalid
};
/// Converts a ExecutionMode enumerant to a string
char const *to_string(AlgorithmMode mode, bool pretty = false);
/// Parses a ExecutionMode enumerant from a string
template <>
AlgorithmMode from_string<AlgorithmMode>(std::string const &str);
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Providers
enum class Provider {
kCUTLASS,
kReferenceHost,
kReferenceDevice,
kCUBLAS,
kInvalid
};
using ProviderVector = std::vector<Provider>;
/// Converts a Provider enumerant to a string
char const *to_string(Provider provider, bool pretty = false);
/// Parses a Provider enumerant from a string
template <>
Provider from_string<Provider>(std::string const &str);
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Outcome of a performance test
enum class Disposition {
kPassed,
kFailed,
kNotRun,
kIncorrect,
kNotVerified,
kNotSupported,
kInvalid
};
/// Converts a Disposition enumerant to a string
char const *to_string(Disposition provider, bool pretty = false);
/// Parses a Disposition enumerant from a string
template <>
Disposition from_string<Disposition>(std::string const &str);
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Indicates when to save
enum class SaveWorkspace {
kNever,
kIncorrect,
kAlways,
kInvalid
};
/// Converts a SaveWorkspace enumerant to a string
char const *to_string(SaveWorkspace save_option, bool pretty = false);
/// Parses a SaveWorkspace enumerant from a string
template <>
SaveWorkspace from_string<SaveWorkspace>(std::string const &str);
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Indicates the type of kernel argument
// ArgumentType can be both ScalarType or NumericType. Thus, enums kScalar and kNumeric
// 1) kScalar: e.g. of a Scalar ArgumentType is u32 is a Scalar type.
// Its c++ equivalent as "type name = initializer" is "u32 m = 32"
// 2) kNumeric: e.g. of a Numeric ArgumentType is NumericTypeID is a Numeric type.
// Its c++ equivalent as "type name = initializer" is "NumericTypeID numeric_type = u32"
enum class ArgumentTypeID {
kScalar,
kInteger,
kTensor,
kBatchedTensor,
kStructure,
kEnumerated,
kInvalid
};
/// Converts a ArgumentTypeID enumerant to a string
char const *to_string(ArgumentTypeID type, bool pretty = false);
/// Parses a ArgumentTypeID enumerant from a string
template <>
ArgumentTypeID from_string<ArgumentTypeID>(std::string const &str);
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
@@ -0,0 +1,772 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Execution environment
*/
#include <iostream>
#include <stdexcept>
#include <iomanip>
#include <ios>
#include "cublas_helpers.h"
#include "gemm_operation_profiler.h"
#include "gpu_timer.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Ctor
GemmOperationProfiler::GemmOperationProfiler():
OperationProfiler(library::OperationKind::kGemm,{
{ArgumentTypeID::kEnumerated, {"Gemm_kind"}, "Variant of GEMM (e.g. gemm, planar complex, batched, ...)"},
{ArgumentTypeID::kInteger, {"m", "problem-size::m"}, "M dimension of the GEMM problem space"},
{ArgumentTypeID::kInteger, {"n", "problem-size::n"}, "N dimension of the GEMM problem space"},
{ArgumentTypeID::kInteger, {"k", "problem-size::k"}, "K dimension of the GEMM problem space"},
{ArgumentTypeID::kTensor, {"A"}, "Tensor storing the A operand"},
{ArgumentTypeID::kTensor, {"B"}, "Tensor storing the B operand"},
{ArgumentTypeID::kTensor, {"C"}, "Tensor storing the C operand"},
{ArgumentTypeID::kScalar, {"alpha", "epilogue::alpha"}, "Epilogue scalar alpha"},
{ArgumentTypeID::kScalar, {"beta", "epilogue::beta"}, "Epilogue scalar beta"},
{ArgumentTypeID::kInteger, {"split_k_slices"}, "Number of partitions of K dimension"},
{ArgumentTypeID::kInteger, {"batch_count"}, "Number of GEMMs computed in one batch"},
}) {
description_ = "General matrix-matrix product. D = alpha * A*B + beta * C";
}
/// Destructor
GemmOperationProfiler::~GemmOperationProfiler() {
}
/// Prints usage statement for the math function
void GemmOperationProfiler::print_usage(std::ostream &out) const {
out << "GEMM" << "\n\n";
OperationProfiler::print_usage(out);
}
/// Prints examples
void GemmOperationProfiler::print_examples(std::ostream &out) const {
out << "\nExamples:\n\n"
<< "Profile a particular problem size:\n"
<< " $ cutlass_profiler --operation=Gemm --m=1024 --n=1024 --k=128\n\n"
<< "Schmoo over problem size and beta:\n"
<< " $ cutlass_profiler --operation=Gemm --m=1024:4096:256 --n=1024:4096:256 --k=128:8192:128 --beta=0,1,2.5\n\n"
<< "Schmoo over accumulator types:\n"
<< " $ cutlass_profiler --operation=Gemm --accumulator-type=f16,f32\n\n"
<< "Run when A is f16 with column-major and B is any datatype with row-major (For column major, use column, col, or n. For row major use, row or t):\n"
<< " $ cutlass_profiler --operation=Gemm --A=f16:column --B=*:row\n\n"
<< "Using various input value distribution:\n"
<< " $ cutlass_profiler --operation=Gemm --dist=uniform,min:0,max:3\n"
<< " $ cutlass_profiler --operation=Gemm --dist=gaussian,mean:0,stddev:3\n"
<< " $ cutlass_profiler --operation=Gemm --dist=sequential,start:0,delta:1\n\n"
<< "Run a kernel with cta tile size of 256x128x32 and save workspace if results are incorrect (note that --cta-tile::k=32 is default cta-tile size):\n"
<< " $ cutlass_profiler --operation=Gemm --cta_m=256 --cta_n=128 --cta_k=32 --save-workspace=incorrect\n\n"
<< "Test your changes to gemm kernels with a quick functional test and save results in functional-test.csv:\n"
<< " $ cutlass_profiler --operation=Gemm \\ \n"
<< " --m=8,56,120,136,256,264,512,520,1024,1032,4096,8192,16384 \\ \n"
<< " --n=8,56,120,136,256,264,512,520,1024,1032,4096,8192,16384 \\ \n"
<< " --k=8,16,32,64,128,256,288,384,504,512,520 \\ \n"
<< " --beta=0,1,2 --profiling-iterations=1 \\ \n"
<< " --providers=cutlass --output=functional-test.csv\n\n";
}
#if 0
// used this for debugging
static std::string byte_string(std::vector<uint8_t> const &bytes) {
std::stringstream ss;
ss << "0x";
for (size_t idx = bytes.size(); idx > 0; --idx) {
ss << std::hex << std::setw(2) << std::setfill('0') << uint32_t(bytes.at(idx - 1));
}
return ss.str();
}
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Extracts the problem dimensions
Status GemmOperationProfiler::initialize_configuration(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
library::GemmDescription const &operation_desc =
static_cast<library::GemmDescription const &>(operation->description());
if (operation_desc.gemm_kind != library::GemmKind::kGemm) {
return Status::kErrorInvalidProblem;
}
if (!arg_as_int(problem_.m, "m", problem_space, problem)) {
// default value
problem_.m = 1024;
}
if (!arg_as_int(problem_.n, "n", problem_space, problem)) {
// default value
problem_.n = 1024;
}
if (!arg_as_int(problem_.k, "k", problem_space, problem)) {
// default value
problem_.k = 1024;
}
if (!arg_as_int(problem_.split_k_slices, "split_k_slices", problem_space, problem)) {
// default value
problem_.split_k_slices = 1;
}
if (!arg_as_int(problem_.batch_count, "batch_count", problem_space, problem)) {
// default value
problem_.batch_count = 1;
}
if (!tensor_description_satisfies(operation_desc.A, "A", problem_space, problem)) {
return Status::kErrorInvalidProblem;
}
if (!tensor_description_satisfies(operation_desc.B, "B", problem_space, problem)) {
return Status::kErrorInvalidProblem;
}
if (!tensor_description_satisfies(operation_desc.C, "C", problem_space, problem)) {
return Status::kErrorInvalidProblem;
}
if (!arg_as_scalar(
problem_.alpha,
operation_desc.element_epilogue,
"alpha",
problem_space,
problem)) {
if (!cast_from_double(problem_.alpha, operation_desc.element_epilogue, 1)) {
return Status::kErrorInternal;
}
}
if (!arg_as_scalar(
problem_.beta,
operation_desc.element_epilogue,
"beta",
problem_space,
problem)) {
if (!cast_from_double(problem_.beta, operation_desc.element_epilogue, 0)) {
return Status::kErrorInternal;
}
}
problem_.lda = DeviceAllocation::get_packed_layout(
operation_desc.A.layout, {int(problem_.m), int(problem_.k)}).front();
problem_.ldb = DeviceAllocation::get_packed_layout(
operation_desc.B.layout, {int(problem_.k), int(problem_.n)}).front();
problem_.ldc = DeviceAllocation::get_packed_layout(
operation_desc.C.layout, {int(problem_.m), int(problem_.n)}).front();
gemm_workspace_.configuration.problem_size.m() = int(problem_.m);
gemm_workspace_.configuration.problem_size.n() = int(problem_.n);
gemm_workspace_.configuration.problem_size.k() = int(problem_.k);
gemm_workspace_.configuration.lda = problem_.lda;
gemm_workspace_.configuration.ldb = problem_.ldb;
gemm_workspace_.configuration.ldc = problem_.ldc;
gemm_workspace_.configuration.ldd = problem_.ldc;
gemm_workspace_.configuration.split_k_slices = int(problem_.split_k_slices);
gemm_workspace_.arguments.A = nullptr;
gemm_workspace_.arguments.B = nullptr;
gemm_workspace_.arguments.C = nullptr;
gemm_workspace_.arguments.D = nullptr;
gemm_workspace_.arguments.alpha = problem_.alpha.data();
gemm_workspace_.arguments.beta = problem_.beta.data();
gemm_workspace_.arguments.pointer_mode = library::ScalarPointerMode::kHost;
initialize_result_(this->model_result_, options, operation_desc, problem_space);
return operation->can_implement(&gemm_workspace_.configuration, &gemm_workspace_.arguments);
}
/// Initializes the performance result
void GemmOperationProfiler::initialize_result_(
PerformanceResult &result,
Options const &options,
library::GemmDescription const &operation_desc,
ProblemSpace const &problem_space) {
result.provider = Provider::kCUTLASS;
result.disposition = Disposition::kNotRun;
result.status = Status::kSuccess;
result.operation_name = operation_desc.name;
result.arguments.resize(problem_space.rank());
set_argument_(result, "A", problem_space,
std::string(library::to_string(operation_desc.A.element)) + ":" + library::to_string(operation_desc.A.layout));
set_argument_(result, "B", problem_space,
std::string(library::to_string(operation_desc.B.element)) + ":" + library::to_string(operation_desc.B.layout));
set_argument_(result, "C", problem_space,
std::string(library::to_string(operation_desc.C.element)) + ":" + library::to_string(operation_desc.C.layout));
set_argument_(result, "m", problem_space, problem_.m);
set_argument_(result, "n", problem_space, problem_.n);
set_argument_(result, "k", problem_space, problem_.k);
set_argument_(result, "split_k_slices", problem_space, problem_.split_k_slices);
set_argument_(result, "batch_count", problem_space, problem_.batch_count);
set_argument_(result, "alpha", problem_space,
library::lexical_cast(problem_.alpha, operation_desc.element_epilogue));
set_argument_(result, "beta", problem_space,
library::lexical_cast(problem_.beta, operation_desc.element_epilogue));
OperationProfiler::initialize_result_(result, operation_desc, problem_space);
result.bytes =
int64_t(library::sizeof_bits(operation_desc.A.element) * problem_.m / 8) * problem_.k +
int64_t(library::sizeof_bits(operation_desc.B.element) * problem_.n / 8) * problem_.k +
int64_t(library::sizeof_bits(operation_desc.C.element) * problem_.m / 8) * problem_.n * 2;
result.flops = 2 * (problem_.m * problem_.n * problem_.k + problem_.m * problem_.n);
result.runtime = 0;
}
/// Initializes workspace
Status GemmOperationProfiler::initialize_workspace(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
library::GemmDescription const &operation_desc =
static_cast<library::GemmDescription const &>(operation->description());
if (options.execution_mode != ExecutionMode::kDryRun) {
gemm_workspace_.A = device_context.allocate_tensor(
options,
"A",
operation_desc.A.element,
operation_desc.A.layout,
{int(problem_.m), int(problem_.k)},
{int(problem_.lda)}
);
gemm_workspace_.B = device_context.allocate_tensor(
options,
"B",
operation_desc.B.element,
operation_desc.B.layout,
{int(problem_.k), int(problem_.n)},
{int(problem_.ldb)}
);
gemm_workspace_.C = device_context.allocate_tensor(
options,
"C",
operation_desc.C.element,
operation_desc.C.layout,
{int(problem_.m), int(problem_.n)},
{int(problem_.ldc)}
);
gemm_workspace_.Computed = device_context.allocate_tensor(
"D",
operation_desc.C.element,
operation_desc.C.layout,
{int(problem_.m), int(problem_.n)},
{int(problem_.ldc)}
);
gemm_workspace_.Reference = device_context.allocate_tensor(
"Reference",
operation_desc.C.element,
operation_desc.C.layout,
{int(problem_.m), int(problem_.n)},
{int(problem_.ldc)}
);
gemm_workspace_.Reference->copy_from_device(gemm_workspace_.C->data());
}
//
// Initialize the CUTLASS operation
//
Status status = Status::kSuccess;
if (options.profiling.provider_enabled(Provider::kCUTLASS)) {
if (options.execution_mode != ExecutionMode::kDryRun) {
uint64_t workspace_size = operation->get_host_workspace_size(&gemm_workspace_.configuration);
gemm_workspace_.host_workspace.resize(workspace_size, 0);
workspace_size = operation->get_device_workspace_size(&gemm_workspace_.configuration);
gemm_workspace_.device_workspace.reset(library::NumericTypeID::kU8, workspace_size);
status = operation->initialize(
&gemm_workspace_.configuration,
gemm_workspace_.host_workspace.data(),
gemm_workspace_.device_workspace.data());
}
//
// If CUTLASS is enabled, generate a result for it
//
results_.push_back(model_result_);
results_.back().provider = Provider::kCUTLASS;
results_.back().disposition = Disposition::kNotRun;
}
return status;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Verifies CUTLASS against references
bool GemmOperationProfiler::verify_cutlass(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
if (!options.profiling.provider_enabled(Provider::kCUTLASS)) {
return true;
}
if (options.execution_mode == ExecutionMode::kDryRun) {
return true;
}
// Initialize structure containing GEMM arguments
gemm_workspace_.arguments.A = gemm_workspace_.A->data();
gemm_workspace_.arguments.B = gemm_workspace_.B->data();
gemm_workspace_.arguments.C = gemm_workspace_.C->data();
gemm_workspace_.arguments.D = gemm_workspace_.Computed->data();
gemm_workspace_.arguments.alpha = problem_.alpha.data();
gemm_workspace_.arguments.beta = problem_.beta.data();
gemm_workspace_.arguments.pointer_mode = library::ScalarPointerMode::kHost;
//
// Run the CUTLASS operation
//
results_.back().status = operation->run(
&gemm_workspace_.arguments,
gemm_workspace_.host_workspace.data(),
gemm_workspace_.device_workspace.data());
if (results_.back().status != Status::kSuccess) {
results_.back().disposition = Disposition::kFailed;
return false;
}
cudaError_t result = cudaDeviceSynchronize();
if (result != cudaSuccess) {
results_.back().disposition = Disposition::kFailed;
return false;
}
results_.back().disposition = Disposition::kNotVerified;
if (options.verification.enabled) {
#if CUTLASS_ENABLE_CUBLAS
if (options.verification.provider_enabled(Provider::kCUBLAS)) {
// Guard against unsupported cases
auto const & gemm_desc = static_cast<library::GemmDescription const &>(operation->description());
if (cublas_satisfies(gemm_desc) != Status::kSuccess) {
return true;
}
return verify_with_cublas_(
options,
report,
device_context,
operation,
problem_space,
problem);
}
#endif // #if CUTLASS_ENABLE_CUBLAS
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
#if CUTLASS_ENABLE_CUBLAS
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace detail {
/// Selects one or more cuBLAS algorithms.
static void select_cublas_algorithms(
std::vector<cublasGemmAlgo_t> &algorithms,
Options const &options,
library::GemmDescription const &op_desc) {
library::OpcodeClassID const & opcode_class =
op_desc.tile_description.math_instruction.opcode_class;
switch (options.library.algorithm_mode) {
case AlgorithmMode::kMatching:
{
algorithms.push_back(get_cublas_gemm_algo(
op_desc.tile_description.threadblock_shape.m(),
op_desc.tile_description.threadblock_shape.n(),
op_desc.tile_description.threadblock_shape.k(),
opcode_class));
break;
}
case AlgorithmMode::kBest:
{
// Choose first enumerated mode. If none are enumerated, choose based on opcode class
// and evaluate all of them.
if (options.library.algorithms.empty()) {
// Enumerate all algorithms
if (opcode_class == library::OpcodeClassID::kSimt) {
for (int algo = CUBLAS_GEMM_DEFAULT;
algo <= CUBLAS_GEMM_ALGO23;
++algo) {
algorithms.push_back(cublasGemmAlgo_t(algo));
}
}
else {
for (int algo = CUBLAS_GEMM_DEFAULT_TENSOR_OP;
algo <= CUBLAS_GEMM_ALGO15_TENSOR_OP;
++algo) {
algorithms.push_back(cublasGemmAlgo_t(algo));
}
}
}
else {
// Use the listed algorithms
algorithms.reserve(options.library.algorithms.size());
for (int algo : options.library.algorithms) {
algorithms.push_back(reinterpret_cast<cublasGemmAlgo_t const &>(algo));
}
}
break;
}
case AlgorithmMode::kDefault:
{
// Use the library's default algorithm
algorithms.push_back((opcode_class == library::OpcodeClassID::kSimt ?
CUBLAS_GEMM_DEFAULT : CUBLAS_GEMM_DEFAULT_TENSOR_OP));
break;
}
default:
{
break;
}
}
}
/// Dispatcher to cublasGemmEx()
struct cublasGemmExDispatcher {
//
// Data members
//
library::GemmConfiguration configuration;
library::GemmArguments arguments;
cublasOperation_t trans_A;
cublasOperation_t trans_B;
cudaDataType_t data_type_A;
cudaDataType_t data_type_B;
cudaDataType_t data_type_C;
cudaDataType_t compute_type;
cublasGemmAlgo_t algo;
Status status;
//
// Methods
//
cublasGemmExDispatcher(
library::GemmDescription const &op_desc,
library::GemmConfiguration configuration_,
library::GemmArguments arguments_,
cublasGemmAlgo_t algorithm = CUBLAS_GEMM_DFALT
):
configuration(configuration_), arguments(arguments_), algo(algorithm), status(Status::kSuccess) {
trans_A = get_cublas_transpose_operation(op_desc.A.layout);
trans_B = get_cublas_transpose_operation(op_desc.B.layout);
bool good = true;
good = (good && get_cublas_datatype(data_type_A, op_desc.A.element));
good = (good && get_cublas_datatype(data_type_B, op_desc.B.element));
good = (good && get_cublas_datatype(data_type_C, op_desc.C.element));
good = (good && get_cublas_datatype(
compute_type,
op_desc.tile_description.math_instruction.element_accumulator));
if (!good) {
status = Status::kErrorNotSupported;
}
}
/// Executes GEMM using these arguments
cublasStatus_t operator()(cublasHandle_t handle) {
return cublasGemmEx(
handle,
trans_A,
trans_B,
configuration.problem_size.m(),
configuration.problem_size.n(),
configuration.problem_size.k(),
arguments.alpha,
arguments.A,
data_type_A,
int(configuration.lda),
arguments.B,
data_type_B,
int(configuration.ldb),
arguments.beta,
arguments.D,
data_type_C,
int(configuration.ldc),
compute_type,
algo
);
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace detail
#endif // CUTLASS_ENABLE_CUBLAS
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Verifies CUTLASS against references
bool GemmOperationProfiler::verify_with_cublas_(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
#if CUTLASS_ENABLE_CUBLAS
library::GemmDescription const &gemm_desc =
static_cast<library::GemmDescription const &>(operation->description());
CublasCreate handle;
cublasStatus_t status = handle.get_cublas_create_status();
if (status != CUBLAS_STATUS_SUCCESS) {
results_.back().status = get_cutlass_status(status);
results_.back().disposition = Disposition::kFailed;
return true;
}
std::vector<cublasGemmAlgo_t> algorithms;
detail::select_cublas_algorithms(
algorithms,
options,
gemm_desc);
if (algorithms.empty()) {
// no algorithm selected
return true;
}
//
// Initialize state
//
try {
//
// Construct dispatcher to cublasGemmEx()
//
// Initialize structure containing GEMM arguments
gemm_workspace_.arguments.A = gemm_workspace_.A->data();
gemm_workspace_.arguments.B = gemm_workspace_.B->data();
gemm_workspace_.arguments.C = gemm_workspace_.Reference->data();
gemm_workspace_.arguments.D = gemm_workspace_.Reference->data();
gemm_workspace_.arguments.alpha = problem_.alpha.data();
gemm_workspace_.arguments.beta = problem_.beta.data();
gemm_workspace_.arguments.pointer_mode = library::ScalarPointerMode::kHost;
detail::cublasGemmExDispatcher gemm_op(
gemm_desc,
gemm_workspace_.configuration,
gemm_workspace_.arguments,
algorithms.front()
);
if (gemm_op.status != Status::kSuccess) {
results_.back().disposition = Disposition::kNotVerified;
return true;
}
results_.back().status = Status::kSuccess;
status = gemm_op(handle);
// Handle errors
if (status != CUBLAS_STATUS_SUCCESS) {
results_.back().status = get_cutlass_status(status);
results_.back().disposition = Disposition::kNotVerified;
return true;
}
//
// Verify results
//
results_.back().disposition = compare_tensors(
options,
*gemm_workspace_.Computed,
*gemm_workspace_.Reference
);
// Save workspace if incorrect
if (options.verification.save_workspace == SaveWorkspace::kIncorrect &&
results_.back().disposition == Disposition::kIncorrect) {
save_workspace(
device_context,
options,
gemm_desc,
Provider::kCUTLASS,
Provider::kCUBLAS);
}
}
catch (...) {
results_.back().disposition = Disposition::kFailed;
results_.back().status = Status::kErrorNotSupported;
}
#endif
// Return true means continue profiling
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Measures performance results
bool GemmOperationProfiler::profile(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
if (options.profiling.provider_enabled(Provider::kCUTLASS)) {
// Initialize structure containing GEMM arguments
gemm_workspace_.arguments.A = gemm_workspace_.A->data();
gemm_workspace_.arguments.B = gemm_workspace_.B->data();
gemm_workspace_.arguments.C = gemm_workspace_.C->data();
gemm_workspace_.arguments.D = gemm_workspace_.Computed->data();
gemm_workspace_.arguments.alpha = problem_.alpha.data();
gemm_workspace_.arguments.beta = problem_.beta.data();
gemm_workspace_.arguments.pointer_mode = library::ScalarPointerMode::kHost;
results_.back().status = profile_cutlass_(
results_.back().runtime,
options,
operation,
&gemm_workspace_.arguments,
gemm_workspace_.host_workspace.data(),
gemm_workspace_.device_workspace.data()
);
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,197 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Defines a math function
*/
#pragma once
#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
// CUTLASS Library includes
#include "cutlass/library/library.h"
#include "cutlass/library/manifest.h"
// Profiler includes
#include "options.h"
#include "device_context.h"
#include "operation_profiler.h"
#include "performance_result.h"
#include "problem_space.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Abstract base class for each math function
class GemmOperationProfiler : public OperationProfiler {
public:
/// Problem structure obtained from problem space
struct GemmProblem {
int64_t m;
int64_t n;
int64_t k;
int64_t lda;
int64_t ldb;
int64_t ldc;
std::vector<uint8_t> alpha;
std::vector<uint8_t> beta;
int64_t split_k_slices;
int64_t batch_count;
//
// Methods
//
GemmProblem():
m(16), n(16), k(16), lda(0), ldb(0), ldc(0), split_k_slices(1), batch_count(1) { }
};
/// Workspace used
struct GemmWorkspace {
DeviceAllocation *A;
DeviceAllocation *B;
DeviceAllocation *C;
DeviceAllocation *Computed;
DeviceAllocation *Reference;
library::GemmConfiguration configuration;
library::GemmArguments arguments;
/// Buffer used for the operation's host workspace
std::vector<uint8_t> host_workspace;
/// Buffer used for the operations' device workspace
DeviceAllocation device_workspace;
//
// Methods
//
GemmWorkspace():
A(nullptr), B(nullptr), C(nullptr), Computed(nullptr), Reference(nullptr) { }
};
protected:
//
// Data members
//
/// GEMM problem obtained from problem space
GemmProblem problem_;
/// Device memory allocations
GemmWorkspace gemm_workspace_;
public:
//
// Methods
//
/// Ctor
GemmOperationProfiler();
/// Destructor
virtual ~GemmOperationProfiler();
/// Prints usage statement for the math function
virtual void print_usage(std::ostream &out) const;
/// Prints examples
virtual void print_examples(std::ostream &out) const;
/// Extracts the problem dimensions
virtual Status initialize_configuration(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Initializes workspace
virtual Status initialize_workspace(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Verifies CUTLASS against references
virtual bool verify_cutlass(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Measures performance results
virtual bool profile(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
protected:
/// Initializes the performance result
void initialize_result_(
PerformanceResult &result,
Options const &options,
library::GemmDescription const &operation_desc,
ProblemSpace const &problem_space);
/// Verifies CUTLASS against references
bool verify_with_cublas_(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+107
View File
@@ -0,0 +1,107 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Defines a math function
*/
#include <stdexcept>
#include "gpu_timer.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
GpuTimer::GpuTimer() {
cudaError_t result;
for (auto & event : events) {
result = cudaEventCreate(&event);
if (result != cudaSuccess) {
throw std::runtime_error("Failed to create CUDA event");
}
}
}
GpuTimer::~GpuTimer() {
for (auto & event : events) {
cudaEventDestroy(event);
}
}
/// Records a start event in the stream
void GpuTimer::start(cudaStream_t stream) {
cudaError_t result = cudaEventRecord(events[0], stream);
if (result != cudaSuccess) {
throw std::runtime_error("Failed to record start event.");
}
}
/// Records a stop event in the stream
void GpuTimer::stop(cudaStream_t stream) {
cudaError_t result = cudaEventRecord(events[1], stream);
if (result != cudaSuccess) {
throw std::runtime_error("Failed to record stop event.");
}
}
/// Records a stop event in the stream and synchronizes on the stream
void GpuTimer::stop_and_wait(cudaStream_t stream) {
stop(stream);
cudaError_t result;
if (stream) {
result = cudaStreamSynchronize(stream);
if (result != cudaSuccess) {
throw std::runtime_error("Failed to synchronize with non-null CUDA stream.");
}
}
else {
result = cudaDeviceSynchronize();
if (result != cudaSuccess) {
throw std::runtime_error("Failed to synchronize with CUDA device.");
}
}
}
/// Returns the duration in miliseconds
double GpuTimer::duration(int iterations) const {
float avg_ms;
cudaError_t result = cudaEventElapsedTime(&avg_ms, events[0], events[1]);
if (result != cudaSuccess) {
throw std::runtime_error("Failed to query elapsed time from CUDA events.");
}
return double(avg_ms) / double(iterations);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+65
View File
@@ -0,0 +1,65 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Defines a math function
*/
#pragma once
#include <cuda_runtime.h>
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
struct GpuTimer {
cudaEvent_t events[2];
//
// Methods
//
GpuTimer();
~GpuTimer();
/// Records a start event in the stream
void start(cudaStream_t stream = nullptr);
/// Records a stop event in the stream
void stop(cudaStream_t stream = nullptr);
/// Records a stop event in the stream and synchronizes on the stream
void stop_and_wait(cudaStream_t stream = nullptr);
/// Returns the duration in miliseconds
double duration(int iterations = 1) const;
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+47
View File
@@ -0,0 +1,47 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief
*/
#include <iostream>
#include "options.h"
#include "cutlass_profiler.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *arg[]) {
cutlass::CommandLine cmdline(argc, arg);
cutlass::profiler::Options options(cmdline);
cutlass::profiler::CutlassProfiler profiler(options);
return profiler();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
+595
View File
@@ -0,0 +1,595 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Defines a math function
*/
#include <algorithm>
#include <stdexcept>
#include <iomanip>
#include <cstring>
#include <fstream>
#ifdef __unix__
#include <unistd.h>
#elif defined(_WIN32) || defined(WIN32)
#include <windows.h>
#else
// sleep not supported
#endif
#include "options.h"
#include "operation_profiler.h"
#include "gpu_timer.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
///////////////////////////////////////////////////////////////////////////////////////////////////
OperationProfiler::OperationProfiler(): kind_(library::OperationKind::kInvalid) { }
/// Ctor
OperationProfiler::OperationProfiler(
library::OperationKind kind,
ArgumentDescriptionVector const &arguments,
ProviderVector const & reference_providers
):
kind_(kind), arguments_(arguments), reference_providers_(reference_providers) {
ArgumentDescriptionVector tile_description_arguments{
{ArgumentTypeID::kEnumerated, {"op_class", "opcode-class"}, "Class of math instruction (SIMT or TensorOp)."},
{ArgumentTypeID::kEnumerated, {"accum", "accumulator-type"}, "Math instruction accumulator data type."},
{ArgumentTypeID::kInteger, {"cta_m", "threadblock-shape::m"}, "Threadblock shape in the M dimension."},
{ArgumentTypeID::kInteger, {"cta_n", "threadblock-shape::n"}, "Threadblock shape in the N dimension."},
{ArgumentTypeID::kInteger, {"cta_k", "threadblock-shape::k"}, "Threadblock shape in the K dimension."},
{ArgumentTypeID::kInteger, {"stages", "threadblock-stages"}, "Number of stages of threadblock-scoped matrix multiply."},
{ArgumentTypeID::kInteger, {"warps_m", "warp-count::m"}, "Number of warps within threadblock along the M dimension."},
{ArgumentTypeID::kInteger, {"warps_n", "warp-count::n"}, "Number of warps within threadblock along the N dimension."},
{ArgumentTypeID::kInteger, {"warps_k", "warp-count::k"}, "Number of warps within threadblock along the K dimension."},
{ArgumentTypeID::kInteger, {"inst_m", "instruction-shape::m"}, "Math instruction shape in the M dimension."},
{ArgumentTypeID::kInteger, {"inst_n", "instruction-shape::n"}, "Math instruction shape in the N dimension."},
{ArgumentTypeID::kInteger, {"inst_k", "instruction-shape::k"}, "Math instruction shape in the K dimension."},
{ArgumentTypeID::kInteger, {"min_cc", "minimum-compute-capability"}, "Minimum device compute capability."},
{ArgumentTypeID::kInteger, {"max_cc", "maximum-compute-capability"}, "Maximum device compute capability."}
};
arguments_.insert(arguments_.end(), tile_description_arguments.begin(), tile_description_arguments.end());
}
/// Destructor
OperationProfiler::~OperationProfiler() {
}
/// Gets the schema description
std::string const & OperationProfiler::description() const {
return description_;
}
/// Prints usage statement for the math function
void OperationProfiler::print_usage(std::ostream &out) const {
for (auto const & desc : arguments_) {
size_t const kAliasStart = 10;
size_t columns = 0;
std::string type_str = to_string(desc.type);
columns += type_str.size();
out << " [" << type_str << "]";
if (columns < kAliasStart) {
out << std::string(kAliasStart - columns, ' ');
}
columns = 0;
int j = 0;
for (auto const & alias : desc.aliases) {
columns += alias.size() + (j ? 1 : 0) + 2;
out << (j++ ? "," : "") << "--" << alias;
}
size_t const kTotalColumns = 50;
if (columns < kTotalColumns) {
out << std::string(kTotalColumns - columns, ' ');
}
out << desc.description << "\n";
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns true if the current operation description satisfies the problem space
bool OperationProfiler::satisfies(
library::OperationDescription const &op_desc,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
library::OpcodeClassID opcode_class;
if (arg_as_OpcodeClassID(opcode_class, "op_class", problem_space, problem)) {
if (opcode_class != op_desc.tile_description.math_instruction.opcode_class) {
return false;
}
}
int64_t int_value;
if (arg_as_int(int_value, "inst_m", problem_space, problem)) {
if (int64_t(op_desc.tile_description.math_instruction.instruction_shape.m()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "inst_n", problem_space, problem)) {
if (int64_t(op_desc.tile_description.math_instruction.instruction_shape.n()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "inst_k", problem_space, problem)) {
if (int64_t(op_desc.tile_description.math_instruction.instruction_shape.k()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "cta_m", problem_space, problem)) {
if (int64_t(op_desc.tile_description.threadblock_shape.m()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "cta_n", problem_space, problem)) {
if (int64_t(op_desc.tile_description.threadblock_shape.n()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "cta_k", problem_space, problem)) {
if (int64_t(op_desc.tile_description.threadblock_shape.k()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "stages", problem_space, problem)) {
if (int64_t(op_desc.tile_description.threadblock_stages) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "warps_m", problem_space, problem)) {
if (int64_t(op_desc.tile_description.warp_count.m()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "warps_n", problem_space, problem)) {
if (int64_t(op_desc.tile_description.warp_count.n()) != int_value) {
return false;
}
}
if (arg_as_int(int_value, "warps_k", problem_space, problem)) {
if (int64_t(op_desc.tile_description.warp_count.k()) != int_value) {
return false;
}
}
library::NumericTypeID numeric_type;
if (arg_as_NumericTypeID(numeric_type, "accum", problem_space, problem)) {
if (numeric_type != op_desc.tile_description.math_instruction.element_accumulator) {
return false;
}
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Entry point to profile all operations in the manifest
int OperationProfiler::profile_all(
Options const &options,
library::Manifest const &manifest,
DeviceContext &device_context) {
ProblemSpace problem_space(arguments_, options.cmdline);
// 1. Construct performance report
PerformanceReport report(options, problem_space.argument_names());
// 2. For each problem in problem space
ProblemSpace::Iterator problem_it = problem_space.begin();
ProblemSpace::Iterator problem_end = problem_space.end();
bool continue_profiling = true;
// For each problem in problem space
for (; continue_profiling && problem_it != problem_end; ++problem_it) {
ProblemSpace::Problem problem = problem_it.at();
report.next_problem();
// For each operation in manifest
for (auto const & operation_ptr : manifest) {
library::Operation const *operation = operation_ptr.get();
auto min_cc = operation->description().tile_description.minimum_compute_capability;
auto max_cc = operation->description().tile_description.maximum_compute_capability;
// Execute compatible operations if they satisfy the current device's compute capability
if (operation->description().kind == kind_ &&
options.device.compute_capability() >= min_cc &&
options.device.compute_capability() <= max_cc) {
std::string operation_name(operation->description().name);
bool filtered_by_name = options.operation_names.empty();
if (!filtered_by_name) {
for (auto const & op_name : options.operation_names) {
if (operation_name.find(op_name) !=std::string::npos) {
filtered_by_name = true;
break;
}
}
}
if (!filtered_by_name || !satisfies(operation->description(), problem_space, problem)) {
continue;
}
// A. Initialize configuration
Status status = this->initialize_configuration(
options,
report,
device_context,
operation,
problem_space,
problem);
if (status == Status::kErrorInternal) {
// Stop profiling if there was an internal error
return false;
}
else if (status != Status::kSuccess) {
// If the workspace could not be initialized for any other reason, continue to
// the next operation.
continue;
}
if (continue_profiling) {
status = this->initialize_workspace(
options,
report,
device_context,
operation,
problem_space,
problem);
if (status == Status::kErrorInternal) {
// Stop profiling if there was an internal error
return false;
}
else if (status != Status::kSuccess) {
// If the workspace could not be initialized for any other reason, continue to
// the next operation.
continue;
}
}
//
// Profile CUTLASS if it is enabled
//
// B. Verify CUTLASS
if (continue_profiling) {
continue_profiling = this->verify_cutlass(
options,
report,
device_context,
operation,
problem_space,
problem);
}
if (options.execution_mode == ExecutionMode::kDryRun) {
report.append_results(results_);
results_.clear();
continue;
}
//
// C. Optionally save workspace
//
if (options.verification.save_workspace == SaveWorkspace::kAlways) {
save_workspace(
device_context,
options,
operation->description(),
Provider::kCUTLASS);
}
//
// D. Profile
//
if (continue_profiling && options.profiling.enabled) {
continue_profiling = this->profile(
options,
report,
device_context,
operation,
problem_space,
problem);
}
// Clear named allocations
device_context.free();
report.append_results(results_);
results_.clear();
}
if (!continue_profiling) {
break;
}
}
}
// 3. Emit report
report.close();
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Sleep for a given duration in ms
void OperationProfiler::sleep(int sleep_duration) {
if (sleep_duration) {
#ifdef __unix__
usleep(sleep_duration * 1000);
#elif defined(_WIN32) || defined(WIN32)
SleepEx(sleep_duration, false);
#else
// sleep not supported
#endif
}
}
/// Compares tensors for equality
Disposition OperationProfiler::compare_tensors(
Options const &options,
DeviceAllocation &experimental,
DeviceAllocation &reference) {
if (experimental.type() != reference.type()) {
return Disposition::kIncorrect;
}
bool passed = false;
if (options.verification.epsilon == 0) {
// bit-level equality
passed = DeviceAllocation::block_compare_equal(
experimental.type(),
experimental.data(),
reference.data(),
experimental.capacity());
}
else {
// relative error function
passed = DeviceAllocation::block_compare_relatively_equal(
experimental.type(),
experimental.data(),
reference.data(),
experimental.capacity(),
options.verification.epsilon,
options.verification.nonzero_floor);
}
return passed ? Disposition::kPassed : Disposition::kIncorrect;
}
/// Saves the workspace
void OperationProfiler::save_workspace(
DeviceContext &device_context,
Options const &options,
library::OperationDescription const &desc,
Provider provider,
Provider verification_provider) {
for (auto const & named_allocation : device_context) {
DeviceAllocation *allocation = named_allocation.second;
std::stringstream filename;
filename << desc.name << "_" << to_string(provider) << "_";
if (verification_provider != Provider::kInvalid) {
filename << "verified_by_" << to_string(verification_provider) << "_";
}
filename << named_allocation.first + ".mat";
std::ofstream out(filename.str());
allocation->write_tensor_csv(out);
if (options.report.verbose) {
std::cout << "wrote '" << filename.str() << "'" << std::endl;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Method to profile a CUTLASS Operation
Status OperationProfiler::profile_cutlass_(
double &runtime,
Options const &options,
library::Operation const *operation,
void const *arguments,
void *host_workspace,
void *device_workspace) {
GpuTimer timer;
//
// Optional sleep to limit power consumption and thermals
//
sleep(options.profiling.sleep_duration);
//
// Warmup loop
//
Status status;
for (int iteration = 0; iteration < options.profiling.warmup_iterations; ++iteration) {
status = operation->run(
arguments,
host_workspace,
device_workspace);
if (status != Status::kSuccess) {
return status;
}
}
//
// Initialize GPU timer
//
timer.start();
//
// Profiling loop
//
int Iterations = options.profiling.iterations;
int iteration = 0;
for (; iteration < Iterations; ++iteration) {
status = operation->run(
arguments,
host_workspace,
device_workspace);
if (status != Status::kSuccess) {
return status;
}
}
//
// Wait for completion
//
timer.stop_and_wait();
//
// Update performance result
//
runtime = timer.duration(iteration);
return status;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Sets operation description
void OperationProfiler::initialize_result_(
PerformanceResult &result,
library::OperationDescription const &operation_desc,
ProblemSpace const &problem_space) {
set_argument_(result, "op_class", problem_space,
library::to_string(operation_desc.tile_description.math_instruction.opcode_class));
set_argument_(result, "accum", problem_space,
library::to_string(operation_desc.tile_description.math_instruction.element_accumulator));
set_argument_(result, "cta_m", problem_space, operation_desc.tile_description.threadblock_shape.m());
set_argument_(result, "cta_n", problem_space, operation_desc.tile_description.threadblock_shape.n());
set_argument_(result, "cta_k", problem_space, operation_desc.tile_description.threadblock_shape.k());
set_argument_(result, "stages", problem_space, operation_desc.tile_description.threadblock_stages);
set_argument_(result, "warps_m", problem_space, operation_desc.tile_description.warp_count.m());
set_argument_(result, "warps_n", problem_space, operation_desc.tile_description.warp_count.n());
set_argument_(result, "warps_k", problem_space, operation_desc.tile_description.warp_count.k());
set_argument_(result, "inst_m", problem_space, operation_desc.tile_description.math_instruction.instruction_shape.m());
set_argument_(result, "inst_n", problem_space, operation_desc.tile_description.math_instruction.instruction_shape.n());
set_argument_(result, "inst_k", problem_space, operation_desc.tile_description.math_instruction.instruction_shape.k());
set_argument_(result, "min_cc", problem_space, operation_desc.tile_description.minimum_compute_capability);
set_argument_(result, "max_cc", problem_space, operation_desc.tile_description.maximum_compute_capability);
}
/// Helper
void OperationProfiler::set_argument_(
PerformanceResult &result,
char const *name,
ProblemSpace const &problem_space,
std::string const &value) {
result.arguments.at(problem_space.argument_index(name)) = make_pair(std::string(name), value);
}
void OperationProfiler::set_argument_(
PerformanceResult &result,
char const *name,
ProblemSpace const &problem_space,
int64_t value) {
result.arguments.at(problem_space.argument_index(name)) = make_pair(std::string(name), library::lexical_cast(value));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
///////////////////////////////////////////////////////////////////////////////////////////////////
+240
View File
@@ -0,0 +1,240 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Defines a math function
*/
#pragma once
#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
// CUTLASS Library includes
#include "cutlass/library/library.h"
#include "cutlass/library/manifest.h"
// Profiler includes
#include "options.h"
#include "device_context.h"
#include "performance_result.h"
#include "performance_report.h"
#include "problem_space.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Abstract base class for each math function
class OperationProfiler {
public:
protected:
//
// Data members
//
/// Top-level operation kind
library::OperationKind kind_;
/// Human readable description
std::string description_;
/// Arguments parsed from command line
ArgumentDescriptionVector arguments_;
/// List of providers used to verify and compare each result
ProviderVector reference_providers_;
/// Model performance result initailized by the operation profiler with workload statistics
/// and reasonable default state.
PerformanceResult model_result_;
/// Performance result vector constructed by profiling the operation
PerformanceResultVector results_;
public:
//
// Methods
//
/// Ctor
OperationProfiler();
OperationProfiler(
library::OperationKind kind,
ArgumentDescriptionVector const &arguments = ArgumentDescriptionVector(),
ProviderVector const & reference_providers = ProviderVector());
/// Destructor
virtual ~OperationProfiler();
/// Obtains the operation kind
library::OperationKind kind() const { return kind_; }
/// Gets the schema description
std::string const &description() const;
/// Returns a reference to the arguments
ArgumentDescriptionVector const &arguments() const { return arguments_; }
public:
//
// Basic overrides
//
/// Prints usage statement for the math function
virtual void print_usage(std::ostream &out) const;
/// Prints examples
virtual void print_examples(std::ostream &out) const =0;
/// Entry point to profile all operations in the manifest
virtual int profile_all(
Options const &options,
library::Manifest const &manifest,
DeviceContext &device_context);
public:
//
// Operation-specific phases of verification and profiling
//
/// Extracts the problem dimensions
virtual Status initialize_configuration(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) = 0;
/// Initializes workspace
virtual Status initialize_workspace(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) = 0;
/// Verifies CUTLASS against references
virtual bool verify_cutlass(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) = 0;
/// Measures performance results
virtual bool profile(
Options const &options,
PerformanceReport &report,
DeviceContext &device_context,
library::Operation const *operation,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) = 0;
public:
//
// Static helpers
//
/// Sleep for a given duration in ms
static void sleep(int sleep_duration);
/// Returns true if the current operation description satisfies the problem space
static bool satisfies(
library::OperationDescription const &op_desc,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Compares tensors for equality
static Disposition compare_tensors(
Options const &options,
DeviceAllocation &experimental,
DeviceAllocation &reference);
static void save_workspace(
DeviceContext &device_context,
Options const &options,
library::OperationDescription const &desc,
Provider provider,
Provider verification_provider = Provider::kInvalid);
protected:
/// Sets operation description
static void initialize_result_(
PerformanceResult &result,
library::OperationDescription const &operation_desc,
ProblemSpace const &problem_space);
/// Helper to set a performance result member
static void set_argument_(
PerformanceResult &result,
char const *name,
ProblemSpace const &problem_space,
std::string const &value);
/// Helper to set a performance result member
static void set_argument_(
PerformanceResult &result,
char const *name,
ProblemSpace const &problem_space,
int64_t value);
/// Method to profile an initialized CUTLASS operation
virtual Status profile_cutlass_(
double &runtime,
Options const &options,
library::Operation const *operation,
void const *arguments,
void *host_workspace,
void *device_workspace);
};
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Vector of owning operation profilers
using OperationProfilerVector = std::vector<std::unique_ptr<OperationProfiler>>;
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+750
View File
@@ -0,0 +1,750 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Command line options for performance test program
*/
#include <algorithm>
#include "cutlass/cutlass.h"
#include "cutlass/version.h"
#include "options.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Newline and indent for help strings
static char const *end_of_line = "\n ";
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Device::Device(cutlass::CommandLine const &cmdline) {
cmdline.get_cmd_line_argument("device", device, 0);
cudaError_t result;
result = cudaGetDeviceProperties(&properties, device);
if (result != cudaSuccess) {
throw std::runtime_error("cudaGetDeviceProperties() failed for given device");
}
result = cudaSetDevice(device);
if (result != cudaSuccess) {
throw std::runtime_error("cudaSetDevice() failed for given device.");
}
// Permit overriding the compute capability
if (cmdline.check_cmd_line_flag("compute-capability")) {
int cc = compute_capability();
cmdline.get_cmd_line_argument("compute-capability", cc, cc);
properties.major = cc / 10;
properties.minor = cc % 10;
}
}
void Options::Device::print_usage(std::ostream &out) const {
out << "Device:\n"
<< " --device=<int> "
<< " CUDA Device ID\n\n";
int device_count = 0;
cudaError_t result = cudaGetDeviceCount(&device_count);
if (result != cudaSuccess) {
out << " <could not query for CUDA devices>\n";
}
else {
for (int idx = 0; idx < device_count; ++idx) {
cudaDeviceProp prop;
result = cudaGetDeviceProperties(&prop, idx);
if (result != cudaSuccess) {
out << " <could not obtain device properties for device " << idx << ">" << std::endl;
break;
}
else {
out << " [" << idx << "] - "
<< prop.name << " - SM " << prop.major << "." << prop.minor << ", "
<< prop.multiProcessorCount << " SMs @ " << (prop.clockRate / 1000.0) << " MHz, "
<< "L2 cache: " << (prop.l2CacheSize >> 20) << " MB, Global Memory: " << (prop.totalGlobalMem >> 30) << " GB"
<< std::endl;
}
}
out << "\n";
}
out
<< " --compute-capability=<int> "
<< " Override the compute capability.\n\n";
}
void Options::Device::print_device_info(std::ostream &out) const {
int num_devices;
cudaDeviceProp props;
cudaError_t result;
result = cudaGetDeviceCount(&num_devices);
if (result != cudaSuccess) {
throw std::runtime_error("cudaGetNumDevices() failed");
}
out << "Device Name,SM,CUDA Device ID,Phy Device ID" << std::endl;
for(int device = 0; device < num_devices; device++) {
result = cudaSetDevice(device);
if (result != cudaSuccess) {
throw std::runtime_error("cudaSetDevice() failed for device");
}
result = cudaGetDeviceProperties(&props, device);
if (result != cudaSuccess) {
throw std::runtime_error("cudaGetDeviceProperties failed for device");
}
out << props.name << "," << props.major << props.minor << ","
<< device << "," << props.multiGpuBoardGroupID << std::endl;
}
}
void Options::Device::print_options(std::ostream &out, int indent) const {
out
<< indent_str(indent) << "device: " << device << "\n"
<< indent_str(indent) << "clock: " << int(double(properties.clockRate) / 1000.0) << "\n"
<< indent_str(indent) << "compute-capability: " << compute_capability() << "\n";
}
/// Returns the compute capability of the listed device (e.g. 61, 60, 70, 75)
int Options::Device::compute_capability() const {
return properties.major * 10 + properties.minor;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Initialization::Initialization(cutlass::CommandLine const &cmdline) {
cmdline.get_cmd_line_argument("initialization-enabled", enabled, true);
if (cmdline.check_cmd_line_flag("initialization-provider")) {
std::string str;
cmdline.get_cmd_line_argument("initialization-provider", str);
provider = from_string<Provider>(str);
if (provider == Provider::kInvalid) {
enabled = false;
}
else if (provider != Provider::kReferenceHost && provider != Provider::kReferenceDevice) {
throw std::runtime_error("Unsupported intialization provider specified.");
}
}
else {
provider = Provider::kReferenceDevice;
}
cmdline.get_cmd_line_argument("seed", seed, 2019);
if (cmdline.check_cmd_line_flag("dist")) {
get_distribution(cmdline, "dist", data_distribution);
}
else {
data_distribution.set_uniform(-4, 4, 0);
}
}
/// Gets the initial distribution
void Options::Initialization::get_distribution(
cutlass::CommandLine const &args,
std::string const &arg,
cutlass::Distribution &dist) {
struct {
const char *label;
cutlass::Distribution::Kind kind;
} distribution_kinds[] = {
{"uniform", cutlass::Distribution::Uniform},
{"gaussian", cutlass::Distribution::Gaussian},
{"identity", cutlass::Distribution::Identity},
{"sequential", cutlass::Distribution::Sequential},
{0, cutlass::Distribution::Invalid}
};
struct {
char const *label;
double *member;
} members[] = {
{"min", &dist.uniform.min},
{"max", &dist.uniform.max},
{"mean", &dist.gaussian.mean},
{"stddev", &dist.gaussian.stddev},
{"start", &dist.sequential.start},
{"delta", &dist.sequential.delta},
{0, 0}
};
using KeyValueVector = std::vector<std::pair<std::string, std::string> >;
KeyValueVector values;
args.get_cmd_line_argument_pairs(arg.c_str(), values);
// The parser expects the first token to be a string identifying the distribution type.
auto it = values.begin();
if (it != values.end()) {
for (int i = 0; distribution_kinds[i].label; ++i) {
if (it->first == distribution_kinds[i].label) {
dist.kind = distribution_kinds[i].kind;
break;
}
}
++it;
}
// Subsequent key-value pairs update the named field of the distribution struct.
for (; it != values.end(); ++it) {
// Integer scaling factor - if < 0, no integer rounding is performed.
if ((it->first.compare("scale") == 0) && !it->second.empty()) {
std::stringstream ss;
ss << it->second;
ss >> dist.int_scale;
continue; // next token
}
// Casts as integer without scaling
if (it->first.compare("integer") == 0) {
dist.int_scale = 0;
continue; // next token
}
// Casts as integer without scaling
if (it->first.compare("integer") == 0) {
dist.int_scale = 0;
continue; // next token
}
// initialize other members
for (int m = 0; members[m].label; ++m) {
if (it->first == members[m].label && !it->second.empty()) {
std::stringstream ss;
ss << it->second;
ss >> *(members[m].member);
}
}
}
}
void Options::Initialization::print_usage(std::ostream &out) const {
out << "Initialization:\n"
<< " --initialization=<bool> "
<< " Enables initialization (default: true). If false, device memory is" << end_of_line
<< "not initialized after allocation.\n\n"
<< " --initialization-provider=<provider> "
<< " Selects 'device' or 'host' initialization.\n\n"
<< " --dist=<distribution> "
<< " Data distribution of input tensors\n\n"
<< " --seed=<int> "
<< " Random number generator seed. Used to enforce deterministic" << end_of_line
<< "initialization.\n\n";
}
void Options::Initialization::print_options(std::ostream &out, int indent) const {
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Library::Library(cutlass::CommandLine const &cmdline) {
algorithm_mode = AlgorithmMode::kDefault;
if (cmdline.check_cmd_line_flag("library-algo-mode")) {
std::string mode = "default";
cmdline.get_cmd_line_argument("library-algo-mode", mode);
algorithm_mode = from_string<AlgorithmMode>(mode);
}
if (cmdline.check_cmd_line_flag("library-algos")) {
// If algorithms are specified, override as kBest.
algorithm_mode = AlgorithmMode::kBest;
std::vector<std::string> tokens;
cmdline.get_cmd_line_arguments("library-algos", tokens);
algorithms.reserve(tokens.size());
for (auto const & token : tokens) {
if (token.find(":")) {
// todo - tokenized range
}
else {
int algo;
std::stringstream ss;
ss << token;
ss >> algo;
algorithms.push_back(algo);
}
}
}
}
void Options::Library::print_usage(std::ostream &out) const {
out << "Library:\n"
<< " --library-algo-mode=<mode> "
<< " Indicates algorithm mode used to call libraries such as cuBLAS and cuDNN.\n"
<< " "
<< " mode={default*,matching,best}\n\n"
<< " --library-algos=<range-list> "
<< " If --algorithm-mode=best, permits specifying a selection of algorithms.\n\n";
}
void Options::Library::print_options(std::ostream &out, int indent) const {
out
<< indent_str(indent) << "library-algo-mode: " << to_string(algorithm_mode) << "\n"
<< indent_str(indent) << "library-algos: ";
int j = 0;
for (int x : algorithms) {
out << (j++ ? "," : "") << x;
}
out << "\n\n";
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Profiling::Profiling(cutlass::CommandLine const &cmdline) {
cmdline.get_cmd_line_argument("warmup-iterations", warmup_iterations, 10);
cmdline.get_cmd_line_argument("profiling-iterations", iterations, 100);
cmdline.get_cmd_line_argument("sleep-duration", sleep_duration, 50);
cmdline.get_cmd_line_argument("profiling-enabled", enabled, true);
if (cmdline.check_cmd_line_flag("providers")) {
std::vector<std::string> tokens;
cmdline.get_cmd_line_arguments("providers", tokens);
providers.clear();
for (auto const &token : tokens) {
providers.push_back(from_string<Provider>(token));
}
}
else {
providers.push_back(Provider::kCUTLASS);
providers.push_back(Provider::kCUBLAS);
}
}
void Options::Profiling::print_usage(std::ostream &out) const {
out << "Profiling:\n"
<< " --profiling-iterations=<iterations> "
<< " Number of iterations to profile each kernel. If zero, kernels" << end_of_line
<< "are launched up to the profiling duration.\n\n"
<< " --warmup-iterations=<iterations> "
<< " Number of iterations to execute each kernel prior to profiling.\n\n"
<< " --sleep-duration=<duration> "
<< " Number of ms to sleep between profiling periods (ms)\n\n"
<< " --profiling-enabled=<bool> "
<< " If true, profiling is actually conducted.\n\n"
<< " --providers=<providers> "
<< " List of providers to be profiled for performance\n\n";
}
void Options::Profiling::print_options(std::ostream &out, int indent) const {
out
<< indent_str(indent) << "profiling_iterations: " << iterations << "\n"
<< indent_str(indent) << "sleep_duration: " << sleep_duration << "\n"
<< indent_str(indent) << "profiling_enabled: " << enabled << "\n"
<< indent_str(indent) << "providers: [";
int j = 0;
for (auto const & provider : providers) {
out << (j++ ? ", " : "") << to_string(provider);
}
out << "]\n";
}
/// Returns true if a provider is enabled
bool Options::Profiling::provider_enabled(Provider provider) const {
return std::find(providers.begin(), providers.end(), provider) != providers.end();
}
/// Returns the index of a provider if its enabled
size_t Options::Profiling::index(Provider provider) const {
size_t idx = 0;
for (auto const & x : providers) {
if (x == provider) {
return idx;
}
++idx;
}
return idx;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Verification::Verification(cutlass::CommandLine const &cmdline) {
cmdline.get_cmd_line_argument("verification-enabled", enabled, true);
cmdline.get_cmd_line_argument("epsilon", epsilon, 0.05);
cmdline.get_cmd_line_argument("nonzero-floor", nonzero_floor, 1.0 / 256.0);
if (cmdline.check_cmd_line_flag("save-workspace")) {
std::string value;
cmdline.get_cmd_line_argument("save-workspace", value);
save_workspace = from_string<SaveWorkspace>(value);
}
else {
save_workspace = SaveWorkspace::kNever;
}
if (cmdline.check_cmd_line_flag("verification-providers")) {
std::vector<std::string> tokens;
cmdline.get_cmd_line_arguments("verification-providers", tokens);
providers.clear();
for (auto const &token : tokens) {
Provider provider = from_string<Provider>(token);
if (provider != Provider::kInvalid) {
providers.push_back(provider);
}
}
}
else {
providers.push_back(Provider::kCUBLAS);
}
}
void Options::Verification::print_usage(std::ostream &out) const {
out << "Verification:\n"
<< " --verification-enabled=<bool> "
<< " Whether to perform verification checks.\n\n"
<< " --epsilon=<error> "
<< " Error threshold. Setting to zero (default) requires" << end_of_line
<< "bit-level equivalence.\n\n"
<< " --nonzero-floor=<floor> "
<< " Results whose absolute value is less than this quantity" << end_of_line
<< "are treated as zero for comparisons.\n\n"
<< " --save-workspace={*never,incorrect,always}"
<< " Specifies when to save the GEMM inputs and results to the filesystem.\n\n"
<< " --verification-providers=<providers> "
<< " List of providers used to verify result. (default: device)\n\n";
}
void Options::Verification::print_options(std::ostream &out, int indent) const {
out
<< indent_str(indent) << "verification_enabled: " << enabled << "\n"
<< indent_str(indent) << "epsilon: " << epsilon << "\n"
<< indent_str(indent) << "save_workspace: " << to_string(save_workspace) << "\n"
<< indent_str(indent) << "verification_providers: [";
int j = 0;
for (auto const & provider : providers) {
out << (j++ ? ", " : "") << to_string(provider);
}
out << "]\n";
}
/// Returns true if a provider is enabled
bool Options::Verification::provider_enabled(Provider provider) const {
return std::find(providers.begin(), providers.end(), provider) != providers.end();
}
/// Returns the index of a provider if its enabled
size_t Options::Verification::index(Provider provider) const {
size_t idx = 0;
for (auto const & x : providers) {
if (x == provider) {
return idx;
}
++idx;
}
return idx;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Report::Report(cutlass::CommandLine const &cmdline) {
cmdline.get_cmd_line_argument("append", append, false);
cmdline.get_cmd_line_argument("output", output_path);
if (cmdline.check_cmd_line_flag("tags")) {
cmdline.get_cmd_line_argument_pairs("tags", pivot_tags);
}
cmdline.get_cmd_line_argument("report-not-run", report_not_run, false);
cmdline.get_cmd_line_argument("verbose", verbose, true);
}
void Options::Report::print_usage(std::ostream &out) const {
out << "Report:\n"
<< " --append=<bool> "
<< " If true, result is appended to possibly existing file. Otherwise, " << end_of_line
<< "any existing file is overwritten.\n\n"
<< " --output=<path> "
<< " Path to output file for machine readable results.\n\n"
<< " --report-not-run=<bool> "
<< " If true, reports the status of all kernels including those that" << end_of_line
<< "do not satisfy the given arguments.\n\n"
<< " --tags=<column:tag,...> "
<< " Inserts leading columns in output table and uniform values for each" << end_of_line
<< "column. Useful for generating pivot tables.\n\n"
<< " --verbose=<bool> "
<< " Prints human-readable text to stdout. If false, nothing is written to stdout.\n\n";
}
void Options::Report::print_options(std::ostream &out, int indent) const {
out
<< indent_str(indent) << "append: " << append << "\n"
<< indent_str(indent) << "output: " << output_path << "\n"
<< indent_str(indent) << "report_not_run: " << report_not_run << "\n"
<< indent_str(indent) << "tags:\n";
for (auto const & tag : pivot_tags) {
out << indent_str(indent + 1) << tag.first << ": " << tag.second << "\n";
}
out
<< indent_str(indent) << "verbose: " << verbose << "\n";
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::About::About(cutlass::CommandLine const &cmdline) {
help = cmdline.check_cmd_line_flag("help");
version = cmdline.check_cmd_line_flag("version");
device_info = cmdline.check_cmd_line_flag("device-info");
}
void Options::About::print_usage(std::ostream &out) const {
out << "About:\n"
<< " --version ";
print_version(out);
out << "\n";
}
void Options::About::print_version(std::ostream &out) {
out << "CUTLASS " << cutlass::getVersionString()
<< " built on " << __DATE__ << " at " << __TIME__;
if (!cutlass::getGitRevision().empty()) out << " with commit " << cutlass::getGitRevision() << "";
}
void Options::About::print_options(std::ostream &out, int indent) const {
}
/////////////////////////////////////////////////////////////////////////////////////////////////
Options::Options(cutlass::CommandLine const &cmdline):
cmdline(cmdline),
device(cmdline),
initialization(cmdline),
library(cmdline),
profiling(cmdline),
verification(cmdline),
report(cmdline),
about(cmdline) {
if (cmdline.check_cmd_line_flag("mode")) {
std::string token;
cmdline.get_cmd_line_argument("mode", token);
execution_mode = from_string<ExecutionMode>(token);
}
else {
execution_mode = ExecutionMode::kProfile;
}
// Enumerating kernels is equivalent to a dry run.
if (execution_mode == ExecutionMode::kEnumerate) {
execution_mode = ExecutionMode::kDryRun;
}
if (cmdline.check_cmd_line_flag("operation")) {
std::string str;
cmdline.get_cmd_line_argument("operation", str);
operation_kind = library::from_string<library::OperationKind>(str);
}
else if (cmdline.check_cmd_line_flag("function")) {
std::string str;
cmdline.get_cmd_line_argument("function", str);
operation_kind = library::from_string<library::OperationKind>(str);
}
else {
operation_kind = library::OperationKind::kInvalid;
}
if (cmdline.check_cmd_line_flag("operation_names")) {
cmdline.get_cmd_line_arguments("operation_names", operation_names);
}
else if (cmdline.check_cmd_line_flag("kernels")) {
cmdline.get_cmd_line_arguments("kernels", operation_names);
}
// Prevent launches on the device for anything other than CUTLASS operation
if (execution_mode == ExecutionMode::kTrace) {
initialization.provider = Provider::kReferenceHost;
verification.enabled = false;
profiling.enabled = false;
}
}
void Options::print_usage(std::ostream &out) const {
out
<< "CUTLASS Performance Tool\n"
<< "usage:\n\n"
<< " cutlass_profiler [options]\n\n"
<< " --help\n\n"
<< " --mode={profile*,single,dry,trace,enumerate} "
<< " Regular profiling, single kernel mode only, or no profiling.\n\n"
<< " --device-info "
<< " Prints information on all GPUs present in the system\n\n"
<< " --operation=<operation_kind> "
<< " CUTLASS operation to profile.\n\n"
<< " --kernels=<string_list> "
<< " List of substrings to filter operations by name.\n\n"
;
//
// Detailed options
//
device.print_usage(out);
out << "\n";
initialization.print_usage(out);
out << "\n";
library.print_usage(out);
out << "\n";
profiling.print_usage(out);
out << "\n";
verification.print_usage(out);
out << "\n";
report.print_usage(out);
out << "\n";
about.print_usage(out);
out << "\n";
}
void Options::print_options(std::ostream &out) const {
out
<< "options:\n"
<< " help: " << about.help << "\n"
<< " mode: " << to_string(execution_mode) << "\n";
out
<< " device:\n";
device.print_options(out, 2);
out
<< " initialization:\n";
initialization.print_options(out, 2);
out
<< " profiling:\n";
profiling.print_options(out, 2);
out
<< " verification:\n";
verification.print_options(out, 2);
out
<< " report:\n";
report.print_options(out, 2);
}
std::string Options::indent_str(int indent) {
return std::string(indent * 2, ' ');
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+300
View File
@@ -0,0 +1,300 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Command line options for performance test program
*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <cuda_runtime.h>
#include "cutlass/util/command_line.h"
#include "cutlass/util/distribution.h"
#include "cutlass/library/library.h"
#include "enumerated_types.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Global options
class Options {
public:
/// Cublas and cuDNN options
struct Library {
//
// Data members
//
/// Algorithm mode
AlgorithmMode algorithm_mode;
/// Algorithm enumerants
std::vector<int> algorithms;
//
// Methods
//
Library(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
};
/// Options related to the selected device
struct Device {
/// Device ID
int device;
/// CUDA Device properties
cudaDeviceProp properties;
/// Total memory allocation on device
size_t maximum_capacity;
//
// Methods
//
Device(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
void print_device_info(std::ostream &out) const;
/// Returns the compute capability of the listed device (e.g. 61, 60, 70, 75)
int compute_capability() const;
};
/// Options related to initializing input tensors
struct Initialization {
/// If true, data is initialized randomly. If false, no initialization is performed after
/// allocating tensors.
bool enabled;
/// Data distribution for input tensors
Distribution data_distribution;
/// Source of random tensor elements
Provider provider;
/// Random number generator seed.
int seed;
//
// Methods
//
Initialization(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
/// Helper to parse a Distribution object from the command line parser
static void get_distribution(
cutlass::CommandLine const &args,
std::string const &arg,
cutlass::Distribution &dist);
};
/// Options related to verification of the result
struct Verification {
//
// Data members
//
/// If true, kernels are verified before they are profiled
bool enabled;
/// Relative error threshold - zero to require bit-level consistency
double epsilon;
/// Values smaller than this are assumed to be zero
double nonzero_floor;
/// List of providers used to verify each result
ProviderVector providers;
/// Indicates when to save the workspace
SaveWorkspace save_workspace;
//
// Methods
//
Verification(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
/// Returns true if a provider is enabled
bool provider_enabled(Provider provider) const;
/// Returns the index of a provider if its enabled
size_t index(Provider provider) const;
};
/// Options related to profiling
struct Profiling {
/// Number of iterations to warmup each kernel prior to profiling
int warmup_iterations;
/// Number of iterations to profile each kernel - if 0, kernels are launched up to the profiling duration
int iterations;
/// Number of ms to sleep between profiling periods (ms)
int sleep_duration;
/// If true, profiling is actually conducted.
bool enabled;
/// List of providers of each functionality to be profiled
ProviderVector providers;
//
// Methods
//
Profiling(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
/// Returns true if a provider is enabled
bool provider_enabled(Provider provider) const;
/// Returns the index of a provider if its enabled
size_t index(Provider provider) const;
};
/// Options related to reporting
struct Report {
/// If true, result is appended to possibly existing file
bool append;
/// Path to a file containing results
std::string output_path;
/// Sequence of tags to attach to each result
std::vector<std::pair<std::string, std::string>> pivot_tags;
/// If true, reports status of all kernels including those that were
/// not run for the given argumetns
bool report_not_run;
/// Prints human-readable text to stdout. If false, nothing is written to stdout
bool verbose;
//
// Methods
//
Report(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
};
/// Options related to printing usage and version information
struct About {
/// If true, usage is printed and the program ends.
bool help;
/// Prints version string
bool version;
/// Print information about devices
bool device_info;
//
// Methods
//
About(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out, int indent = 0) const;
static void print_version(std::ostream &out);
};
public:
//
// Data members
//
/// Top-level execution mode
ExecutionMode execution_mode;
/// Name of math function to profile
library::OperationKind operation_kind;
/// Vector of operation name substrings
std::vector<std::string> operation_names;
//
// Detailed configuration options
//
/// Configuration
CommandLine cmdline;
Device device;
Initialization initialization;
Library library;
Verification verification;
Profiling profiling;
Report report;
About about;
public:
Options(CommandLine const &cmdline);
void print_usage(std::ostream &out) const;
void print_options(std::ostream &out) const;
static std::string indent_str(int indent);
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+302
View File
@@ -0,0 +1,302 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Execution environment
*/
#include <iostream>
#include <stdexcept>
#include <iomanip>
#include "performance_report.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
#if defined(__unix__)
#define SHELL_COLOR_BRIGHT() "\033[1;37m"
#define SHELL_COLOR_GREEN() "\033[1;32m"
#define SHELL_COLOR_RED() "\033[1;31m"
#define SHELL_COLOR_END() "\033[0m"
#else
#define SHELL_COLOR_BRIGHT() ""
#define SHELL_COLOR_GREEN() ""
#define SHELL_COLOR_RED() ""
#define SHELL_COLOR_END() ""
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////
PerformanceReport::PerformanceReport(
Options const &options,
std::vector<std::string> const &argument_names
):
options_(options), argument_names_(argument_names), problem_index_(0), good_(true) {
//
// Open output file
//
if (!options_.report.output_path.empty()) {
bool print_header = true;
if (options_.report.append) {
std::ifstream test_output_file(options_.report.output_path.c_str());
if (test_output_file.is_open()) {
print_header = false;
test_output_file.close();
}
output_file_.open(options_.report.output_path.c_str(), std::ios::app);
}
else {
output_file_.open(options_.report.output_path.c_str());
}
if (!output_file_.good()) {
std::cerr << "Could not open output file at path '"
<< options_.report.output_path << "'" << std::endl;
good_ = false;
}
if (print_header) {
print_csv_header_(output_file_) << std::endl;
}
}
}
void PerformanceReport::next_problem() {
++problem_index_;
}
void PerformanceReport::append_result(PerformanceResult result) {
result.problem_index = problem_index_;
if (options_.report.verbose) {
std::cout << "\n";
print_result_pretty_(std::cout, result) << std::flush;
}
if (output_file_.is_open()) {
print_result_csv_(output_file_, result) << std::endl;
}
else {
concatenated_results_.push_back(result);
}
}
void PerformanceReport::append_results(PerformanceResultVector const &results) {
if (options_.report.verbose) {
std::cout << "\n\n";
}
// For each result
for (auto const & result : results) {
append_result(result);
}
}
void PerformanceReport::close() {
//
// Output results to stdout if they were not written to a file already.
//
if (options_.report.verbose && !concatenated_results_.empty()) {
std::cout << "\n\n";
std::cout << "=============================\n\n";
std::cout << "CSV Results:\n\n";
print_csv_header_(std::cout) << std::endl;
for (auto const &result : concatenated_results_) {
print_result_csv_(std::cout, result) << "\n";
}
}
else if (output_file_.is_open() && options_.report.verbose) {
std::cout << "\n\nWrote results to '" << options_.report.output_path << "'" << std::endl;
}
}
static const char *disposition_status_color(Disposition disposition) {
switch (disposition) {
case Disposition::kPassed: return SHELL_COLOR_GREEN();
case Disposition::kFailed: return SHELL_COLOR_RED();
default:
break;
}
return SHELL_COLOR_END();
}
/// Prints the result in human readable form
std::ostream & PerformanceReport::print_result_pretty_(
std::ostream &out,
PerformanceResult const &result) {
out << "=============================\n"
<< " Problem ID: " << result.problem_index << "\n";
if (!options_.report.pivot_tags.empty()) {
out << " Tags: ";
int column_idx = 0;
for (auto const & tag : options_.report.pivot_tags) {
out << (column_idx++ ? "," : "") << tag.first << ":" << tag.second;
}
out << "\n";
}
out
<< "\n"
<< " Provider: " << SHELL_COLOR_BRIGHT() << to_string(result.provider, true) << SHELL_COLOR_END() << "\n"
<< " Operation: " << result.operation_name << "\n\n"
<< " Disposition: " << disposition_status_color(result.disposition) << to_string(result.disposition, true) << SHELL_COLOR_END() << "\n"
<< " Status: " << SHELL_COLOR_BRIGHT() << library::to_string(result.status, true) << SHELL_COLOR_END() << "\n";
out
<< "\n Arguments: ";
int column_idx = 0;
for (auto const &arg : result.arguments) {
if (!arg.second.empty()) {
out << " --" << arg.first << "=" << arg.second;
column_idx += 4 + arg.first.size() + arg.second.size();
if (column_idx > 90) {
out << " \\\n ";
column_idx = 0;
}
}
}
out << "\n\n";
out
<< " Bytes: " << result.bytes << " bytes\n"
<< " FLOPs: " << result.flops << " flops\n\n";
if (result.good()) {
out
<< " Runtime: " << result.runtime << " ms\n"
<< " Memory: " << result.gbytes_per_sec() << " GiB/s\n"
<< "\n Math: " << result.gflops_per_sec() << " GFLOP/s\n";
}
return out;
}
/// Prints the CSV header
std::ostream & PerformanceReport::print_csv_header_(
std::ostream &out) {
int column_idx = 0;
// Pivot tags
for (auto const & tag : options_.report.pivot_tags) {
out << (column_idx++ ? "," : "") << tag.first;
}
out
<< (column_idx ? "," : "") << "Problem,Provider"
<< ",Operation,Disposition,Status";
for (auto const &arg_name : argument_names_) {
out << "," << arg_name;
}
out
<< ",Bytes"
<< ",Flops"
<< ",Runtime"
<< ",GB/s"
<< ",GFLOPs"
;
return out;
}
/// Print the result in CSV output
std::ostream & PerformanceReport::print_result_csv_(
std::ostream &out,
PerformanceResult const &result) {
int column_idx = 0;
// Pivot tags
for (auto const & tag : options_.report.pivot_tags) {
out << (column_idx++ ? "," : "") << tag.second;
}
out
<< (column_idx ? "," : "")
<< result.problem_index
<< "," << to_string(result.provider, true)
<< "," << result.operation_name
<< "," << to_string(result.disposition)
<< "," << library::to_string(result.status);
for (auto const & arg : result.arguments) {
out << "," << arg.second;
}
out
<< "," << result.bytes
<< "," << result.flops
<< "," << result.runtime;
if (result.good()) {
out
<< "," << result.gbytes_per_sec()
<< "," << result.gflops_per_sec()
;
}
else {
out << std::string(2
, ','
);
}
return out;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+94
View File
@@ -0,0 +1,94 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Class performing output during profiling
*/
#pragma once
#include <vector>
#include <fstream>
#include "options.h"
#include "enumerated_types.h"
#include "performance_result.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
class PerformanceReport {
private:
/// Reference to options
Options const &options_;
/// Output file containing results
std::ofstream output_file_;
/// Flag indicating the performance report is valid
bool good_;
/// Vector of argument names
std::vector<std::string> argument_names_;
/// Counter uniquely identifying problem within the report
size_t problem_index_;
/// Collection of all results
PerformanceResultVector concatenated_results_;
public:
PerformanceReport(Options const &options, std::vector<std::string> const &argument_names);
bool good() const { return good_; }
void next_problem();
void append_result(PerformanceResult result);
void append_results(PerformanceResultVector const &results);
void close();
public:
/// Prints the CSV header
std::ostream & print_csv_header_(std::ostream &out);
/// Prints the CSV
std::ostream & print_result_csv_(std::ostream &out, PerformanceResult const &result);
/// Prints the result in human readable form
std::ostream & print_result_pretty_(
std::ostream &out,
PerformanceResult const &result);
};
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+109
View File
@@ -0,0 +1,109 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief Defines a math function
*/
#pragma once
#include <vector>
#include "cutlass/cutlass.h"
#include "enumerated_types.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Performance result object
struct PerformanceResult {
/// Index of problem
size_t problem_index;
/// Provider
Provider provider;
/// Outcome of test
Disposition disposition;
/// CUTLASS status result from kernels
Status status;
/// Operation object
std::string operation_name;
/// Stringified vector of argument values
std::vector<std::pair<std::string, std::string> > arguments;
/// Number of bytes read or written
int64_t bytes;
/// Number of DL flops performed by the math function
int64_t flops;
/// Average runtime in ms
double runtime;
//
// Members
//
/// Ctor
PerformanceResult():
problem_index(0),
provider(Provider::kInvalid),
disposition(Disposition::kNotRun),
status(Status::kInvalid),
bytes(0),
flops(0),
runtime(0)
{ }
/// Returns true if the runtime is valid
bool good() const {
return runtime > 0;
}
/// Math throughput in units of GFLOP/s
double gflops_per_sec() const {
return double(flops) / runtime / 1.0e6;
}
/// memory bandwidth in units of GiB/s
double gbytes_per_sec() const {
return double(bytes) / double(1 << 30) / runtime * 1000.0;
}
};
using PerformanceResultVector = std::vector<PerformanceResult>;
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
+947
View File
@@ -0,0 +1,947 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief
*/
#include <string>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include "problem_space.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
static T lexical_cast(std::string const &str) {
std::stringstream ss;
T value;
ss << str;
ss >> value;
return value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
std::ostream & KernelArgument::ValueIterator::print(std::ostream &out) const {
out << "[" << (void *)this << " " << argument->qualified_name() << "] ";
if (this->null_argument) {
out << "<null>";
}
else {
out << "<not null>";
}
return out;
}
KernelArgument::~KernelArgument() {
}
//////////////////////////////////////////////////////////////////////////////////////////////////
ScalarArgument::ScalarValue::ScalarValue(
std::string const &value_,
ScalarArgument const *argument_,
bool not_null_
):
KernelArgument::Value(argument_, not_null_),
value(value_) {
}
std::ostream &ScalarArgument::ScalarValue::print(std::ostream &out) const {
out << argument->qualified_name() << ": ";
if (not_null) {
out << value;
}
else {
out << "<null>";
}
return out;
}
ScalarArgument::ScalarValueIterator::ScalarValueIterator(
ScalarArgument const *argument_
):
KernelArgument::ValueIterator(argument_) {
if (argument_) {
value_it = argument_->values.begin();
}
}
void ScalarArgument::ScalarValueIterator::operator++() {
if (this->null_argument) {
this->null_argument = false;
}
else {
++value_it;
}
}
bool ScalarArgument::ScalarValueIterator::operator==(ValueIterator const &it) const {
if (it.type() != ArgumentTypeID::kScalar) {
throw std::runtime_error("Cannot compare ScalarValueIterator with iterator of different type");
}
auto const & scalar_it = static_cast<ScalarValueIterator const &>(it);
return value_it == scalar_it.value_it;
}
/// Gets the value pointed to
std::unique_ptr<KernelArgument::Value> ScalarArgument::ScalarValueIterator::at() const {
if (this->null_argument) {
return std::unique_ptr<KernelArgument::Value>(
new ScalarArgument::ScalarValue(
std::string(),
static_cast<ScalarArgument const *>(argument),
false));
}
else {
return std::unique_ptr<KernelArgument::Value>(
new ScalarArgument::ScalarValue(
*value_it,
static_cast<ScalarArgument const *>(argument)));
}
}
std::unique_ptr<KernelArgument::ValueIterator> ScalarArgument::begin() const {
return std::unique_ptr<KernelArgument::ValueIterator>(new ScalarValueIterator(this));
}
std::unique_ptr<KernelArgument::ValueIterator> ScalarArgument::end() const {
ScalarValueIterator *it = new ScalarValueIterator(this);
it->value_it = this->values.end();
it->null_argument = false;
return std::unique_ptr<ValueIterator>(it);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
IntegerArgument::IntegerValue::IntegerValue(
int64_t value_,
IntegerArgument const *argument_,
bool not_null_
): KernelArgument::Value(argument_, not_null_), value(value_) {
}
/// Pretty printer for debugging
std::ostream &IntegerArgument::IntegerValue::print(std::ostream &out) const {
out << argument->qualified_name() << ": ";
if (not_null) {
out << value;
}
else {
out << "<null>";
}
return out;
}
IntegerArgument::IntegerValueIterator::IntegerValueIterator(IntegerArgument const *argument_):
KernelArgument::ValueIterator(argument_) {
if (argument_) {
range_it = argument_->ranges.begin();
if (range_it != argument_->ranges.end()) {
value_it = range_it->begin();
}
}
}
void IntegerArgument::IntegerValueIterator::operator++() {
if (this->null_argument) {
this->null_argument = false;
}
else {
++value_it;
if (value_it == range_it->end()) {
++range_it;
if (range_it != static_cast<IntegerArgument const *>(argument)->ranges.end()) {
value_it = range_it->begin();
}
}
}
}
bool IntegerArgument::IntegerValueIterator::operator==(ValueIterator const &it) const {
if (it.type() != ArgumentTypeID::kInteger) {
throw std::runtime_error("Cannot compare IntegerValueIterator with iterator of different type");
}
auto const & integer_iterator = static_cast<IntegerValueIterator const &>(it);
if (this->null_argument) {
return it.null_argument;
}
else {
if (range_it != integer_iterator.range_it) {
return false;
}
if (range_it == static_cast<IntegerArgument const *>(argument)->ranges.end() &&
range_it == integer_iterator.range_it) {
return true;
}
return value_it == integer_iterator.value_it;
}
}
std::unique_ptr<KernelArgument::Value> IntegerArgument::IntegerValueIterator::at() const {
if (this->null_argument) {
return std::unique_ptr<KernelArgument::Value>(
new IntegerArgument::IntegerValue(
0, static_cast<IntegerArgument const *>(argument), false));
}
else {
return std::unique_ptr<KernelArgument::Value>(
new IntegerArgument::IntegerValue(
*value_it, static_cast<IntegerArgument const *>(argument)));
}
}
std::unique_ptr<KernelArgument::ValueIterator> IntegerArgument::begin() const {
return std::unique_ptr<KernelArgument::ValueIterator>(new IntegerValueIterator(this));
}
std::unique_ptr<KernelArgument::ValueIterator> IntegerArgument::end() const {
IntegerValueIterator *it = new IntegerValueIterator(this);
it->range_it = this->ranges.end();
it->null_argument = false;
return std::unique_ptr<ValueIterator>(it);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
TensorArgument::TensorValue::TensorValue(
TensorDescription const &desc_,
TensorArgument const *argument_,
bool not_null_
):
KernelArgument::Value(argument_, not_null_),
desc(desc_) {
}
/// Pretty printer for debugging
std::ostream &TensorArgument::TensorValue::print(std::ostream &out) const {
out << argument->qualified_name() << ": " << to_string(desc.element) << ": " << to_string(desc.layout);
return out;
}
TensorArgument::TensorValueIterator::TensorValueIterator(
TensorArgument const *argument_
):
KernelArgument::ValueIterator(argument_) {
if (argument_) {
value_it = argument_->values.begin();
}
}
void TensorArgument::TensorValueIterator::operator++() {
if (this->null_argument) {
this->null_argument = false;
}
else {
++value_it;
}
}
bool TensorArgument::TensorValueIterator::operator==(ValueIterator const &it) const {
if (it.type() != ArgumentTypeID::kTensor) {
throw std::runtime_error("Cannot compare TensorValueIterator with iterator of different type");
}
auto const & tensor_it = static_cast<TensorValueIterator const &>(it);
return value_it == tensor_it.value_it;
}
/// Gets the value pointed to
std::unique_ptr<KernelArgument::Value> TensorArgument::TensorValueIterator::at() const {
if (this->null_argument) {
return std::unique_ptr<KernelArgument::Value>(
new TensorArgument::TensorValue(
TensorDescription(), static_cast<TensorArgument const *>(argument), false));
}
else {
return std::unique_ptr<KernelArgument::Value>(
new TensorArgument::TensorValue(
*value_it, static_cast<TensorArgument const *>(argument)));
}
}
std::unique_ptr<KernelArgument::ValueIterator> TensorArgument::begin() const {
return std::unique_ptr<KernelArgument::ValueIterator>(new TensorValueIterator(this));
}
std::unique_ptr<KernelArgument::ValueIterator> TensorArgument::end() const {
TensorValueIterator *it = new TensorValueIterator(this);
it->value_it = this->values.end();
it->null_argument = false;
return std::unique_ptr<ValueIterator>(it);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
EnumeratedTypeArgument::EnumeratedTypeValue::EnumeratedTypeValue(
std::string const & element_,
EnumeratedTypeArgument const *argument_,
bool not_null_
):
KernelArgument::Value(argument_, not_null_),
element(element_) {
}
/// Pretty printer for debugging
std::ostream &EnumeratedTypeArgument::EnumeratedTypeValue::print(std::ostream &out) const {
out << argument->qualified_name() << ": " << element;
return out;
}
EnumeratedTypeArgument::EnumeratedTypeValueIterator::EnumeratedTypeValueIterator(
EnumeratedTypeArgument const *argument_
):
KernelArgument::ValueIterator(argument_) {
if (argument_) {
value_it = argument_->values.begin();
}
}
void EnumeratedTypeArgument::EnumeratedTypeValueIterator::operator++() {
if (this->null_argument) {
this->null_argument = false;
}
else {
++value_it;
}
}
bool EnumeratedTypeArgument::EnumeratedTypeValueIterator::operator==(ValueIterator const &it) const {
if (it.type() != ArgumentTypeID::kEnumerated) {
throw std::runtime_error("Cannot compare EnumeratedTypeValueIterator with iterator of different type");
}
auto const & enumerated_type_it = static_cast<EnumeratedTypeValueIterator const &>(it);
return value_it == enumerated_type_it.value_it;
}
/// Gets the value pointed to
std::unique_ptr<KernelArgument::Value> EnumeratedTypeArgument::EnumeratedTypeValueIterator::at() const {
if (this->null_argument) {
return std::unique_ptr<KernelArgument::Value>(
new EnumeratedTypeValue(
std::string(), static_cast<EnumeratedTypeArgument const *>(argument), false));
}
else {
return std::unique_ptr<KernelArgument::Value>(
new EnumeratedTypeValue(
*value_it, static_cast<EnumeratedTypeArgument const *>(argument)));
}
}
std::unique_ptr<KernelArgument::ValueIterator> EnumeratedTypeArgument::begin() const {
return std::unique_ptr<KernelArgument::ValueIterator>(new EnumeratedTypeValueIterator(this));
}
std::unique_ptr<KernelArgument::ValueIterator> EnumeratedTypeArgument::end() const {
EnumeratedTypeValueIterator *it = new EnumeratedTypeValueIterator(this);
it->value_it = this->values.end();
it->null_argument = false;
return std::unique_ptr<ValueIterator>(it);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
ProblemSpace::Iterator::Iterator() {
}
ProblemSpace::Iterator::Iterator(ProblemSpace const &problem_space) {
for (auto const & arg_ptr : problem_space.arguments) {
construct_(arg_ptr.get());
}
}
ProblemSpace::Iterator::Iterator(Iterator && it) {
iterators = std::move(it.iterators);
}
/// Helper for recursively constructing iterators
void ProblemSpace::Iterator::construct_(KernelArgument const *argument) {
iterators.emplace_back(argument->begin());
}
/// Given a set of ranges, iterate over the points within their Cartesian product. No big deal.
void ProblemSpace::Iterator::operator++() {
// Define a pair of iterator into the vector of iterators.
IteratorVector::iterator iterator_it = iterators.begin();
IteratorVector::iterator next_iterator = iterator_it;
// Advance the first argument.
++(**iterator_it);
// Maintain a pair of iterators over consecutive arguments.
++next_iterator;
// Carry logic
while (next_iterator != iterators.end() &&
**iterator_it == *((*iterator_it)->argument->end())) { // Did an iterator reach the end of its range?
(*iterator_it) = (*iterator_it)->argument->begin(); // Reset that iterator,
++(**next_iterator); // and increment the next argument's iterator.
iterator_it = next_iterator; // Advance to the next argument
++next_iterator;
}
}
/// Moves iterator to end
void ProblemSpace::Iterator::move_to_end() {
if (!iterators.empty()) {
std::unique_ptr<KernelArgument::ValueIterator> new_iter = iterators.back()->argument->end();
std::swap(iterators.back(), new_iter);
}
}
ProblemSpace::Problem ProblemSpace::Iterator::at() const {
Problem problem;
for (std::unique_ptr<KernelArgument::ValueIterator> const & it : iterators) {
problem.emplace_back(it->at());
}
return problem;
}
/// Equality operator
bool ProblemSpace::Iterator::operator==(Iterator const &it) const {
// This would be an opportunity for auto, but explicitly denoting references to
// owning smart pointers to dynamic polymorphic objects seems like a kindness to the reader.
IteratorVector::const_iterator first_it = iterators.begin();
IteratorVector::const_iterator second_it = it.iterators.begin();
int idx = 0;
for (; first_it != iterators.end(); ++first_it, ++second_it, ++idx) {
KernelArgument::ValueIterator const *my_it = first_it->get();
KernelArgument::ValueIterator const *their_it = second_it->get();
if (*my_it != *their_it) {
return false;
}
}
return true;
}
std::ostream &ProblemSpace::Iterator::print(std::ostream &out) const {
for (std::unique_ptr<KernelArgument::ValueIterator> const & iter_ptr : iterators) {
out << " [iter " << (iter_ptr->null_argument ? "null" : "<not null>")
<< ", type: " << to_string(iter_ptr->argument->description->type) << "]" << std::endl;
}
return out;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
ProblemSpace::ProblemSpace(ArgumentDescriptionVector const &schema, CommandLine const &cmdline) {
// Clone the arguments
for (ArgumentDescription const & arg_desc : schema) {
clone_(arguments, &arg_desc);
}
// Parse values from the command line
for (auto & arg : arguments) {
parse_(arg.get(), cmdline);
}
}
/// Returns the index of an argument by name
size_t ProblemSpace::argument_index(char const *name) const {
return argument_index_map.at(name);
}
/// Helper for recursively cloning
void ProblemSpace::clone_(
KernelArgumentVector &kernel_args,
ArgumentDescription const *arg_desc) {
KernelArgument *kernel_arg = nullptr;
switch (arg_desc->type) {
case ArgumentTypeID::kScalar:
kernel_arg = new ScalarArgument(arg_desc);
break;
case ArgumentTypeID::kInteger:
kernel_arg = new IntegerArgument(arg_desc);
break;
case ArgumentTypeID::kTensor:
kernel_arg = new TensorArgument(arg_desc);
break;
case ArgumentTypeID::kStructure:
{
throw std::runtime_error("ArgumentTypeID::kStructure not supported");
}
break;
case ArgumentTypeID::kEnumerated:
kernel_arg = new EnumeratedTypeArgument(arg_desc);
break;
default: break;
}
if (kernel_arg) {
size_t idx = kernel_args.size();
for (auto const &alias : arg_desc->aliases) {
argument_index_map.insert(std::make_pair(alias, idx));
}
kernel_args.emplace_back(kernel_arg);
}
}
/// Parses a command line
void ProblemSpace::parse_(KernelArgument *arg, CommandLine const &cmdline) {
switch (arg->description->type) {
case ArgumentTypeID::kScalar:
{
auto * scalar = static_cast<ScalarArgument *>(arg);
for (auto const &alias : arg->description->aliases) {
if (cmdline.check_cmd_line_flag(alias.c_str())) {
std::vector<std::vector<std::string>> tokens;
cmdline.get_cmd_line_argument_ranges(alias.c_str(), tokens);
for (auto const & vec : tokens) {
if (!vec.empty()) {
scalar->values.push_back(vec.front());
}
}
break;
}
}
}
break;
case ArgumentTypeID::kInteger:
{
auto *integer = static_cast<IntegerArgument *>(arg);
for (auto const &alias : arg->description->aliases) {
if (cmdline.check_cmd_line_flag(alias.c_str())) {
std::vector<std::vector<std::string> > tokens;
cmdline.get_cmd_line_argument_ranges(alias.c_str(), tokens);
for (auto const &range_tokens : tokens) {
if (!range_tokens.empty()) {
Range range(lexical_cast<int64_t>(range_tokens.front()));
if (range_tokens.size() > 1) {
range.last = lexical_cast<int64_t>(range_tokens.at(1));
}
if (range_tokens.size() > 2) {
range.increment = lexical_cast<int64_t>(range_tokens.at(2));
}
integer->ranges.push_back(range);
}
}
break;
}
}
}
break;
case ArgumentTypeID::kTensor:
{
auto *tensor = static_cast<TensorArgument *>(arg);
for (auto const &alias : arg->description->aliases) {
if (cmdline.check_cmd_line_flag(alias.c_str())) {
std::vector<std::vector<std::string>> tokens;
cmdline.get_cmd_line_argument_ranges(alias.c_str(), tokens);
for (auto const & tensor_tokens : tokens) {
if (!tensor_tokens.empty()) {
TensorArgument::TensorDescription tensor_desc;
tensor_desc.element = cutlass::library::from_string<library::NumericTypeID>(tensor_tokens.front());
// Layout
if (tensor_tokens.size() > 1) {
tensor_desc.layout = cutlass::library::from_string<library::LayoutTypeID>(tensor_tokens.at(1));
}
// Stride
for (size_t i = 2; i < tensor_tokens.size(); ++i) {
tensor_desc.stride.push_back(lexical_cast<int>(tensor_tokens.at(i)));
}
tensor->values.push_back(tensor_desc);
}
}
break;
}
}
}
break;
case ArgumentTypeID::kStructure:
{
throw std::runtime_error("Structure arguments not supported");
}
break;
case ArgumentTypeID::kEnumerated:
{
auto *enumerated_type = static_cast<EnumeratedTypeArgument *>(arg);
for (auto const &alias : arg->description->aliases) {
if (cmdline.check_cmd_line_flag(alias.c_str())) {
std::vector<std::string> tokens;
cmdline.get_cmd_line_arguments(alias.c_str(), tokens);
for (auto const & token : tokens) {
enumerated_type->values.push_back(token);
}
break;
}
}
}
break;
default:
break;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
ProblemSpace::Iterator ProblemSpace::begin() const {
return ProblemSpace::Iterator(*this);
}
ProblemSpace::Iterator ProblemSpace::end() const {
ProblemSpace::Iterator it(*this);
it.move_to_end();
return it;
}
/// Gets all argument names as an ordered vector
std::vector<std::string> ProblemSpace::argument_names() const {
Problem problem = this->begin().at();
std::vector<std::string> names;
names.reserve(problem.size());
for (auto const & arg : problem) {
names.push_back(arg->argument->description->aliases.front());
}
return names;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_int(int64_t &int_value, KernelArgument::Value const *value_ptr) {
if (value_ptr->not_null) {
if (value_ptr->argument->description->type == ArgumentTypeID::kInteger) {
int_value = static_cast<IntegerArgument::IntegerValue const *>(value_ptr)->value;
}
else if (value_ptr->argument->description->type == ArgumentTypeID::kScalar) {
std::stringstream ss;
ss << static_cast<ScalarArgument::ScalarValue const *>(value_ptr)->value;
ss >> int_value;
}
else {
throw std::runtime_error(
"arg_as_int64_t() - illegal cast. Problem space argument must be integer or scalar");
}
return true;
}
return false;
}
/// Lexically casts an argument to an int64
bool arg_as_int(
int64_t &int_value,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
size_t idx = problem_space.argument_index(name);
KernelArgument::Value const *value_ptr = problem.at(idx).get();
return arg_as_int(int_value, value_ptr);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_NumericTypeID(
library::NumericTypeID &numeric_type,
KernelArgument::Value const *value_ptr) {
if (value_ptr->not_null) {
if (value_ptr->argument->description->type == ArgumentTypeID::kEnumerated) {
numeric_type = library::from_string<library::NumericTypeID>(
static_cast<EnumeratedTypeArgument::EnumeratedTypeValue const *>(value_ptr)->element);
if (numeric_type == library::NumericTypeID::kInvalid) {
throw std::runtime_error(
"arg_as_NumericTypeID() - illegal cast.");
}
}
else {
throw std::runtime_error(
"arg_as_NumericTypeID() - illegal cast.");
}
return true;
}
return false;
}
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_NumericTypeID(
library::NumericTypeID &numeric_type,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
size_t idx = problem_space.argument_index(name);
KernelArgument::Value const *value_ptr = problem.at(idx).get();
return arg_as_NumericTypeID(numeric_type, value_ptr);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_LayoutTypeID(
library::LayoutTypeID &layout_type,
KernelArgument::Value const *value_ptr) {
if (value_ptr->not_null) {
if (value_ptr->argument->description->type == ArgumentTypeID::kEnumerated) {
layout_type = library::from_string<library::LayoutTypeID>(
static_cast<EnumeratedTypeArgument::EnumeratedTypeValue const *>(value_ptr)->element);
if (layout_type == library::LayoutTypeID::kInvalid) {
throw std::runtime_error(
"arg_as_LayoutTypeID() - illegal cast.");
}
}
else {
throw std::runtime_error(
"arg_as_LayoutTypeID() - illegal cast.");
}
return true;
}
return false;
}
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_LayoutTypeID(
library::LayoutTypeID &layout_type,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
size_t idx = problem_space.argument_index(name);
KernelArgument::Value const *value_ptr = problem.at(idx).get();
return arg_as_LayoutTypeID(layout_type, value_ptr);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_OpcodeClassID(
library::OpcodeClassID &opcode_class,
KernelArgument::Value const *value_ptr) {
if (value_ptr->not_null) {
if (value_ptr->argument->description->type == ArgumentTypeID::kEnumerated) {
opcode_class = library::from_string<library::OpcodeClassID>(
static_cast<EnumeratedTypeArgument::EnumeratedTypeValue const *>(value_ptr)->element);
if (opcode_class == library::OpcodeClassID::kInvalid) {
throw std::runtime_error(
"arg_as_OpcodeClassID() - illegal cast.");
}
}
else {
throw std::runtime_error(
"arg_as_OpcodeClassID() - illegal cast.");
}
return true;
}
return false;
}
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_OpcodeClassID(
library::OpcodeClassID &opcode_class,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
size_t idx = problem_space.argument_index(name);
KernelArgument::Value const *value_ptr = problem.at(idx).get();
return arg_as_OpcodeClassID(opcode_class, value_ptr);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Lexically casts an argument to a given type stored in a byte array. Returns true if not null.
bool arg_as_scalar(
std::vector<uint8_t> &bytes,
library::NumericTypeID numeric_type,
KernelArgument::Value const *value_ptr) {
if (value_ptr->not_null) {
if (value_ptr->argument->description->type == ArgumentTypeID::kInteger) {
int64_t int_value = static_cast<IntegerArgument::IntegerValue const *>(value_ptr)->value;
// TODO - convert int64_t => destination type
}
else if (value_ptr->argument->description->type == ArgumentTypeID::kScalar) {
std::string const &str_value = static_cast<ScalarArgument::ScalarValue const *>(value_ptr)->value;
return lexical_cast(bytes, numeric_type, str_value);
}
else {
throw std::runtime_error(
"arg_as_int() - illegal cast. Problem space argument must be integer or scalar");
}
return true;
}
return false;
}
/// Lexically casts an argument to a given type and returns a byte array
bool arg_as_scalar(
std::vector<uint8_t> &bytes,
library::NumericTypeID numeric_type,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
size_t idx = problem_space.argument_index(name);
KernelArgument::Value const *value_ptr = problem.at(idx).get();
return arg_as_scalar(bytes, numeric_type, value_ptr);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns true if a tensor description satisfies a `tensor` value
bool tensor_description_satisfies(
library::TensorDescription const &tensor_desc,
TensorArgument::TensorValue const *value_ptr) {
if (value_ptr->not_null) {
if (value_ptr->desc.element != library::NumericTypeID::kUnknown &&
value_ptr->desc.element != tensor_desc.element) {
return false;
}
if (value_ptr->desc.layout != library::LayoutTypeID::kUnknown &&
value_ptr->desc.layout != tensor_desc.layout) {
return false;
}
}
return true;
}
/// Returns true if a tensor description satisfies a `tensor` value
bool tensor_description_satisfies(
library::TensorDescription const &tensor_desc,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem) {
size_t idx = problem_space.argument_index(name);
KernelArgument::Value const *value_ptr = problem.at(idx).get();
if (value_ptr->argument->description->type == ArgumentTypeID::kTensor) {
return tensor_description_satisfies(
tensor_desc,
static_cast<TensorArgument::TensorValue const *>(value_ptr));
}
else {
throw std::runtime_error("Kernel argument mismatch");
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
/////////////////////////////////////////////////////////////////////////////////////////////////
+846
View File
@@ -0,0 +1,846 @@
/***************************************************************************************************
* Copyright (c) 2017-2019, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************************************/
/* \file
\brief
"Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified,
bug-ridden, slow implementation of half of Common Lisp."
- Greenspun's Tenth Rule of Programming
cutlass::profiler::ProblemSpace defines a set of data structures which represent the Cartesian
product of sequences defined by integer ranges, lists of scalars, and sets of enumerated types.
These permit a single invocation of the CUTLASS Profiler to iterate over a large set of problems,
verify and profile various operations when they are compatible with the command line, and
construct data tables of results that are convenient inputs to post processing in Excel or Pandas.
By executing multiple problems per invocation, startup overheads may be amortized across many
kernel launches.
*/
#pragma once
// Standard Library includes
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
// CUTLASS Utility includes
#include "cutlass/util/command_line.h"
// CUTLASS Library includes
#include "cutlass/library/library.h"
// Profiler includes
#include "enumerated_types.h"
namespace cutlass {
namespace profiler {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Defines the argument schema
struct ArgumentDescription {
/// Type of argument
ArgumentTypeID type;
/// Prioritized array of aliases used in command line parsing
std::vector<std::string> aliases;
/// Description of argument
std::string description;
//
// Methods
//
/// Default ctor
ArgumentDescription():
type(ArgumentTypeID::kInvalid) { }
/// Constructor with aliases
ArgumentDescription(
ArgumentTypeID type_,
std::vector<std::string> const &aliases_,
std::string const &description_
):
type(type_), aliases(aliases_), description(description_) { }
};
/// Vector of arguments
using ArgumentDescriptionVector = std::vector<ArgumentDescription>;
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Base class for kernel arguments
struct KernelArgument {
//
// Type definitions
//
/// Value base class
struct Value {
KernelArgument const *argument;
bool not_null;
//
// Methods
//
Value(
KernelArgument const *argument_ = nullptr,
bool not_null_ = true
): argument(argument_), not_null(not_null_) { }
virtual ~Value() { }
virtual std::ostream &print(std::ostream &out) const =0;
};
/// Abstract base class to iterate over values within arguments
struct ValueIterator {
/// Indicates type of kernel argument
KernelArgument const *argument;
/// If the iterator points to an argument that is null, it needs to be distinguished
/// from end.
bool null_argument;
//
// Methods
//
/// Constructs a value iterator - no methods are valid if argument_ == nullptr
ValueIterator(
KernelArgument const *argument_ = nullptr,
bool null_argument_ = false):
argument(argument_), null_argument(null_argument_) {
if (!argument_->not_null()) {
null_argument = true;
}
}
virtual ~ValueIterator() { }
/// Advances to next point in range
virtual void operator++() = 0;
/// Compares against another value iterator - must be of the same KernelArgument type
virtual bool operator==(ValueIterator const &it) const = 0;
/// Returns a unique_ptr<Value> object pointing to a newly created value object
virtual std::unique_ptr<Value> at() const = 0;
/// Gets the type of the iterator
ArgumentTypeID type() const {
return argument->description->type;
}
/// Helper to compute inequality
bool operator!=(ValueIterator const &it) const {
return !(*this == it);
}
std::ostream &print(std::ostream &out) const;
};
//
// Data members
//
/// Describes the argument
ArgumentDescription const *description;
/// Parent node
KernelArgument *parent;
/// Sequence in which the kernel argument is to be iterated over.
/// Smaller means faster changing. -1 is don't care
int ordinal;
//
// Methods
//
/// Default ctor
KernelArgument(
ArgumentDescription const *description_ = nullptr,
KernelArgument *parent_ = nullptr,
int ordinal_ = -1
): description(description_), parent(parent_), ordinal(ordinal_) { }
virtual ~KernelArgument();
/// Returns true if the kernel argument iself is empty
virtual bool not_null() const =0;
/// Returns a string name for debugging
std::string qualified_name() const {
if (description) {
if (description->aliases.empty()) {
return "<description_not_null_no_aliases>";
}
return description->aliases.front();
}
return "<description_null>";
}
virtual std::unique_ptr<ValueIterator> begin() const =0;
virtual std::unique_ptr<ValueIterator> end() const =0;
};
using KernelArgumentVector = std::vector<std::unique_ptr<KernelArgument>>;
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Defines a scalar argument type as a string that is lexically cast to the appropriate kernel
/// type.
struct ScalarArgument : public KernelArgument {
//
// Type definitions
//
/// Value type
struct ScalarValue : public KernelArgument::Value {
std::string value;
//
// Methods
//
ScalarValue(
std::string const &value_ = "",
ScalarArgument const *argument = nullptr,
bool not_null_ = true
);
virtual std::ostream &print(std::ostream &out) const;
};
using ValueCollection = std::vector<std::string>;
/// Abstract base class to iterate over values within arguments
struct ScalarValueIterator : public KernelArgument::ValueIterator {
//
// Data members
//
ValueCollection::const_iterator value_it;
//
// Methods
//
ScalarValueIterator(ScalarArgument const *argument = nullptr);
virtual void operator++();
virtual bool operator==(ValueIterator const &it) const;
/// Gets the value pointed to
virtual std::unique_ptr<KernelArgument::Value> at() const;
};
//
// Data members
//
/// Set of posible values
ValueCollection values;
//
// Methods
//
/// Default ctor
ScalarArgument(
ArgumentDescription const *description
):
KernelArgument(description) { }
virtual bool not_null() const {
return !values.empty();
}
virtual std::unique_ptr<KernelArgument::ValueIterator> begin() const;
virtual std::unique_ptr<KernelArgument::ValueIterator> end() const;
};
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Closed range supporting additive increment
struct Range {
//
// Type definitions
//
struct Iterator {
int64_t value;
int64_t increment;
//
// Methods
//
Iterator(
int64_t value_ = 0,
int64_t increment_ = 1
):
value(value_), increment(increment_) { }
Iterator & operator++() {
value += increment;
return *this;
}
Iterator operator++(int) {
Iterator self(*this);
++(*this);
return self;
}
bool operator==(Iterator const &it) const {
return value == it.value;
}
bool operator!=(Iterator const &it) const {
return !(*this == it);
}
int64_t at() const {
return value;
}
int64_t operator*() const {
return at();
}
};
//
// Data members
//
int64_t first; ///< first element in range
int64_t last; ///< last element in range
int64_t increment; ///< additive increment between values
//
// Methods
//
/// Default constructor - range acts as a scalar
Range(int64_t first_ = 0): first(first_), last(first_), increment(1) { }
/// Range acts as a range
Range(
int64_t first_,
int64_t last_,
int64_t increment_ = 1
): first(first_), last(last_), increment(increment_) {
// Helpers to avoid constructing invalid ranges
if (increment > 0) {
if (last < first) {
std::swap(last, first);
}
}
else if (increment < 0) {
if (first < last) {
std::swap(last, first);
}
}
else if (last != first) {
last = first;
increment = 1;
}
}
/// Returns an iterator to the first element within the range
Iterator begin() const {
return Iterator(first, increment);
}
/// Returns an iterator to the first element *after* the range
Iterator end() const {
return Iterator(first + ((last - first)/increment + 1) * increment, increment);
}
};
/// Integer-valued argument - represented as a list of integer-valued ranges
struct IntegerArgument : public KernelArgument {
//
// Type definitions
//
/// Value type
struct IntegerValue : public KernelArgument::Value {
int64_t value;
//
// Methods
//
IntegerValue(
int64_t value_ = 0,
IntegerArgument const *argument_ = nullptr,
bool not_null_ = true
);
/// Pretty printer for debugging
virtual std::ostream &print(std::ostream &out) const;
};
/// Collection of ranges represent the IntegerArgument's state
using RangeCollection = std::vector<Range>;
/// Abstract base class to iterate over values within arguments
struct IntegerValueIterator : public KernelArgument::ValueIterator {
//
// Data members
//
RangeCollection::const_iterator range_it;
Range::Iterator value_it;
//
// Methods
//
IntegerValueIterator();
IntegerValueIterator(IntegerArgument const *argument);
virtual void operator++();
virtual bool operator==(ValueIterator const &it) const;
/// Gets the value pointed to
virtual std::unique_ptr<KernelArgument::Value> at() const;
};
//
// Data members
//
/// Set of posible values
RangeCollection ranges;
//
// Methods
//
/// Default ctor
IntegerArgument(
ArgumentDescription const *description
):
KernelArgument(description) { }
virtual bool not_null() const {
bool _not_null = !ranges.empty();
return _not_null;
}
virtual std::unique_ptr<KernelArgument::ValueIterator> begin() const;
virtual std::unique_ptr<KernelArgument::ValueIterator> end() const;
};
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Structure defining the data type of tensors
struct TensorArgument : public KernelArgument {
//
// Type definitions
//
struct TensorDescription {
/// Data type of elements
library::NumericTypeID element;
/// Layout definition
library::LayoutTypeID layout;
/// Computed extent
std::vector<int> extent;
/// Enables directly specifying stride value used to size tensor
std::vector<int> stride;
//
// Methods
//
TensorDescription(
library::NumericTypeID element_ = library::NumericTypeID::kUnknown,
library::LayoutTypeID layout_ = library::LayoutTypeID::kUnknown,
std::vector<int> extent_ = std::vector<int>(),
std::vector<int> stride_ = std::vector<int>()
):
element(element_), layout(layout_), extent(extent_), stride(stride_) {}
};
using ValueCollection = std::vector<TensorDescription>;
/// Value structure
struct TensorValue : public KernelArgument::Value {
TensorDescription desc;
//
// Methods
//
TensorValue(
TensorDescription const &desc_ = TensorDescription(),
TensorArgument const *argument_ = nullptr,
bool not_null_ = true
);
/// Pretty printer for debugging
virtual std::ostream &print(std::ostream &out) const;
};
/// Abstract base class to iterate over values within arguments
struct TensorValueIterator : public KernelArgument::ValueIterator {
//
// Data members
//
ValueCollection::const_iterator value_it;
//
// Methods
//
TensorValueIterator(TensorArgument const *argument_);
virtual void operator++();
virtual bool operator==(ValueIterator const &it) const;
/// Gets the value pointed to
virtual std::unique_ptr<KernelArgument::Value> at() const;
};
/// Set of possible values
ValueCollection values;
//
// Methods
//
/// Default ctor
TensorArgument(
ArgumentDescription const *description
):
KernelArgument(description) { }
virtual bool not_null() const {
return !values.empty();
}
virtual std::unique_ptr<KernelArgument::ValueIterator> begin() const;
virtual std::unique_ptr<KernelArgument::ValueIterator> end() const;
};
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Numeric data type
struct EnumeratedTypeArgument : public KernelArgument {
//
// Type definitions
//
struct EnumeratedTypeValue : public KernelArgument::Value {
/// Data type of element
std::string element;
//
// Methods
//
EnumeratedTypeValue(
std::string const &element_ = std::string(),
EnumeratedTypeArgument const *argument_ = nullptr,
bool not_null_ = true
);
/// Pretty printer for debugging
virtual std::ostream &print(std::ostream &out) const;
};
using ValueCollection = std::vector<std::string>;
/// Abstract base class to iterate over values within arguments
struct EnumeratedTypeValueIterator : public KernelArgument::ValueIterator {
//
// Data members
//
ValueCollection::const_iterator value_it;
//
// Methods
//
EnumeratedTypeValueIterator(EnumeratedTypeArgument const *argument_ = nullptr);
virtual void operator++();
virtual bool operator==(ValueIterator const &it) const;
/// Gets the value pointed to
virtual std::unique_ptr<KernelArgument::Value> at() const;
};
//
// Data members
//
ValueCollection values;
//
// Members
//
/// Default ctor
EnumeratedTypeArgument(ArgumentDescription const *description):
KernelArgument(description) {}
virtual bool not_null() const {
return !values.empty();
}
virtual std::unique_ptr<KernelArgument::ValueIterator> begin() const;
virtual std::unique_ptr<KernelArgument::ValueIterator> end() const;
};
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Object storing the space argument values
class ProblemSpace {
public:
/// Tuple of arguments
using Problem = std::vector<std::unique_ptr<KernelArgument::Value>>;
/// Type used to iterator over things
using IteratorVector = std::vector<std::unique_ptr<KernelArgument::ValueIterator>>;
/// Iterates over points in the design space
class Iterator {
private:
/// One iterator per argument
IteratorVector iterators;
public:
//
// Methods
//
explicit Iterator();
Iterator(ProblemSpace const &problem_space);
Iterator(Iterator &&it);
// Rule of three
Iterator(Iterator const &) = delete;
Iterator &operator=(Iterator const &it) = delete;
~Iterator() = default;
/// Pre-increment - advances to next point in argument range
void operator++();
/// Gets the current argument value
Problem at() const;
/// Moves iterator to end
void move_to_end();
/// Equality operator
bool operator==(Iterator const &it) const;
/// Inequality operator
bool operator!=(Iterator const &it) const {
return !(*this == it);
}
/// Helper to call at() method
Problem operator*() const {
return at();
}
/// Helper to print iterator state
std::ostream & print(std::ostream &out) const;
private:
/// Helper for recursively constructing iterators
void construct_(KernelArgument const *argument);
};
public:
//
// Data members
//
KernelArgumentVector arguments;
/// Map of argument names to their position within the argument vector
std::unordered_map<std::string, size_t> argument_index_map;
public:
//
// Methods
//
/// Default ctor
ProblemSpace() {}
/// Constructs a problem space from a vector of arguments. This vector must outlive
/// the ProblemSpace object, which stores pointers to objects within the
/// ArgumentDescriptionVector.
ProblemSpace(ArgumentDescriptionVector const &schema, CommandLine const &cmdline);
Iterator begin() const; // returns an iterator to the first point in the range
Iterator end() const; // returns an iterator to the first point after the range
/// Returns the index of an argument by name
size_t argument_index(char const *name) const;
/// Gets all argument names as an ordered vector
std::vector<std::string> argument_names() const;
/// Returns the number of dimensions of the problem space
size_t rank() const { return arguments.size(); }
private:
/// Helper for recursively cloning
void clone_(
KernelArgumentVector &kernel_args,
ArgumentDescription const *arg_desc);
/// Parses command line argument
void parse_(
KernelArgument *arg,
CommandLine const &cmdline);
};
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_int(int64_t &int_value, KernelArgument::Value const *value_ptr);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_int(
int64_t &int_value,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_NumericTypeID(library::NumericTypeID &numeric_type, KernelArgument::Value const *value_ptr);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_NumericTypeID(
library::NumericTypeID &numeric_type,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_LayoutTypeID(library::LayoutTypeID &layout_type, KernelArgument::Value const *value_ptr);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_LayoutTypeID(
library::LayoutTypeID &layout_type,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_OpcodeClassID(library::OpcodeClassID &opcode_class, KernelArgument::Value const *value_ptr);
/// Lexically casts an argument to an int64 if it is defined. Returns true if not null.
bool arg_as_OpcodeClassID(
library::OpcodeClassID &opcode_class,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Lexically casts an argument to a given type stored in a byte array. Returns true if not null.
bool arg_as_scalar(
std::vector<uint8_t> &bytes,
library::NumericTypeID numeric_type,
KernelArgument::Value const *value_ptr);
/// Lexically casts an argument to a given type stored in a byte array. Returns true if not null.
bool arg_as_scalar(
std::vector<uint8_t> &bytes,
library::NumericTypeID numeric_type,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/// Returns true if a tensor description satisfies a `tensor` value
bool tensor_description_satisfies(
library::TensorDescription const &tensor_desc,
TensorArgument::TensorValue const *value_ptr);
/// Returns true if a tensor description satisfies a `tensor` value
bool tensor_description_satisfies(
library::TensorDescription const &tensor_desc,
char const *name,
ProblemSpace const &problem_space,
ProblemSpace::Problem const &problem);
/////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace profiler
} // namespace cutlass
////////////////////////////////////////////////////////////////////////////////////////////////