CUTLASS 3.0.0 (#786)

* CUTLASS 3.0.0
This commit is contained in:
Vijay Thakkar
2023-01-23 20:55:28 -05:00
committed by GitHub
parent 66d9cddc83
commit 277bd6e537
377 changed files with 76396 additions and 1186 deletions
+635 -122
View File
@@ -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