Build a CuTe Layout Inspector

Build a CUDA tool that visualizes and validates CuTe memory layouts.

Introduction

30 Second Summary

A numbered grid can look perfectly orderly while its entries are stored in a completely different sequence. That hidden sequence decides where every value is found.

In this project, you will build a terminal inspector that turns CuTe layouts into coordinate-to-index tables. You will make a flat mapping fail before proving that a hierarchical layout with 2x2 tiles fixes the mismatch.

What You'll Build

One run of your finished inspector shows exactly how each coordinate lands in memory across three 4x4 layouts.

By the end of this project, you'll have:

  • A GPU-aware executable that prints the detected device with its compute capability.
  • A cell-by-cell comparison where each 4x4 table reveals how its layout changes memory offsets.
  • An automated validation report that identifies the expected mismatch at (0, 2) before ending with FINAL STATUS: PASS.
  • Secret Mission: Generalize the inspector to a 4x8 matrix made of 2x4 tiles.

Are there any prerequisites?

You'll need a native Ubuntu 26.04 installation on a laptop with an NVIDIA RTX 4060 Laptop GPU. You'll also need administrator access.

Before We Start

Before any hands-on work, this is your moment to lock in what the CuTe layout mapping inspector reveals and why that matters. A clear coordinate-to-offset model helps your future CUDA kernels use the intended memory layout.

Set Up the CUDA C++ Workspace

Your layout inspector depends on CuTe headers plus a compiler that understands CUDA C++. Before the inspector can reveal any coordinate mappings, the machine needs a working NVIDIA CUDA Toolkit installation.

Ubuntu 26.04 also needs an active NVIDIA driver so the toolkit can reach your laptop GPU. This connection lets the program identify the real device at runtime.

GCC compiles the host-side C++ code. CMake turns the project definition into a CUDA build.

Git brings the pinned CUTLASS source into your workspace. Visual Studio Code gives you an editor for the files you build in the next step.

In this step, get ready to:
  • Update Ubuntu and activate the recommended NVIDIA driver.
  • Install the CUDA toolkit plus the C++ development tools.
  • Create the inspector workspace with the pinned CUTLASS headers.
Update Ubuntu and activate the NVIDIA driver

The driver is the system layer that lets CUDA software communicate with the RTX 4060 Laptop GPU. Ubuntu can inspect the hardware and select its recommended driver automatically.

  • Press the Windows key to open Ubuntu search.
  • Type Terminal and press Enter to open it.

These system commands may request your administrator password. The update can take several minutes while Ubuntu downloads current package information.

  • Enter your administrator password if Ubuntu requests it.
  • Confirm the package upgrade if Ubuntu pauses for approval.
  • Update the Ubuntu package index and installed packages by running this command:
sudo apt update && sudo apt upgrade

What does this command do?

  • The first command refreshes Ubuntu's local package index.
  • The second command applies available updates to packages already installed on the machine.
  • Check whether the active driver already detects your GPU by running:
nvidia-smi

What does this check prove?

A successful result lists the RTX 4060 Laptop GPU. That result proves the operating system can communicate with the graphics hardware.

✔️ The RTX 4060 Laptop GPU is listed

Your NVIDIA driver is active. The GPU is ready for the CUDA toolkit.

ⓧ The GPU is missing

Ubuntu can select the driver branch that best matches your hardware. The installation changes a system component, so a restart is expected.

  • Install the Ubuntu-recommended driver by running this command:
sudo ubuntu-drivers install

How does Ubuntu choose the driver?

Ubuntu inspects the graphics hardware and repository packages. It installs the driver considered the best match for a desktop system.

  • Restart Ubuntu from the system menu after the installation finishes.
  • Press the Windows key after signing in again.
  • Type Terminal and press Enter to open a new terminal.
  • Check the restarted driver by running:
nvidia-smi

What should you see now?

The output should list the RTX 4060 Laptop GPU. This confirms the newly installed driver loaded during the restart.

Still unable to see the GPU?

  • Confirm that you restarted Ubuntu after the driver installation.
  • Check that the laptop is running the native Ubuntu installation described in the project prerequisites.
  • Use this prompt for targeted help: Help me diagnose why Ubuntu cannot detect my RTX 4060 Laptop GPU after installing the recommended NVIDIA driver.
Install the development toolchain

The CUDA compiler handles CUDA C++ source files. The host compiler and build tools handle the surrounding C++ project.

Ubuntu provides the required GCC, CMake, Git, and wget packages. Visual Studio Code is available through its documented Snap package.

  • Confirm each package installation if Ubuntu requests approval.
  • Install the compiler and command-line development tools by running these commands:
sudo apt install build-essential
sudo apt install -y make cmake
sudo apt install git-all
sudo apt install wget

What gets installed?

  • The build tools package supplies GCC plus the supporting C++ compilation utilities.
  • CMake generates the build system used by the inspector.
  • Git downloads the pinned CUTLASS repository.
  • wget downloads the CUDA repository key package.
  • Confirm the compiler and build tools respond by running:
gcc --version
cmake --version
git --version

What should these checks show?

  • The GCC command should print its compiler version.
  • The CMake command should print its build-tool version.
  • The Git command should print its source-control version.
  • Install Visual Studio Code by running this command:
sudo snap install --classic code

What does this install?

