CUTLASS 2.1 (#83)
CUTLASS 2.1 contributes: - BLAS-style host-side API added to CUTLASS Library - Planar Complex GEMM kernels targeting Volta and Turing Tensor Cores - Minor enhancements and bug fixes
This commit is contained in:
@@ -119,6 +119,16 @@ struct CommandLine {
|
||||
val = !(value == "0" || value == "false");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the value specified for a given commandline parameter --<flag>=<value>
|
||||
*/
|
||||
template <typename value_t>
|
||||
void get_cmd_line_argument(const char* arg_name,
|
||||
value_t& val) const {
|
||||
|
||||
get_cmd_line_argument(arg_name, val, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the value specified for a given commandline parameter --<flag>=<value>
|
||||
@@ -126,7 +136,7 @@ struct CommandLine {
|
||||
template <typename value_t>
|
||||
void get_cmd_line_argument(const char* arg_name,
|
||||
value_t& val,
|
||||
value_t const& _default = value_t()) const {
|
||||
value_t const& _default) const {
|
||||
using namespace std;
|
||||
|
||||
val = _default;
|
||||
|
||||
@@ -40,10 +40,14 @@ namespace device_memory {
|
||||
/// Allocate a buffer of \p count elements of type \p T on the current CUDA device
|
||||
template <typename T>
|
||||
T* allocate(size_t count = 1) {
|
||||
|
||||
T* ptr = 0;
|
||||
size_t bytes = sizeof(T) * count;
|
||||
size_t bytes = 0;
|
||||
|
||||
bytes = count * sizeof(T);
|
||||
|
||||
cudaError_t cuda_error = cudaMalloc((void**)&ptr, bytes);
|
||||
|
||||
if (cuda_error != cudaSuccess) {
|
||||
throw cuda_exception("Failed to allocate memory", cuda_error);
|
||||
}
|
||||
@@ -111,13 +115,16 @@ void insert_to_device(T* device_begin, InputIterator begin, InputIterator end) {
|
||||
copy_to_device(device_begin, &*begin, elements);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* "Smart" device memory allocation
|
||||
******************************************************************************/
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace device_memory
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Device allocation abstraction that tracks size and capacity
|
||||
template <typename T>
|
||||
struct allocation {
|
||||
class DeviceAllocation {
|
||||
public:
|
||||
|
||||
/// Delete functor for CUDA device memory
|
||||
struct deleter {
|
||||
void operator()(T* ptr) {
|
||||
@@ -130,6 +137,7 @@ struct allocation {
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
@@ -140,23 +148,55 @@ struct allocation {
|
||||
/// Smart pointer
|
||||
platform::unique_ptr<T, deleter> smart_ptr;
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Static methods
|
||||
//
|
||||
|
||||
/// Static member to compute the number of bytes needed for a given number of elements
|
||||
static size_t bytes(size_t elements) {
|
||||
if (sizeof_bits<T>::value < 8) {
|
||||
size_t const kElementsPerByte = 8 / sizeof_bits<T>::value;
|
||||
return elements / kElementsPerByte;
|
||||
}
|
||||
else {
|
||||
size_t const kBytesPerElement = sizeof_bits<T>::value / 8;
|
||||
return elements * kBytesPerElement;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Constructor: allocates no memory
|
||||
allocation() : capacity(0) {}
|
||||
DeviceAllocation() : capacity(0) {}
|
||||
|
||||
/// Constructor: allocates \p capacity elements on the current CUDA device
|
||||
allocation(size_t _capacity) : smart_ptr(allocate<T>(_capacity)), capacity(_capacity) {}
|
||||
DeviceAllocation(size_t _capacity) :
|
||||
smart_ptr(device_memory::allocate<T>(_capacity)), capacity(_capacity) {}
|
||||
|
||||
/// Constructor: allocates \p capacity elements on the current CUDA device taking ownership of the allocation
|
||||
DeviceAllocation(T *ptr, size_t _capacity) : smart_ptr(ptr), capacity(_capacity) {}
|
||||
|
||||
/// Copy constructor
|
||||
allocation(allocation const &p): smart_ptr(allocate<T>(p.capacity)), capacity(p.capacity) {
|
||||
copy_device_to_device(smart_ptr.get(), p.get(), capacity);
|
||||
DeviceAllocation(DeviceAllocation const &p):
|
||||
smart_ptr(device_memory::allocate<T>(p.capacity)), capacity(p.capacity) {
|
||||
|
||||
device_memory::copy_device_to_device(smart_ptr.get(), p.get(), capacity);
|
||||
}
|
||||
|
||||
/// Move constructor
|
||||
DeviceAllocation(DeviceAllocation &&p): capacity(0) {
|
||||
std::swap(smart_ptr, p.smart_ptr);
|
||||
std::swap(capacity, p.capacity);
|
||||
}
|
||||
|
||||
/// Destructor
|
||||
~allocation() { reset(); }
|
||||
~DeviceAllocation() { reset(); }
|
||||
|
||||
/// Returns a pointer to the managed object
|
||||
T* get() const { return smart_ptr.get(); }
|
||||
@@ -173,12 +213,41 @@ struct allocation {
|
||||
smart_ptr.reset();
|
||||
}
|
||||
|
||||
/// Deletes managed object, if owned, and allocates a new object
|
||||
void reset(size_t _capacity) {
|
||||
reset(device_memory::allocate<T>(_capacity), _capacity);
|
||||
}
|
||||
|
||||
/// Deletes managed object, if owned, and replaces its reference with a given pointer and capacity
|
||||
void reset(T* _ptr, size_t _capacity) {
|
||||
smart_ptr.reset(_ptr);
|
||||
capacity = _capacity;
|
||||
}
|
||||
|
||||
/// Allocates a new buffer and copies the old buffer into it. The old buffer is then released.
|
||||
void reallocate(size_t new_capacity) {
|
||||
|
||||
platform::unique_ptr<T, deleter> new_allocation(device_memory::allocate<T>(new_capacity));
|
||||
|
||||
device_memory::copy_device_to_device(
|
||||
new_allocation.get(),
|
||||
smart_ptr.get(),
|
||||
std::min(new_capacity, capacity));
|
||||
|
||||
std::swap(smart_ptr, new_allocation);
|
||||
std::swap(new_capacity, capacity);
|
||||
}
|
||||
|
||||
/// Returns the number of elements
|
||||
size_t size() const {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/// Returns the number of bytes needed to store the allocation
|
||||
size_t bytes() const {
|
||||
return bytes(capacity);
|
||||
}
|
||||
|
||||
/// Returns a pointer to the object owned by *this
|
||||
T* operator->() const { return smart_ptr.get(); }
|
||||
|
||||
@@ -189,15 +258,69 @@ struct allocation {
|
||||
const deleter& get_deleter() const { return smart_ptr.get_deleter(); }
|
||||
|
||||
/// Copies a device-side memory allocation
|
||||
allocation & operator=(allocation const &p) {
|
||||
DeviceAllocation & operator=(DeviceAllocation const &p) {
|
||||
if (capacity != p.capacity) {
|
||||
smart_ptr.reset(allocate<T>(p.capacity));
|
||||
smart_ptr.reset(device_memory::allocate<T>(p.capacity));
|
||||
capacity = p.capacity;
|
||||
}
|
||||
copy_device_to_device(smart_ptr.get(), p.get(), capacity);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Move assignment
|
||||
DeviceAllocation & operator=(DeviceAllocation && p) {
|
||||
std::swap(smart_ptr, p.smart_ptr);
|
||||
std::swap(capacity, p.capacity);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Copies the entire allocation from another location in device memory.
|
||||
void copy_from_device(T const *ptr) const {
|
||||
copy_from_device(ptr, capacity);
|
||||
}
|
||||
|
||||
/// Copies a given number of elements from device memory
|
||||
void copy_from_device(T const *ptr, size_t elements) const {
|
||||
device_memory::copy_device_to_device(get(), ptr, elements);
|
||||
}
|
||||
|
||||
void copy_to_device(T *ptr) const {
|
||||
copy_to_device(ptr, capacity);
|
||||
}
|
||||
|
||||
void copy_to_device(T *ptr, size_t elements) const {
|
||||
device_memory::copy_device_to_device(ptr, get(), elements);
|
||||
}
|
||||
|
||||
void copy_from_host(T const *ptr) const {
|
||||
copy_from_host(ptr, capacity);
|
||||
}
|
||||
|
||||
void copy_from_host(T const *ptr, size_t elements) const {
|
||||
device_memory::copy_to_device(get(), ptr, elements);
|
||||
}
|
||||
|
||||
void copy_to_host(T *ptr) const {
|
||||
copy_to_host(ptr, capacity);
|
||||
}
|
||||
|
||||
void copy_to_host(T *ptr, size_t elements) const {
|
||||
device_memory::copy_to_host(ptr, get(), elements);
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace device_memory {
|
||||
|
||||
/// Device allocation abstraction that tracks size and capacity
|
||||
template <typename T>
|
||||
using allocation = cutlass::DeviceAllocation<T>;
|
||||
|
||||
} // namespace device_memory
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
using ConstReference = typename ConstTensorRef::Reference;
|
||||
|
||||
/// Used to handle packing of subbyte elements
|
||||
static int const kElementsPerStoredItem = (sizeof_bits<Element>::value < 8 ? sizeof(Element) * 8 / sizeof_bits<Element>::value : 1);
|
||||
static int const kElementsPerStoredItem = (sizeof_bits<Element>::value < 8 ? (8 / sizeof_bits<Element>::value) : 1);
|
||||
|
||||
private:
|
||||
|
||||
@@ -232,7 +232,7 @@ public:
|
||||
|
||||
/// Returns the logical capacity based on extent and layout. May differ from size().
|
||||
LongIndex capacity() const {
|
||||
return layout_.capacity(extent_) * kElementsPerStoredItem;
|
||||
return layout_.capacity(extent_);
|
||||
}
|
||||
|
||||
/// Gets pointer to host data
|
||||
|
||||
423
tools/util/include/cutlass/util/host_tensor_planar_complex.h
Normal file
423
tools/util/include/cutlass/util/host_tensor_planar_complex.h
Normal file
@@ -0,0 +1,423 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017-2018, 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.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
/*! \file
|
||||
\brief HostTensor contributes management for both host and device memory.
|
||||
|
||||
HostTensor allocates host and device memory upon construction. Basic element-wise operations on
|
||||
host memory synchronize device memory automatically. Explicit copy operations provide abstractions
|
||||
for CUDA memcpy operations.
|
||||
|
||||
Call {host, device}_{data, ref, view}() for accessing host or device memory.
|
||||
|
||||
See cutlass/tensor_ref.h and cutlass/tensor_view.h for more details.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/matrix_traits.h"
|
||||
|
||||
#include "cutlass/tensor_ref_planar_complex.h"
|
||||
#include "cutlass/tensor_view_planar_complex.h"
|
||||
|
||||
#include "device_memory.h"
|
||||
|
||||
namespace cutlass {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Host tensor
|
||||
template <
|
||||
/// Data type of element stored within tensor (concept: NumericType)
|
||||
typename Element_,
|
||||
/// Defines a mapping from logical coordinate to linear memory (concept: Layout)
|
||||
typename Layout_
|
||||
>
|
||||
class HostTensorPlanarComplex {
|
||||
public:
|
||||
|
||||
/// Data type of individual access
|
||||
using Element = Element_;
|
||||
|
||||
/// Mapping function from logical coordinate to linear memory
|
||||
using Layout = Layout_;
|
||||
|
||||
/// Logical rank of tensor index space
|
||||
static int const kRank = Layout::kRank;
|
||||
|
||||
/// Index type
|
||||
using Index = typename Layout::Index;
|
||||
|
||||
/// Long index used for pointer offsets
|
||||
using LongIndex = typename Layout::LongIndex;
|
||||
|
||||
/// Coordinate in logical tensor space
|
||||
using TensorCoord = typename Layout::TensorCoord;
|
||||
|
||||
/// Layout's stride vector
|
||||
using Stride = typename Layout::Stride;
|
||||
|
||||
/// Tensor reference to device memory
|
||||
using TensorRef = TensorRefPlanarComplex<Element, Layout>;
|
||||
|
||||
/// Tensor reference to constant device memory
|
||||
using ConstTensorRef = typename TensorRef::ConstTensorRef;
|
||||
|
||||
/// Tensor reference to device memory
|
||||
using TensorView = TensorViewPlanarComplex<Element, Layout>;
|
||||
|
||||
/// Tensor reference to constant device memory
|
||||
using ConstTensorView = typename TensorView::ConstTensorView;
|
||||
|
||||
/// Reference to element in tensor
|
||||
using Reference = typename TensorRef::Reference;
|
||||
|
||||
/// Constant reference to element in tensor
|
||||
using ConstReference = typename ConstTensorRef::Reference;
|
||||
|
||||
private:
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Extent of tensor in logical dimensions
|
||||
TensorCoord extent_;
|
||||
|
||||
/// Layout object
|
||||
Layout layout_;
|
||||
|
||||
/// Host-side memory allocation
|
||||
std::vector<Element> host_;
|
||||
|
||||
/// Device-side memory
|
||||
device_memory::allocation<Element> device_;
|
||||
|
||||
public:
|
||||
//
|
||||
// Device and Host Methods
|
||||
//
|
||||
|
||||
/// Default constructor
|
||||
HostTensorPlanarComplex() {}
|
||||
|
||||
/// Constructs a tensor given an extent. Assumes a packed layout
|
||||
HostTensorPlanarComplex(
|
||||
TensorCoord const &extent,
|
||||
bool device_backed = true
|
||||
) {
|
||||
|
||||
this->reset(extent, Layout::packed(extent), device_backed);
|
||||
}
|
||||
|
||||
/// Constructs a tensor given an extent and layout
|
||||
HostTensorPlanarComplex(
|
||||
TensorCoord const &extent,
|
||||
Layout const &layout,
|
||||
bool device_backed = true
|
||||
) {
|
||||
|
||||
this->reset(extent, layout, device_backed);
|
||||
}
|
||||
|
||||
~HostTensorPlanarComplex() { }
|
||||
|
||||
/// Clears the HostTensor allocation to size/capacity = 0
|
||||
void reset() {
|
||||
extent_ = TensorCoord();
|
||||
layout_ = Layout::packed(extent_);
|
||||
|
||||
host_.clear();
|
||||
device_.reset();
|
||||
}
|
||||
|
||||
/// Resizes internal memory allocations without affecting layout or extent
|
||||
void reserve(
|
||||
size_t count, ///< size of tensor in elements
|
||||
bool device_backed_ = true) { ///< if true, device memory is also allocated
|
||||
|
||||
device_.reset();
|
||||
host_.clear();
|
||||
|
||||
host_.resize(count * 2);
|
||||
|
||||
// Allocate memory
|
||||
Element* device_memory = nullptr;
|
||||
if (device_backed_) {
|
||||
device_memory = device_memory::allocate<Element>(count * 2);
|
||||
}
|
||||
device_.reset(device_memory, device_backed_ ? count * 2 : 0);
|
||||
}
|
||||
|
||||
/// Updates the extent and layout of the HostTensor. Allocates memory according to the new
|
||||
/// extent and layout.
|
||||
void reset(
|
||||
TensorCoord const &extent, ///< extent of logical tensor
|
||||
Layout const &layout, ///< layout object of tensor
|
||||
bool device_backed_ = true) { ///< if true, device memory is also allocated.
|
||||
|
||||
extent_ = extent;
|
||||
layout_ = layout;
|
||||
|
||||
reserve(size_t(layout_.capacity(extent_)), device_backed_);
|
||||
}
|
||||
|
||||
/// Updates the extent and layout of the HostTensor. Allocates memory according to the new
|
||||
/// extent and layout. Assumes a packed tensor configuration.
|
||||
void reset(
|
||||
TensorCoord const &extent, ///< extent of logical tensor
|
||||
bool device_backed_ = true) { ///< if true, device memory is also allocated.
|
||||
|
||||
reset(extent, Layout::packed(extent), device_backed_);
|
||||
}
|
||||
|
||||
/// Changes the size of the logical tensor. Only allocates memory if new capacity exceeds reserved capacity.
|
||||
/// To force allocation, call reset().
|
||||
void resize(
|
||||
TensorCoord const &extent, ///< extent of logical tensor
|
||||
Layout const &layout, ///< layout object of tensor
|
||||
bool device_backed_ = true) { ///< if true, device memory is also allocated.
|
||||
|
||||
extent_ = extent;
|
||||
layout_ = layout;
|
||||
|
||||
LongIndex new_size = size_t(layout_.capacity(extent_));
|
||||
|
||||
if (static_cast<decltype(host_.size())>(new_size * 2) > host_.size()) {
|
||||
reserve(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the size of the logical tensor. Only allocates memory if new capacity exceeds reserved capacity.
|
||||
/// To force allocation, call reset(). Note, this form of resize() assumes a packed tensor configuration.
|
||||
void resize(
|
||||
TensorCoord const &extent, ///< extent of logical tensor
|
||||
bool device_backed_ = true) { ///< if true, device memory is also allocated.
|
||||
|
||||
resize(extent, Layout::packed(extent), device_backed_);
|
||||
}
|
||||
|
||||
/// Returns the number of elements stored in the host tensor
|
||||
size_t size() const {
|
||||
return host_.size() / 2;
|
||||
}
|
||||
|
||||
/// Returns the logical capacity based on extent and layout. May differ from size().
|
||||
LongIndex capacity() const {
|
||||
return layout_.capacity(extent_);
|
||||
}
|
||||
|
||||
/// Stride between real and imaginary parts
|
||||
LongIndex imaginary_stride() const {
|
||||
return host_.size() / 2;
|
||||
}
|
||||
|
||||
/// Gets pointer to host data
|
||||
Element * host_data() { return host_.data(); }
|
||||
|
||||
/// Gets pointer to host data imaginary part
|
||||
Element * host_data_imag() { return host_.data() + imaginary_stride(); }
|
||||
|
||||
/// Gets pointer to host data with a pointer offset
|
||||
Element * host_data_ptr_offset(LongIndex ptr_element_offset) { return host_data() + ptr_element_offset; }
|
||||
|
||||
/// Gets pointer to host data with a pointer offset
|
||||
Element * host_data_imag_ptr_offset(LongIndex ptr_element_offset) { return host_data_imag() + ptr_element_offset; }
|
||||
|
||||
/// Gets a reference to an element in host memory
|
||||
Reference host_data(LongIndex idx) {
|
||||
return PlanarComplexReference<Element>(host_data() + idx, host_data_imag() + idx);
|
||||
}
|
||||
|
||||
/// Gets pointer to host data
|
||||
Element const * host_data() const { return host_.data(); }
|
||||
|
||||
/// Gets pointer to host data imaginary part
|
||||
Element const * host_data_imag() const { return host_.data() + imaginary_stride(); }
|
||||
|
||||
/// Gets a constant reference to an element in host memory
|
||||
ConstReference host_data(LongIndex idx) const {
|
||||
return PlanarComplexReference<Element const>(host_data() + idx, host_data_imag() + idx);
|
||||
}
|
||||
|
||||
/// Gets pointer to device data
|
||||
Element * device_data() { return device_.get(); }
|
||||
|
||||
/// Gets pointer to device data with a pointer offset
|
||||
Element * device_data_ptr_offset(LongIndex ptr_element_offset) { return device_.get() + ptr_element_offset; }
|
||||
|
||||
/// Gets pointer to device data
|
||||
Element const * device_data() const { return device_.get(); }
|
||||
|
||||
/// Gets pointer to device data with a pointer offset
|
||||
Element const * device_data_ptr_offset(LongIndex ptr_element_offset) const { return device_.get() + ptr_element_offset; }
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
TensorRef host_ref(LongIndex ptr_element_offset=0) {
|
||||
return TensorRef(host_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride());
|
||||
}
|
||||
|
||||
/// Returns a tensor reference to the real part of the tensor
|
||||
cutlass::TensorRef<Element, Layout> host_ref_real() {
|
||||
return cutlass::TensorRef<Element, Layout>(host_data(), layout_);
|
||||
}
|
||||
|
||||
/// Returns a tensor reference to the real part of the tensor
|
||||
cutlass::TensorRef<Element, Layout> host_ref_imag() {
|
||||
return cutlass::TensorRef<Element, Layout>(host_data_ptr_offset(imaginary_stride()), layout_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
ConstTensorRef host_ref(LongIndex ptr_element_offset=0) const {
|
||||
return ConstTensorRef(host_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride());
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
TensorRef device_ref(LongIndex ptr_element_offset=0) {
|
||||
return TensorRef(device_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride());
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
ConstTensorRef device_ref(LongIndex ptr_element_offset=0) const {
|
||||
return TensorRef(device_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride());
|
||||
}
|
||||
|
||||
/// Returns a tensor reference to the real part of the tensor
|
||||
cutlass::TensorRef<Element, Layout> device_ref_real() {
|
||||
return cutlass::TensorRef<Element, Layout>(device_data(), layout_);
|
||||
}
|
||||
|
||||
/// Returns a tensor reference to the real part of the tensor
|
||||
cutlass::TensorRef<Element, Layout> device_ref_imag() {
|
||||
return cutlass::TensorRef<Element, Layout>(device_data_ptr_offset(imaginary_stride()), layout_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
TensorView host_view(LongIndex ptr_element_offset=0) {
|
||||
return TensorView(host_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride(), extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
ConstTensorView host_view(LongIndex ptr_element_offset=0) const {
|
||||
return ConstTensorView(host_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride(), extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
cutlass::TensorView<Element, Layout> host_view_real() {
|
||||
return cutlass::TensorView<Element, Layout>(host_data(), layout_, extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
cutlass::TensorView<Element, Layout> host_view_imag() {
|
||||
return cutlass::TensorView<Element, Layout>(host_data_ptr_offset(imaginary_stride()), layout_, extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
TensorView device_view(LongIndex ptr_element_offset=0) {
|
||||
return TensorView(device_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride(), extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
ConstTensorView device_view(LongIndex ptr_element_offset=0) const {
|
||||
return ConstTensorView(device_data_ptr_offset(ptr_element_offset), layout_, imaginary_stride(), extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
cutlass::TensorView<Element, Layout> device_view_real() {
|
||||
return cutlass::TensorView<Element, Layout>(device_data(), layout_, extent_);
|
||||
}
|
||||
|
||||
/// Accesses the tensor reference pointing to data
|
||||
cutlass::TensorView<Element, Layout> device_view_imag() {
|
||||
return cutlass::TensorView<Element, Layout>(device_data_ptr_offset(imaginary_stride()), layout_, extent_);
|
||||
}
|
||||
|
||||
/// Returns true if device memory is allocated
|
||||
bool device_backed() const {
|
||||
return (device_.get() == nullptr) ? false : true;
|
||||
}
|
||||
|
||||
/// Returns the layout object
|
||||
Layout layout() const {
|
||||
return layout_;
|
||||
}
|
||||
|
||||
/// Returns the layout object's stride vector
|
||||
Stride stride() const {
|
||||
return layout_.stride();
|
||||
}
|
||||
|
||||
/// Returns the layout object's stride in a given physical dimension
|
||||
Index stride(int dim) const {
|
||||
return layout_.stride().at(dim);
|
||||
}
|
||||
|
||||
/// Computes the offset of an index from the origin of the tensor
|
||||
LongIndex offset(TensorCoord const& coord) const {
|
||||
return layout_(coord);
|
||||
}
|
||||
|
||||
/// Returns a reference to the element at the logical Coord in host memory
|
||||
Reference at(TensorCoord const& coord) {
|
||||
return host_data(offset(coord));
|
||||
}
|
||||
|
||||
/// Returns a const reference to the element at the logical Coord in host memory
|
||||
ConstReference at(TensorCoord const& coord) const {
|
||||
return host_data(offset(coord));
|
||||
}
|
||||
|
||||
/// Returns the extent of the tensor
|
||||
TensorCoord extent() const {
|
||||
return extent_;
|
||||
}
|
||||
|
||||
/// Returns the extent of the tensor
|
||||
TensorCoord & extent() {
|
||||
return extent_;
|
||||
}
|
||||
|
||||
/// Copies data from device to host
|
||||
void sync_host() {
|
||||
if (device_backed()) {
|
||||
device_memory::copy_to_host(
|
||||
host_data(), device_data(), imaginary_stride() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies data from host to device
|
||||
void sync_device() {
|
||||
if (device_backed()) {
|
||||
device_memory::copy_to_device(
|
||||
device_data(), host_data(), imaginary_stride() * 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass
|
||||
@@ -0,0 +1,306 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017-2018, 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 Reference implementation for complex-valued GEMM in device code.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/coord.h"
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/matrix_coord.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
#include "cutlass/functional.h"
|
||||
#include "cutlass/numeric_conversion.h"
|
||||
#include "cutlass/tensor_ref_planar_complex.h"
|
||||
|
||||
#include "cutlass/matrix_traits.h"
|
||||
#include "cutlass/tensor_view.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
|
||||
namespace cutlass {
|
||||
namespace reference {
|
||||
namespace device {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace kernel {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int const kGemmPlanarComplexBlockSize = 4;
|
||||
|
||||
template <
|
||||
typename ElementA,
|
||||
typename LayoutA,
|
||||
typename ElementB,
|
||||
typename LayoutB,
|
||||
typename ElementC,
|
||||
typename LayoutC,
|
||||
typename ScalarType,
|
||||
typename ComputeType,
|
||||
typename ConvertOp = NumericConverter<ElementC, ScalarType>,
|
||||
typename InnerProductOp = multiply_add<complex<ComputeType>>
|
||||
>
|
||||
__global__ void GemmPlanarComplex(
|
||||
gemm::GemmCoord problem_size,
|
||||
complex<ScalarType> alpha,
|
||||
TensorRefPlanarComplex<ElementA, LayoutA> tensor_a,
|
||||
ComplexTransform transform_a,
|
||||
TensorRefPlanarComplex<ElementB, LayoutB> tensor_b,
|
||||
ComplexTransform transform_b,
|
||||
complex<ScalarType> beta,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_c,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_d,
|
||||
complex<ComputeType> initial_accum) {
|
||||
|
||||
int const kMblock = kGemmPlanarComplexBlockSize;
|
||||
int const kNblock = kGemmPlanarComplexBlockSize;
|
||||
|
||||
using ComplexA = typename TensorRefPlanarComplex<ElementA, LayoutA>::ComplexElement;
|
||||
using ComplexB = typename TensorRefPlanarComplex<ElementB, LayoutB>::ComplexElement;
|
||||
using ComplexC = typename TensorRefPlanarComplex<ElementC, LayoutC>::ComplexElement;
|
||||
|
||||
// Note: batch is ignored.
|
||||
int const M = problem_size.m();
|
||||
int const N = problem_size.n();
|
||||
int const K = problem_size.k();
|
||||
|
||||
ConvertOp convert_op;
|
||||
InnerProductOp inner_product_op;
|
||||
|
||||
complex<ComputeType> accum[kMblock][kNblock];
|
||||
|
||||
int row_block = (blockIdx.x * blockDim.x + threadIdx.x) * kMblock;
|
||||
int col_block = (blockIdx.y * blockDim.y + threadIdx.y) * kNblock;
|
||||
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int j = 0; j < kNblock; j++) {
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < kMblock; i++) {
|
||||
accum[i][j] = initial_accum;
|
||||
}
|
||||
}
|
||||
|
||||
CUTLASS_PRAGMA_NO_UNROLL
|
||||
for (int k_block = 0; k_block < K; ++k_block) {
|
||||
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int j = 0; j < kNblock; j++) {
|
||||
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < kMblock; i++) {
|
||||
|
||||
int row = row_block + i;
|
||||
int col = col_block + j;
|
||||
|
||||
if (row < M && col < N) {
|
||||
|
||||
ComplexA a_ik = tensor_a.at(MatrixCoord(row, k_block));
|
||||
ComplexB b_kj = tensor_b.at(MatrixCoord(k_block, col));
|
||||
|
||||
complex<ComputeType> a = complex<ComputeType>{
|
||||
ComputeType(a_ik.real()),
|
||||
ComputeType(a_ik.imag())
|
||||
};
|
||||
|
||||
complex<ComputeType> b = complex<ComputeType>{
|
||||
ComputeType(b_kj.real()),
|
||||
ComputeType(b_kj.imag())
|
||||
};
|
||||
|
||||
if (transform_a == ComplexTransform::kConjugate) {
|
||||
a = conj(a);
|
||||
}
|
||||
|
||||
if (transform_b == ComplexTransform::kConjugate) {
|
||||
b = conj(b);
|
||||
}
|
||||
|
||||
accum[i][j] = inner_product_op(a, b, accum[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int j = 0; j < kNblock; j++) {
|
||||
CUTLASS_PRAGMA_UNROLL
|
||||
for (int i = 0; i < kMblock; i++) {
|
||||
|
||||
int row = row_block + i;
|
||||
int col = col_block + j;
|
||||
|
||||
MatrixCoord coord = MatrixCoord(row, col);
|
||||
|
||||
if (row < M && col < N) {
|
||||
|
||||
complex<ScalarType> acc{
|
||||
ScalarType(accum[i][j].real()),
|
||||
ScalarType(accum[i][j].imag())
|
||||
};
|
||||
|
||||
ComplexC c_ij = ComplexC();
|
||||
|
||||
if (beta.real() != ScalarType() || beta.imag() != ScalarType()) {
|
||||
c_ij = tensor_c.at(coord);
|
||||
}
|
||||
|
||||
complex<ScalarType> src{
|
||||
ScalarType(c_ij.real()),
|
||||
ScalarType(c_ij.imag())
|
||||
};
|
||||
|
||||
complex<ScalarType> result = alpha * acc + beta * src;
|
||||
|
||||
ComplexC d_ij;
|
||||
|
||||
d_ij.real() = convert_op(result.real());
|
||||
d_ij.imag() = convert_op(result.imag());;
|
||||
|
||||
tensor_d.at(coord) = d_ij;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace kernel
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Computes a general matrix product among matrices (tensors of rank=2) pointed to by TensorRef
|
||||
/// objects.
|
||||
///
|
||||
/// Explicitly naming types needed by this template can be cumbersome, particularly for the
|
||||
/// accumulator type, so a function argument 'initial_accum' is exposed. Passing
|
||||
/// AccumulatorType(0) as the last function argument can be easier than naming all template
|
||||
/// arguments explicitly.
|
||||
template <
|
||||
typename ElementA,
|
||||
typename LayoutA,
|
||||
typename ElementB,
|
||||
typename LayoutB,
|
||||
typename ElementC,
|
||||
typename LayoutC,
|
||||
typename ScalarType,
|
||||
typename ComputeType,
|
||||
typename ConvertOp = NumericConverter<ElementC, ScalarType>,
|
||||
typename InnerProductOp = multiply_add<complex<ComputeType>>
|
||||
>
|
||||
void GemmPlanarComplex(
|
||||
gemm::GemmCoord problem_size,
|
||||
complex<ScalarType> alpha,
|
||||
TensorRefPlanarComplex<ElementA, LayoutA> tensor_a,
|
||||
ComplexTransform transform_a,
|
||||
TensorRefPlanarComplex<ElementB, LayoutB> tensor_b,
|
||||
ComplexTransform transform_b,
|
||||
complex<ScalarType> beta,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_c,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_d,
|
||||
complex<ComputeType> initial_accum) {
|
||||
|
||||
static_assert(
|
||||
LayoutA::kRank == 2 &&
|
||||
LayoutB::kRank == 2 &&
|
||||
LayoutC::kRank == 2, "Tensors must be of rank 2");
|
||||
|
||||
int const kMblock = kernel::kGemmPlanarComplexBlockSize;
|
||||
int const kNblock = kernel::kGemmPlanarComplexBlockSize;
|
||||
|
||||
dim3 block(16, 8);
|
||||
|
||||
dim3 grid(
|
||||
(problem_size.m() + block.x * kMblock - 1) / (block.x * kMblock),
|
||||
(problem_size.n() + block.y * kNblock - 1) / (block.y * kNblock),
|
||||
1);
|
||||
|
||||
kernel::GemmPlanarComplex<
|
||||
ElementA, LayoutA,
|
||||
ElementB, LayoutB,
|
||||
ElementC, LayoutC,
|
||||
ScalarType,
|
||||
ComputeType,
|
||||
ConvertOp,
|
||||
InnerProductOp
|
||||
><<< grid, block >>>(
|
||||
problem_size,
|
||||
alpha,
|
||||
tensor_a,
|
||||
transform_a,
|
||||
tensor_b,
|
||||
transform_b,
|
||||
beta,
|
||||
tensor_c,
|
||||
tensor_d,
|
||||
initial_accum
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Computes a general matrix product among matrices (tensors of rank=2) pointed to by TensorRef
|
||||
/// objects.
|
||||
///
|
||||
/// This assumes the accumulator type is the same type as the scalars.
|
||||
template <
|
||||
typename ElementA,
|
||||
typename LayoutA,
|
||||
typename ElementB,
|
||||
typename LayoutB,
|
||||
typename ElementC,
|
||||
typename LayoutC,
|
||||
typename ScalarType
|
||||
>
|
||||
void GemmPlanarComplex(
|
||||
gemm::GemmCoord problem_size,
|
||||
complex<ScalarType> alpha,
|
||||
TensorRefPlanarComplex<ElementA, LayoutA> tensor_a,
|
||||
ComplexTransform transform_a,
|
||||
TensorRefPlanarComplex<ElementB, LayoutB> tensor_b,
|
||||
ComplexTransform transform_b,
|
||||
complex<ScalarType> beta,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_c,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_d) {
|
||||
|
||||
GemmPlanarComplex(
|
||||
problem_size,
|
||||
alpha,
|
||||
tensor_a, transform_a,
|
||||
tensor_b, transform_b,
|
||||
beta,
|
||||
tensor_c,
|
||||
tensor_d,
|
||||
complex<ScalarType>());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace device
|
||||
} // namespace reference
|
||||
} // namespace cutlass
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -43,12 +43,12 @@
|
||||
#endif
|
||||
|
||||
// CUDA includes
|
||||
#include <cublas_v2.h>
|
||||
#include <curand_kernel.h>
|
||||
|
||||
// Cutlass includes
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/array.h"
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/tensor_view.h"
|
||||
|
||||
#include "cutlass/util/reference/device/tensor_foreach.h"
|
||||
@@ -169,6 +169,95 @@ struct RandomGaussianFunc {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename Real>
|
||||
struct RandomGaussianFunc<complex<Real>> {
|
||||
|
||||
using Element = complex<Real>;
|
||||
using FloatType = typename std::conditional<(sizeof(Real) > 4), double, float>::type;
|
||||
using IntType = typename std::conditional<(sizeof(Real) > 4), int64_t, int>::type;
|
||||
|
||||
/// Parameters structure
|
||||
struct Params {
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
uint64_t seed;
|
||||
FloatType mean;
|
||||
FloatType stddev;
|
||||
int int_scale;
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Construction of Gaussian RNG functor.
|
||||
Params(
|
||||
uint64_t seed_ = 0,
|
||||
Real mean_ = 0,
|
||||
Real stddev_ = 1,
|
||||
int int_scale_ = -1
|
||||
):
|
||||
seed(seed_),
|
||||
mean(static_cast<FloatType>(mean_)),
|
||||
stddev(static_cast<FloatType>(stddev_)),
|
||||
int_scale(int_scale_) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Parameters object
|
||||
Params params;
|
||||
|
||||
/// RNG state object
|
||||
curandState_t rng_state;
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Device-side initialization of RNG
|
||||
CUTLASS_DEVICE
|
||||
RandomGaussianFunc(Params const ¶ms): params(params) {
|
||||
|
||||
uint64_t gtid = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
|
||||
curand_init(params.seed, gtid, 0, &rng_state);
|
||||
}
|
||||
|
||||
/// Compute random value and update RNG state
|
||||
CUTLASS_DEVICE
|
||||
Element operator()() {
|
||||
|
||||
FloatType rnd_r = random_normal_float<FloatType>(&rng_state);
|
||||
FloatType rnd_i = random_normal_float<FloatType>(&rng_state);
|
||||
rnd_r = params.mean + params.stddev * rnd_r;
|
||||
rnd_i = params.mean + params.stddev * rnd_i;
|
||||
|
||||
Element result;
|
||||
if (params.int_scale >= 0) {
|
||||
rnd_r = FloatType(IntType(rnd_r * FloatType(IntType(1) << params.int_scale)));
|
||||
rnd_i = FloatType(IntType(rnd_i * FloatType(IntType(1) << params.int_scale)));
|
||||
|
||||
result = {
|
||||
Real(rnd_r / FloatType(IntType(1) << params.int_scale)),
|
||||
Real(rnd_i / FloatType(IntType(1) << params.int_scale))
|
||||
};
|
||||
}
|
||||
else {
|
||||
result = Element(Real(rnd_r), Real(rnd_i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// Computes a random Gaussian distribution
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
@@ -269,12 +358,12 @@ template <typename Element> ///< Element type
|
||||
void BlockFillRandomGaussian(
|
||||
Element *ptr,
|
||||
size_t capacity,
|
||||
uint64_t seed, ///< seed for RNG
|
||||
Element mean = Element(0), ///< Gaussian distribution's mean
|
||||
Element stddev = Element(1), ///< Gaussian distribution's standard deviation
|
||||
int bits = -1) { ///< If non-negative, specifies number of fractional bits that
|
||||
/// are not truncated to zero. Permits reducing precision of
|
||||
/// data.
|
||||
uint64_t seed, ///< seed for RNG
|
||||
typename RealType<Element>::Type mean, ///< Gaussian distribution's mean
|
||||
typename RealType<Element>::Type stddev, ///< Gaussian distribution's standard deviation
|
||||
int bits = -1) { ///< If non-negative, specifies number of fractional bits that
|
||||
/// are not truncated to zero. Permits reducing precision of
|
||||
/// data.
|
||||
|
||||
using RandomFunc = detail::RandomGaussianFunc<Element>;
|
||||
|
||||
@@ -383,6 +472,111 @@ struct RandomUniformFunc {
|
||||
}
|
||||
};
|
||||
|
||||
/// Computes a random Gaussian distribution
|
||||
template <typename Real> ///< Layout function
|
||||
struct RandomUniformFunc<complex<Real>> {
|
||||
|
||||
using Element = complex<Real>;
|
||||
|
||||
using FloatType = typename std::conditional<
|
||||
(sizeof(Real) > 4),
|
||||
double,
|
||||
float>::type;
|
||||
|
||||
using IntType = typename std::conditional<
|
||||
(sizeof(Real) > 4),
|
||||
int64_t,
|
||||
int>::type;
|
||||
|
||||
/// Parameters structure
|
||||
struct Params {
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
uint64_t seed;
|
||||
FloatType range;
|
||||
FloatType min;
|
||||
int int_scale;
|
||||
|
||||
/// Default ctor
|
||||
CUTLASS_HOST_DEVICE
|
||||
Params() { }
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Construction of Gaussian RNG functor.
|
||||
Params(
|
||||
uint64_t seed_ = 0,
|
||||
FloatType max = 1,
|
||||
FloatType min_ = 0,
|
||||
int int_scale_ = -1
|
||||
):
|
||||
seed(seed_),
|
||||
range(static_cast<FloatType>(max - min_)),
|
||||
min(static_cast<FloatType>(min_)),
|
||||
int_scale(int_scale_) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Data members
|
||||
//
|
||||
|
||||
/// Parameters object
|
||||
Params params;
|
||||
|
||||
/// RNG state object
|
||||
curandState_t rng_state;
|
||||
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
|
||||
/// Device-side initialization of RNG
|
||||
CUTLASS_DEVICE
|
||||
RandomUniformFunc(Params const ¶ms): params(params) {
|
||||
|
||||
uint64_t gtid = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
|
||||
curand_init(params.seed, gtid, 0, &rng_state);
|
||||
}
|
||||
|
||||
/// Compute random value and update RNG state
|
||||
CUTLASS_DEVICE
|
||||
Element operator()() {
|
||||
|
||||
FloatType rnd_r = random_uniform_float<FloatType>(&rng_state);
|
||||
FloatType rnd_i = random_uniform_float<FloatType>(&rng_state);
|
||||
|
||||
rnd_r = params.min + params.range * rnd_r;
|
||||
rnd_i = params.min + params.range * rnd_i;
|
||||
|
||||
// Random values are cast to integer after scaling by a power of two to facilitate error
|
||||
// testing
|
||||
Element result;
|
||||
|
||||
if (params.int_scale >= 0) {
|
||||
rnd_r = FloatType(IntType(rnd_r * FloatType(IntType(1) << params.int_scale)));
|
||||
rnd_i = FloatType(IntType(rnd_i * FloatType(IntType(1) << params.int_scale)));
|
||||
|
||||
result = {
|
||||
Real(rnd_r / FloatType(IntType(1) << params.int_scale)),
|
||||
Real(rnd_i / FloatType(IntType(1) << params.int_scale))
|
||||
};
|
||||
}
|
||||
else {
|
||||
result = Element(Real(rnd_r), Real(rnd_i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// Computes a random Gaussian distribution
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
@@ -489,8 +683,8 @@ void BlockFillRandomUniform(
|
||||
Element *ptr,
|
||||
size_t capacity,
|
||||
uint64_t seed, ///< seed for RNG
|
||||
Element max = Element(1), ///< upper bound of distribution
|
||||
Element min = Element(0), ///< lower bound for distribution
|
||||
typename RealType<Element>::Type max, ///< upper bound of distribution
|
||||
typename RealType<Element>::Type min, ///< lower bound for distribution
|
||||
int bits = -1) { ///< If non-negative, specifies number of fractional bits that
|
||||
/// are not truncated to zero. Permits reducing precision of
|
||||
/// data.
|
||||
@@ -976,13 +1170,15 @@ void BlockFillRandom(
|
||||
uint64_t seed,
|
||||
Distribution dist) {
|
||||
|
||||
using Real = typename RealType<Element>::Type;
|
||||
|
||||
if (dist.kind == Distribution::Gaussian) {
|
||||
BlockFillRandomGaussian<Element>(
|
||||
ptr,
|
||||
capacity,
|
||||
seed,
|
||||
static_cast<Element>(dist.gaussian.mean),
|
||||
static_cast<Element>(dist.gaussian.stddev),
|
||||
static_cast<Real>(dist.gaussian.mean),
|
||||
static_cast<Real>(dist.gaussian.stddev),
|
||||
dist.int_scale);
|
||||
}
|
||||
else if (dist.kind == Distribution::Uniform) {
|
||||
@@ -990,8 +1186,8 @@ void BlockFillRandom(
|
||||
ptr,
|
||||
capacity,
|
||||
seed,
|
||||
static_cast<Element>(dist.uniform.max),
|
||||
static_cast<Element>(dist.uniform.min),
|
||||
static_cast<Real>(dist.uniform.max),
|
||||
static_cast<Real>(dist.uniform.min),
|
||||
dist.int_scale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ void GemmComplex(
|
||||
ComplexTransform transform_b,
|
||||
ScalarType beta,
|
||||
TensorRef<ElementC, LayoutC> tensor_c,
|
||||
TensorRef<ElementC, LayoutC> tensor_d,
|
||||
ComputeType initial_accum) {
|
||||
|
||||
static_assert(
|
||||
@@ -138,7 +139,7 @@ void GemmComplex(
|
||||
|
||||
if (row < M && col < N) {
|
||||
|
||||
tensor_c.at(coord) = convert_op(
|
||||
tensor_d.at(coord) = convert_op(
|
||||
alpha * ScalarType(accum[i][j]) +
|
||||
beta * ScalarType(tensor_c.at(coord)));
|
||||
}
|
||||
@@ -171,9 +172,10 @@ void GemmComplex(
|
||||
TensorRef<ElementB, LayoutB> tensor_b,
|
||||
ComplexTransform transform_b,
|
||||
ScalarType beta,
|
||||
TensorRef<ElementC, LayoutC> tensor_c) {
|
||||
TensorRef<ElementC, LayoutC> tensor_c,
|
||||
TensorRef<ElementC, LayoutC> tensor_d) {
|
||||
|
||||
GemmComplex(problem_size, alpha, tensor_a, transform_a, tensor_b, transform_b, beta, tensor_c, ScalarType(0));
|
||||
GemmComplex(problem_size, alpha, tensor_a, transform_a, tensor_b, transform_b, beta, tensor_c, tensor_d, ScalarType(0));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2017-2018, 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 Reference implementation for complex-valued GEMM in host-side code.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cutlass/coord.h"
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
#include "cutlass/functional.h"
|
||||
#include "cutlass/numeric_conversion.h"
|
||||
#include "cutlass/tensor_ref_planar_complex.h"
|
||||
|
||||
#include "cutlass/matrix_traits.h"
|
||||
#include "cutlass/tensor_view.h"
|
||||
#include "cutlass/gemm/gemm.h"
|
||||
|
||||
namespace cutlass {
|
||||
namespace reference {
|
||||
namespace host {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Computes a general matrix product among matrices (tensors of rank=2) pointed to by TensorRef
|
||||
/// objects.
|
||||
///
|
||||
/// Explicitly naming types needed by this template can be cumbersome, particularly for the
|
||||
/// accumulator type, so a function argument 'initial_accum' is exposed. Passing
|
||||
/// AccumulatorType(0) as the last function argument can be easier than naming all template
|
||||
/// arguments explicitly.
|
||||
template <
|
||||
typename ElementA,
|
||||
typename LayoutA,
|
||||
typename ElementB,
|
||||
typename LayoutB,
|
||||
typename ElementC,
|
||||
typename LayoutC,
|
||||
typename ScalarType,
|
||||
typename ComputeType,
|
||||
typename ConvertOp = NumericConverter<ElementC, ScalarType>,
|
||||
typename InnerProductOp = multiply_add<complex<ComputeType>>
|
||||
>
|
||||
void GemmPlanarComplex(
|
||||
gemm::GemmCoord problem_size,
|
||||
complex<ScalarType> alpha,
|
||||
TensorRefPlanarComplex<ElementA, LayoutA> tensor_a,
|
||||
ComplexTransform transform_a,
|
||||
TensorRefPlanarComplex<ElementB, LayoutB> tensor_b,
|
||||
ComplexTransform transform_b,
|
||||
complex<ScalarType> beta,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_c,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_d,
|
||||
complex<ComputeType> initial_accum) {
|
||||
|
||||
static_assert(
|
||||
LayoutA::kRank == 2 &&
|
||||
LayoutB::kRank == 2 &&
|
||||
LayoutC::kRank == 2, "Tensors must be of rank 2");
|
||||
|
||||
using ComplexA = typename TensorRefPlanarComplex<ElementA, LayoutA>::ComplexElement;
|
||||
using ComplexB = typename TensorRefPlanarComplex<ElementB, LayoutB>::ComplexElement;
|
||||
using ComplexC = typename TensorRefPlanarComplex<ElementC, LayoutC>::ComplexElement;
|
||||
|
||||
// Note: batch is ignored.
|
||||
int const M = problem_size.m();
|
||||
int const N = problem_size.n();
|
||||
int const K = problem_size.k();
|
||||
|
||||
// Blocking necessary to speedup reference implementation
|
||||
int const Mblock = 16;
|
||||
int const Nblock = 16;
|
||||
|
||||
ConvertOp convert_op;
|
||||
InnerProductOp inner_product_op;
|
||||
|
||||
for (int row_block = 0; row_block < M; row_block += Mblock) {
|
||||
for (int col_block = 0; col_block < N; col_block += Nblock) {
|
||||
|
||||
complex<ComputeType> accum[Mblock][Nblock];
|
||||
|
||||
for (int j = 0; j < Nblock; j++) {
|
||||
for (int i = 0; i < Mblock; i++) {
|
||||
accum[i][j] = initial_accum;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k_block = 0; k_block < K; ++k_block) {
|
||||
for (int j = 0; j < Nblock; j++) {
|
||||
for (int i = 0; i < Mblock; i++) {
|
||||
int row = row_block + i;
|
||||
int col = col_block + j;
|
||||
|
||||
if (row < M && col < N) {
|
||||
|
||||
ComplexA a_ik = tensor_a.at(MatrixCoord(row, k_block));
|
||||
ComplexB b_kj = tensor_b.at(MatrixCoord(k_block, col));
|
||||
|
||||
complex<ComputeType> a = complex<ComputeType>{
|
||||
ComputeType(a_ik.real()),
|
||||
ComputeType(a_ik.imag())
|
||||
};
|
||||
|
||||
complex<ComputeType> b = complex<ComputeType>{
|
||||
ComputeType(b_kj.real()),
|
||||
ComputeType(b_kj.imag())
|
||||
};
|
||||
|
||||
if (transform_a == ComplexTransform::kConjugate) {
|
||||
a = conj(a);
|
||||
}
|
||||
|
||||
if (transform_b == ComplexTransform::kConjugate) {
|
||||
b = conj(b);
|
||||
}
|
||||
|
||||
accum[i][j] = inner_product_op(a, b, accum[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < Nblock; j++) {
|
||||
for (int i = 0; i < Mblock; i++) {
|
||||
int row = row_block + i;
|
||||
int col = col_block + j;
|
||||
|
||||
MatrixCoord coord = MatrixCoord(row, col);
|
||||
|
||||
if (row < M && col < N) {
|
||||
|
||||
complex<ScalarType> acc{
|
||||
ScalarType(accum[i][j].real()),
|
||||
ScalarType(accum[i][j].imag())
|
||||
};
|
||||
|
||||
ComplexC d_ij = tensor_c.at(coord);
|
||||
|
||||
complex<ScalarType> src{
|
||||
ScalarType(d_ij.real()),
|
||||
ScalarType(d_ij.imag())
|
||||
};
|
||||
|
||||
complex<ScalarType> result = alpha * acc + beta * src;
|
||||
|
||||
d_ij.real() = convert_op(result.real());
|
||||
d_ij.imag() = convert_op(result.imag());;
|
||||
|
||||
tensor_d.at(coord) = d_ij;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Computes a general matrix product among matrices (tensors of rank=2) pointed to by TensorRef
|
||||
/// objects.
|
||||
///
|
||||
/// This assumes the accumulator type is the same type as the scalars.
|
||||
template <
|
||||
typename ElementA,
|
||||
typename LayoutA,
|
||||
typename ElementB,
|
||||
typename LayoutB,
|
||||
typename ElementC,
|
||||
typename LayoutC,
|
||||
typename ScalarType
|
||||
>
|
||||
void GemmPlanarComplex(
|
||||
gemm::GemmCoord problem_size,
|
||||
complex<ScalarType> alpha,
|
||||
TensorRefPlanarComplex<ElementA, LayoutA> tensor_a,
|
||||
ComplexTransform transform_a,
|
||||
TensorRefPlanarComplex<ElementB, LayoutB> tensor_b,
|
||||
ComplexTransform transform_b,
|
||||
complex<ScalarType> beta,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_c,
|
||||
TensorRefPlanarComplex<ElementC, LayoutC> tensor_d) {
|
||||
|
||||
GemmPlanarComplex(
|
||||
problem_size,
|
||||
alpha,
|
||||
tensor_a, transform_a,
|
||||
tensor_b, transform_b,
|
||||
beta,
|
||||
tensor_c,
|
||||
tensor_d,
|
||||
complex<ScalarType>());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace host
|
||||
} // namespace reference
|
||||
} // namespace cutlass
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -33,6 +33,9 @@
|
||||
|
||||
// Cutlass includes
|
||||
#include "cutlass/cutlass.h"
|
||||
#include "cutlass/tensor_view.h"
|
||||
#include "cutlass/tensor_view_planar_complex.h"
|
||||
|
||||
#include "cutlass/util/distribution.h"
|
||||
//#include "cutlass/util/type_traits.h"
|
||||
#include "tensor_foreach.h"
|
||||
@@ -112,6 +115,46 @@ bool TensorEquals(
|
||||
return bool(func);
|
||||
}
|
||||
|
||||
/// Returns true if two tensor views are equal.
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
typename Layout> ///< Layout function
|
||||
bool TensorEquals(
|
||||
TensorViewPlanarComplex<Element, Layout> const &lhs,
|
||||
TensorViewPlanarComplex<Element, Layout> const &rhs) {
|
||||
|
||||
// Extents must be identical
|
||||
if (lhs.extent() != rhs.extent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
detail::TensorEqualsFunc<Element, Layout> real_func(
|
||||
{lhs.data(), lhs.layout(), lhs.extent()},
|
||||
{rhs.data(), rhs.layout(), rhs.extent()}
|
||||
);
|
||||
|
||||
TensorForEach(
|
||||
lhs.extent(),
|
||||
real_func
|
||||
);
|
||||
|
||||
if (!bool(real_func)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
detail::TensorEqualsFunc<Element, Layout> imag_func(
|
||||
{lhs.data() + lhs.imaginary_stride(), lhs.layout(), lhs.extent()},
|
||||
{rhs.data() + rhs.imaginary_stride(), rhs.layout(), rhs.extent()}
|
||||
);
|
||||
|
||||
TensorForEach(
|
||||
lhs.extent(),
|
||||
imag_func
|
||||
);
|
||||
|
||||
return bool(imag_func);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -137,6 +180,17 @@ bool TensorNotEquals(
|
||||
return !bool(func);
|
||||
}
|
||||
|
||||
/// Returns true if two tensor views are equal.
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
typename Layout> ///< Layout function
|
||||
bool TensorNotEquals(
|
||||
TensorViewPlanarComplex<Element, Layout> const &lhs,
|
||||
TensorViewPlanarComplex<Element, Layout> const &rhs) {
|
||||
|
||||
return !TensorEquals(lhs, rhs);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include "cutlass/complex.h"
|
||||
#include "cutlass/array.h"
|
||||
#include "cutlass/numeric_types.h"
|
||||
#include "cutlass/tensor_view.h"
|
||||
#include "cutlass/tensor_view_planar_complex.h"
|
||||
|
||||
#include "cutlass/util/distribution.h"
|
||||
#include "tensor_foreach.h"
|
||||
@@ -101,6 +103,18 @@ void TensorFill(
|
||||
);
|
||||
}
|
||||
|
||||
/// Fills a tensor with a uniform value
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
typename Layout> ///< Layout function
|
||||
void TensorFill(
|
||||
TensorViewPlanarComplex<Element, Layout> dst, ///< destination tensor
|
||||
cutlass::complex<Element> val = cutlass::complex<Element>(0)) { ///< value to uniformly fill it with
|
||||
|
||||
TensorFill(dst.view_real(), val.real());
|
||||
TensorFill(dst.view_imag(), val.imag());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -268,6 +282,23 @@ void TensorFillRandomGaussian(
|
||||
);
|
||||
}
|
||||
|
||||
/// Fills a tensor with random values with a Gaussian distribution.
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
typename Layout> ///< Layout function
|
||||
void TensorFillRandomGaussian(
|
||||
TensorViewPlanarComplex<Element, Layout> dst, ///< destination tensor
|
||||
uint64_t seed, ///< seed for RNG
|
||||
double mean = 0, ///< Gaussian distribution's mean
|
||||
double stddev = 1, ///< Gaussian distribution's standard deviation
|
||||
int bits = -1) { ///< If non-negative, specifies number of fractional bits that
|
||||
/// are not truncated to zero. Permits reducing precision of
|
||||
/// data.
|
||||
|
||||
TensorFillRandomGaussian(dst.view_real(), seed, mean, stddev, bits);
|
||||
TensorFillRandomGaussian(dst.view_imag(), ~seed, mean, stddev, bits);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Fills a tensor with random values with a Gaussian distribution.
|
||||
@@ -461,6 +492,23 @@ void TensorFillRandomUniform(
|
||||
);
|
||||
}
|
||||
|
||||
/// Fills a tensor with random values with a uniform random distribution.
|
||||
template <
|
||||
typename Element, ///< Element type
|
||||
typename Layout> ///< Layout function
|
||||
void TensorFillRandomUniform(
|
||||
TensorViewPlanarComplex<Element, Layout> dst, ///< destination tensor
|
||||
uint64_t seed, ///< seed for RNG
|
||||
double max = 1, ///< upper bound of distribution
|
||||
double min = 0, ///< lower bound for distribution
|
||||
int bits = -1) { ///< If non-negative, specifies number of fractional bits that
|
||||
/// are not truncated to zero. Permits reducing precision of
|
||||
/// data.
|
||||
|
||||
TensorFillRandomUniform(dst.view_real(), seed, max, min, bits);
|
||||
TensorFillRandomUniform(dst.view_imag(), ~seed, max, min, bits);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Fills a tensor with random values with a uniform random distribution.
|
||||
@@ -774,6 +822,27 @@ void BlockFillSequential(
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills a block of data with sequential elements
|
||||
template <
|
||||
typename Element
|
||||
>
|
||||
void BlockFillSequentialModN(
|
||||
Element *ptr,
|
||||
int64_t capacity,
|
||||
int64_t mod,
|
||||
int64_t v = int64_t(1),
|
||||
int64_t s = int64_t(0)) {
|
||||
int i = 0;
|
||||
|
||||
while (i < capacity) {
|
||||
cutlass::ReferenceFactory<Element, (cutlass::sizeof_bits<Element>::value <
|
||||
8)>::get(ptr, i) = Element(s);
|
||||
|
||||
s = int64_t(s + v) % mod;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "cutlass/core_io.h"
|
||||
#include "cutlass/tensor_view.h"
|
||||
#include "cutlass/tensor_view_planar_complex.h"
|
||||
#include "cutlass/complex.h"
|
||||
|
||||
namespace cutlass {
|
||||
|
||||
@@ -87,13 +89,13 @@ inline std::ostream & TensorView_WriteRank(
|
||||
coord[rank] = idx;
|
||||
|
||||
if (rank + 2 == Layout::kRank) {
|
||||
// Write least significant ranks asa matrix with rows delimited by ";\n"
|
||||
out << (idx ? ";\n" : "");
|
||||
// Write least significant ranks asa matrix with rows delimited by "\n"
|
||||
out << (idx ? ",\n" : "");
|
||||
TensorView_WriteLeastSignificantRank(out, view, coord, rank + 1, width);
|
||||
}
|
||||
else {
|
||||
// Higher ranks are separated by newlines
|
||||
out << (idx ? "\n" : "");
|
||||
out << (idx ? ",\n\n" : "");
|
||||
TensorView_WriteRank(out, view, coord, rank + 1, width);
|
||||
}
|
||||
}
|
||||
@@ -101,6 +103,76 @@ inline std::ostream & TensorView_WriteRank(
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Helper to write the least significant rank of a TensorView
|
||||
template <
|
||||
typename Element,
|
||||
typename Layout
|
||||
>
|
||||
inline std::ostream & TensorViewPlanarComplex_WriteLeastSignificantRank(
|
||||
std::ostream& out,
|
||||
TensorViewPlanarComplex<Element, Layout> const& view,
|
||||
Coord<Layout::kRank> const &start_coord,
|
||||
int rank,
|
||||
std::streamsize width) {
|
||||
|
||||
for (int idx = 0; idx < view.extent(rank); ++idx) {
|
||||
|
||||
Coord<Layout::kRank> coord(start_coord);
|
||||
coord[rank] = idx;
|
||||
|
||||
if (idx) {
|
||||
out.width(0);
|
||||
out << ", ";
|
||||
}
|
||||
if (idx || coord) {
|
||||
out.width(width);
|
||||
}
|
||||
|
||||
complex<Element> x = view.at(coord);
|
||||
out << x;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Helper to write a rank of a TensorView
|
||||
template <
|
||||
typename Element,
|
||||
typename Layout
|
||||
>
|
||||
inline std::ostream & TensorViewPlanarComplex_WriteRank(
|
||||
std::ostream& out,
|
||||
TensorViewPlanarComplex<Element, Layout> const& view,
|
||||
Coord<Layout::kRank> const &start_coord,
|
||||
int rank,
|
||||
std::streamsize width) {
|
||||
|
||||
// If called on the least significant rank, write the result as a row
|
||||
if (rank + 1 == Layout::kRank) {
|
||||
return TensorViewPlanarComplex_WriteLeastSignificantRank(out, view, start_coord, rank, width);
|
||||
}
|
||||
|
||||
// Otherwise, write a sequence of rows and newlines
|
||||
for (int idx = 0; idx < view.extent(rank); ++idx) {
|
||||
|
||||
Coord<Layout::kRank> coord(start_coord);
|
||||
coord[rank] = idx;
|
||||
|
||||
if (rank + 2 == Layout::kRank) {
|
||||
// Write least significant ranks asa matrix with rows delimited by ";\n"
|
||||
out << (idx ? ";\n" : "");
|
||||
TensorViewPlanarComplex_WriteLeastSignificantRank(out, view, coord, rank + 1, width);
|
||||
}
|
||||
else {
|
||||
// Higher ranks are separated by newlines
|
||||
out << (idx ? "\n" : "");
|
||||
TensorViewPlanarComplex_WriteRank(out, view, coord, rank + 1, width);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -143,4 +215,42 @@ inline std::ostream& operator<<(
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Prints human-readable representation of a TensorView to an ostream
|
||||
template <
|
||||
typename Element,
|
||||
typename Layout
|
||||
>
|
||||
inline std::ostream& TensorViewWrite(
|
||||
std::ostream& out,
|
||||
TensorViewPlanarComplex<Element, Layout> const& view) {
|
||||
|
||||
// Prints a TensorView according to the following conventions:
|
||||
// - least significant rank is printed as rows separated by ";\n"
|
||||
// - all greater ranks are delimited with newlines
|
||||
//
|
||||
// The result is effectively a whitespace-delimited series of 2D matrices.
|
||||
|
||||
return detail::TensorViewPlanarComplex_WriteRank(out, view, Coord<Layout::kRank>(), 0, out.width());
|
||||
}
|
||||
|
||||
/// Prints human-readable representation of a TensorView to an ostream
|
||||
template <
|
||||
typename Element,
|
||||
typename Layout
|
||||
>
|
||||
inline std::ostream& operator<<(
|
||||
std::ostream& out,
|
||||
TensorViewPlanarComplex<Element, Layout> const& view) {
|
||||
|
||||
// Prints a TensorView according to the following conventions:
|
||||
// - least significant rank is printed as rows separated by ";\n"
|
||||
// - all greater ranks are delimited with newlines
|
||||
//
|
||||
// The result is effectively a whitespace-delimited series of 2D matrices.
|
||||
|
||||
return TensorViewWrite(out, view);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace cutlass
|
||||
|
||||
Reference in New Issue
Block a user