@@ -6,32 +6,23 @@
|
||||
|
||||
## Hierarchical Organization
|
||||
|
||||
CUTLASS embodies a design paradigm exemplified by the [CUB library](https://nvlabs.github.io/cub/)
|
||||
for expressing collective operations. Objects expose an interface for a problem that is then decomposed
|
||||
into concurrent subtasks executed by cooperating threadblocks, warps, and threads. For example, a grid-level
|
||||
object may be constructed with base pointers to the start of a GEMM operation, add a threadblock-dependent
|
||||
offset to partition the problem, and then compute a per-threadblock GEMM. This in turn performs some
|
||||
operations as a collection of cooperating threads, while it may partition other parts of the task into
|
||||
warp-level subtasks.
|
||||
|
||||
Consequently, CUTLASS components are organized by the computation then by the layer of
|
||||
the following hierarchy.
|
||||
|
||||
* *device*: an operation is _device-wide_ and may launch one or more kernels on the GPU
|
||||
* *kernel*: an operation is implemented by a CUDA kernel with definitions for `__shared__` memory and constant memory allocations
|
||||
* *threadblock*: an operation is collectivey executed by a threadblock; any component calling `__syncthreads()` is likely to be threadblock-scope
|
||||
* *warp*: an operation is collectively executed by a warp; threads within the context of a warp are referred to as _lane_
|
||||
* *thread*: an operation is performed by an individual thread with no other data sharing or interaction with other threads
|
||||
* *instruction*: an operation corresponds to an individual hardware or PTX instruction
|
||||
The [CUTLASS 3.0 GEMM API](./gemm_api_3x.md) document
|
||||
explains CUTLASS 3.0's hierarchical organization,
|
||||
based conceptually on parallelization strategy.
|
||||
This differs from CUTLASS 2.x's approach,
|
||||
which more closely mirrors the GPU hardware hierarchy
|
||||
of thread blocks, warps, and threads.
|
||||
|
||||
## Design Patterns
|
||||
|
||||
CUTLASS strives to achieve the highest performance possible on NVIDIA GPUs while also offering a
|
||||
flexible composition that an be easily applied to solve new problems related to Deep Learning and
|
||||
linear algebra. Though we intend to make CUTLASS as simple and straightforward as possible, given
|
||||
a tradeoff between simplicity and performance, CUTLASS chooses performance. Consequently, several
|
||||
design patterns are necessary to yield a composable structure while also satisfying these performance
|
||||
objectives. This section is intended to provide more detail.
|
||||
CUTLASS aims for the highest performance possible on NVIDIA GPUs.
|
||||
It also offers flexible components that can be assembled and customized
|
||||
to solve new problems related to deep learning and linear algebra.
|
||||
Given a tradeoff between simplicity and performance,
|
||||
CUTLASS chooses performance.
|
||||
Consequently, several design patterns are necessary
|
||||
to yield a composable structure
|
||||
while also satisfying these performance objectives.
|
||||
|
||||
### Templates
|
||||
|
||||
@@ -75,8 +66,9 @@ objects for each data member.
|
||||
|
||||
To be consistent, this pattern defines a convention in which classes define internal shared memory storage requirements.
|
||||
Classes should consider all SharedStorage structures to be opaque other than their own child class. When the lifetimes
|
||||
of child objects are known to be non-overlapping, unions may be used to alias multiple SharedStorage objects to the same
|
||||
shared memory region and reduce overall SMEM capacity.
|
||||
of child objects are known to be non-overlapping, `union`s may be used to alias multiple SharedStorage objects to the same
|
||||
shared memory region and reduce overall shared memory capacity. Developers should carefully note that C++ `union` rules
|
||||
require that they only access the most recently written ("active") member of the `union`; this differs from C rules.
|
||||
|
||||
### Loop Unrolling
|
||||
|
||||
@@ -104,123 +96,578 @@ for (int idx = 0; idx < kN; ++idx) { // Loop has constant number of iterati
|
||||
|
||||
## Style
|
||||
|
||||
### C++ Style
|
||||
### No automatic code formatting
|
||||
|
||||
CUTLASS source code follows the
|
||||
[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) with exceptions and extensions.
|
||||
Do not use any kind of automatic code formatting,
|
||||
like `clang-format`, on CUTLASS code.
|
||||
|
||||
Design choices should be consistent with the
|
||||
[CppCoreGuidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) recommendations by Stroustrup and Sutter.
|
||||
### C++ style
|
||||
|
||||
### CUDA Built-in Variables
|
||||
#### CUTLASS is a C++ project
|
||||
|
||||
Avoid direct access to CUDA built-in variables `threadIdx`, `blockIdx`, `blockDim`, and `gridDim` within
|
||||
CUTLASS components except in special circumstances.
|
||||
CUTLASS is a C++ project. CUDA C++ is a C++ dialect.
|
||||
Therefore, we write using standard C++ idioms as much as possible.
|
||||
We aim for portability to as many compilers as possible,
|
||||
by writing host code in Standard C++
|
||||
and device code in CUDA C++
|
||||
that resembles Standard C++ as much as possible.
|
||||
This improves usability
|
||||
for the general community of C++ developers,
|
||||
and makes it easier for new staff to join the project.
|
||||
|
||||
Using built-in 'global' variables directly within resuable components necessitates that all components
|
||||
use them consistently which may not be possible if CUTLASS components are used in other contexts.
|
||||
#### Follow Standard C++ idioms where possible
|
||||
|
||||
Instead, components should accept a linear ID identifying threads, warps, and threadblocks from calling
|
||||
code. The top-level kernel may then decide how to map threads, warps, and blocks to the problem it is
|
||||
solving.
|
||||
Regarding "standard C++ idioms,"
|
||||
CUTLASS source code follows the following guidelines,
|
||||
with deviations only because of compiler limitations
|
||||
or where performance absolutely requires it.
|
||||
"Performance requires it" implies measurement.
|
||||
Deviations should be limited in scope
|
||||
and we should always strive to eliminate them.
|
||||
|
||||
### Use CUTLASS Fundamental Types
|
||||
* [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md)
|
||||
|
||||
Use the [fundamental types](fundamental_types.md) defined in CUTLASS consistently. Doing so contributes
|
||||
to a framework of interoperable, consistent components.
|
||||
* [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
|
||||
|
||||
In particular, be sure to use:
|
||||
#### Spacing and line length
|
||||
|
||||
* [Numeric types](fundamental_types.md#numeric-types) to represent numeric data in host and device code
|
||||
* [Containers](fundamental_types.md#containers) to store data in register-backed arrays
|
||||
* [functional.h](fundamental_types.md#functional) to perform numeric operations in generic code
|
||||
* [Layouts](layout.md) to store stride and partially specialize template classes
|
||||
* [`TensorRef` and `TensorView`](layout.md#tensorref) to pass pointers and layout objects
|
||||
* Use spaces, not tabs.
|
||||
|
||||
Avoid defining alternative implementations of the same functionality. Instead, prefer to enhance
|
||||
or extend additional components where it makes sense.
|
||||
* Use 2 spaces to indent.
|
||||
|
||||
### Classes and Structs
|
||||
* Max 100 characters per line.
|
||||
|
||||
Type names use `CapitalLetters` except when implementations are a _perfect_ drop-in replacement for
|
||||
Standard Library components.
|
||||
Lines longer than 100 characters typically wrap unfavorably
|
||||
when viewed in Github's pretty printer.
|
||||
|
||||
Follow the [CppCoreGuidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-struct)
|
||||
to decide whether to use `class` or `struct`. Namely,
|
||||
* use `class` when the object must maintain an invariant. Data members related to the invariant should be private.
|
||||
* use `struct` when the class has no invariant to maintain, and data members may vary arbitrarily.
|
||||
#### Function indentation
|
||||
|
||||
### Class Members
|
||||
When calling a function or function object with a long name,
|
||||
break the line right after the invoking open parenthesis.
|
||||
Here is an example.
|
||||
|
||||
```c++
|
||||
detail::very_long_function_object_name<TemplateArgument>{}(
|
||||
params.long_parameter_name, some_operator.another_long_function_name());
|
||||
```
|
||||
|
||||
When declaring functions, indent function parameters like this.
|
||||
|
||||
```c++
|
||||
void possibly_an_unusually_long_function_name(
|
||||
std::uint32_t foo
|
||||
std::uint32_t const* bar,
|
||||
TypeA a,
|
||||
TypeB b,
|
||||
TypeC c)
|
||||
{
|
||||
// ... the function's body ...
|
||||
}
|
||||
```
|
||||
|
||||
For function definitions only,
|
||||
break the line between the parenthesis
|
||||
that closes the function's parameters,
|
||||
and the curly bracket
|
||||
that opens the function's body.
|
||||
|
||||
#### If-else brackets and spacing
|
||||
|
||||
* Always use braces with conditionals such as `if`.
|
||||
|
||||
* Use a space after control flow keywords
|
||||
such as `if`, `for`, and `while`.
|
||||
|
||||
* Use a space after the parenthesis closing a conditional
|
||||
such as `if`, and the curly bracket opening a scope.
|
||||
|
||||
* Use a new line between the closing brace
|
||||
of an `if` branch, and the `else` keyword.
|
||||
|
||||
```c++
|
||||
if (condition) {
|
||||
// ... code ...
|
||||
}
|
||||
else {
|
||||
// ... other code ...
|
||||
}
|
||||
|
||||
for (int k = 0; k < num_iters; ++k) {
|
||||
// ... still more code ...
|
||||
}
|
||||
```
|
||||
|
||||
#### East const
|
||||
|
||||
CUTLASS uses the
|
||||
["East const"](http://slashslash.info/2018/02/a-foolish-consistency/)
|
||||
convention.
|
||||
That is, the `const` or `constexpr` keyword
|
||||
goes after the type, not before.
|
||||
The general rule is that `const` or `constexpr`
|
||||
modifies the type to the left of it.
|
||||
Here are some examples.
|
||||
|
||||
```c++
|
||||
float constexpr compile_time_constant = 42.3f;
|
||||
|
||||
float const const_float = /* whatever */;
|
||||
float const& reference_to_const_float = const_float;
|
||||
float const* pointer_to_const_float = &const_float;
|
||||
float const* const const_pointer_to_const_float = &const_float;
|
||||
|
||||
float nonconst_float;
|
||||
float& reference_to_nonconst_float = nonconst_float;
|
||||
float* pointer_to_nonconst_float = &nonconst_float;
|
||||
float* const pointer_to_nonconst_float = &nonconst_float;
|
||||
```
|
||||
|
||||
Contrast this with "West const" style, e.g.,
|
||||
|
||||
```c++
|
||||
const float const_float = /* whatever */;
|
||||
const float* pointer_to_const_float = &const_float;
|
||||
```
|
||||
|
||||
#### Alignment of reference and pointer types
|
||||
|
||||
For reference and pointer types,
|
||||
align the `&` resp. `*` flush against the type
|
||||
that it modifies. This is called "left alignment."
|
||||
|
||||
For example, do this:
|
||||
|
||||
```c++
|
||||
int const& var;
|
||||
int const* var;
|
||||
```
|
||||
|
||||
and not this.
|
||||
|
||||
```c++
|
||||
int const &var;
|
||||
int const *var;
|
||||
```
|
||||
|
||||
#### Avoid calling functions "fast" or "optimized"
|
||||
|
||||
Putting words like "fast" or "optimized"
|
||||
in the name of a function
|
||||
assumes that the "fast" path is actually faster.
|
||||
That might be true now, but later changes
|
||||
(in the code, compilers, or GPU hardware)
|
||||
might make it false. In that case,
|
||||
your name could be unintentionally misleading.
|
||||
Consider instead a name that briefly describes
|
||||
the algorithm or feature that is relevant for optimization.
|
||||
For example, `compute_on_host` is more meaningful
|
||||
than `compute_slowly`, and computing on host
|
||||
might be faster in some cases
|
||||
(e.g., if the data are already on host
|
||||
and the algorithm is not GPU-friendly).
|
||||
|
||||
CUTLASS code has not always followed this rule in the past.
|
||||
Some functions and classes might have words like "fast" in their name.
|
||||
New code should follow this rule, however.
|
||||
|
||||
#### Avoid creating unconstrained templated functions with common names
|
||||
|
||||
See [C++ Core Guidelines T.47](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#t47-avoid-highly-visible-unconstrained-templates-with-common-names):
|
||||
"Avoid highly visible unconstrained templates
|
||||
with common names."
|
||||
Argument-dependent lookup (ADL) means that
|
||||
if users call a function name without specifying the namespace,
|
||||
the compiler can find overloads
|
||||
of that function in any namespace.
|
||||
This can lead to ambiguous overloads in users' code,
|
||||
just because they happened to include one of your header files
|
||||
that exposes an unconstrained function template.
|
||||
The following illustrates this
|
||||
with an unconstrained swap overload in the `cutlass` namespace.
|
||||
|
||||
```c++
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
// Uncomment the line below to observe unwarranted build errors.
|
||||
//#define BAD_CUTLASS_SWAP 1
|
||||
|
||||
namespace cutlass {
|
||||
struct Bar {
|
||||
float f;
|
||||
};
|
||||
} // namespace cutlass
|
||||
|
||||
#ifdef BAD_CUTLASS_SWAP
|
||||
namespace cutlass {
|
||||
|
||||
template<class T>
|
||||
void swap(T& a, T& b) // don't do this
|
||||
{
|
||||
T tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
} // namespace cutlass
|
||||
#endif // BAD_CUTLASS_SWAP
|
||||
|
||||
namespace other {
|
||||
|
||||
#ifdef BAD_CUTLASS_SWAP
|
||||
using cutlass::swap;
|
||||
#endif // BAD_CUTLASS_SWAP
|
||||
|
||||
// Imagine for the sake of this example
|
||||
// that "foo" is a less common name,
|
||||
// and that T is constrained via
|
||||
// std::enable_if or a requires clause.
|
||||
template<class T>
|
||||
void foo(T& a, T& b)
|
||||
{
|
||||
// The usual idiom for using std::swap is the "swap two-step":
|
||||
//
|
||||
// 1. import std::swap into the current scope, then
|
||||
// 2. call swap without namespace qualification.
|
||||
//
|
||||
// That won't build if we have another swap
|
||||
// overload available in the scope already.
|
||||
|
||||
using std::swap;
|
||||
swap(a, b); // OBSERVE UNWARRANTED BUILD ERROR HERE
|
||||
}
|
||||
|
||||
} // namespace other
|
||||
|
||||
int main()
|
||||
{
|
||||
int x = 42;
|
||||
int y = 43;
|
||||
other::foo(x, y);
|
||||
assert(x == 43);
|
||||
assert(y == 42);
|
||||
|
||||
cutlass::Bar a{42.0};
|
||||
cutlass::Bar b{43.0};
|
||||
other::foo(a, b);
|
||||
assert(a.f == 43.0);
|
||||
assert(b.f == 42.0);
|
||||
|
||||
// GCC 7.5 std::unique_ptr::reset calls swap,
|
||||
// leading to the same issue as above.
|
||||
// GCC 12.2's implementation of std::unique_ptr
|
||||
// does not have this issue. Nevertheless,
|
||||
// breaking the swap two-step will break users' code,
|
||||
// just by them happening to include your headers.
|
||||
auto ptr = std::make_unique<cutlass::Bar>(cutlass::Bar{666.0f});
|
||||
ptr.reset(new cutlass::Bar{777.0f}); // OBSERVE UNWARRANTED BUILD ERROR HERE
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### Function return values and in-out parameters
|
||||
|
||||
##### Prefer return values to output parameters
|
||||
|
||||
In general, avoid in-out mutable references to return a value.
|
||||
If you need to return multiple values,
|
||||
you can return them by `struct` or `tuple`,
|
||||
rather than by output references.
|
||||
This includes the special case of error reporting
|
||||
by returning either a value or an error code.
|
||||
Please see the next section for details.
|
||||
|
||||
```c++
|
||||
// Instead of passing in-out mutable references ...
|
||||
void not_preferred(float& input_and_output); // not preferred
|
||||
|
||||
// keep functions pure and return value types instead
|
||||
float preferred(float input); // preferred
|
||||
```
|
||||
|
||||
##### Return multiple values by struct or tuple
|
||||
|
||||
Sometimes a function needs to return multiple values. In that case, consider the following, in decreasing order of preference.
|
||||
|
||||
1. Return a `struct`. This lets you name the fields
|
||||
(for more self-documenting code),
|
||||
yet still permits use of structured binding.
|
||||
|
||||
2. Return a `tuple`. If you need a tuple type
|
||||
that works on device, use `cute::tuple`.
|
||||
(Please note that `cute::tuple` does not work
|
||||
for all the types that work in `std::tuple`.
|
||||
CuTe's documentation explains.)
|
||||
|
||||
Here is an example of the struct approach for named values.
|
||||
For a comparable example in the C++ Standard,
|
||||
please see [`std::allocate_at_least`](https://en.cppreference.com/w/cpp/memory/allocate_at_least),
|
||||
which returns `std::allocation_result`.
|
||||
|
||||
```c++
|
||||
struct my_computation_result {
|
||||
float value = 0.0f;
|
||||
float relative_error = 0.0f;
|
||||
bool success = false;
|
||||
};
|
||||
|
||||
my_computation_result my_computation(float tolerance);
|
||||
|
||||
void foo(float tolerance)
|
||||
{
|
||||
// Approach 1: Use structured binding. The names
|
||||
// you choose on the left-hand side have nothing
|
||||
// to do with the struct, so it's up to you
|
||||
// to get the order right. On the other hand,
|
||||
// this code works whether my_computation returns
|
||||
// a struct or a tuple.
|
||||
auto [val, rel_err, ok] = my_computation(tolerance);
|
||||
|
||||
// Approach 2: Keep the struct and use its named fields.
|
||||
// This approach prevents errors like mixing the order of return types.
|
||||
// However, it only works for structs, not for tuples.
|
||||
|
||||
auto result = my_computation(tolerance);
|
||||
if (not result.success) {
|
||||
// computation did not succeed
|
||||
}
|
||||
else if (result.relative_error > tolerance) {
|
||||
// successful but relative error too large
|
||||
}
|
||||
else {
|
||||
// successful and relative error is in bounds
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Reporting errors from a function that returns one or more values
|
||||
|
||||
We may want to return one or more values
|
||||
from a function that could fail
|
||||
or otherwise report errors.
|
||||
That is, the function either
|
||||
|
||||
* returns one or more valid values, or
|
||||
|
||||
* does not return any values and reports an error,
|
||||
|
||||
but NOT BOTH. We contrast this with cases
|
||||
when it's meaningful to report both a result
|
||||
and whether the result is satisfactory.
|
||||
For example, when solving
|
||||
a system of nonlinear equations iteratively,
|
||||
users may want the approximate computed solution,
|
||||
even if the iteration did not succeed
|
||||
by converging to the desired tolerance
|
||||
in the desired number of steps.
|
||||
(Users may want to invest more steps,
|
||||
or use the current approximation
|
||||
to jump-start a different algorithm.)
|
||||
|
||||
We're talking here about the "either valid value(s),
|
||||
or error, but not both" case.
|
||||
For this case, C++ offers a few options.
|
||||
|
||||
1. Return the value(s), or throw an exception on error
|
||||
|
||||
2. `std::expected` (requiring C++23) or something like it
|
||||
|
||||
3. `std::optional` (for a Boolean error state)
|
||||
or something like it
|
||||
|
||||
4. `std::variant` (a C++17 fall-back for `std::expected`)
|
||||
or something like it
|
||||
|
||||
5. C-style interface: return an error code,
|
||||
and "return" the values as output parameters
|
||||
|
||||
We usually cannot or do not want to
|
||||
throw exceptions on device.
|
||||
Some code projects forbid exceptions entirely
|
||||
(on host or device)
|
||||
and tell the compiler to disable them.
|
||||
If we exclude a C-style interface (the last option)
|
||||
as not idiomatic C++, then for host-only code,
|
||||
`std::expected`, `std::optional`, and `std::variant`
|
||||
all work.
|
||||
For code that needs to build and run on device,
|
||||
we can fall back to libcu++ equivalents
|
||||
in the `cuda::std::` namespace, when they exist.
|
||||
Otherwise, we must resort to returning a struct or tuple
|
||||
with the value and the error information,
|
||||
and ask users not to use the value on error.
|
||||
This is acceptable if the value can be constructed
|
||||
cheaply with a reasonable default.
|
||||
|
||||
##### Performance of different value-or-error reporting methods
|
||||
|
||||
[P1886R0](https://wg21.link/P1886R0)
|
||||
(Ben Craig, "Error speed benchmarking")
|
||||
surveys different ways in Standard C++
|
||||
to report errors from a function
|
||||
that returns one or more values,
|
||||
and compares their (host-only) performance
|
||||
with different compilers.
|
||||
|
||||
##### Use aggregate initialization when returning a struct or tuple
|
||||
|
||||
Use aggregate initialization when returning a struct or tuple.
|
||||
This avoids duplication of the return type name.
|
||||
|
||||
```c++
|
||||
struct foo_result {
|
||||
float value = 0.0f;
|
||||
float error = 0.0f;
|
||||
bool success = false;
|
||||
};
|
||||
|
||||
foo_result foo(std::span<const float> input)
|
||||
{
|
||||
// ... code ...
|
||||
|
||||
// Prefer this. We know what type the function returns.
|
||||
return {val, err, ok}; // prefer this
|
||||
|
||||
// Naming foo_result again here is unnecessary.
|
||||
// return foo_result{val, err, ok};
|
||||
}
|
||||
```
|
||||
|
||||
However, note that this won't work if the function returns `auto`.
|
||||
The general rule is to avoid code duplication.
|
||||
|
||||
```c++
|
||||
auto foo(std::span<const float> input)
|
||||
{
|
||||
// ... code ...
|
||||
|
||||
if constexpr (some_condition) {
|
||||
return foo_result{val, err, ok};
|
||||
}
|
||||
else {
|
||||
return bar_result{val, err, ok};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Prefer using the actual return type to auto, if you know the type
|
||||
|
||||
C++ lets you use `auto` to deduce the type returned from a function.
|
||||
|
||||
* If you know the actual type, prefer using the type instead of `auto`.
|
||||
|
||||
* Use [Constructor Type Argument Deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)
|
||||
(CTAD) if you know that a function returns some type
|
||||
(e.g., `Tensor`), but don't know the type's template arguments.
|
||||
|
||||
* Use `auto` in structured bindings (where you have to use it anyway). This also makes your code agnostic of whether the return type is a `struct`, `tuple`, `pair`, or other tuple-like type.
|
||||
|
||||
* Be careful using `auto` with types that provide expression templates.
|
||||
|
||||
Contrast this with "Almost Always Auto" (AAA) style.
|
||||
We deliberately choose not to follow AAA style,
|
||||
for the following reasons.
|
||||
|
||||
* Using the actual type when we know it can help prevent common loss-of-precision errors in mixed-precision computations, an important use case for CUTLASS.
|
||||
|
||||
* CTAD gives us much of the brevity of AAA, with more clarity.
|
||||
|
||||
* Using the actual type instead of `auto` can prevent common dangling errors with expression templates.
|
||||
|
||||
#### Classes and structs
|
||||
|
||||
Type names use `CamelCase`.
|
||||
That is, words start with capital letters.
|
||||
The remaining letters in the word are lower case,
|
||||
and words are joined with no intervening underscores.
|
||||
The only exception is when implementations are
|
||||
a drop-in replacement for C++ Standard Library components.
|
||||
|
||||
Follow the
|
||||
[C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-struct)
|
||||
to decide whether to use `class` or `struct`.
|
||||
|
||||
* Use `class` when the object must maintain an invariant.
|
||||
Data members related to the invariant should be `private`.
|
||||
|
||||
* Use `struct` when the class has no invariant to maintain,
|
||||
and data members may vary arbitrarily with respect to each other.
|
||||
|
||||
Prefer nonmember functions and statelessness where possible.
|
||||
Member functions imply invariants.
|
||||
More invariants make code maintenance and testing harder.
|
||||
|
||||
#### Class members
|
||||
|
||||
Methods and members are written using `snake_case`.
|
||||
|
||||
Private data and function members have suffix `_`.
|
||||
|
||||
### Constant names
|
||||
|
||||
CUTLASS makes extensive use of constants and compile-time evaluation. Constant variable names should have
|
||||
prefix `k` and use mixed case. True compile-time constsants should be defined as `constexpr` to enable
|
||||
dependent `constexpr` functions.
|
||||
|
||||
CUTLASS uses ["East const"](http://slashslash.info/2018/02/a-foolish-consistency/) style, placing `constexpr` keyword
|
||||
after the type name.
|
||||
|
||||
```c++
|
||||
float constexpr kPi = 3.14159f;
|
||||
```
|
||||
|
||||
### Class Member Order
|
||||
#### Class Member Order
|
||||
|
||||
Members within classes and structures should be organized as follows:
|
||||
|
||||
1. Type and constant definitions
|
||||
|
||||
2. Data members
|
||||
|
||||
3. Constructors
|
||||
|
||||
4. Other methods
|
||||
|
||||
This convention follows the [CUB library](https://nvlabs.github.io/cub/) and is also described by
|
||||
[Howard Hinnant](https://howardhinnant.github.io/classdecl.html). Unsurprisingly, it approximates
|
||||
the usual ordering of chapters in a typical Systems and Controls textbook. That is,
|
||||
(1.) identify relevant constants, (2.) define a state-space representation of the dynamical system
|
||||
under study (i.e. the data members), and (3.) devote subsequent chapters to definining dynamical behavior
|
||||
of the system (i.e. the methods).
|
||||
This convention follows the
|
||||
[CUB library](https://nvlabs.github.io/cub/)
|
||||
and is also described by
|
||||
[Howard Hinnant](https://howardhinnant.github.io/classdecl.html).
|
||||
It also approximates the usual ordering of chapters
|
||||
in a typical Systems and Controls textbook.
|
||||
That is, it
|
||||
|
||||
1. identifies relevant constants,
|
||||
|
||||
2. defines a state-space representation
|
||||
of the dynamical system under study
|
||||
(the class's data members), and then
|
||||
|
||||
3. devotes the remaining "chapters" to defining
|
||||
the system's dynamical behavior
|
||||
(the class's methods).
|
||||
|
||||
Here is an example class.
|
||||
|
||||
_Example_:
|
||||
```c++
|
||||
class A {
|
||||
public:
|
||||
// Type definitions
|
||||
// type definitions
|
||||
protected:
|
||||
// protected Type definitions
|
||||
// protected type definitions
|
||||
private:
|
||||
// private Type definitions
|
||||
// private type definitions
|
||||
|
||||
public:
|
||||
// Data members
|
||||
// data members
|
||||
protected:
|
||||
// protected data members
|
||||
// STRONGLY TO BE AVOIDED;
|
||||
// please see C++ Core Guidelines
|
||||
private:
|
||||
// private data members
|
||||
|
||||
public:
|
||||
// Methods
|
||||
// methods
|
||||
protected:
|
||||
// protected methods
|
||||
private:
|
||||
// private methods
|
||||
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### File Names
|
||||
#### Use scoped enums
|
||||
|
||||
Files should be named using `snake_case` with extension `.h` for header files, `.cu` for CUDA sources,
|
||||
and `.cpp` for C++ host-only source files.
|
||||
|
||||
### Use scoped enums
|
||||
|
||||
Use scoped enums added in C++11 for enumerated types. Use capital letters for the enumerated type name
|
||||
Use scoped enums (a C++11 feature) for enumerated types.
|
||||
Use capital letters for the enumerated type name
|
||||
and prefix `k` for enumerators like other constants.
|
||||
|
||||
```c++
|
||||
@@ -232,63 +679,129 @@ enum class MatrixOperation {
|
||||
};
|
||||
```
|
||||
|
||||
### Namespaces
|
||||
#### Namespaces
|
||||
|
||||
Namespaces are all lower case. The top-level namespace is `cutlass::`. The second nested namespace refers
|
||||
top the general category of operation performed by its members, and the third nested namespace refers to
|
||||
the CUDA execution model scope (if applicable).
|
||||
Namespaces are all lower case.
|
||||
The top-level namespace is `cutlass::`.
|
||||
The second nested namespace refers to
|
||||
the general category of operation
|
||||
performed by its members: e.g., `gemm::`.
|
||||
The third nested namespace refers to
|
||||
the operations' position in the conceptual hierarchy:
|
||||
e.g., `device::`, `kernel::`, or `collective::`.
|
||||
|
||||
The bodies of namespace definitions should not be intented, and comments on the closing brace are welcome.
|
||||
The bodies of namespace definitions should not be indented.
|
||||
Comments on the closing brace to indicate
|
||||
the namespace being closed are welcome.
|
||||
|
||||
```c++
|
||||
namespace cutlass {
|
||||
namespace gemm {
|
||||
namespace warp {
|
||||
|
||||
struct MmaTensorCore {
|
||||
namespace kernel {
|
||||
|
||||
struct AnotherGemmKernel {
|
||||
// ... contents ...
|
||||
};
|
||||
|
||||
} // namespace warp
|
||||
} // namespace kernel
|
||||
} // namespace gemm
|
||||
} // namespace cutlass
|
||||
```
|
||||
|
||||
### Macros
|
||||
#### File Names
|
||||
|
||||
Avoid defining macros except where preprocessing is obligatory. In particular,
|
||||
avoid using macros for constants.
|
||||
New files should be named using `snake_case`
|
||||
with extension `.hpp` for header files,
|
||||
`.cu` for CUDA sources,
|
||||
and `.cpp` for C++ host-only source files.
|
||||
|
||||
Several existing macros defined in `cutlass/cutlass.h` are useful for working around compiler-dependent
|
||||
behavior.
|
||||
Header files with extension `.h`
|
||||
are CUTLASS 2.x legacy headers.
|
||||
|
||||
Annotations for device code:
|
||||
* `CUTLASS_HOST_DEVICE` for functions running on the host and the device
|
||||
* `CUTLASS_DEVICE` for functions running on the device only
|
||||
#### Macros
|
||||
|
||||
Loop unrolling:
|
||||
* `CUTLASS_PRAGMA_UNROLL` for full unrolling of loops with constant trip counts
|
||||
* `CUTLASS_PRAGMA_NO_UNROLL` to prevent unrolling
|
||||
Only use macros when the preprocessor
|
||||
is the only way to accomplish the task.
|
||||
Do not use macros for literal constants.
|
||||
Instead, if inside the body of a function,
|
||||
use `constexpr` values,
|
||||
and if at namespace scope, use
|
||||
[`inline constexpr` variables](https://en.cppreference.com/w/cpp/language/inline)
|
||||
(a C++17 feature).
|
||||
|
||||
### #pragma once
|
||||
"Namespace" macros by starting them with the module name, e.g., `CUTLASS_`.
|
||||
Macros and ONLY MACROS use all capital letters with underscores between words.
|
||||
For example:
|
||||
|
||||
```c++
|
||||
#define CUTLASS_MACROS_USE_ALL_CAPS inline __host__ __device__
|
||||
```
|
||||
|
||||
Header files such as
|
||||
[cutlass/cutlass.h](../../include/cutlass/cutlass.h)
|
||||
and
|
||||
[cute/config.hpp](../../include/cutlass/cutlass.h)
|
||||
offer macros for expressing compiler-dependent behavior.
|
||||
These include
|
||||
|
||||
* replacements for `__device__` and/or `__host__`
|
||||
annotations:
|
||||
|
||||
* `CUTLASS_HOST_DEVICE` or `CUTE_HOST_DEVICE`
|
||||
for functions that run on the host and the device,
|
||||
|
||||
* `CUTLASS_DEVICE` or `CUTE_DEVICE`
|
||||
for functions that run on the device only, and
|
||||
|
||||
* `CUTE_HOST`
|
||||
for functions that run on the host only; and
|
||||
|
||||
* annotations to loop unrolling:
|
||||
|
||||
* `CUTLASS_PRAGMA_UNROLL` or `CUTE_UNROLL`
|
||||
for full unrolling of loops with constant trip counts, and
|
||||
|
||||
* `CUTLASS_PRAGMA_NO_UNROLL` or `CUTE_NO_UNROLL` to prevent unrolling.
|
||||
|
||||
#### Guard all headers with `#pragma once`
|
||||
|
||||
Use `#pragma once` to guard all headers.
|
||||
|
||||
```c++
|
||||
/*!
|
||||
### CUDA C++ style
|
||||
|
||||
*/
|
||||
#### CUDA Built-in Variables
|
||||
|
||||
#pragma once
|
||||
Avoid direct access to CUDA built-in variables `threadIdx`, `blockIdx`, `blockDim`, and `gridDim` within
|
||||
CUTLASS components except in special circumstances.
|
||||
|
||||
...
|
||||
```
|
||||
Using built-in global variables directly within resuable components necessitates that all components
|
||||
use them consistently which may not be possible if CUTLASS components are used in other contexts.
|
||||
|
||||
### Source Line Length
|
||||
Instead, components should accept a linear ID identifying threads, warps, and threadblocks from calling
|
||||
code. The top-level kernel may then decide how to map threads, warps, and blocks to the problem it is
|
||||
solving.
|
||||
|
||||
Avoid lines longer than 100 characters. These typically wrap unfavorably when viewed in
|
||||
Github's pretty printer.
|
||||
#### Use CUTLASS's and CuTe's fundamental types and operations
|
||||
|
||||
Use the
|
||||
[fundamental types and operations](fundamental_types.md)
|
||||
defined in CUTLASS consistently.
|
||||
This contributes to a framework of interoperable, consistent components.
|
||||
It reduces code duplication, which reduces build and test times.
|
||||
It also saves developer effort.
|
||||
|
||||
CUTLASS's fundamental types and operations include
|
||||
|
||||
* [Numeric types](fundamental_types.md#numeric-types) to represent numeric data in host and device code, and
|
||||
|
||||
* [functional.h](fundamental_types.md#functional) to perform numeric operations in generic code.
|
||||
|
||||
CUTLASS 3.0 uses CuTe components to represent data layouts and multidimensional arrays.
|
||||
Please refer to the [CuTe Tutorial](./cute/00_quickstart.md) for details.
|
||||
CuTe has replaced CUTLASS 2.x components such as
|
||||
[Containers](fundamental_types.md#containers),
|
||||
[Layouts](layout.md), and
|
||||
[`TensorRef` and `TensorView`](layout.md#tensorref).
|
||||
|
||||
# Copyright
|
||||
|
||||
|
||||
Reference in New Issue
Block a user