The Snap package installs Visual Studio Code with the permissions needed to access files outside its package sandbox. You will use it to edit the inspector project.

  • Confirm the editor command is available by running:
code --version

What should you see?

The command should print the installed Visual Studio Code version. A version result confirms the editor command is available from the terminal.

Did a tool installation fail?

  • Confirm that the earlier Ubuntu update completed without a package-management error.
  • Check your internet connection before retrying the failed installation command.
  • Use this prompt for targeted help: Help me identify which Ubuntu development tool failed to install.

The inspector is pinned to CUDA Toolkit 13.3 for alignment with CUTLASS 4.7.1. Start with a compiler check so you can follow the path that matches your machine.

  • Check the CUDA compiler currently available in this terminal by running:
nvcc --version

Why check before installing?

The result separates a ready installation from an older toolkit or a missing compiler. Each state has a clear route to the pinned release.

✔️ I see CUDA 13.3

The pinned CUDA compiler is already available. Continue below to make its installation path persistent in new terminals.

ⓧ I see an older CUDA version

Install the pinned toolkit package from NVIDIA's Ubuntu repository. The later PATH entry selects the 13.3 compiler for new terminals.

  • Confirm the toolkit installation if the package manager requests approval.
  • Install CUDA Toolkit 13.3 by running these commands:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2604/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
sudo apt install cuda-toolkit-13-3

How does this installation work?

  • wget downloads NVIDIA's repository key package for Ubuntu 26.04.
  • The package installer adds the keyring that authenticates repository packages.
  • The package index refresh makes the NVIDIA repository available to the package manager.
  • The versioned toolkit package locks the installation to CUDA 13.3.

ⓧ Command not found

The CUDA compiler is not available yet. Add NVIDIA's Ubuntu repository and install the pinned toolkit package.

  • Confirm the toolkit installation if the package manager requests approval.
  • Install CUDA Toolkit 13.3 by running these commands:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2604/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
sudo apt install cuda-toolkit-13-3

How does this installation work?

  • wget downloads NVIDIA's repository key package for Ubuntu 26.04.
  • The package installer adds the keyring that authenticates repository packages.
  • The package index refresh makes the NVIDIA repository available to the package manager.
  • The versioned toolkit package installs CUDA 13.3.

The CUDA compiler lives in /usr/local/cuda-13.3/bin. Adding that directory to ~/.bashrc makes nvcc available whenever you open a new terminal.

The ~/.bashrc file is hidden, which makes this edit a little fiddly. Visual Studio Code can open it directly through the editor's file picker.

  • Press the Windows key to open Ubuntu search.
  • Type Visual Studio Code and press Enter to open it.
  • Use the editor's file picker to open ~/.bashrc.
  • Add this line at the bottom of ~/.bashrc:
export PATH=/usr/local/cuda-13.3/bin${PATH:+:${PATH}}

What does this PATH entry do?

The entry places the CUDA 13.3 binary directory on the shell's command search path. The conditional portion preserves the existing PATH when one is already defined.

  • Save ~/.bashrc.
  • Press the Windows key to open Ubuntu search.
  • Type Terminal and press Enter to open a new terminal.
  • Confirm the new terminal can find the CUDA compiler by running:
nvcc --version

What should the new terminal report?

The output should identify CUDA compilation tools from release 13.3. This proves the saved PATH entry is active in newly opened terminals.

Still unable to run nvcc?

  • Confirm that ~/.bashrc contains the PATH entry exactly once.
  • Confirm that you opened a new terminal after saving the file.
  • Use this prompt for targeted help: Help me fix my CUDA PATH configuration.
Create the workspace and pin CUTLASS

CUTLASS is header-only, so the inspector can include CuTe directly from a local clone. Pinning the clone to tag v4.7.1 keeps the layout API reproducible.

  • Create the ~/projects/cute-layout-inspector workspace and its third_party directory by running these commands:
mkdir -p ~/projects/cute-layout-inspector/third_party
cd ~/projects/cute-layout-inspector

How is the workspace organized?

The project lives at ~/projects/cute-layout-inspector. The third_party directory holds source code maintained outside your inspector.

The clone can take a few minutes because CUTLASS contains many CUDA components. The shallow clone downloads only the commit needed for the selected tag.

  • Clone the CUTLASS repository at tag v4.7.1 into third_party/cutlass by running:
git clone --branch v4.7.1 --depth 1 https://github.com/NVIDIA/cutlass.git third_party/cutlass

Why use a pinned shallow clone?

  • The branch option selects the verified v4.7.1 tag.
  • The depth option limits the download to the selected revision's recent history.
  • The destination argument places the repository at third_party/cutlass.
  • Confirm the required CuTe layout header exists by running:
ls third_party/cutlass/include/cute/layout.hpp

What does this file prove?

The listed layout.hpp file supplies the CuTe shape, stride, and layout types used by the inspector. Its location matches the include path added to the project in the next step.

Cannot find the CuTe header?

  • Confirm that your terminal is currently inside ~/projects/cute-layout-inspector.
  • Confirm that the clone completed without a network interruption.
  • Use this prompt for targeted help: Help me locate the missing CuTe layout header.

Before you run the final check, which command do you expect to prove that the driver can see the physical GPU?

  • Verify the complete workspace toolchain by running these commands:
nvidia-smi
nvcc --version
gcc --version
cmake --version
git --version
code --version

