Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

497 lines
24 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/DataTypes/DataTypes.hpp>
#include <rocRoller/GPUArchitecture/GPUArchitectureTarget.hpp>
#include <rocRoller/InstructionValues/Register.hpp>
#include <rocRoller/KernelOptions_detail.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include "common/Utilities.hpp"
#include "TestContext.hpp"
using namespace rocRoller;
namespace WMMAObserverTests
{
class WMMAObserverTest : public TestContext
{
public:
WMMAObserverTest(GPUArchitectureGFX gfx)
: TestContext(TestContext::ForTarget({gfx})){};
WMMAObserverTest(GPUArchitectureTarget target, KernelOptions kernelOptions)
: TestContext(TestContext::ForTarget(target, kernelOptions)){};
void peekAndSchedule(Instruction& inst, uint expectedNops = 0)
{
auto peeked = m_context->observer()->peek(inst);
CHECK(peeked.nops == expectedNops);
m_context->schedule(inst);
}
};
TEST_CASE("RAW hazards on GFX1200/GFX1201", "[codegen]")
{
auto target = GENERATE(GPUArchitectureGFX::GFX1200, GPUArchitectureGFX::GFX1201);
WMMAObserverTest t{target};
{ // A/B is previous D
const auto v0 = t.createRegisters(Register::Type::Vector, DataType::Half, 5);
const auto v1 = t.createRegisters(Register::Type::Vector, DataType::Half, 1, 2);
SECTION("Expect one V_NOP If the second WMMA's A is the first WMMA's D")
{
std::vector<Instruction> insts = {Instruction("v_wmma_f16_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v0[2]},
{},
""),
Instruction("v_wmma_f16_16x16x16_f16",
{v0[3]},
{v1[0]->subset({0, 1}), v0[4], v0[3]},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], 1);
CHECK(1 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect one V_NOP If the second WMMA's B is the first WMMA's D")
{
std::vector<Instruction> insts = {Instruction("v_wmma_f16_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v0[2]},
{},
""),
Instruction("v_wmma_f16_16x16x16_f16",
{v0[3]},
{v1[0]->subset({0, 1}), v0[4], v0[3]},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], 1);
CHECK(1 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
}
{ // A/B overlap with D
const auto v0 = t.createRegisters(Register::Type::Vector, DataType::Half, 6);
const auto v1 = t.createRegisters(Register::Type::Vector, DataType::Half, 1, 3);
SECTION("Expect one V_NOP If the second WMMA's A overlaps with the first WMMA's D")
{
std::vector<Instruction> insts = {Instruction("v_wmma_f16_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v0[2]},
{},
""),
Instruction("v_wmma_f16_16x16x16_f16",
{v0[3]},
{v1[0]->subset({1, 2}), v0[4], v0[5]},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], 1);
CHECK(1 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect one V_NOP If the second WMMA's B overlaps with the first WMMA's D")
{
// If the second WMMA's B overlaps the first WMMA's D then one V_NOP is expected
std::vector<Instruction> insts = {Instruction("v_wmma_f16_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v0[2]},
{},
""),
Instruction("v_wmma_f16_16x16x16_f16",
{v0[3]},
{v0[4], v1[0]->subset({1, 2}), v0[5]},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], 1);
CHECK(1 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
}
SECTION("No register overlap should no be a hazard")
{
const auto v0 = t.createRegisters(Register::Type::Vector, DataType::Half, 4);
const auto v1 = t.createRegisters(Register::Type::Vector, DataType::Half, 1, 4);
std::vector<Instruction> insts = {Instruction("v_wmma_f16_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v1[0]->subset({0, 1})},
{},
""),
Instruction("v_wmma_f16_16x16x16_f16",
{v1[0]->subset({2, 3})},
{v0[2], v0[3], v1[0]->subset({2, 3})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1]);
CHECK(0 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
}
TEST_CASE("WAR/WAW hazards on GFX1200/GFX1201", "[codegen]")
{
auto target = GENERATE(GPUArchitectureGFX::GFX1200, GPUArchitectureGFX::GFX1201);
WMMAObserverTest t{target};
const auto v0 = t.createRegisters(Register::Type::Vector, DataType::Half, 4);
const auto v1 = t.createRegisters(Register::Type::Vector, DataType::Float, 1, 4);
const auto v2 = t.createRegisters(Register::Type::Vector, DataType::Float, 1);
SECTION("WMMA writes D then VALU reads D")
{
std::vector<Instruction> insts = {
Instruction("v_wmma_f32_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v1[0]->subset({0, 1})},
{},
""),
Instruction("v_add_f32", {v2[0]}, {v2[0], v1[0]->subset({0})}, {}, ""),
Instruction("s_endpgm", {}, {}, {}, ""),
};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], 8);
CHECK(8 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("WMMA writes D then VALU writes D")
{
std::vector<Instruction> insts = {
Instruction("v_wmma_f32_16x16x16_f16",
{v1[0]->subset({0, 1})},
{v0[0], v0[1], v1[0]->subset({0, 1})},
{},
""),
Instruction("v_add_f32", {v1[0]->subset({0})}, {v2[0], v2[0]}, {}, ""),
Instruction("s_endpgm", {}, {}, {}, ""),
};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], 8);
CHECK(8 == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
}
TEST_CASE("RAW hazards on GFX1250", "[codegen]")
{
const auto target = GENERATE(GPUArchTargetGFX1250Rev0, GPUArchTargetGFX1250Rev1);
const auto coexecutionEnabled = GENERATE(true, false);
KernelOptions kernelOptions{};
kernelOptions->coexecutionEnabled = coexecutionEnabled;
WMMAObserverTest t{target, kernelOptions};
const auto FC = Register::AllocationOptions::FullyContiguous();
SECTION("Expect 1 + coexecSlots V_NOP(s) if second WMMA's A is first WMMA's D and A && B "
"are *NOT* F8")
{
const auto v = t.createRegisters(Register::Type::Vector, DataType::Half, 5, 8, FC);
// coexecSlots = 4, if first WMMA is not:
// - v_wmma*_iu8; and
// - v_wmma*_iu4; and
// - v_wmma*f8f6f4 && A and B are not F8
const auto expectedVNops = 1 + (coexecutionEnabled ? 4 : 0);
std::vector<Instruction> insts
= {Instruction("v_wmma_f16_16x16x32_f16", {v[0]}, {v[1], v[2], v[3]}, {}, ""),
Instruction("v_wmma_f16_16x16x32_f16", {v[4]}, {v[0], v[2], v[3]}, {}, ""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect 1 + coexecSlots V_NOP(s) if second WMMA's A is first WMMA's D and A"
"*IS* F8")
{
const auto [wmmaInst, types, regCounts] = GENERATE(
std::make_tuple(std::string{"v_wmma_f32_16x16x128_f8f6f4"},
std::make_tuple(DataType::Float, DataType::FP8x4, DataType::FP4x8),
std::make_tuple(8, 16, 8)),
std::make_tuple(std::string{"v_wmma_i32_16x16x64_iu8"},
std::make_tuple(DataType::Int32, DataType::Int32, DataType::Int32),
std::make_tuple(8, 8, 8)),
std::make_tuple(std::string{"v_wmma_f32_32x16x128_f4"},
std::make_tuple(DataType::Float, DataType::FP4x8, DataType::FP4x8),
std::make_tuple(16, 16, 8)));
const auto [v0_dt, vA_dt, vB_dt] = types;
const auto [v0_count, vA_count, vB_count] = regCounts;
const auto v0 = t.createRegisters(Register::Type::Vector, v0_dt, 3, v0_count, FC);
const auto vA = t.createRegisters(Register::Type::Vector, vA_dt, 2, vA_count, FC);
const auto vB = t.createRegisters(Register::Type::Vector, vB_dt, 2, vB_count, FC);
// coexecSlots = 8, if first WMMA is:
// - v_wmma*_iu8; or
// - v_wmma*_iu4; or
// - v_wmma*f8f6f4 && A or B is F8;
// - v_wmma*_f4
const auto expectedVNops = 1 + (coexecutionEnabled ? 8 : 0);
std::vector<Instruction> insts
= {Instruction(wmmaInst, {v0[0]}, {vA[0], vB[0], v0[1]}, {}, ""),
Instruction(wmmaInst, {v0[2]}, {vA[1], vB[1], v0[0]}, {}, ""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION(
"Expect 0 + coexecSlots V_NOP(s) if VALU reads D after WMMA and A && B are *NOT* F8")
{
const auto v = t.createRegisters(Register::Type::Vector, DataType::Half, 5, 8, FC);
// coexecSlots = 4, if first WMMA is not:
// - v_wmma*_iu8; and
// - v_wmma*_iu4; and
// - v_wmma*f8f6f4 && A and B are not F8
const auto expectedVNops = 0 + (coexecutionEnabled ? 4 : 0);
std::vector<Instruction> insts
= {Instruction("v_wmma_f16_16x16x32_f16", {v[0]}, {v[1], v[2], v[3]}, {}, ""),
Instruction("v_add_f16",
{v[4]->subset({0})},
{v[0]->subset({1}), v[4]->subset({1})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect 0 + coexecSlots V_NOP(s) if VALU reads D after WMMA and A *IS* F8")
{
const auto [wmmaInst, types, regCounts] = GENERATE(
std::make_tuple(std::string{"v_wmma_f32_16x16x128_f8f6f4"},
std::make_tuple(DataType::Float, DataType::FP8x4, DataType::FP4x8),
std::make_tuple(8, 16, 8)),
std::make_tuple(std::string{"v_wmma_i32_16x16x64_iu8"},
std::make_tuple(DataType::Int32, DataType::Int32, DataType::Int32),
std::make_tuple(8, 8, 8)),
std::make_tuple(std::string{"v_wmma_f32_32x16x128_f4"},
std::make_tuple(DataType::Float, DataType::FP4x8, DataType::FP4x8),
std::make_tuple(16, 16, 8)));
const auto [v0_dt, vA_dt, vB_dt] = types;
const auto [v0_count, vA_count, vB_count] = regCounts;
const auto v0 = t.createRegisters(Register::Type::Vector, v0_dt, 3, v0_count, FC);
const auto vA = t.createRegisters(Register::Type::Vector, vA_dt, 1, vA_count, FC);
const auto vB = t.createRegisters(Register::Type::Vector, vB_dt, 1, vB_count, FC);
// coexecSlots = 8, if first WMMA is:
// - v_wmma*_iu8; or
// - v_wmma*_iu4; or
// - v_wmma*f8f6f4 && A or B is F8
const auto expectedVNops = 0 + (coexecutionEnabled ? 8 : 0);
std::vector<Instruction> insts
= {Instruction(wmmaInst, {v0[0]}, {vA[0], vB[0], v0[1]}, {}, ""),
Instruction("v_add_f16",
{v0[2]->subset({0})},
{v0[0]->subset({1}), v0[2]->subset({1})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
}
TEST_CASE("WAR/WAW hazards on GFX1250", "[codegen]")
{
const auto target = GENERATE(GPUArchTargetGFX1250Rev0, GPUArchTargetGFX1250Rev1);
const auto coexecutionEnabled = GENERATE(true, false);
KernelOptions kernelOptions{};
kernelOptions->coexecutionEnabled = coexecutionEnabled;
WMMAObserverTest t{target, kernelOptions};
const auto FC = Register::AllocationOptions::FullyContiguous();
SECTION(
"Expect 0 + coexecSlots V_NOP(s) if VALU writes A after WMMA and A && B are *NOT* F8")
{
const auto v = t.createRegisters(Register::Type::Vector, DataType::Half, 5, 8, FC);
// coexecSlots = 4, if first WMMA is not:
// - v_wmma*_iu8; and
// - v_wmma*_iu4; and
// - v_wmma*f8f6f4 && A and B are not F8
const auto expectedVNops = 0 + (coexecutionEnabled ? 4 : 0);
std::vector<Instruction> insts
= {Instruction("v_wmma_f16_16x16x32_f16", {v[0]}, {v[1], v[2], v[3]}, {}, ""),
Instruction("v_add_f16",
{v[1]->subset({0})},
{v[4]->subset({1}), v[4]->subset({1})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect 0 + coexecSlots V_NOP(s) if VALU writes A after WMMA and A *IS* F8")
{
const auto [wmmaInst, types, regCounts] = GENERATE(
std::make_tuple(std::string{"v_wmma_f32_16x16x128_f8f6f4"},
std::make_tuple(DataType::Float, DataType::FP8x4, DataType::FP4x8),
std::make_tuple(8, 16, 8)),
std::make_tuple(std::string{"v_wmma_i32_16x16x64_iu8"},
std::make_tuple(DataType::Int32, DataType::Int32, DataType::Int32),
std::make_tuple(8, 8, 8)),
std::make_tuple(std::string{"v_wmma_f32_32x16x128_f4"},
std::make_tuple(DataType::Float, DataType::FP4x8, DataType::FP4x8),
std::make_tuple(16, 16, 8)));
if(wmmaInst == "v_wmma_f32_32x16x128_f4" && target.asicRevisionId != 1)
{
SKIP(fmt::format(
"Instruction {} is not supported on target {}\n", wmmaInst, toString(target)));
}
const auto [v0_dt, vA_dt, vB_dt] = types;
const auto [v0_count, vA_count, vB_count] = regCounts;
const auto v0 = t.createRegisters(Register::Type::Vector, v0_dt, 3, v0_count, FC);
const auto vA = t.createRegisters(Register::Type::Vector, vA_dt, 1, vA_count, FC);
const auto vB = t.createRegisters(Register::Type::Vector, vB_dt, 1, vB_count, FC);
// coexecSlots = 8, if first WMMA is:
// - v_wmma*_iu8; or
// - v_wmma*_iu4; or
// - v_wmma*f8f6f4 && A or B is F8
const auto expectedVNops = 0 + (coexecutionEnabled ? 8 : 0);
std::vector<Instruction> insts
= {Instruction(wmmaInst, {v0[0]}, {vA[0], vB[0], v0[1]}, {}, ""),
Instruction("v_add_f16",
{vA[0]->subset({1})},
{v0[2]->subset({1}), v0[2]->subset({1})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect 0 + coexecSlots V_NOP(s) if VALU writes D after WMMA and A && B "
"are *NOT* F8")
{
const auto v = t.createRegisters(Register::Type::Vector, DataType::Half, 5, 8, FC);
// coexecSlots = 4, if first WMMA is not:
// - v_wmma*_iu8; and
// - v_wmma*_iu4; and
// - v_wmma*f8f6f4 && A and B are not F8
const auto expectedVNops = 0 + (coexecutionEnabled ? 4 : 0);
std::vector<Instruction> insts
= {Instruction("v_wmma_f16_16x16x32_f16", {v[0]}, {v[1], v[2], v[3]}, {}, ""),
Instruction("v_add_f16",
{v[0]->subset({0})},
{v[4]->subset({1}), v[4]->subset({1})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
SECTION("Expect 0 + coexecSlots V_NOP(s) if VALU writes D after WMMA and A *IS* F8")
{
const auto [wmmaInst, types, regCounts] = GENERATE(
std::make_tuple(std::string{"v_wmma_f32_16x16x128_f8f6f4"},
std::make_tuple(DataType::Float, DataType::FP8x4, DataType::FP4x8),
std::make_tuple(8, 16, 8)),
std::make_tuple(std::string{"v_wmma_i32_16x16x64_iu8"},
std::make_tuple(DataType::Int32, DataType::Int32, DataType::Int32),
std::make_tuple(8, 8, 8)),
std::make_tuple(std::string{"v_wmma_f32_32x16x128_f4"},
std::make_tuple(DataType::Float, DataType::FP4x8, DataType::FP4x8),
std::make_tuple(16, 16, 8)));
if(wmmaInst == "v_wmma_f32_32x16x128_f4" && target.asicRevisionId != 1)
{
SKIP(fmt::format(
"Instruction {} is not supported on target {}\n", wmmaInst, toString(target)));
}
const auto [v0_dt, vA_dt, vB_dt] = types;
const auto [v0_count, vA_count, vB_count] = regCounts;
const auto v0 = t.createRegisters(Register::Type::Vector, v0_dt, 3, v0_count, FC);
const auto vA = t.createRegisters(Register::Type::Vector, vA_dt, 1, vA_count, FC);
const auto vB = t.createRegisters(Register::Type::Vector, vB_dt, 1, vB_count, FC);
// coexecSlots = 8, if first WMMA is:
// - v_wmma*_iu8; or
// - v_wmma*_iu4; or
// - v_wmma*f8f6f4 && A or B is F8
const auto expectedVNops = 0 + (coexecutionEnabled ? 8 : 0);
std::vector<Instruction> insts
= {Instruction(wmmaInst, {v0[0]}, {vA[0], vB[0], v0[1]}, {}, ""),
Instruction("v_add_f16",
{v0[0]->subset({0})},
{v0[2]->subset({1}), v0[2]->subset({1})},
{},
""),
Instruction("s_endpgm", {}, {}, {}, "")};
t.peekAndSchedule(insts[0]);
t.peekAndSchedule(insts[1], expectedVNops);
CHECK(expectedVNops == countSubstring(t.output(), "v_nop"));
t.output().clear();
}
}
}