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
820 lines
28 KiB
C++
820 lines
28 KiB
C++
// Copyright Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include <catch2/catch_approx.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <origami/simulator/tensilelite/formocast_simulator.hpp>
|
|
#include <origami/hardware.hpp>
|
|
#include <queue>
|
|
|
|
using Catch::Approx;
|
|
using namespace origami;
|
|
|
|
// Helper function to create a ProblemInfo
|
|
Formocast::ProblemInfo make_problem_info(double M, double N, double K,
|
|
uint32_t bpeA = 2, uint32_t bpeB = 2,
|
|
uint32_t bpeD = 2, uint32_t bpeCompute = 2,
|
|
bool transA = true, bool transB = false,
|
|
double numBatches = 1,
|
|
data_type_t dataType = data_type_t::BFloat16)
|
|
{
|
|
Formocast::ProblemInfo problem;
|
|
problem.M = M;
|
|
problem.N = N;
|
|
problem.K = K;
|
|
problem.NumBatches = numBatches;
|
|
problem.bpeA = bpeA;
|
|
problem.bpeB = bpeB;
|
|
problem.bpeD = bpeD;
|
|
problem.bpeCompute = bpeCompute;
|
|
problem.transA = transA;
|
|
problem.transB = transB;
|
|
problem.swizzleTensorA = false;
|
|
problem.swizzleTensorB = false;
|
|
problem.dataType = dataType;
|
|
return problem;
|
|
}
|
|
|
|
// Helper function to create a SizeMapping
|
|
Formocast::SizeMapping make_size_mapping(int MT0 = 128, int MT1 = 128, int depthU = 32,
|
|
int miM = 32, int miN = 32, int miK = 8,
|
|
int waveNum = 4, int globalSplitU = 1)
|
|
{
|
|
Formocast::SizeMapping mapping;
|
|
mapping.macroTile[0] = MT0;
|
|
mapping.macroTile[1] = MT1;
|
|
mapping.macroTile[2] = 1;
|
|
mapping.matrixInstruction[0] = miM;
|
|
mapping.matrixInstruction[1] = miN;
|
|
mapping.matrixInstruction[2] = miK;
|
|
mapping.matrixInstruction[3] = 1;
|
|
mapping.depthU = depthU;
|
|
mapping.waveNum = waveNum;
|
|
mapping.globalSplitU = globalSplitU;
|
|
mapping.workGroupMapping = 8;
|
|
mapping.CUOccupancy = 2;
|
|
mapping.PrefetchGlobalRead = 2;
|
|
mapping.MathClocksUnrolledLoop = 2048;
|
|
mapping.grvwA = 4;
|
|
mapping.grvwB = 4;
|
|
mapping.gwvwD = 4;
|
|
mapping.VectorWidthA = 4;
|
|
mapping.VectorWidthB = 4;
|
|
mapping.LocalSplitU = 1;
|
|
mapping.DirectToVgprA = false;
|
|
mapping.DirectToVgprB = false;
|
|
mapping.DirectToLdsA = false;
|
|
mapping.DirectToLdsB = false;
|
|
mapping.NumLoadsCoalescedA = 1;
|
|
mapping.NumLoadsCoalescedB = 1;
|
|
mapping.waveGroup[0] = 2;
|
|
mapping.waveGroup[1] = 2;
|
|
mapping.globalAccumulation = 0;
|
|
mapping.globalSplitUCoalesced = false;
|
|
mapping.globalSplitUWorkGroupMappingRoundRobin = false;
|
|
return mapping;
|
|
}
|
|
|
|
TEST_CASE("Formocast: Hardware constants retrieval", "[formocast]") {
|
|
Formocast simulator;
|
|
|
|
SECTION("gfx950 is supported") {
|
|
auto hw = simulator.getHardwareConstants(hardware_t::architecture_t::gfx950);
|
|
REQUIRE(hw.architecture == hardware_t::architecture_t::gfx950);
|
|
}
|
|
|
|
SECTION("gfx942 is supported") {
|
|
auto hw = simulator.getHardwareConstants(hardware_t::architecture_t::gfx942);
|
|
REQUIRE(hw.architecture == hardware_t::architecture_t::gfx942);
|
|
}
|
|
|
|
SECTION("gfx1201 is supported") {
|
|
auto hw = simulator.getHardwareConstants(hardware_t::architecture_t::gfx1201);
|
|
REQUIRE(hw.architecture == hardware_t::architecture_t::gfx1201);
|
|
}
|
|
|
|
SECTION("Unsupported architecture throws exception") {
|
|
REQUIRE_THROWS_AS(
|
|
simulator.getHardwareConstants(static_cast<hardware_t::architecture_t>(9999)),
|
|
std::runtime_error
|
|
);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Basic problem and solution setup", "[formocast]") {
|
|
Formocast simulator;
|
|
|
|
SECTION("Set valid problem") {
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
REQUIRE_NOTHROW(simulator.setProblem(problem));
|
|
REQUIRE(simulator.problem.M == 1024);
|
|
REQUIRE(simulator.problem.N == 1024);
|
|
REQUIRE(simulator.problem.K == 1024);
|
|
}
|
|
|
|
SECTION("Set problem with zero dimension throws exception") {
|
|
auto problem = make_problem_info(0, 1024, 1024);
|
|
REQUIRE_THROWS_AS(simulator.setProblem(problem), std::runtime_error);
|
|
|
|
problem = make_problem_info(1024, 0, 1024);
|
|
REQUIRE_THROWS_AS(simulator.setProblem(problem), std::runtime_error);
|
|
|
|
problem = make_problem_info(1024, 1024, 0);
|
|
REQUIRE_THROWS_AS(simulator.setProblem(problem), std::runtime_error);
|
|
}
|
|
|
|
SECTION("Set solution mapping") {
|
|
auto mapping = make_size_mapping();
|
|
REQUIRE_NOTHROW(simulator.setSolution(mapping));
|
|
REQUIRE(simulator.sizeMapping.macroTile[0] == 128);
|
|
REQUIRE(simulator.sizeMapping.macroTile[1] == 128);
|
|
REQUIRE(simulator.sizeMapping.depthU == 32);
|
|
}
|
|
|
|
SECTION("Set hardware architecture") {
|
|
REQUIRE_NOTHROW(simulator.setHardware(hardware_t::architecture_t::gfx950));
|
|
REQUIRE(simulator.hw_consts.architecture == hardware_t::architecture_t::gfx950);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Performance prediction", "[formocast]") {
|
|
Formocast simulator;
|
|
|
|
SECTION("Basic performance prediction for square problem") {
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
auto mapping = make_size_mapping(128, 128, 32);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
REQUIRE(perf.microSeconds > 0);
|
|
REQUIRE(perf.microSeconds < 1000000); // Reasonable upper bound
|
|
REQUIRE(perf.hitRate >= 0);
|
|
REQUIRE(perf.hitRate <= 100);
|
|
}
|
|
|
|
SECTION("Performance prediction for rectangular problem") {
|
|
auto problem = make_problem_info(2048, 512, 1024);
|
|
auto mapping = make_size_mapping(256, 64, 32);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx942);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
// Check specific expected values for 2048x512x1024 on gfx942
|
|
REQUIRE(perf.microSeconds == Approx(73.448).epsilon(0.01));
|
|
REQUIRE(perf.hitRate == Approx(17.5).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("Performance prediction with batched problem") {
|
|
auto problem = make_problem_info(512, 512, 512, 2, 2, 2, 2, true, false, 4);
|
|
auto mapping = make_size_mapping(128, 128, 32);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
// Check specific expected values for batched 512x512x512 (4 batches)
|
|
REQUIRE(perf.microSeconds == Approx(24.02).epsilon(0.01));
|
|
REQUIRE(perf.hitRate == Approx(25.0).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("Performance prediction with GlobalSplitU") {
|
|
auto problem = make_problem_info(1024, 1024, 2048);
|
|
auto mapping = make_size_mapping(128, 128, 32, 32, 32, 8, 4, 2);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
REQUIRE(perf.microSeconds > 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Cache hit rate computation", "[formocast]") {
|
|
Formocast simulator;
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto hw = simulator.hw_consts;
|
|
|
|
SECTION("L1 cache hit rate computation") {
|
|
auto l1_hit = simulator.computeL1CacheHitRate(
|
|
hw, 128, 128, 32, 2, 2, 0, 0, 4, 4,
|
|
false, false, false, false,
|
|
4, 4, true, false,
|
|
1024, 1024, 1, 1,
|
|
256, 2, 2
|
|
);
|
|
|
|
REQUIRE(l1_hit.tile0HitRate == Approx(0.5).epsilon(0.01));
|
|
REQUIRE(l1_hit.tile1HitRate == Approx(0.5).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("L2 cache hit rate computation") {
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
auto mapping = make_size_mapping();
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
|
|
auto l2_hit = simulator.computeL2CacheHitRate(
|
|
1024, 1024, 1024, hw, 8, 256, 1, 8, 1, 2, 2, 0, 0, false
|
|
);
|
|
|
|
REQUIRE(l2_hit.totalHitRate == Approx(0.4375).epsilon(0.01));
|
|
REQUIRE(l2_hit.tile0HitRate == Approx(0.875).margin(0.01));
|
|
REQUIRE(l2_hit.tile1HitRate == Approx(0.0).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("L3 cache hit rate computation") {
|
|
auto l3_hit = simulator.computeL3CacheHitRate(
|
|
1024, 1024, 1024, hw, 2, 2, 0, 0, 8, 8, 8, 8
|
|
);
|
|
|
|
REQUIRE(l3_hit.totalHitRate == Approx(0.875).epsilon(0.01));
|
|
REQUIRE(l3_hit.tile0HitRate == Approx(0.875).epsilon(0.01));
|
|
REQUIRE(l3_hit.tile1HitRate == Approx(0.875).epsilon(0.01));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Store performance calculation", "[formocast]") {
|
|
Formocast simulator;
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto hw = simulator.hw_consts;
|
|
double store, store_edge;
|
|
|
|
SECTION("Calculate store performance with edge case") {
|
|
// Use 1000x1000 which is not a multiple of 128, creating edge tiles
|
|
simulator.calculateStorePerformance(
|
|
1000, 1000, 1, 1, 128, 128, 4, 2, hw, 304, 38, store, store_edge
|
|
);
|
|
|
|
REQUIRE(store == Approx(14.1824).epsilon(0.01));
|
|
REQUIRE(store_edge == Approx(7.01728).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("Calculate store performance with different GWVWD") {
|
|
double store1, store_edge1, store2, store_edge2;
|
|
|
|
simulator.calculateStorePerformance(
|
|
1024, 1024, 1, 1, 128, 128, 1, 2, hw, 304, 38, store1, store_edge1
|
|
);
|
|
|
|
simulator.calculateStorePerformance(
|
|
1024, 1024, 1, 1, 128, 128, 4, 2, hw, 304, 38, store2, store_edge2
|
|
);
|
|
|
|
// GWVWD=1 should have higher cost
|
|
REQUIRE(store1 > store2);
|
|
|
|
// Check specific expected values
|
|
REQUIRE(store1 == Approx(29.503).epsilon(0.01));
|
|
REQUIRE(store2 == Approx(14.301).epsilon(0.01));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Tie-breaker comparison", "[formocast]") {
|
|
Formocast simulator;
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
simulator.setProblem(problem);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
SECTION("Standalone tie-breaker function for skinny N") {
|
|
// Skinny N case: N <= 32 && M >= 1024 && K >= 1024
|
|
bool result = compareConfigTieBreaker(
|
|
1024, 32, 1024, 1, // problem dimensions
|
|
128, 32, 64, 4, // config A
|
|
64, 32, 64, 4 // config B
|
|
);
|
|
|
|
// Config A has larger skinny ratio when mt1 == N
|
|
REQUIRE(result == true);
|
|
}
|
|
|
|
SECTION("Standalone tie-breaker function for skinny M") {
|
|
// Skinny M case: M <= 32 && N >= 1024 && K >= 1024
|
|
bool result = compareConfigTieBreaker(
|
|
32, 1024, 1024, 1, // problem dimensions
|
|
32, 128, 64, 4, // config A
|
|
32, 64, 64, 4 // config B
|
|
);
|
|
|
|
// Config A has larger skinny ratio when mt0 == M
|
|
REQUIRE(result == true);
|
|
}
|
|
|
|
SECTION("Standalone tie-breaker function for wide & short K") {
|
|
// Wide & short K case: batch == 1 && K <= 512 && M >= 1024 && N >= 1024
|
|
bool result = compareConfigTieBreaker(
|
|
1024, 1024, 512, 1, // problem dimensions
|
|
128, 128, 32, 8, // config A (svw=8)
|
|
128, 128, 32, 4 // config B (svw=4)
|
|
);
|
|
|
|
// Config A has larger store vector width
|
|
REQUIRE(result == true);
|
|
}
|
|
|
|
SECTION("Tie-breaker returns false for equal configs") {
|
|
bool result = compareConfigTieBreaker(
|
|
1024, 1024, 1024, 1,
|
|
128, 128, 32, 4,
|
|
128, 128, 32, 4
|
|
);
|
|
|
|
REQUIRE(result == false);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: GSU overhead calculation", "[formocast]") {
|
|
Formocast simulator;
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
auto mapping = make_size_mapping();
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto hw = simulator.hw_consts;
|
|
|
|
SECTION("GSU overhead with MultipleBuffer method") {
|
|
double gsu_overhead = simulator.calculateGlobalSplitUOverhead(
|
|
1024, 1024, 1024, 1, 2, 2, // gsuMethod=2 (MultipleBuffer)
|
|
problem, hw, 1, 1, 304, 38, 128, 128, 32, 1.0, 1.0
|
|
);
|
|
|
|
REQUIRE(gsu_overhead == Approx(1.979094).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("GSU overhead with MBSK method") {
|
|
double gsu_overhead = simulator.calculateGlobalSplitUOverhead(
|
|
1024, 1024, 1024, 1, 2, 3, // gsuMethod=3 (MBSK)
|
|
problem, hw, 1, 1, 304, 38, 128, 128, 32, 1.0, 1.0
|
|
);
|
|
|
|
REQUIRE(gsu_overhead == Approx(2.762).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("No GSU overhead when GlobalSplitU=1") {
|
|
double gsu_overhead = simulator.calculateGlobalSplitUOverhead(
|
|
1024, 1024, 1024, 1, 1, 2, // GlobalSplitU=1
|
|
problem, hw, 1, 1, 304, 38, 128, 128, 32, 1.0, 1.0
|
|
);
|
|
|
|
REQUIRE(gsu_overhead == 0);
|
|
}
|
|
|
|
SECTION("GSU overhead with small M and N values") {
|
|
// Test with very small matrix dimensions M=2, N=4
|
|
double gsu_overhead = simulator.calculateGlobalSplitUOverhead(
|
|
2, 4, 1024, 1, 2, 2, // M=2, N=4, K=1024, GlobalSplitU=2, MultipleBuffer
|
|
problem, hw, 1, 1, 304, 38, 32, 32, 256, 1.0, 1.0
|
|
);
|
|
|
|
// With small M and N, the overhead should be very small
|
|
REQUIRE(gsu_overhead == Approx(0.0000151).epsilon(0.01));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: LSU overhead calculation", "[formocast]") {
|
|
Formocast simulator;
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto hw = simulator.hw_consts;
|
|
|
|
SECTION("LSU overhead calculation") {
|
|
double lsu_overhead = simulator.calculateLocalSplitUOverhead(
|
|
128, 128, 2, 4, 256, problem, hw
|
|
);
|
|
|
|
REQUIRE(lsu_overhead == Approx(1.066667).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("LSU overhead increases with larger LSU value") {
|
|
double lsu_overhead1 = simulator.calculateLocalSplitUOverhead(
|
|
128, 128, 2, 4, 256, problem, hw
|
|
);
|
|
|
|
double lsu_overhead2 = simulator.calculateLocalSplitUOverhead(
|
|
128, 128, 4, 4, 256, problem, hw
|
|
);
|
|
|
|
REQUIRE(lsu_overhead2 > lsu_overhead1);
|
|
|
|
// Check specific expected values
|
|
REQUIRE(lsu_overhead1 == Approx(1.066667).epsilon(0.01));
|
|
REQUIRE(lsu_overhead2 == Approx(1.351111).epsilon(0.01));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: FIFO queue operations", "[formocast]") {
|
|
Formocast simulator;
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
SECTION("Check global read FIFO full - no stall") {
|
|
std::deque<int> fifo;
|
|
int result = simulator.getGlobalReadQueueFullStallCycles(100, fifo, 8, 4, false, false);
|
|
REQUIRE(result == 100);
|
|
REQUIRE(fifo.size() == 0);
|
|
}
|
|
|
|
SECTION("Check global read FIFO full - with stall") {
|
|
std::deque<int> fifo;
|
|
fifo.push_back(10);
|
|
fifo.push_back(11);
|
|
fifo.push_back(12);
|
|
fifo.push_back(13);
|
|
fifo.push_back(15);
|
|
fifo.push_back(16);
|
|
fifo.push_back(17);
|
|
fifo.push_back(18);
|
|
fifo.push_back(20);
|
|
fifo.push_back(21);
|
|
fifo.push_back(22);
|
|
fifo.push_back(23);
|
|
fifo.push_back(25);
|
|
fifo.push_back(26);
|
|
fifo.push_back(27);
|
|
fifo.push_back(28);
|
|
// stall
|
|
int result = simulator.getGlobalReadQueueFullStallCycles(29, fifo, 8, 4, true, false);
|
|
REQUIRE(result == 32);
|
|
REQUIRE(fifo.size() == 20);
|
|
}
|
|
|
|
SECTION("Check local read FIFO full - no stall") {
|
|
std::queue<int> fifo;
|
|
// no stall
|
|
int result = simulator.getLocalReadQueueFullStallCycles(100, fifo, 8, 4, false, 1.0);
|
|
REQUIRE(result == 100);
|
|
REQUIRE(fifo.size() == 1);
|
|
}
|
|
|
|
SECTION("Check local read FIFO full - with bank conflict") {
|
|
std::queue<int> fifo;
|
|
fifo.push(10);
|
|
fifo.push(20);
|
|
fifo.push(30);
|
|
fifo.push(40);
|
|
// no stall
|
|
int result = simulator.getLocalReadQueueFullStallCycles(100, fifo, 8, 4, true, 1.5);
|
|
REQUIRE(result == 100);
|
|
REQUIRE(fifo.size() == 4);
|
|
}
|
|
|
|
SECTION("Check local read completion - queue not full") {
|
|
std::queue<int> fifo;
|
|
fifo.push(50);
|
|
fifo.push(60);
|
|
// fifo.size()=2 <= numLR=2, should return currentCycle
|
|
int result = simulator.getLocalReadCompletionCycle(100, fifo, 2);
|
|
REQUIRE(result == 100);
|
|
REQUIRE(fifo.size() == 2);
|
|
}
|
|
|
|
SECTION("Check local read completion - remove finished reads") {
|
|
std::queue<int> fifo;
|
|
fifo.push(50);
|
|
fifo.push(60);
|
|
fifo.push(70);
|
|
// no stall
|
|
int result = simulator.getLocalReadCompletionCycle(100, fifo, 2);
|
|
REQUIRE(result == 100);
|
|
REQUIRE(fifo.size() == 2);
|
|
REQUIRE(fifo.front() == 60);
|
|
}
|
|
|
|
SECTION("Check local read completion - wait for unfinished reads") {
|
|
std::queue<int> fifo;
|
|
fifo.push(110);
|
|
fifo.push(120);
|
|
fifo.push(130);
|
|
// stall to 120
|
|
int result = simulator.getLocalReadCompletionCycle(100, fifo, 1);
|
|
REQUIRE(result == 120);
|
|
REQUIRE(fifo.size() == 1);
|
|
}
|
|
|
|
SECTION("Push local read write with bank conflict") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 8, 1.5, true, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
REQUIRE(fifo.front() == 111); // 100 + 11
|
|
}
|
|
|
|
SECTION("Push local read write without bank conflict") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 8, 1.0, true, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
REQUIRE(fifo.front() == 110); // 100 + 10
|
|
}
|
|
|
|
SECTION("Push local write without bank conflict - bpr=8") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 8, 1.0, false, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
// For local write: latency = baseLatency + bankConflict * conflictMultiplier
|
|
// With bankConflict=1.0: latency = baseLatency + conflictMultiplier
|
|
// For gfx950: LocalWriteBaseLatencyB64=10, LocalWriteConflictMultiplierB64=2
|
|
// So: latency = 10 + 1.0 * 2 = 12
|
|
REQUIRE(fifo.front() == 112); // 100 + 12
|
|
}
|
|
|
|
SECTION("Push local write with bank conflict - bpr=8") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 8, 1.5, false, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
// With bankConflict=1.5: latency = baseLatency + 1.5 * conflictMultiplier
|
|
// For gfx950: LocalWriteBaseLatencyB64=10, LocalWriteConflictMultiplierB64=2
|
|
// So: latency = 10 + 1.5 * 2 = 13
|
|
REQUIRE(fifo.front() == 113); // 100 + 13
|
|
}
|
|
|
|
SECTION("Push local write without bank conflict - bpr=16") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 16, 1.0, false, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
// For gfx950: LocalWriteBaseLatencyB128=10, LocalWriteConflictMultiplierB128=4
|
|
// So: latency = 10 + 1.0 * 4 = 14
|
|
REQUIRE(fifo.front() == 114); // 100 + 14
|
|
}
|
|
|
|
SECTION("Push local write with bank conflict - bpr=16") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 16, 1.5, false, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
// For gfx950: LocalWriteBaseLatencyB128=10, LocalWriteConflictMultiplierB128=4
|
|
// So: latency = 10 + 1.5 * 4 = 16
|
|
REQUIRE(fifo.front() == 116); // 100 + 16
|
|
}
|
|
|
|
SECTION("Push local write without bank conflict - bpr=4") {
|
|
std::queue<int> fifo;
|
|
|
|
simulator.pushLocalReadWrite(100, fifo, 4, 1.0, false, 0);
|
|
|
|
REQUIRE(fifo.size() == 1);
|
|
// For gfx950: LocalWriteBaseLatencyB32=10, LocalWriteConflictMultiplierB32=1
|
|
// So: latency = 10 + 1.0 * 1 = 11
|
|
REQUIRE(fifo.front() == 111); // 100 + 11
|
|
}
|
|
|
|
SECTION("Get local write queue full stall cycles - numWaves != 4") {
|
|
// When numWaves != 4, no penalty is applied
|
|
// Test case 1: no penalty
|
|
int result1 = simulator.getLocalWriteQueueFullStallCycles(100, 50, 3, 8, 2);
|
|
REQUIRE(result1 == 103);
|
|
|
|
// Test case 2: previousLW dominates without penalty
|
|
int result2 = simulator.getLocalWriteQueueFullStallCycles(100, 70, 3, 8, 2);
|
|
REQUIRE(result2 == 103);
|
|
|
|
// Test case 3: closer previousLW dominates without penalty
|
|
int result3 = simulator.getLocalWriteQueueFullStallCycles(100, 99, 3, 8, 2);
|
|
REQUIRE(result3 == 103);
|
|
}
|
|
|
|
SECTION("Get local write queue full stall cycles - numWaves == 4, bpWrite == 16") {
|
|
// When numWaves == 4 and bpWrite == 16, penalty = issueCycles
|
|
// Test case 1: currentCycle dominates
|
|
int result1 = simulator.getLocalWriteQueueFullStallCycles(100, 50, 5, 16, 4);
|
|
REQUIRE(result1 == 105);
|
|
|
|
// Test case 2: previousLW + penalty dominates
|
|
int result2 = simulator.getLocalWriteQueueFullStallCycles(100, 99, 5, 16, 4);
|
|
REQUIRE(result2 == 110);
|
|
}
|
|
|
|
SECTION("Get local write queue full stall cycles - edge cases") {
|
|
// Edge case: previousLW == currentCycle
|
|
int result1 = simulator.getLocalWriteQueueFullStallCycles(100, 100, 10, 8, 2);
|
|
REQUIRE(result1 == 110);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Bank conflict analysis", "[formocast]") {
|
|
Formocast simulator;
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
SECTION("Analyze bank conflicts from VGPR state") {
|
|
// Create mock VGPR state for 64 threads
|
|
std::vector<std::unordered_map<std::string, int64_t>> vgprState(64);
|
|
|
|
// Initialize with some test addresses (no conflicts in this pattern)
|
|
for (int i = 0; i < 64; i++) {
|
|
vgprState[i]["vgprLocalReadAddrA"] = i * 32;
|
|
vgprState[i]["vgprLocalReadAddrB"] = i * 16;
|
|
}
|
|
|
|
auto result = simulator.analyzeBankConflictsFromVGPR(
|
|
vgprState, "vgprLocalReadAddrA", "vgprLocalReadAddrB", 4, 8
|
|
);
|
|
|
|
// Check specific expected values (no conflicts = 1.0)
|
|
REQUIRE(result.ratioA == Approx(8.0).epsilon(0.01));
|
|
REQUIRE(result.ratioB == Approx(2.0).epsilon(0.01));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Memory access costs calculation", "[formocast]") {
|
|
Formocast simulator;
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto hw = simulator.hw_consts;
|
|
|
|
SECTION("Calculate memory access costs") {
|
|
auto mem_costs = simulator.calculateMTMemoryAccessCosts(
|
|
1024, 1024, 256,
|
|
128, 128, hw,
|
|
32, 256, 32,
|
|
false, false,
|
|
2, 2,
|
|
64, 8, 8,
|
|
false, false,
|
|
1, 1, false, false, 1, 1,
|
|
256, 2, 2,
|
|
8, -1, 1, 1, 1, true,
|
|
8, 8, 1, 8, 1
|
|
);
|
|
|
|
// Check specific expected values with tolerance
|
|
REQUIRE(mem_costs.mem_overall == Approx(1.32139).epsilon(0.0001));
|
|
REQUIRE(mem_costs.l1_hit == Approx(0.5).epsilon(0.0001));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Loop overall calculation", "[formocast]") {
|
|
Formocast simulator;
|
|
|
|
SECTION("Loop overall with prefetch") {
|
|
Formocast::MemoryAccessCosts mem;
|
|
mem.mem_overall = 10.0;
|
|
|
|
double loop = simulator.getLoop_time(mem, 8.0, 10, 2.0, 1, false);
|
|
|
|
// Check specific expected value
|
|
REQUIRE(loop == Approx(82.0).epsilon(0.0001));
|
|
}
|
|
|
|
SECTION("Loop overall without prefetch") {
|
|
Formocast::MemoryAccessCosts mem;
|
|
mem.mem_overall = 10.0;
|
|
|
|
double loop = simulator.getLoop_time(mem, 8.0, 10, 1.0, 1, false);
|
|
|
|
// Check specific expected value
|
|
REQUIRE(loop == Approx(100.0).epsilon(0.0001));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Occupancy resolution", "[formocast]") {
|
|
Formocast simulator;
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto hw = simulator.hw_consts;
|
|
|
|
SECTION("check occupancy value") {
|
|
// Calculate performance with occupancy = 1 (last parameter)
|
|
double perf_occ1 = simulator.resolveOccupancy(hw, 100.0, 10.0, 50.0, 20.0, 2, 1, 1);
|
|
|
|
// Calculate performance with occupancy = 2 (last parameter)
|
|
double perf_occ2 = simulator.resolveOccupancy(hw, 100.0, 10.0, 50.0, 20.0, 2, 2, 1);
|
|
|
|
REQUIRE(perf_occ1 == Approx(100.1).epsilon(0.01));
|
|
REQUIRE(perf_occ2 == Approx(222.7).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("NumTiles comparison: numTile=1 perf should be <= numTile=2 perf") {
|
|
// Calculate performance with numTile = 1, occupancy = 1
|
|
double perf_tile1 = simulator.resolveOccupancy(hw, 100.0, 10.0, 50.0, 20.0, 1, 1, 1);
|
|
|
|
// Calculate performance with numTile = 2, occupancy = 1
|
|
double perf_tile2 = simulator.resolveOccupancy(hw, 100.0, 10.0, 50.0, 20.0, 2, 1, 1);
|
|
|
|
// numTile=1 perf should be less than or equal to numTile=2 perf
|
|
REQUIRE(perf_tile1 <= perf_tile2);
|
|
|
|
REQUIRE(perf_tile1 == Approx(100.0).epsilon(0.01));
|
|
REQUIRE(perf_tile2 == Approx(100.1).epsilon(0.01));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Tie-breaker info", "[formocast]") {
|
|
Formocast simulator;
|
|
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
auto mapping = make_size_mapping();
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
// Run prediction to populate perfInfo
|
|
simulator.predictedPerformance();
|
|
|
|
SECTION("Get tie-breaker info") {
|
|
auto tbInfo = simulator.getTieBreakerInfo();
|
|
|
|
REQUIRE(tbInfo.mt0 > 0);
|
|
REQUIRE(tbInfo.mt1 > 0);
|
|
REQUIRE(tbInfo.du > 0);
|
|
REQUIRE(tbInfo.perf > 0);
|
|
}
|
|
|
|
SECTION("Get min tie-breaker info") {
|
|
auto minTbInfo = simulator.getMinTieBreakerInfo();
|
|
|
|
REQUIRE(minTbInfo.mt0 > 0);
|
|
REQUIRE(minTbInfo.mt1 > 0);
|
|
REQUIRE(minTbInfo.du > 0);
|
|
}
|
|
|
|
SECTION("Compare tie-breaker configs") {
|
|
Formocast::MinTieBreakerInfo config1;
|
|
config1.mt0 = 128;
|
|
config1.mt1 = 128;
|
|
config1.du = 32;
|
|
config1.svw = 4;
|
|
|
|
Formocast::MinTieBreakerInfo config2;
|
|
config2.mt0 = 64;
|
|
config2.mt1 = 64;
|
|
config2.du = 32;
|
|
config2.svw = 4;
|
|
|
|
// Equal configs
|
|
REQUIRE(simulator.isBetter(config1, config1) == false);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Formocast: Edge cases and error handling", "[formocast]") {
|
|
Formocast simulator;
|
|
|
|
SECTION("Very small problem size") {
|
|
auto problem = make_problem_info(32, 32, 32);
|
|
auto mapping = make_size_mapping(128, 128, 32);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
// Should return a large penalty for inefficient configuration (32x32x32)
|
|
REQUIRE(perf.microSeconds == Approx(1e+07).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("Very large problem size") {
|
|
auto problem = make_problem_info(8192, 8192, 8192);
|
|
auto mapping = make_size_mapping(256, 256, 64);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
// Check specific expected value for large problem (8192x8192x8192)
|
|
REQUIRE(perf.microSeconds == Approx(1573.751).epsilon(0.01));
|
|
}
|
|
|
|
SECTION("Problem with GlobalSplitU=2") {
|
|
auto problem = make_problem_info(1024, 1024, 1024);
|
|
auto mapping = make_size_mapping(128, 128, 32, 32, 32, 8, 4, 2);
|
|
|
|
simulator.setProblem(problem);
|
|
simulator.setSolution(mapping);
|
|
simulator.setHardware(hardware_t::architecture_t::gfx950);
|
|
|
|
auto perf = simulator.predictedPerformance();
|
|
|
|
// Check specific expected value for GlobalSplitU=2
|
|
REQUIRE(perf.microSeconds == Approx(26.017).epsilon(0.01));
|
|
}
|
|
}
|