What should the final check show?

  • The nvidia-smi result lists the RTX 4060 Laptop GPU.
  • The nvcc result reports CUDA 13.3.
  • The GCC result prints a compiler version.
  • The CMake result prints a build-tool version.
  • The Git result prints a source-control version.
  • The Visual Studio Code result prints an editor version.

That's the setup hurdle cleared. Your GPU is visible, the CUDA compiler is available, and the pinned CuTe headers are ready for the inspector.

✔️ Awesome, I've got everything!

Your development environment now matches the workspace required by the project. Keep the terminal in ~/projects/cute-layout-inspector for the next step.

ⓧ I'd like to double check the full code

  • The third_party/cutlass directory is a Git clone of https://github.com/NVIDIA/cutlass.git checked out at tag v4.7.1.
  • The third_party/cutlass/include/cute/layout.hpp file is the CuTe layout header supplied by the local CUTLASS v4.7.1 clone.
  • The ~/.bashrc file contains this line:
export PATH=/usr/local/cuda-13.3/bin${PATH:+:${PATH}}

Why compare this line exactly?

This entry makes the CUDA 13.3 compiler available in newly opened terminals. Matching it exactly prevents the shell from searching the wrong toolkit directory.

Your CUDA C++ workspace is ready. Next up, you will build the first executable and watch CuTe turn row-major and column-major coordinates into visible offsets.

Print Your First CuTe Layouts

Your CUDA toolchain is ready. The pinned CUTLASS headers are also in place.

A CuTe Layout maps logical coordinates to linear offsets. Printing those offsets gives you a working mental model before validation adds another layer.

In this step, get ready to:
  • Configure a CMake target for the native GPU architecture.
  • Query the installed GPU through the CUDA Runtime API.
  • Print distinct row-major and column-major mapping tables.
Create the CUDA build target

CMake turns your source files into a repeatable build. This configuration enables C++17 plus CUDA 17.

  • Launch Visual Studio Code with the current cute-layout-inspector folder by running this command in the terminal from earlier:
code .

What does this command do?

The code . command opens your current terminal directory as a Visual Studio Code workspace. The editor inherits the CUDA path from this terminal session.

Did the editor stay closed?

Check that the terminal prompt points to ~/projects/cute-layout-inspector. The current-directory shortcut only works from the folder you want to open.

If the command is unavailable, help me check why the Visual Studio Code command is missing.

  • Review the project contents if the Workspace Trust dialog appears.
  • Select Yes, I trust the authors after you recognize the CUTLASS clone from earlier.
  • Select the new-file control beside cute-layout-inspector in the Explorer sidebar.
  • Enter CMakeLists.txt as the file name.
  • Configure the executable by pasting this code into CMakeLists.txt:
cmake_minimum_required(VERSION 3.24)

project(cute_layout_inspector LANGUAGES CXX CUDA)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_ARCHITECTURES native)

add_executable(cute_layout_inspector src/main.cu)

target_include_directories(
  cute_layout_inspector
  PRIVATE
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party/cutlass/include"
)

What does this build file do?

  • The minimum version is 3.24 because that release added native as a CUDA architecture value.
  • The project enables the C++ and CUDA languages.
  • Both language standards are fixed at 17.
  • The native architecture value targets the GPU installed in your laptop.
  • The include path exposes the local CuTe headers to src/main.cu.
  • Save CMakeLists.txt.
  • Confirm CMakeLists.txt appears beside third_party in the Explorer sidebar.

Is CMakeLists.txt in the wrong place?

Move CMakeLists.txt to the top level of cute-layout-inspector. It must sit beside the third_party folder.

If the file still looks misplaced, help me check my CMake project structure.

Print the GPU and layout mappings

The inspector needs one reusable function for every mapping. A function template lets the same loop accept row-major or column-major layouts.

  • Select the new-folder control beside cute-layout-inspector in the Explorer sidebar.
  • Enter src as the folder name.
  • Select the new-file control beside the new src folder.
  • Enter main.cu as the file name.
  • Add the headers plus the reusable mapping printer by pasting this first chunk into src/main.cu:
#include <cuda_runtime.h>
#include <cute/layout.hpp>

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

using namespace cute;

template <class LayoutT>
void print_mapping(std::string const& label, LayoutT const& layout) {
  std::cout << "\n" << label << "\n";
  std::cout << "Layout: " << layout << "\n";

  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      std::cout << std::setw(4) << int(layout(row, column));
    }
    std::cout << "\n";
  }
}

How does the mapping printer work?

  • The CUDA Runtime header provides the device-query functions used by main().
  • The CuTe header provides layouts plus coordinate mapping helpers.
  • The LayoutT template parameter allows one function to print different layout types.
  • The size<0>(layout) and size<1>(layout) calls provide the row and column loop bounds.
  • The layout(row, column) call converts each coordinate into its linear offset.
  • Save src/main.cu.
  • Confirm the file begins with the CUDA header.
  • Confirm the file currently ends with the closing brace of print_mapping.

The application entry point now connects that printer to your real GPU and two compile-time layouts.

  • Place your cursor after the closing brace of print_mapping.
  • Complete src/main.cu by pasting this code below the function:
