CUTLASS 2.10 (#615)

Co-authored-by: Aniket Shivam <ashivam@nvidia.com>
This commit is contained in:
ANIKET SHIVAM
2022-09-03 15:48:46 -07:00
committed by GitHub
parent ca23ff7924
commit b72cbf957d
289 changed files with 43708 additions and 2513 deletions

View File

@@ -0,0 +1,644 @@
/******************************************************************************
* Copyright (c) 2017 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 TORT (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 cuda kernels to do layernorm on a device memory tensor with RowMajor layout.
*/
#include "cutlass/cutlass.h"
#include "cutlass/layout/tensor.h"
#include "cutlass/numeric_types.h"
#include "cutlass/tensor_coord.h"
#include "cutlass/tensor_ref.h"
#include "device_utils.h"
#include <float.h>
namespace cutlass {
/** \brief interface to do layernorm on a device memory tensor with RowMajor layout.
* \tparam T: data type
*/
template <typename T>
void layernorm(cutlass::MatrixCoord tensor_size,
TensorRef<T, layout::RowMajor> ref_output,
TensorRef<T, layout::RowMajor> ref_input,
TensorRef<T, layout::RowMajor> ref_gamma,
TensorRef<T, layout::RowMajor> ref_beta,
cudaStream_t stream);
/**
* output [m, n] row-major
* input [m, n] row-major
* gamma [n]
* beta [n]
* grid(m)
* block(block_size) -- each block deals with n elements ; each thread deals with ITEM_PER_THREAD elements
*/
template<typename T, int ITEM_PER_THREAD>
__global__ void layernorm_twoPassAlgo_stored_locally_e1(T* output,
const T* input,
const T* gamma,
const T* beta,
const int m,
const int n)
{
const int m_idx = blockIdx.x;
const int tid = threadIdx.x;
const int bdimx = blockDim.x;
__shared__ float s_mean, s_variance;
T local_val[ITEM_PER_THREAD];
float local_sums[1] = {0.0f};
int offset = m_idx * n;
input += offset;
output += offset;
const T zero = T(0.0f);
#pragma unroll
for (int i = 0 ; i < ITEM_PER_THREAD ; i++){
int index = tid + i*bdimx;
local_val[i] = index < n ? input[index] : zero;
local_sums[0] += static_cast<float>(local_val[i]);
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_mean = local_sums[0] / n;
}
__syncthreads();
local_sums[0] = 0.0f;
#pragma unroll
for (int i = 0 ; i < ITEM_PER_THREAD ; i++){
int index = tid + i*bdimx;
if (index < n){
const float tmp = static_cast<float>(local_val[i]) - s_mean;
local_sums[0] += tmp * tmp;
}
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_variance = rsqrtf(local_sums[0] / n + 1e-5);
}
__syncthreads();
#pragma unroll
for (int i = 0 ; i < ITEM_PER_THREAD ; i++){
int index = tid + i*bdimx;
if (index < n) {
const T gamma_val = gamma[index];
const T beta_val = beta[index];
output[index] = T((static_cast<float>(local_val[i]) - s_mean) * s_variance * static_cast<float>(gamma_val) + static_cast<float>(beta_val));
}
}
}
/**
* output [m, n] row-major
* input [m, n] row-major
* gamma [n]
* beta [n]
* grid(m)
* block(block_size) -- each block deals with block_size*ITEM_PER_THREAD*2 elements;
*/
template<typename T2, typename T, int ITEM_PER_THREAD>
__global__ void layernorm_twoPassAlgo_stored_locally_e2(T2* output,
const T2* input,
const T2* gamma,
const T2* beta,
const int m,
const int n)
{
const int m_idx = blockIdx.x;
const int tid = threadIdx.x;
const int bdimx = blockDim.x;
__shared__ float s_mean, s_variance;
float local_sums[1] = {0.0f};
T2 local_val[ITEM_PER_THREAD];
const int n_2 = n / 2;
int offset = m_idx * n_2;
input += offset;
output += offset;
const T2 zero = {T(0.0f), T(0.0f)};
#pragma UNROLL
for (int i = 0; i < ITEM_PER_THREAD; i += 1) {
const int index = i*bdimx + tid;
local_val[i] = index < n_2 ? input[index] : zero;
local_sums[0] += static_cast<float>(local_val[i].x) + static_cast<float>(local_val[i].y);
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_mean = local_sums[0] / n;
}
__syncthreads();
local_sums[0] = 0.0f;
#pragma UNROLL
for (int i = 0; i < ITEM_PER_THREAD; i += 1) {
const int index = i*bdimx + tid;
if (index < n_2){
const float2 tmp = {static_cast<float>(local_val[i].x) - s_mean,
static_cast<float>(local_val[i].y) - s_mean};
local_sums[0] += tmp.x * tmp.x + tmp.y * tmp.y;
}
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_variance = rsqrtf(local_sums[0] / n + 1e-5);
}
__syncthreads();
#pragma UNROLL
for (int i = 0; i < ITEM_PER_THREAD; i += 1) {
const int index = i*bdimx + tid;
if (index < n_2){
const T2 gamma_val = gamma[index];
const T2 beta_val = beta[index];
T2 tmp;
tmp.x = T((static_cast<float>(local_val[i].x) - s_mean)*s_variance*static_cast<float>(gamma_val.x) + static_cast<float>(beta_val.x));
tmp.y = T((static_cast<float>(local_val[i].y) - s_mean)*s_variance*static_cast<float>(gamma_val.y) + static_cast<float>(beta_val.y));
output[index] = tmp;
}
}
}
/**
* output [m, n] row-major
* input [m, n] row-major
* gamma [n]
* beta [n]
* grid(m)
* block(block_size) -- each block deals with block_size*ITEM_PER_THREAD*4 elements;
*/
template<typename T4, typename T, int ITEM_PER_THREAD>
__global__ void layernorm_twoPassAlgo_stored_locally_e4(T4* output,
const T4* input,
const T4* gamma,
const T4* beta,
const int m,
const int n)
{
const int m_idx = blockIdx.x;
const int tid = threadIdx.x;
const int bdimx = blockDim.x;
__shared__ float s_mean, s_variance;
float local_sums[1] = {0.0f};
T4 local_val[ITEM_PER_THREAD];
const int n_4 = n / 4;
int offset = m_idx * n_4;
input += offset;
output += offset;
const T4 zero = {T(0.0f), T(0.0f), T(0.0f), T(0.0f)};
#pragma UNROLL
for (int i = 0; i < ITEM_PER_THREAD; i += 1) {
const int index = i*bdimx + tid;
local_val[i] = index < n_4 ? input[index] : zero;
local_sums[0] += static_cast<float>(local_val[i].x) + static_cast<float>(local_val[i].y) +
static_cast<float>(local_val[i].z) + static_cast<float>(local_val[i].w);
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_mean = local_sums[0] / n;
}
__syncthreads();
local_sums[0] = 0.0f;
#pragma UNROLL
for (int i = 0; i < ITEM_PER_THREAD; i += 1) {
const int index = i*bdimx + tid;
if (index < n_4){
const float4 tmp = {static_cast<float>(local_val[i].x) - s_mean,
static_cast<float>(local_val[i].y) - s_mean,
static_cast<float>(local_val[i].z) - s_mean,
static_cast<float>(local_val[i].w) - s_mean};
local_sums[0] += tmp.x * tmp.x + tmp.y * tmp.y + tmp.z * tmp.z + tmp.w * tmp.w;
}
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_variance = rsqrtf(local_sums[0] / n + 1e-5);
}
__syncthreads();
#pragma UNROLL
for (int i = 0; i < ITEM_PER_THREAD; i += 1) {
const int index = i*bdimx + tid;
if (index < n_4){
const T4 gamma_val = gamma[index];
const T4 beta_val = beta[index];
T4 tmp;
tmp.x = T((static_cast<float>(local_val[i].x) - s_mean)*s_variance*static_cast<float>(gamma_val.x) + static_cast<float>(beta_val.x));
tmp.y = T((static_cast<float>(local_val[i].y) - s_mean)*s_variance*static_cast<float>(gamma_val.y) + static_cast<float>(beta_val.y));
tmp.z = T((static_cast<float>(local_val[i].z) - s_mean)*s_variance*static_cast<float>(gamma_val.z) + static_cast<float>(beta_val.z));
tmp.w = T((static_cast<float>(local_val[i].w) - s_mean)*s_variance*static_cast<float>(gamma_val.w) + static_cast<float>(beta_val.w));
output[index] = tmp;
}
}
}
/**
* output [m, n] row-major
* input [m, n] row-major
* gamma [n]
* beta [n]
* grid(m)
* block(block_size) -- each block deals with n elements ; each thread deals with ITEM_PER_THREAD elements
*/
template<typename T>
__global__ void layernorm_twoPassAlgo_e1(T* output,
const T* input,
const T* gamma,
const T* beta,
const int m,
const int n)
{
const int m_idx = blockIdx.x;
const int tid = threadIdx.x;
const int bdimx = blockDim.x;
__shared__ float s_mean, s_variance;
float local_sums[1] = {0.0f};
int offset = m_idx * n;
input += offset;
output += offset;
for (int index = tid ; index < n ; index += bdimx){
float local_val = static_cast<float>(input[index]);
local_sums[0] += local_val;
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_mean = local_sums[0] / n;
}
__syncthreads();
local_sums[0] = 0.0f;
for (int index = tid ; index < n ; index += bdimx){
float local_val = static_cast<float>(input[index]);
local_val = local_val - s_mean;
local_sums[0] += local_val * local_val;
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_variance = rsqrtf(local_sums[0] / n + 1e-5);
}
__syncthreads();
for (int index = tid ; index < n ; index += bdimx){
const T gamma_val = gamma[index];
const T beta_val = beta[index];
const T local_val = input[index];
output[index] = T((static_cast<float>(local_val) - s_mean) * s_variance * static_cast<float>(gamma_val) + static_cast<float>(beta_val));
}
}
/**
* output [m, n] row-major
* input [m, n] row-major
* gamma [n]
* beta [n]
* grid(m)
* block(block_size) -- each block deals with block_size*ITEM_PER_THREAD*2 elements;
*/
template<typename T2, typename T>
__global__ void layernorm_twoPassAlgo_e2(T2* output,
const T2* input,
const T2* gamma,
const T2* beta,
const int m,
const int n)
{
const int m_idx = blockIdx.x;
const int tid = threadIdx.x;
const int bdimx = blockDim.x;
__shared__ float s_mean, s_variance;
float local_sums[1] = {0.0f};
const int n_2 = n / 2;
int offset = m_idx * n_2;
input += offset;
output += offset;
for (int index = tid; index < n_2; index += bdimx) {
const T2 local_val = input[index];
local_sums[0] += static_cast<float>(local_val.x) + static_cast<float>(local_val.y);
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_mean = local_sums[0] / n;
}
__syncthreads();
local_sums[0] = 0.0f;
for (int index = tid; index < n_2; index += bdimx) {
const T2 local_val = input[index];
const float2 tmp = {static_cast<float>(local_val.x) - s_mean,
static_cast<float>(local_val.y) - s_mean};
local_sums[0] += tmp.x * tmp.x + tmp.y * tmp.y;
}
if (blockDim.x <= 32) {
warpReduceSum<float, 1>(local_sums);
}
else {
blockReduceSum<float, 1>(local_sums);
}
if (threadIdx.x == 0) {
s_variance = rsqrtf(local_sums[0] / n + 1e-5);
}
__syncthreads();
for (int index = tid; index < n_2; index += bdimx) {
const T2 local_val = input[index];
const T2 gamma_val = gamma[index];
const T2 beta_val = beta[index];
T2 tmp;
tmp.x = T((static_cast<float>(local_val.x) - s_mean)*s_variance*static_cast<float>(gamma_val.x) + static_cast<float>(beta_val.x));
tmp.y = T((static_cast<float>(local_val.y) - s_mean)*s_variance*static_cast<float>(gamma_val.y) + static_cast<float>(beta_val.y));
output[index] = tmp;
}
}
template <typename T>
void layernorm(cutlass::MatrixCoord tensor_size,
TensorRef<T, layout::RowMajor> ref_output,
TensorRef<T, layout::RowMajor> ref_input,
TensorRef<T, layout::RowMajor> ref_gamma,
TensorRef<T, layout::RowMajor> ref_beta,
cudaStream_t stream){
const int m = tensor_size.row();
const int n = tensor_size.column();
T* output = ref_output.data();
const T* input = ref_input.data();
const T* gamma = ref_gamma.data();
const T* beta = ref_beta.data();
dim3 grid(m);
dim3 block((n + 31)/32*32);
if (block.x > 1024){
block.x = 1024;
}
// TODO : There should be better configs for different cases, we only use several samples to show how to use here
// TODO : using registers to store values locally can reduce the ldgs from global memory and speedup the kernels.
if ((n % 4 == 0) && (n >= 128) && (n <= 4096)) {
block.x = (n/4 + 31)/32*32;
if (std::is_same<T, float>::value) {
layernorm_twoPassAlgo_stored_locally_e4<float4, float, 1><<<grid, block, 0, stream>>>(
(float4*)output,
(const float4*)input,
(const float4*)gamma,
(const float4*)beta,
m,
n);
} // if (std::is_same<T, float>::value)
else {
layernorm_twoPassAlgo_stored_locally_e4<half4, half, 1><<<grid, block, 0, stream>>>(
(half4*)output,
(const half4*)input,
(const half4*)gamma,
(const half4*)beta,
m,
n);
}
} //if ((n % 4 == 0) && (n >= 128) && (n <= 4096))
else if (n % 2 == 0) {
if (n / 2 <= 1024) {
block.x = (n/2 + 31)/32*32;
if (std::is_same<T, float>::value) {
layernorm_twoPassAlgo_stored_locally_e2<float2, float, 1><<<grid, block, 0, stream>>>(
(float2*)output,
(const float2*)input,
(const float2*)gamma,
(const float2*)beta,
m,
n);
} //if (std::is_same<T, float>::value)
else {
layernorm_twoPassAlgo_stored_locally_e2<half2, half, 1><<<grid, block, 0, stream>>>(
(half2*)output,
(const half2*)input,
(const half2*)gamma,
(const half2*)beta,
m,
n);
}
} // if (n / 2 <= 1024)
else if (n <= 8192) {
block.x = ((n + 7)/8 + 31)/32*32;
if (std::is_same<T, float>::value) {
layernorm_twoPassAlgo_stored_locally_e2<float2, float, 4><<<grid, block, 0, stream>>>(
(float2*)output,
(const float2*)input,
(const float2*)gamma,
(const float2*)beta,
m,
n);
} // if (std::is_same<T, float>::value)
else {
layernorm_twoPassAlgo_stored_locally_e2<half2, half, 4><<<grid, block, 0, stream>>>(
(half2*)output,
(const half2*)input,
(const half2*)gamma,
(const half2*)beta,
m,
n);
}
} // if (n <= 8192)
else if (n <= 16384) {
block.x = ((n + 15)/ 16 + 31)/32*32;
if (std::is_same<T, float>::value) {
layernorm_twoPassAlgo_stored_locally_e2<float2, float, 8><<<grid, block, 0, stream>>>(
(float2*)output,
(const float2*)input,
(const float2*)gamma,
(const float2*)beta,
m,
n);
} // if (std::is_same<T, float>::value)
else {
layernorm_twoPassAlgo_stored_locally_e2<half2, half, 8><<<grid, block, 0, stream>>>(
(half2*)output,
(const half2*)input,
(const half2*)gamma,
(const half2*)beta,
m,
n);
}
} // if (n <= 16384)
else if (n <= 32768) {
block.x = ((n + 31)/32 + 31)/32*32;
if (std::is_same<T, float>::value) {
layernorm_twoPassAlgo_stored_locally_e2<float2, float, 16><<<grid, block, 0, stream>>>(
(float2*)output,
(const float2*)input,
(const float2*)gamma,
(const float2*)beta,
m,
n);
} // if (std::is_same<T, float>::value)
else {
layernorm_twoPassAlgo_stored_locally_e2<half2, half, 16><<<grid, block, 0, stream>>>(
(half2*)output,
(const half2*)input,
(const half2*)gamma,
(const half2*)beta,
m,
n);
}
} // if (n <= 32768)
else {
if (block.x > 512)
block.x = 512;
if (std::is_same<T, float>::value) {
layernorm_twoPassAlgo_e2<float2, float><<<grid, block, 0, stream>>>(
(float2 *)output,
(const float2 *)input,
(const float2 *)gamma,
(const float2 *)beta,
m,
n);
} // if (std::is_same<T, float>::value)
else {
layernorm_twoPassAlgo_e2<half2, half><<<grid, block, 0, stream>>>(
(half2 *)output,
(const half2 *)input,
(const half2 *)gamma,
(const half2 *)beta,
m,
n);
}
}
} // if (n % 2 == 0)
else {
if (n <= 1024) {
layernorm_twoPassAlgo_stored_locally_e1<T, 1><<<grid, block, 0, stream>>>(
output,
input,
gamma,
beta,
m,
n);
} // if (n <= 1024)
else if (n <= 8192) {
block.x = ((n + 7)/8 + 31)/32*32;
layernorm_twoPassAlgo_stored_locally_e1<T, 8><<<grid, block, 0, stream>>>(
output,
input,
gamma,
beta,
m,
n);
} // if (n <= 8192)
else if (n <= 16384) {
block.x = ((n + 15)/16 + 32)/32*32;
layernorm_twoPassAlgo_stored_locally_e1<T, 16><<<grid, block, 0, stream>>>(
output,
input,
gamma,
beta,
m,
n);
} // if (n <= 16384)
else if (n <= 32768) {
block.x = ((n + 31)/32 + 31)/32*32;
layernorm_twoPassAlgo_stored_locally_e1<T, 32><<<grid, block, 0, stream>>>(
output,
input,
gamma,
beta,
m,
n);
} // if (n <= 32768)
else{
if (block.x > 512) {
block.x = 512;
}
layernorm_twoPassAlgo_e1<<<grid, block, 0, stream>>>(
output,
input,
gamma,
beta,
m,
n);
}
}
}
} //namespace cutlass

View File

@@ -0,0 +1,576 @@
/******************************************************************************
* Copyright (c) 2017 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 TORT (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 cuda kernels to do avg/max pooling on a device memory tensor with NHWC layout.
*/
#include "cutlass/cutlass.h"
#include "cutlass/layout/tensor.h"
#include "cutlass/numeric_types.h"
#include "cutlass/tensor_coord.h"
#include "cutlass/tensor_ref.h"
#include "device_utils.h"
#include <float.h>
namespace cutlass {
/** \brief interface to do avg/max pooling on a device memory tensor with NHWC layout.
* \tparam T: data type
*/
template <typename T>
void pooling_nhwc(cutlass::Tensor4DCoord input_tensor_size,
cutlass::Tensor4DCoord filter_tensor_size,
cutlass::Tensor4DCoord output_tensor_size,
cutlass::MatrixCoord padding,
cutlass::MatrixCoord stride,
TensorRef<T, layout::TensorNHWC> ref_input,
TensorRef<T, layout::TensorNHWC> ref_output,
int poolingType, //0 for avg pooling ; 1 for max pooling
cudaStream_t stream);
/** get the output size of pooling
*/
inline int getOutputSize(int H_W, int padding, int kernel_size, int stride)
{
return (H_W + 2 * padding - kernel_size) / stride + 1;
}
/**
* input is [N, H, W, C]
* assume stride == kernel_size
* output_h = (H + 2*padding_H - kernel_H)/stride_H
* output_w = (W + 2*padding_W - kernel_W)/stride_W
* output is [N, output_h, output_w, C]
* grid(N, output_h, output_w)
* block(min(C, 256)) :
* each block deals with C elements of output when each thread deals with ((C + 255)/256 element of output)
*/
template<typename T, bool IS_AVG_POOLING>
__global__ void pooling_nhwc_element1_kernel(T* output,
const T* input,
const int N,
const int H,
const int W,
const int C,
const int output_H,
const int output_W,
const int kernel_H,
const int kernel_W,
const int stride_H,
const int stride_W,
const int padding_H,
const int padding_W)
{
const int tid = threadIdx.x;
const int n_idx = blockIdx.x;
const int output_h_idx = blockIdx.y;
const int output_w_idx = blockIdx.z;
int h_start_idx = output_h_idx * stride_H - padding_H;
int h_end_idx = h_start_idx + kernel_H;
h_start_idx = (h_start_idx < 0) ? 0 : h_start_idx;
h_end_idx = h_end_idx > H ? H : h_end_idx;
int w_start_idx = output_w_idx * stride_W - padding_W;
int w_end_idx = w_start_idx + kernel_W;
w_start_idx = (w_start_idx < 0) ? 0 : w_start_idx;
w_end_idx = w_end_idx > W ? W : w_end_idx;
input += n_idx * H * W * C;
output += ((n_idx * output_H + output_h_idx) * output_W + output_w_idx) * C;
const int kernel_size2 = kernel_H * kernel_W;
for (int c_idx = tid; c_idx < C; c_idx += blockDim.x) {
float pooling;
if (IS_AVG_POOLING){
pooling = 0.0f;
}
else{
pooling = -FLT_MAX;
}
for (int h = h_start_idx; h < h_end_idx; h++) {
for (int w = w_start_idx; w < w_end_idx; w++) {
const int idx = (h * W + w) * C;
const float tmp = static_cast<float>(input[idx + c_idx]);
if (IS_AVG_POOLING){
pooling = pooling + tmp;
}
else{
pooling = pooling > tmp ? pooling : tmp;
}
}
}
T output_val;
if (IS_AVG_POOLING){
output_val = T(pooling/kernel_size2);
}
else{
output_val = T(pooling);
}
output[c_idx] = output_val;
}
}
template<typename T2, typename T, bool IS_AVG_POOLING>
__global__ void pooling_nhwc_element2_kernel(T2* output,
const T2* input,
const int N,
const int H,
const int W,
const int C,
const int output_H,
const int output_W,
const int kernel_H,
const int kernel_W,
const int stride_H,
const int stride_W,
const int padding_H,
const int padding_W)
{
const int tid = threadIdx.x;
const int n_idx = blockIdx.x;
const int output_h_idx = blockIdx.y;
const int output_w_idx = blockIdx.z;
int h_start_idx = output_h_idx * stride_H - padding_H;
int h_end_idx = h_start_idx + kernel_H;
h_start_idx = (h_start_idx < 0) ? 0 : h_start_idx;
h_end_idx = h_end_idx > H ? H : h_end_idx;
int w_start_idx = output_w_idx * stride_W - padding_W;
int w_end_idx = w_start_idx + kernel_W;
w_start_idx = (w_start_idx < 0) ? 0 : w_start_idx;
w_end_idx = w_end_idx > W ? W : w_end_idx;
input += n_idx * H * W * C;
output += ((n_idx * output_H + output_h_idx) * output_W + output_w_idx) * C;
const int kernel_size2 = kernel_H * kernel_W;
for (int c_idx = tid; c_idx < C; c_idx += blockDim.x) {
float2 pooling;
if (IS_AVG_POOLING) {
pooling = {0.0f, 0.0f};
}
else {
pooling = {-FLT_MAX, -FLT_MAX};
}
for (int h = h_start_idx; h < h_end_idx; h++) {
for (int w = w_start_idx; w < w_end_idx; w++) {
const int idx = (h * W + w) * C;
const T2 tmp = input[idx + c_idx];
const float2 tmp_flt2 = {static_cast<float>(tmp.x), static_cast<float>(tmp.y)};
if (IS_AVG_POOLING) {
pooling.x += tmp_flt2.x;
pooling.y += tmp_flt2.y;
}
else {
pooling.x = pooling.x > tmp_flt2.x ? pooling.x : tmp_flt2.x;
pooling.y = pooling.y > tmp_flt2.y ? pooling.y : tmp_flt2.y;
}
}
}
T2 output_val;
if (IS_AVG_POOLING) {
output_val.x = T(pooling.x/kernel_size2);
output_val.y = T(pooling.y/kernel_size2);
}
else {
output_val.x = T(pooling.x);
output_val.y = T(pooling.y);
}
output[c_idx] = output_val;
}
}
/**
* output [N, 1, 1, C]
* input [N, H, W, C]
* grid(C, N)
* block(block_size) -- each block deals with H*W/block_size elements;
*/
template<typename T, bool IS_AVG_POOLING>
__global__ void pooling_nxhTo1x1_element1_kernel(
T* output, const T* input, const int N, const int HW, const int C)
{
const int c_idx = blockIdx.x;
const int n_idx = blockIdx.y;
float pooling[1];
if (IS_AVG_POOLING) {
pooling[0] = 0.0f;
}
else {
pooling[0] = -FLT_MAX;
}
const size_t input_offset = n_idx * HW * C + c_idx;
input += input_offset;
const size_t output_offset = n_idx * C + c_idx;
output += output_offset;
int tid = threadIdx.x;
for (int index = tid; index < HW; index += blockDim.x) {
float val = static_cast<float>(input[index * C]);
if (IS_AVG_POOLING) {
pooling[0] += val;
}
else {
pooling[0] = pooling[0] > val ? pooling[0] : val;
}
}
if (blockDim.x <= 32) {
if (IS_AVG_POOLING) {
warpReduceSum<float, 1>(pooling);
}
else {
warpReduceMax<float, 1>(pooling);
}
}
else {
if (IS_AVG_POOLING) {
blockReduceSum<float, 1>(pooling);
}
else {
blockReduceMax<float, 1>(pooling);
}
}
__syncthreads();
if (threadIdx.x == 0) {
T output_val;
if (IS_AVG_POOLING) {
output_val = T(pooling[0] / HW);
}
else {
output_val = T(pooling[0]);
}
output[0] = output_val;
}
}
/**
* output [N, 1, 1, C]
* input [N, H, W, C]
* grid(C/2, N)
* block(block_size) -- each thread deals with H*W/block_size * 2 elements;
*/
template<typename T2, typename T, bool IS_AVG_POOLING>
__global__ void pooling_nxhTo1x1_element2_kernel(
T2* output, const T2* input, const int N, const int HW, const int C)
{
const int c_idx = blockIdx.x;
const int n_idx = blockIdx.y;
float pooling[2];
if (IS_AVG_POOLING) {
pooling[0] = pooling[1] = 0.0f;
}
else {
pooling[0] = pooling[1] = -FLT_MAX;
}
const int C_2 = C / 2;
const size_t input_offset = n_idx * HW * C_2 + c_idx;
input += input_offset;
const size_t output_offset = n_idx * C_2 + c_idx;
output += output_offset;
int tid = threadIdx.x;
for (int index = tid; index < HW; index += blockDim.x) {
T2 val = input[index * C_2];
float2 val_flt2 = {static_cast<float>(val.x), static_cast<float>(val.y)};
if (IS_AVG_POOLING) {
pooling[0] += val_flt2.x;
pooling[1] += val_flt2.y;
}
else {
pooling[0] = pooling[0] > val_flt2.x ? pooling[0] : val_flt2.x;
pooling[1] = pooling[1] > val_flt2.y ? pooling[1] : val_flt2.y;
}
}
if (blockDim.x <= 32) {
if (IS_AVG_POOLING) {
warpReduceSum<float, 2>(pooling);
}
else {
warpReduceMax<float, 2>(pooling);
}
}
else {
if (IS_AVG_POOLING) {
blockReduceSum<float, 2>(pooling);
}
else {
blockReduceMax<float, 2>(pooling);
}
}
__syncthreads();
if (threadIdx.x == 0) {
T2 output_val;
if (IS_AVG_POOLING) {
output_val.x = T(pooling[0] / HW);
output_val.y = T(pooling[1] / HW);
}
else {
output_val.x = T(pooling[0]);
output_val.y = T(pooling[1]);
}
output[0] = output_val;
}
}
template <typename T>
void pooling_nhwc(cutlass::Tensor4DCoord input_tensor_size,
cutlass::Tensor4DCoord filter_tensor_size,
cutlass::Tensor4DCoord output_tensor_size,
cutlass::Tensor4DCoord padding,
cutlass::MatrixCoord stride,
TensorRef<T, layout::TensorNHWC> ref_input,
TensorRef<T, layout::TensorNHWC> ref_output,
int poolingType, //0 for avg pooling ; 1 for max pooling
cudaStream_t stream) {
assert(input_tensor_size.n() == output_tensor_size.n() &&
input_tensor_size.c() == output_tensor_size.c());
assert(filter_tensor_size.h() == stride.row() &&
filter_tensor_size.w() == stride.column());
const int N = input_tensor_size.n();
const int H = input_tensor_size.h();
const int W = input_tensor_size.w();
const int C = input_tensor_size.c();
const int padding_H = padding.h();
const int padding_W = padding.w();
const int kernel_H = filter_tensor_size.h();
const int kernel_W = filter_tensor_size.w();
const int stride_H = stride.row();
const int stride_W = stride.column();
const int output_H = getOutputSize(H, padding_H, kernel_H, stride_H);
const int output_W = getOutputSize(W, padding_W, kernel_W, stride_W);
assert(output_tensor_size.h() == output_H &&
output_tensor_size.w() == output_W);
if (C % 2 != 0) {
if ((H == kernel_H && padding_H == 0) && (W == kernel_W && padding_W == 0)) {
dim3 grid(C, N);
dim3 block(256);
if (H*W < block.x){
block.x = (H*W + 31)/32*32;
}
if (poolingType == 0) {
pooling_nxhTo1x1_element1_kernel<T, true><<<grid, block, 0, stream>>>(
ref_output.data(),
ref_input.data(),
N,
H*W,
C);
} // if (poolingType == 0)
else {
pooling_nxhTo1x1_element1_kernel<T, false><<<grid, block, 0, stream>>>(
ref_output.data(),
ref_input.data(),
N,
H*W,
C);
}
} // if ((H == kernel_H && padding_H == 0) && (W == kernel_W && padding_W == 0))
else {
dim3 grid(N, output_H, output_W);
dim3 block(256);
if (C < block.x) {
block.x = C;
}
if (poolingType == 0) {
pooling_nhwc_element1_kernel<T, true><<<grid, block, 0, stream>>>(
ref_output.data(),
ref_input.data(),
N,
H,
W,
C,
output_H,
output_W,
kernel_H,
kernel_W,
stride_H,
stride_W,
padding_H,
padding_W);
} // if (poolingType == 0)
else {
pooling_nhwc_element1_kernel<T, false><<<grid, block, 0, stream>>>(
ref_output.data(),
ref_input.data(),
N,
H,
W,
C,
output_H,
output_W,
kernel_H,
kernel_W,
stride_H,
stride_W,
padding_H,
padding_W);
}
}
} // if (C % 2 != 0))
else {
if ((H == kernel_H && padding_H == 0) && (W == kernel_W && padding_W == 0)) {
dim3 grid(C/2, N);
dim3 block(256);
if (H*W < block.x){
block.x = (H*W + 31)/32*32;
}
if (poolingType == 0) {
if (std::is_same<T, float>::value) {
pooling_nxhTo1x1_element2_kernel<float2, float, true><<<grid, block, 0, stream>>>(
(float2*)(ref_output.data()),
(const float2*)(ref_input.data()),
N,
H*W,
C);
} // if (std::is_same<T, float>::value)
else {
pooling_nxhTo1x1_element2_kernel<half2, half, true><<<grid, block, 0, stream>>>(
(half2*)(ref_output.data()),
(const half2*)(ref_input.data()),
N,
H*W,
C);
}
} // if (poolingType == 0)
else {
if (std::is_same<T, float>::value) {
pooling_nxhTo1x1_element2_kernel<float2, float, false><<<grid, block, 0, stream>>>(
(float2*)(ref_output.data()),
(const float2*)(ref_input.data()),
N,
H*W,
C);
} // if (std::is_same<T, float>::value)
else {
pooling_nxhTo1x1_element2_kernel<half2, half, false><<<grid, block, 0, stream>>>(
(half2*)(ref_output.data()),
(const half2*)(ref_input.data()),
N,
H*W,
C);
}
}
} // if ((H == kernel_H && padding_H == 0) && (W == kernel_W && padding_W == 0))
else {
dim3 grid(N, output_H, output_W);
dim3 block(256);
if (C/2 < block.x) {
block.x = C/2;
}
if (poolingType == 0) {
if (std::is_same<T, float>::value) {
pooling_nhwc_element2_kernel<float2, float, true><<<grid, block, 0, stream>>>(
(float2*)(ref_output.data()),
(const float2*)(ref_input.data()),
N,
H,
W,
C/2,
output_H,
output_W,
kernel_H,
kernel_W,
stride_H,
stride_W,
padding_H,
padding_W);
} // if (std::is_same<T, float>::value)
else {
pooling_nhwc_element2_kernel<half2, half, true><<<grid, block, 0, stream>>>(
(half2*)(ref_output.data()),
(const half2*)(ref_input.data()),
N,
H,
W,
C/2,
output_H,
output_W,
kernel_H,
kernel_W,
stride_H,
stride_W,
padding_H,
padding_W);
}
} // if (poolingType == 0)
else {
if (std::is_same<T, float>::value) {
pooling_nhwc_element2_kernel<float2, float, false><<<grid, block, 0, stream>>>(
(float2*)(ref_output.data()),
(const float2*)(ref_input.data()),
N,
H,
W,
C/2,
output_H,
output_W,
kernel_H,
kernel_W,
stride_H,
stride_W,
padding_H,
padding_W);
} // if (std::is_same<T, float>::value)
else {
pooling_nhwc_element2_kernel<half2, half, false><<<grid, block, 0, stream>>>(
(half2*)(ref_output.data()),
(const half2*)(ref_input.data()),
N,
H,
W,
C/2,
output_H,
output_W,
kernel_H,
kernel_W,
stride_H,
stride_W,
padding_H,
padding_W);
}
}
}
}
}
} //namespace cutlass

View File

@@ -0,0 +1,127 @@
/***************************************************************************************************
* Copyright (c) 2017 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 TORT (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 utils code for device cutlass code
*/
#pragma once
#include <cuda_fp16.h>
#include <float.h>
#define FINAL_MASK 0xffffffff
struct half4 {
half x, y, z, w;
};
template<typename T, int NUM>
__inline__ __device__ T warpReduceSum(T* val)
{
#pragma unroll
for (int i = 0; i < NUM; i++) {
#pragma unroll
for (int mask = 16; mask > 0; mask >>= 1)
val[i] += __shfl_xor_sync(FINAL_MASK, val[i], mask, 32);
}
return (T)(0.0f);
}
template<typename T, int NUM>
__inline__ __device__ T blockReduceSum(T* val)
{
__shared__ T shared[NUM][33];
int lane = threadIdx.x & 0x1f;
int wid = threadIdx.x >> 5;
warpReduceSum<T, NUM>(val);
if (lane == 0) {
#pragma unroll
for (int i = 0; i < NUM; i++) {
shared[i][wid] = val[i];
}
}
__syncthreads();
bool is_mask = threadIdx.x < (blockDim.x / 32.f);
#pragma unroll
for (int i = 0; i < NUM; i++) {
val[i] = is_mask ? shared[i][lane] : (T)(0.0f);
}
warpReduceSum<T, NUM>(val);
return (T)0.0f;
}
template<typename T, int NUM>
__inline__ __device__ T warpReduceMax(T* val)
{
#pragma unroll
for (int i = 0; i < NUM; i++) {
#pragma unroll
for (int mask = 16; mask > 0; mask >>= 1)
val[i] = max(val[i], __shfl_xor_sync(FINAL_MASK, val[i], mask, 32));
}
return (T)(0.0f);
}
template<typename T, int NUM>
__inline__ __device__ T blockReduceMax(T* val)
{
static __shared__ T shared[32][NUM];
int lane = threadIdx.x & 0x1f; // in-warp idx
int wid = threadIdx.x >> 5; // warp idx
warpReduceMax<T, NUM>(val); // get maxx in each warp
if (lane == 0) // record in-warp maxx by warp Idx
{
#pragma unroll
for (int i = 0; i < NUM; i++) {
shared[wid][i] = val[i];
}
}
__syncthreads();
// Modify from blockDim.x << 5 to blockDim.x / 32. to prevent
// blockDim.x is not divided by 32
bool is_mask = threadIdx.x < (blockDim.x / 32.f);
#pragma unroll
for (int i = 0; i < NUM; i++) {
val[i] = is_mask ? shared[lane][i] : (T)(-FLT_MAX);
}
warpReduceMax<T, NUM>(val);
return (T)0.0f;
}

View File

@@ -123,11 +123,17 @@ __global__ void Conv2dFprop(
}
}
int c_per_group = problem_size.C / problem_size.groups;
int k_per_group = problem_size.K / problem_size.groups;
// Compute convolution
for (int R = 0; R < problem_size.R; ++R) {
for (int S = 0; S < problem_size.S; ++S) {
for (int C = 0; C < problem_size.C; ++C) {
// Get group id of currnet channel
int c_group_idx = C / c_per_group;
// Load from activations tensor
int filter_r = R;
int filter_s = S;
@@ -154,9 +160,10 @@ __global__ void Conv2dFprop(
CUTLASS_PRAGMA_UNROLL
for (int n = 0; n < kThreadN; ++n) {
int thread_k = k_start + n;
int k_group_idx = thread_k / k_per_group;
if (thread_k < problem_size.K) {
element_B[n] = ElementAccumulator(tensor_w.at({thread_k, R, S, C}));
if (thread_k < problem_size.K && k_group_idx == c_group_idx) {
element_B[n] = ElementAccumulator(tensor_w.at({thread_k, R, S, C % c_per_group}));
}
else {
element_B[n] = ElementAccumulator();

View File

@@ -86,11 +86,14 @@ void Conv2dFprop(
for (int q = 0; q < problem_size.Q; ++q) {
for (int k = 0; k < problem_size.K; ++k) {
int group_idx = k / (problem_size.K / problem_size.groups);
int channels_per_group = problem_size.C / problem_size.groups;
ElementAccumulator acc = ElementAccumulator();
for (int r = 0; r < problem_size.R; ++r) {
for (int s = 0; s < problem_size.S; ++s) {
for (int c = 0; c < problem_size.C; ++c) {
for (int c = 0; c < channels_per_group; ++c) {
int filter_r = r;
int filter_s = s;
@@ -105,7 +108,7 @@ void Conv2dFprop(
if (h >= 0 && h < problem_size.H && w >= 0 && w < problem_size.W) {
ElementA a = tensor_x.at({n, h, w, c});
ElementA a = tensor_x.at({n, h, w, c + group_idx * channels_per_group});
ElementB b = tensor_w.at({k, r, s, c});
acc = inner_product_op(ElementAccumulator(a), ElementAccumulator(b), acc);
@@ -137,21 +140,21 @@ template <typename ElementA,
typename LayoutB,
typename ElementC,
typename LayoutC,
typename ElementAccumulator,
typename ElementCompute,
typename ElementAccumulator = ElementCompute,
typename ConvertOp = NumericConverter<ElementC, ElementCompute>,
typename InnerProductOp = multiply_add<ElementAccumulator> >
void Depsep_Fprop(
cutlass::TensorView<ElementA, LayoutA> tensor_A,
void Depsep_Fprop(cutlass::TensorView<ElementA, LayoutA> tensor_A,
cutlass::TensorView<ElementB, LayoutB> tensor_B,
cutlass::TensorView<ElementC, LayoutC> tensor_C,
cutlass::TensorView<ElementC, LayoutC> tensor_D,
ElementCompute alpha,
ElementCompute beta,
cutlass::Tensor4DCoord padding,
cutlass::Coord<2> conv_stride,
cutlass::Coord<2> dilation,
cutlass::Tensor4DCoord padding = cutlass::Tensor4DCoord(),
cutlass::Coord<2> conv_stride = cutlass::Coord<2>(),
cutlass::Coord<2> dilation = cutlass::Coord<2>(),
cutlass::conv::Mode mode = cutlass::conv::Mode::kCrossCorrelation) {
ConvertOp convert_op;
InnerProductOp inner_product_op;
@@ -163,15 +166,13 @@ void Depsep_Fprop(
ElementAccumulator acc = ElementAccumulator();
for (int r = 0; r < tensor_B.extent().h(); ++r) {
for (int s = 0; s < tensor_B.extent().w(); ++s) {
if ((p * conv_stride[0] - padding[0] + r * dilation[0]) < tensor_A.extent().h() &&
(p * conv_stride[0] - padding[0] + r * dilation[0]) >= 0 &&
(q * conv_stride[1] - padding[2] + s * dilation[1]) < tensor_A.extent().w() &&
(q * conv_stride[1] - padding[2] + s * dilation[1]) >= 0) {
ElementA a = tensor_A.at(
cutlass::make_Coord(n,
p * conv_stride[0] - padding[0] + r * dilation[0],
q * conv_stride[1] - padding[2] + s * dilation[1],
g));
// input activation H and W
int h = p * conv_stride[0] - padding[0] + r * dilation[0];
int w = q * conv_stride[1] - padding[2] + s * dilation[1];
if (h < tensor_A.extent().h() && h >= 0 && w < tensor_A.extent().w() && w >= 0) {
ElementA a = tensor_A.at(cutlass::make_Coord(n, h, w, g));
ElementB b = (mode == cutlass::conv::Mode::kCrossCorrelation)
? tensor_B.at(cutlass::make_Coord(g, r, s, 0))
@@ -185,7 +186,7 @@ void Depsep_Fprop(
// Apply Epilogue, compute ElementCompute, convert and store ElementC
ElementC c_ref = tensor_C.at(cutlass::make_Coord(n, p, q, g));
tensor_C.at(cutlass::make_Coord(n, p, q, g)) =
tensor_D.at(cutlass::make_Coord(n, p, q, g)) =
convert_op(alpha * ElementCompute(acc) + beta * ElementCompute(c_ref));
}
}

View File

@@ -694,10 +694,6 @@ struct TensorFillSymmetricRandomUniformFunc {
}
};
//
// We expect to release this with CUTLASS 2.4. -akerr
/// Computes a random Uniform distribution and pads diagonal with zeros
template <
typename Element, ///< Element type