int main() {
  cudaDeviceProp properties{};
  cudaError_t const status = cudaGetDeviceProperties(&properties, 0);

  if (status != cudaSuccess) {
    std::cerr << "CUDA device query failed: " << cudaGetErrorString(status) << "\n";
    return EXIT_FAILURE;
  }

  std::cout << "GPU: " << properties.name << "\n";
  std::cout << "Compute capability: " << properties.major << "."
            << properties.minor << "\n";

  auto const row_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<4>{}, Int<1>{}));

  auto const column_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<1>{}, Int<4>{}));

  print_mapping("Row-major 4x4", row_major);
  print_mapping("Column-major 4x4", column_major);

  return EXIT_SUCCESS;
}

How do the layouts differ?

  • The cudaGetDeviceProperties call reads the first CUDA device into properties.
  • Each layout has a compile-time shape of 4x4.
  • The row-major stride is (4, 1).
  • The column-major stride is (1, 4).
  • The two print_mapping calls expose the resulting offsets cell by cell.
  • Save src/main.cu.
  • Confirm main() appears directly below print_mapping.
Build and compare the mappings

CMake stores generated build files separately from your source. A Git ignore rule keeps that generated directory out of future commits.

  • Select the new-file control beside cute-layout-inspector in the Explorer sidebar.
  • Enter .gitignore as the file name.
  • Exclude the generated build directory by pasting this line into .gitignore:
build/

Why ignore the build directory?

The build/ directory contains generated CMake files plus the compiled executable. You can recreate all of them from the source files.

  • Save .gitignore.
  • Confirm .gitignore appears beside CMakeLists.txt in the Explorer sidebar.

Before you build, which table do you expect to advance by one across its first row?

  • Return to the terminal from earlier.
  • Generate the build files in build by running the first command below.
  • Compile cute_layout_inspector by running the second command below.
  • Launch the compiled inspector by running the third command below:
cmake -B build
cmake --build build
./build/cute_layout_inspector

What do these commands do?

  • The first command configures the project in build.
  • The second command compiles the CUDA executable.
  • The third command runs the inspector from the generated directory.

That's your first CuTe inspector run complete. Your terminal shows the RTX 4060 Laptop GPU name followed by compute capability 8.9.

The row-major first row reads 0 1 2 3. The column-major first row reads 0 4 8 12.

What do the first rows prove?

A row-major column step adds 1 to the offset. A column-major column step adds 4 because each move crosses a full four-element column stride.

Did the build or device query fail?

  • Confirm src/main.cu is inside the src folder named by CMakeLists.txt.
  • Confirm the CUTLASS header still exists at third_party/cutlass/include/cute/layout.hpp.
  • Confirm the terminal session still has the CUDA toolchain path from ~/.bashrc.
  • If the output still fails, help me debug this CUDA CMake build.

✔️ Awesome, I've got everything!

Your project now builds from source and prints both 4x4 layout mappings.

ⓧ I'd like to double check the full code

These are the cumulative files for this step.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.24)

project(cute_layout_inspector LANGUAGES CXX CUDA)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_ARCHITECTURES native)

add_executable(cute_layout_inspector src/main.cu)

target_include_directories(
  cute_layout_inspector
  PRIVATE
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party/cutlass/include"
)

src/main.cu:

#include <cuda_runtime.h>
#include <cute/layout.hpp>

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

using namespace cute;

template <class LayoutT>
void print_mapping(std::string const& label, LayoutT const& layout) {
  std::cout << "\n" << label << "\n";
  std::cout << "Layout: " << layout << "\n";

  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      std::cout << std::setw(4) << int(layout(row, column));
    }
    std::cout << "\n";
  }
}

int main() {
  cudaDeviceProp properties{};
  cudaError_t const status = cudaGetDeviceProperties(&properties, 0);

  if (status != cudaSuccess) {
    std::cerr << "CUDA device query failed: " << cudaGetErrorString(status) << "\n";
    return EXIT_FAILURE;
  }

  std::cout << "GPU: " << properties.name << "\n";
  std::cout << "Compute capability: " << properties.major << "."
            << properties.minor << "\n";

  auto const row_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<4>{}, Int<1>{}));

  auto const column_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<1>{}, Int<4>{}));

  print_mapping("Row-major 4x4", row_major);
  print_mapping("Column-major 4x4", column_major);

  return EXIT_SUCCESS;
}

.gitignore:

build/

Your inspector now turns CuTe layouts into visible offsets. Next, you'll test those offsets against formulas and expose a deliberate mapping failure.

Make the Flat Layout Fail

Your CuTe inspector already turns coordinates into visible row-major and column-major offsets. Those tables give you a working baseline for testing storage requirements.

A flat row-major table can look reasonable even when storage requirements group values into 2x2 tiles. This step turns that assumption into an automated check. You will preserve the mismatch as a successful diagnostic experiment.

In this step, get ready to:
  • Add a reusable function that compares layout offsets with expected offsets.
  • Validate the row-major and column-major layouts against their formulas.
  • Expose the deliberate mismatch between row-major and 2x2 tile-major storage.
Add a reusable mapping validator

A mapping validator checks every logical coordinate against an expected offset. The first difference identifies exactly where a layout stops matching the storage requirement.

  • In src/main.cu, find the closing brace of print_mapping.
  • Insert this template directly below that brace:
template <class LayoutT, class ExpectedOffset>
bool validate_mapping(
    std::string const& label,
    LayoutT const& layout,
    ExpectedOffset expected_offset) {
  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      int const expected = expected_offset(row, column);
      int const actual = int(layout(row, column));

      if (actual != expected) {
        std::cout << label << ": FAIL at (" << row << ", " << column
                  << "): expected " << expected << ", got " << actual << "\n";
        return false;
      }
    }
  }

  std::cout << label << ": PASS\n";
  return true;
}

What does this validator do?

  • The LayoutT template parameter lets the function inspect any compatible CuTe layout.
  • The ExpectedOffset template parameter accepts a formula that calculates the required offset for each coordinate.
  • The nested loops compare every actual offset with its expected value.
  • The function stops at the first mismatch so the output names the exact coordinate and both offsets.
  • Save src/main.cu.
  • Confirm that the new template parses by rebuilding the existing target with this command:
cmake --build build

What does this build prove?

The build parses the new template with the existing CUDA C++ source. A completed build confirms that the validator declaration and loop structure are valid.

Is the build failing?

Check that validate_mapping sits after the complete print_mapping function. A missing closing brace can place one template inside the other.

Compare the parentheses and braces around both nested loops with the code above.

Help me debug the validator build error.

Define the expected offsets

An expected-offset function describes the storage rule independently from the layout under test. Comparing the two gives each table an objective pass or fail result.

  • In main(), find the two print_mapping calls.
  • Add these expected-offset formulas below the column-major call:
  auto const row_major_offset = [](int row, int column) {
    return row * 4 + column;
  };

  auto const column_major_offset = [](int row, int column) {
    return column * 4 + row;
  };

  auto const tile_2x2_offset = [](int row, int column) {
    return ((row / 2) * 2 + column / 2) * 4
         + (row % 2) * 2
         + column % 2;
  };

How do the formulas differ?

  • The row_major_offset formula advances four positions for each new row.
  • The column_major_offset formula advances four positions for each new column.
  • The tile_2x2_offset formula finds the containing tile first. It then finds the coordinate's position inside that tile.
  • Save src/main.cu.
  • Confirm that all three formulas compile by rebuilding the target with this command:
cmake --build build

What does this build confirm?

The compiler now accepts each lambda as an expected-offset function. Their return values are ready for the validator to compare with CuTe offsets.

Seeing a lambda syntax error?

Check that each lambda ends with };. Confirm that the multiline tile formula keeps each arithmetic operator.

Help me fix the expected-offset lambdas.

The row-major and column-major formulas describe the two tables you already printed. Calling the validator proves that the reusable check agrees with those known mappings.

  • Find the closing brace of tile_2x2_offset.
  • Add these two validation calls below that brace:
  bool const row_ok = validate_mapping(
      "Row-major formula", row_major, row_major_offset);
  bool const column_ok = validate_mapping(
      "Column-major formula", column_major, column_major_offset);

What do these checks establish?

Each call pairs one CuTe layout with its matching expected-offset formula. The returned booleans preserve both results for the final status calculation.

  • Save src/main.cu.
  • Build the updated source and run the inspector with these commands:
cmake --build build
./build/cute_layout_inspector

What should the baseline show?

After the two mapping tables, you should see Row-major formula: PASS followed by Column-major formula: PASS.

These results prove that the validator accepts layouts whose offsets match their stated storage rules.

Missing one of the passing checks?

Confirm that both validation calls sit after all three expected-offset lambdas. Check that each call uses the matching layout and formula names.

Help me debug the baseline mapping checks.

Run the designed obstacle

The validator can also test a layout against a different storage requirement. This comparison turns a vague indexing suspicion into a coordinate-level diagnostic.

  • Find the column_ok validation call in main().
  • Add this diagnostic experiment directly below that call:
  std::cout << "\nDesigned obstacle\n";
  bool const naive_tile_ok = validate_mapping(
      "Naive row-major used as 2x2 tile-major",
      row_major,
      tile_2x2_offset);

  if (!naive_tile_ok) {
    std::cout << "Expected mismatch confirmed: flat strides cannot express this tile order.\n";
  }

What does this experiment compare?

The call keeps row_major as the layout under test. It supplies tile_2x2_offset as the required storage rule.

The conditional prints a confirmation whenever the validator rejects that pairing.

  • Save src/main.cu.

Before you run this experiment, consider which coordinate will reveal the first difference between flat row-major and 2x2 tile-major offsets.

  • Rebuild the inspector and run the designed obstacle with these commands:
cmake --build build
./build/cute_layout_inspector

The mismatch is the result

You should see Naive row-major used as 2x2 tile-major: FAIL at (0, 2): expected 4, got 2.

The next line should read Expected mismatch confirmed: flat strides cannot express this tile order. This failure is the intended diagnostic result.

You have made the incorrect assumption testable. The output now pinpoints the first coordinate where the two storage models diverge.

Did the diagnostic pass unexpectedly?

Check that the diagnostic passes row_major as the layout. Confirm that its expected function is tile_2x2_offset.

Rebuild before running so the executable contains your latest source changes.

Help me reproduce the intended mapping failure.

A successful diagnostic run needs both baseline checks to pass. It also needs the naive tile-major assumption to fail. The final status combines those conditions into one result.

  • At the bottom of main(), find the final return EXIT_SUCCESS; line.
  • Replace that line with this final status logic:
  bool const all_pass = row_ok && column_ok && !naive_tile_ok;
  std::cout << "\nFINAL STATUS: " << (all_pass ? "PASS" : "FAIL") << "\n";

  return all_pass ? EXIT_SUCCESS : EXIT_FAILURE;

Why does the mismatch count as success?

The all_pass expression requires both baseline checks to succeed. The !naive_tile_ok condition requires the incorrect layout assumption to be rejected.

The process returns success only when the learning experiment behaves exactly as designed.

  • Save src/main.cu.

Before the final run, predict whether an intended mapping failure can coexist with a passing final status.

  • Rebuild the complete diagnostic experiment and run it with these commands:
cmake --build build
./build/cute_layout_inspector

What should the final run show?

You should see both baseline checks pass. You should then see the deliberate failure at (0, 2) with expected offset 4 and actual offset 2.

The final line should read FINAL STATUS: PASS. This proves that the inspector detects the incorrect assumption without treating the intended diagnostic as a broken program.

Seeing a final failure status?

Confirm that all_pass uses !naive_tile_ok. Without the negation, the intended mismatch makes the overall experiment fail.

Check that row_ok and column_ok both print passing results before the designed obstacle.

Help me debug the final status calculation.

✔️ Awesome, I've got everything!

Your inspector now validates both flat layouts. It also preserves the expected tile-major mismatch as a passing diagnostic experiment.

  • Confirm that src/main.cu is saved.

ⓧ I'd like to double check the full code

  • Compare your src/main.cu with this complete version:
#include <cuda_runtime.h>
#include <cute/layout.hpp>

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

using namespace cute;

template <class LayoutT>
void print_mapping(std::string const& label, LayoutT const& layout) {
  std::cout << "\n" << label << "\n";
  std::cout << "Layout: " << layout << "\n";

  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      std::cout << std::setw(4) << int(layout(row, column));
    }
    std::cout << "\n";
  }
}

template <class LayoutT, class ExpectedOffset>
bool validate_mapping(
    std::string const& label,
    LayoutT const& layout,
    ExpectedOffset expected_offset) {
  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      int const expected = expected_offset(row, column);
      int const actual = int(layout(row, column));

      if (actual != expected) {
        std::cout << label << ": FAIL at (" << row << ", " << column
                  << "): expected " << expected << ", got " << actual << "\n";
        return false;
      }
    }
  }

  std::cout << label << ": PASS\n";
  return true;
}

int main() {
  cudaDeviceProp properties{};
  cudaError_t const status = cudaGetDeviceProperties(&properties, 0);

  if (status != cudaSuccess) {
    std::cerr << "CUDA device query failed: " << cudaGetErrorString(status) << "\n";
    return EXIT_FAILURE;
  }

  std::cout << "GPU: " << properties.name << "\n";
  std::cout << "Compute capability: " << properties.major << "."
            << properties.minor << "\n";

  auto const row_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<4>{}, Int<1>{}));

  auto const column_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<1>{}, Int<4>{}));

  print_mapping("Row-major 4x4", row_major);
  print_mapping("Column-major 4x4", column_major);

  auto const row_major_offset = [](int row, int column) {
    return row * 4 + column;
  };

  auto const column_major_offset = [](int row, int column) {
    return column * 4 + row;
  };

  auto const tile_2x2_offset = [](int row, int column) {
    return ((row / 2) * 2 + column / 2) * 4
         + (row % 2) * 2
         + column % 2;
  };

  bool const row_ok = validate_mapping(
      "Row-major formula", row_major, row_major_offset);
  bool const column_ok = validate_mapping(
      "Column-major formula", column_major, column_major_offset);

  std::cout << "\nDesigned obstacle\n";
  bool const naive_tile_ok = validate_mapping(
      "Naive row-major used as 2x2 tile-major",
      row_major,
      tile_2x2_offset);

  if (!naive_tile_ok) {
    std::cout << "Expected mismatch confirmed: flat strides cannot express this tile order.\n";
  }

  bool const all_pass = row_ok && column_ok && !naive_tile_ok;
  std::cout << "\nFINAL STATUS: " << (all_pass ? "PASS" : "FAIL") << "\n";

  return all_pass ? EXIT_SUCCESS : EXIT_FAILURE;
}

Your inspector now exposes a flat layout assumption at the exact coordinate where it breaks. Next, you will encode tile movement directly in a hierarchical layout.

Fix the Mapping with a Hierarchical Layout

Your inspector now exposes the exact coordinate where a flat row-major layout breaks the tile-major requirement. That deliberate mismatch gives you a concrete problem to solve.

A hierarchical CuTe layout represents movement inside each tile separately from movement between tiles. Nested shapes and strides can now encode the required storage order directly.

In this step, get ready to:
  • Encode inner-tile movement with a nested layout.
  • Print the hierarchical coordinate-to-index table.
  • Require the hierarchical validation to pass.
Encode the nested shape and stride

Each logical coordinate has an inner position within its tile. It also has an outer position among the four tiles.

  • In src/main.cu, find the column_major layout declaration.
  • Add the following declaration below it:
  auto const tile_2x2_major = make_layout(
      make_shape(
          make_shape(Int<2>{}, Int<2>{}),
          make_shape(Int<2>{}, Int<2>{})),
      make_stride(
          make_stride(Int<2>{}, Int<8>{}),
          make_stride(Int<1>{}, Int<4>{})));

How does the hierarchy work?

  • The nested shape divides each matrix dimension into an inner coordinate of size 2 and an outer coordinate of size 2.
  • The inner row stride of 2 moves between rows inside one tile.
  • The inner column stride of 1 moves between adjacent values inside one tile.
  • The outer strides of 8 and 4 move between tile rows and tile columns.
  • Save src/main.cu.
  • Confirm the nested layout compiles by rebuilding with this command:
cmake --build build

What does this build prove?

The CMake build compiles the new nested shape and stride through the CUDA compiler. A successful build confirms that tile_2x2_major is a valid layout type.

The compiler should complete without errors. Your terminal then returns to its prompt.

Seeing a compilation error?

  • Check that tile_2x2_major is declared inside main().
  • Compare every closing parenthesis with the declaration above.
  • Confirm that each compile-time integer uses the Int<2>{} form.
  • Ask for help with the nested layout error.
Print and validate the hierarchical mapping

The new layout must satisfy the same tile_2x2_offset formula that rejected row_major. Reusing that expectation makes the comparison meaningful.

  • In src/main.cu, find the closing brace of the if (!naive_tile_ok) block.
  • Add the following mapping output and validation below that brace:
  print_mapping("Hierarchical 2x2 tile-major 4x4", tile_2x2_major);
  bool const tile_ok = validate_mapping(
      "Hierarchical 2x2 tile-major",
      tile_2x2_major,
      tile_2x2_offset);

What does this code check?

  • The print_mapping() call turns every hierarchical coordinate into a visible offset.
  • The validate_mapping() call compares those offsets with the existing tile-major formula.
  • The tile_ok result records whether every coordinate matches.
  • Save src/main.cu.

Before you rebuild and run the inspector, what offset pattern do you expect across the first row?

  • Rebuild and run the inspector by entering these commands:
cmake --build build
./build/cute_layout_inspector

What does this run reveal?

The first command compiles your latest edit. The second command prints every mapping and runs each validation.

Below the earlier designed mismatch, you should see the hierarchical table begin with 0 1 4 5. You should also see Hierarchical 2x2 tile-major: PASS.

Does the first row look wrong?

  • Check that the nested row strides are Int<2>{} and Int<8>{}.
  • Check that the nested column strides are Int<1>{} and Int<4>{}.
  • Ask for help tracing the incorrect offsets.
Require the hierarchical check

The inspector currently calculates tile_ok without using it in the final decision. The completed success condition must include that result.

  • In src/main.cu, find this existing success condition:
  bool const all_pass = row_ok && column_ok && !naive_tile_ok;

What does the current condition require?

The current all_pass condition requires both flat formulas to pass. It also requires the designed mismatch to occur.

  • Replace the existing condition with this completed condition:
  bool const all_pass = row_ok && column_ok && !naive_tile_ok && tile_ok;

Why include tile_ok?

The added tile_ok check makes the hierarchical mapping part of the executable's success criteria. Any incorrect tile offset now changes the final status to failure.

  • Save src/main.cu.

Before you run the final check, do you expect the deliberate flat-layout failure to make the executable fail?

  • Rebuild and run the completed inspector by entering these commands:
cmake --build build
./build/cute_layout_inspector

What determines the final status?

The final condition expects the row-major check to pass. It expects the column-major check to pass.

It also expects the naive tile check to fail. The hierarchical tile check must pass.

You should see Hierarchical 2x2 tile-major: PASS followed by FINAL STATUS: PASS.

You have turned the earlier failure into a tested layout requirement. The inspector now proves that hierarchy produces the intended tile-major order.

Final status still fails?

  • Confirm that all_pass includes !naive_tile_ok.
  • Confirm that all_pass includes tile_ok.
  • Look above the final status for the validation label that reports failure.
  • Ask for help tracing the failed condition.

✔️ Awesome, I've got everything!

Your completed inspector preserves the expected flat-layout mismatch. Its hierarchical mapping now passes the tile-major check.

ⓧ I'd like to double check the full code

Compare your completed src/main.cu with this reference:

#include <cuda_runtime.h>
#include <cute/layout.hpp>

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

using namespace cute;

template <class LayoutT>
void print_mapping(std::string const& label, LayoutT const& layout) {
  std::cout << "\n" << label << "\n";
  std::cout << "Layout: " << layout << "\n";

  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      std::cout << std::setw(4) << int(layout(row, column));
    }
    std::cout << "\n";
  }
}

template <class LayoutT, class ExpectedOffset>
bool validate_mapping(
    std::string const& label,
    LayoutT const& layout,
    ExpectedOffset expected_offset) {
  int const rows = int(size<0>(layout));
  int const columns = int(size<1>(layout));

  for (int row = 0; row < rows; ++row) {
    for (int column = 0; column < columns; ++column) {
      int const expected = expected_offset(row, column);
      int const actual = int(layout(row, column));

      if (actual != expected) {
        std::cout << label << ": FAIL at (" << row << ", " << column
                  << "): expected " << expected << ", got " << actual << "\n";
        return false;
      }
    }
  }

  std::cout << label << ": PASS\n";
  return true;
}

int main() {
  cudaDeviceProp properties{};
  cudaError_t const status = cudaGetDeviceProperties(&properties, 0);

  if (status != cudaSuccess) {
    std::cerr << "CUDA device query failed: " << cudaGetErrorString(status) << "\n";
    return EXIT_FAILURE;
  }

  std::cout << "GPU: " << properties.name << "\n";
  std::cout << "Compute capability: " << properties.major << "."
            << properties.minor << "\n";

  auto const row_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<4>{}, Int<1>{}));

  auto const column_major = make_layout(
      make_shape(Int<4>{}, Int<4>{}),
      make_stride(Int<1>{}, Int<4>{}));

  auto const tile_2x2_major = make_layout(
      make_shape(
          make_shape(Int<2>{}, Int<2>{}),
          make_shape(Int<2>{}, Int<2>{})),
      make_stride(
          make_stride(Int<2>{}, Int<8>{}),
          make_stride(Int<1>{}, Int<4>{})));

  print_mapping("Row-major 4x4", row_major);
  print_mapping("Column-major 4x4", column_major);

  auto const row_major_offset = [](int row, int column) {
    return row * 4 + column;
  };

  auto const column_major_offset = [](int row, int column) {
    return column * 4 + row;
  };

  auto const tile_2x2_offset = [](int row, int column) {
    return ((row / 2) * 2 + column / 2) * 4
         + (row % 2) * 2
         + column % 2;
  };

  bool const row_ok = validate_mapping(
      "Row-major formula", row_major, row_major_offset);
  bool const column_ok = validate_mapping(
      "Column-major formula", column_major, column_major_offset);

  std::cout << "\nDesigned obstacle\n";
  bool const naive_tile_ok = validate_mapping(
      "Naive row-major used as 2x2 tile-major",
      row_major,
      tile_2x2_offset);

  if (!naive_tile_ok) {
    std::cout << "Expected mismatch confirmed: flat strides cannot express this tile order.\n";
  }

  print_mapping("Hierarchical 2x2 tile-major 4x4", tile_2x2_major);
  bool const tile_ok = validate_mapping(
      "Hierarchical 2x2 tile-major",
      tile_2x2_major,
      tile_2x2_offset);

  bool const all_pass = row_ok && column_ok && !naive_tile_ok && tile_ok;
  std::cout << "\nFINAL STATUS: " << (all_pass ? "PASS" : "FAIL") << "\n";

  return all_pass ? EXIT_SUCCESS : EXIT_FAILURE;
}

This reference contains the completed hierarchical layout and its final validation condition.

Secret mission

Generalize the Tile Shape

A wider tile reveals whether your layout logic comes from the storage rule. Extend the inspector with a 4x8 matrix made from 2x4 tiles. Then prove the new mapping with the existing validation system.

Clean Up Your Resources

Clean Up Your Resources

Your CUDA inspector runs entirely on this Ubuntu machine, so there are no ongoing cloud costs or paid services. Decide whether to keep the project, pause your work, or delete its local files.

Resources you used:

  • Local CMake CUDA project at ~/projects/cute-layout-inspector, including CMakeLists.txt, .gitignore, and src/main.cu.
  • Generated build/ directory containing the cute_layout_inspector executable.
  • Local CUTLASS v4.7.1 clone at third_party/cutlass.

Keep everything running

No action needed. Choose this option if you plan to extend the inspector or use its layout checks in another CUDA C++ project.

  • Keep ~/projects/cute-layout-inspector for future layout experiments.
  • Reuse the installed development toolchain for future CUDA C++ projects.
  • Run the inspector again whenever you want to compare the 4x4 and 4x8 mappings.

Pause - I'll come back to this later

Pausing closes your workspace while preserving every local file. The inspector exits after each run, so no background service needs stopping.

  • Close Visual Studio Code if it is open.
  • Close the terminal window.
  • Leave ~/projects/cute-layout-inspector in place for your next session.

Delete - I don't want to use this again

Deleting ~/projects/cute-layout-inspector removes the source files, build output, and local CUTLASS clone. Your reusable driver and development tools stay installed.

This deletion is permanent. It targets only the project directory.

  • Remove the project directory from its parent folder by running these commands:
cd ~/projects
rm -rf cute-layout-inspector

What do these commands do?

  • The first command moves the terminal out of the directory you are deleting.
  • The second command permanently removes cute-layout-inspector with its source files, build/ directory, and third_party/cutlass clone.
  • Check the remaining folders in ~/projects by running this command:
ls

What does this check do?

The command lists the directories that remain inside ~/projects.

You should no longer see cute-layout-inspector in the output. That confirms every listed project resource has been removed.

Nice Work!

Nice Work!

You did it! Your CuTe layout inspector now turns compile-time layout definitions into visible coordinate-to-index tables.

You've learned how to:

  • Build a native CUDA C++ development environment with CMake. Use the CUDA Runtime API to report the real GPU name plus its compute capability.
  • Construct a row-major CuTe layout from a compile-time shape paired with a stride. Compare its offsets with a column-major counterpart.
  • Expose the designed 2x2 tile-major mismatch with validate_mapping. Encode the correction as a hierarchical CuTe layout that reaches FINAL STATUS: PASS.
  • Secret Mission: Generalize the inspector to a 4x8 matrix made of 2x4 tiles. Derive its nested stride. Confirm Hierarchical 2x4 tile-major: PASS.

Ready to quiz yourself?