Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/client/main.cpp
T
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

1574 lines
81 KiB
C++

/*******************************************************************************
*
* MIT License
*
* Copyright (C) 2022-2026 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*******************************************************************************/
#include <Tensile/Contractions.hpp>
#include <Tensile/DataTypes.hpp>
#include <Tensile/EmbeddedLibrary.hpp>
#include <Tensile/MasterSolutionLibrary.hpp>
#include <Tensile/Tensile.hpp>
#include <Tensile/hip/HipHardware.hpp>
#include <Tensile/hip/HipSolutionAdapter.hpp>
#include <Tensile/hip/HipUtils.hpp>
#include "BenchmarkTimer.hpp"
#include "ClientProblemFactory.hpp"
#include "DataInitialization.hpp"
#include "HardwareMonitorListener.hpp"
#include "MetaRunListener.hpp"
#include "ProgressListener.hpp"
#include "ReferenceValidator.hpp"
#include "SolutionIterator.hpp"
#include "TimingEvents.hpp"
#include "TimingInstrumentation.hpp"
#include "LibraryUpdateReporter.hpp"
#include "LogReporter.hpp"
#include "MetaResultReporter.hpp"
#include "PerformanceReporter.hpp"
#include "ResultFileReporter.hpp"
#include "ResultReporter.hpp"
#include "ProgramOptions.hpp"
#include "Utility.hpp"
#ifndef TENSILELITE_CLIENT_ENABLE_ROCPROFSDK
#define TENSILELITE_CLIENT_ENABLE_ROCPROFSDK 0
#endif
#if TENSILELITE_CLIENT_ENABLE_ROCPROFSDK
#include "Profiler.hpp"
#endif
#include <algorithm>
#include <chrono>
#include <cstddef>
#if defined(__linux__)
// Used by getMinKernelSizeToGwEnd() to parse .co ELF symbol tables. <elf.h>
// is glibc-only, so the whole ELF code path is Linux-gated.
#include <cstring> // std::memcmp / std::strcmp
#include <elf.h>
#include <limits> // std::numeric_limits<> sentinel
#endif
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
namespace TensileLite
{
namespace Client
{
__global__ void flush_icache()
{
asm __volatile__("s_icache_inv \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t"
"s_nop 0 \n\t" ::
:);
}
uint32_t flush_grid_size()
{
hipDeviceProp_t deviceProps;
HIP_CHECK_EXC(hipGetDeviceProperties(&deviceProps, 0));
uint32_t numBlocks = deviceProps.multiProcessorCount * 60;
return numBlocks;
}
float estimate_flush_kernel_time(hipStream_t stream, bool useGPUTimer)
{
const int flushIter = 100000;
hipEvent_t start, stop;
std::chrono::time_point<std::chrono::high_resolution_clock> begTime;
if(useGPUTimer)
{
HIP_CHECK_EXC(hipEventCreate(&start));
HIP_CHECK_EXC(hipEventCreate(&stop));
}
//warmup runs
for(int i = 0; i < flushIter; i++)
{
hipLaunchKernelGGL(flush_icache, flush_grid_size(), 64, 0, stream);
}
if(useGPUTimer)
{
HIP_CHECK_EXC(hipEventRecord(start, stream));
}
else
{
HIP_CHECK_EXC(hipStreamSynchronize(stream));
begTime = std::chrono::high_resolution_clock::now();
}
for(int i = 0; i < flushIter; i++)
{
hipLaunchKernelGGL(flush_icache, flush_grid_size(), 64, 0, stream);
}
float time{};
if(useGPUTimer)
{
HIP_CHECK_EXC(hipEventRecord(stop, stream));
HIP_CHECK_EXC(hipEventSynchronize(stop));
}
HIP_CHECK_EXC(hipStreamSynchronize(stream));
if(useGPUTimer)
{
HIP_CHECK_EXC(hipEventElapsedTime(&time, start, stop));
HIP_CHECK_EXC(hipEventDestroy(start));
HIP_CHECK_EXC(hipEventDestroy(stop));
}
else
{
time = std::chrono::duration<float,
std::milli>{std::chrono::high_resolution_clock::now()
- begTime}
.count();
}
return time / flushIter;
}
template <typename T>
po::typed_value<T>* value_default(std::string const& desc)
{
return po::value<T>()->default_value(T(), desc);
}
template <typename T>
po::typed_value<T>* value_default()
{
return po::value<T>()->default_value(T());
}
template <typename T>
po::typed_value<std::vector<T>>* vector_default_empty()
{
return value_default<std::vector<T>>("[]");
}
po::options_description all_options()
{
po::options_description options("Tensile client options");
// clang-format off
options.add_options()
("help,h", "Show help message.")
("config-file", vector_default_empty<std::string>(), "INI config file(s) to read.")
("library-file,l", po::value<std::string>(), "Load a (YAML) solution library. If not specified, we will use "
"the embedded library, if available.")
("code-object,c", vector_default_empty<std::string>(), "Code object file with kernel(s). If none are "
"specified, we will use the embedded code "
"object(s) if available.")
("performance-metric", po::value<PerformanceMetric>()->default_value(PerformanceMetric::DeviceEfficiency), "Metric for benchmarking results")
("problem-identifier", po::value<std::string>(), "Problem identifer (Einstein notation). Either "
"this or free/batch/bound must be specified.")
("type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "Data type")
("a-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "A data type")
("b-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "B data type")
("c-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "C data type")
("d-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "D data type")
("e-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "E data type")
("amaxD-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "amaxD data type")
("alpha-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "alpha data type")
("beta-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "beta data type")
("compute-input-type-A", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "compute input data type A")
("compute-input-type-B", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "compute input data type B")
("f32-xdl-math-op", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "Use xf32 compute for float input and output matrices.")
("mx-a-block", po::value<int>()->default_value(0), "block of mx datatype input matrix A")
("mx-b-block", po::value<int>()->default_value(0), "block of mx datatype input matrix B")
("mx-a-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::E8), "type of mx datatype input matrix A")
("mx-b-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::E8), "type of mx datatype input matrix B")
("swizzle-tensor-a", po::value<bool>()->default_value(false), "Swizzle input tensor A.")
("swizzle-tensor-b", po::value<bool>()->default_value(false), "Swizzle input tensor B.")
("mx-scale-format", po::value<int>()->default_value(0), "MX scale data format (0=none, 1=pre-swizzle for GPU kernel layout)")
("activation-compute-type", po::value<rocisa::DataType>()->default_value(rocisa::DataType::None), "Activation compute type.")
("high-precision-accumulate", po::value<bool>()->default_value(false), "Use high-precision accumulate.")
("sparse", po::value<int>()->default_value(0), "A or B matrix is sparse matrix.")
("strided-batched", po::value<bool>()->default_value(true), "Use strided-batched or general batched")
("grouped-gemm", po::value<bool>()->default_value(false), "Use grouped gemm")
("kernel-language", po::value<KernelLanguage>()->default_value(KernelLanguage::Any), "Select kernel language.")
("deterministic-mode", po::value<bool>()->default_value(false), "Enforce deterministic summation patterns"
"by not splitting U among workgroups")
("init-seed", po::value<unsigned int>()->default_value(0), "The seed for srand")
("init-a", po::value<InitMode>()->default_value(InitMode::Random), "Initialization for A")
("init-b", po::value<InitMode>()->default_value(InitMode::Random), "Initialization for B")
("init-c", po::value<InitMode>()->default_value(InitMode::Random), "Initialization for C")
("init-d", po::value<InitMode>()->default_value(InitMode::Zero), "Initialization for D")
("init-e", po::value<InitMode>()->default_value(InitMode::Zero), "Initialization for E")
("init-gate", po::value<InitMode>()->default_value(InitMode::Random), "Initialization for gate residual")
("init-alpha", po::value<InitMode>()->default_value(InitMode::Two), "Initialization for alpha")
("init-beta", po::value<InitMode>()->default_value(InitMode::Two), "Initialization for beta")
("init-bias", po::value<InitMode>()->default_value(InitMode::One), "Initialization for bias")
("init-scaleA", po::value<InitMode>()->default_value(InitMode::Two), "Initialization for scaleA")
("init-scaleB", po::value<InitMode>()->default_value(InitMode::Two), "Initialization for scaleB")
("init-scaleC", po::value<InitMode>()->default_value(InitMode::Two), "Initialization for scaleC")
("init-scaleD", po::value<InitMode>()->default_value(InitMode::Two), "Initialization for scaleD")
("init-scaleAlphaVec", po::value<InitMode>()->default_value(InitMode::One), "Initialization for scaleAlphaVec")
("init-mx-a", po::value<InitMode>()->default_value(InitMode::One), "Initialization for MX Scale for A")
("init-mx-b", po::value<InitMode>()->default_value(InitMode::One), "Initialization for MX Scale for B")
("pristine-on-gpu", po::value<bool>()->default_value(true), "Keep a pristine copy of inputs on GPU for performance")
("c-equal-d", po::value<bool>()->default_value(false), "C equals D")
("offset-a", po::value<size_t>()->default_value(0), "buffer a start offset")
("offset-b", po::value<size_t>()->default_value(0), "buffer b start offset")
("offset-c", po::value<size_t>()->default_value(0), "buffer c start offset")
("offset-d", po::value<size_t>()->default_value(0), "buffer d start offset")
("offset-e", po::value<size_t>()->default_value(0), "buffer e start offset")
("print-valids", po::value<bool>()->default_value(false), "Print values that pass validation")
("print-max", po::value<int>()->default_value(-1), "Max number of values to print")
("num-elements-to-validate", po::value<int>()->default_value(0), "Number of elements to validate")
("bounds-check", po::value<BoundsCheckMode>()->default_value(BoundsCheckMode::Disable),
"1:Use sentinel values to check memory boundaries."
"2:Memory bound check by front guard page"
"3:Memory bound check by back guard page"
"4:Memory bound check by both side guard page")
("prune-mode", po::value<PruneSparseMode>()->default_value(PruneSparseMode::PruneRandom), "prune Sparse mode")
("print-tensor-a", po::value<bool>()->default_value(false), "Print tensor A.")
("print-tensor-b", po::value<bool>()->default_value(false), "Print tensor B.")
("print-tensor-c", po::value<bool>()->default_value(false), "Print tensor C.")
("print-tensor-d", po::value<bool>()->default_value(false), "Print tensor D.")
("print-tensor-ref", po::value<bool>()->default_value(false), "Print reference tensor D.")
("print-tensor-bias", po::value<bool>()->default_value(false), "Print tensor Bias.")
("print-tensor-gate", po::value<bool>()->default_value(false), "Print tensor GateResidual.")
("print-tensor-scale-alpha-vec", po::value<bool>()->default_value(false), "Print tensor ScaleAlphaVec.")
("print-tensor-amaxd", po::value<bool>()->default_value(false), "Print tensor AmaxD value from both CPU and GPU.")
("dump-tensors", po::value<bool>()->default_value(false), "Binary dump tensors instead of printing.")
("device-idx", po::value<int>()->default_value(0), "Device index")
("use-default-stream", po::value<bool>()->default_value(false), "Use default Hip stream to run kernels.")
("platform-idx", po::value<int>()->default_value(0), "OpenCL Platform Index")
("num-warmups", po::value<int>()->default_value(0), "Number of warmups to run")
("sync-after-warmups", po::value<bool>()->default_value(true), "Synchronize GPU after warmup kernel runs")
("num-benchmarks", po::value<int>()->default_value(1), "Number of benchmarks to run")
("num-enqueues-per-sync", po::value<int>()->default_value(1), "Enqueues per sync, will affect by min-flops-per-sync")
("max-enqueues-per-sync", po::value<int>()->default_value(-1), "Max Enqueues per sync, will affect by min-flops-per-sync")
("num-syncs-per-benchmark", po::value<int>()->default_value(1), "Syncs per benchmark")
("skip-slow-solution-ratio", po::value<float>()->default_value(0.0), "ratio to skip slow solution during warm-up stage")
("min-flops-per-sync", po::value<size_t>()->default_value(0), "Minimum number of flops per sync to increase stability for small problems.")
("use-gpu-timer", po::value<bool>()->default_value(true), "Use GPU timer")
("sleep-percent", po::value<int>()->default_value(0), "Sleep percentage")
("hardware-monitor", po::value<bool>()->default_value(true), "Use hardware monitor.")
("perf-l2-read-hits", po::value<double>()->default_value(0.0), "L2 read hits")
("perf-l2-write-hits", po::value<double>()->default_value(0.5), "L2 write hits")
("perf-l2-read-bw-mul", po::value<double>()->default_value(2.0), "L2 read bandwidth multiplier")
("perf-read-efficiency", po::value<double>()->default_value(0.85), "Read efficiency")
("perf-ops-per-cycle", po::value<int>()->default_value(64), "Ops per cycle")
("csv-export-extra-cols", po::value<bool>()->default_value(false), "CSV exports winner information")
("csv-merge-same-problems", po::value<bool>()->default_value(false), "CSV merge rows of same problem id")
("PrintWinnersOnly", po::value<bool>()->default_value(false), "PrintWinnersOnly")
("problem-size,p", vector_default_empty<std::string>(), "Specify a problem size. Comma-separated list of "
"sizes, in the order of the Einstein notation.")
("prob-sol-map", vector_default_empty<std::string>(), "[probIdx, solIdx]")
("a-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("b-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("c-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("d-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("e-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("bias-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("gate-strides", vector_default_empty<std::string>(), "Unspecified means default stride "
"(prev_dim_stride*prev_dim_size)"
"specifying once applies to all problem sizes, "
"otherwise specify once per problem size.")
("problem-start-idx", po::value<int>()->default_value(0), "First problem to run")
("num-problems", po::value<int>()->default_value(-1), "Number of problems to run")
("solution-start-idx", po::value<int>()->default_value(-1), "First solution to run")
("num-solutions", po::value<int>()->default_value(-1), "Number of solutions to run")
("best-solution", po::value<bool>()->default_value(false), "Best solution benchmark mode")
("results-file", po::value<std::string>()->default_value("results.csv"), "File name to write results.")
("log-file", po::value<std::string>(), "File name for output log.")
("log-file-append", po::value<bool>()->default_value(false), "Append to log file.")
("log-level", po::value<LogLevel>()->default_value(LogLevel::Debug), "Log level")
("library-update-file", po::value<std::string>()->default_value(""), "File name for writing indices "
"and speeds suitable for updating "
"an existing library logic file.")
("library-update-comment", po::value<bool>()->default_value(false), "Include solution name as a "
"comment in library update "
"file.")
("a-ops", vector_default_empty<TensorOp>(), "Operations applied to A.")
("b-ops", vector_default_empty<TensorOp>(), "Operations applied to B.")
("c-ops", vector_default_empty<TensorOp>(), "Operations applied to C.")
("d-ops", vector_default_empty<TensorOp>(), "Operations applied to D.")
("exit-on-error", po::value<bool>()->default_value(false), "Exit run early on failed kernels or other errors.")
("selection-only", po::value<bool>()->default_value(false), "Don't run any solutions, only print kernel selections.")
("max-workspace-size", po::value<size_t>()->default_value(32*1024*1024), "Max workspace for training")
("granularity-threshold", po::value<double>()->default_value(0.0), "Don't run a solution if total granularity is below")
("prediction-threshold", po::value<double>()->default_value(2.0), "Don't run a solution if predicted performance is low")
("activation-type", po::value<ActivationType>()->default_value(ActivationType::None), "An activation type")
("activation-hpa", po::value<bool>()->default_value(false), "Use the same data type as high precision accumulate.")
("activation-no-guard", po::value<bool>()->default_value(false), "Use activation guard to deall with nan outputs.")
("activation-additional-args",vector_default_empty<std::string>(), "Activation additional floating-point number arguments.")
("activation-enum-args", po::value<std::vector<ActivationType>>()->default_value(std::vector<ActivationType>(1, ActivationType::None), "[]"), "Activation enum argument.")
("streamk-hybrid-mode", po::value<std::vector<int>>()->default_value(std::vector<int>(1, 0), "[0]"), "StreamK=5 hybrid-mode toggle values. Each element runs the problem once with setParams().setStreamKTileSchedulingMode(value); accepts {0=OFF (static), 1=ON (dynamic per-XCD work-queue), 2=AUTO (heuristic)}. Use [0, 1] in sweep YAMLs to deterministically exercise both SK5 sub-paths in one run. Use [2] (or `--streamk-hybrid-mode 2` to override a YAML default of [0]) to run the AUTO heuristic end-to-end on a real problem.")
("use-bias", po::value<int>()->default_value(0), "Use bias.")
("bias-source", po::value<int>()->default_value(3), "Bias source.")
("use-scaleAB", po::value<std::string>()->default_value(""), "Use scaleAB.")
("use-scaleCD", po::value<bool>()->default_value(false), "Use scaleCD.")
("use-scaleAlphaVec", po::value<int>()->default_value(0), "Use scaleAlphaVec.")
("bias-type-args", po::value<std::vector<rocisa::DataType>>()->default_value(std::vector<rocisa::DataType>(1, rocisa::DataType::None), "[]"), "Bias data type args.")
("use-gate-residual", po::value<bool>()->default_value(false), "Use gate residual.")
("gate-type-args", po::value<std::vector<rocisa::DataType>>()->default_value(std::vector<rocisa::DataType>(1, rocisa::DataType::None), "[]"), "Gate residual data type args.")
("factor-dim-args", po::value<std::vector<int>>()->default_value(std::vector<int>(1, 0), "[]"), "factor dimensions args.")
("icache-flush-args", po::value<std::vector<bool>>()->default_value(std::vector<bool>(1, false), "[]"), "ICache flush args.")
("icache-rotate-copies", po::value<int>()->default_value(0),
"Number of EXTRA hipModule_t copies of each --code-object file to load and "
"rotate per launch (I-cache cold-miss test). "
"0 = disabled, no extra copies (default). "
"N (>0) = load N extra copies (total = N+1 modules including the original). "
"-1 = auto: extras = max(inputArr.size()-1, cache-overflow term). "
"First term aligns with DataInit's rotating buffer cycle; "
"second term targets overflowing L1 I-cache: "
"Linux uses --icache-rotate-size*2*1024 / min(kernel_start->label_GW_End "
"across all --code-object); non-Linux uses --icache-rotate-size directly.")
("icache-rotate-size", po::value<int>()->default_value(64),
"Cache budget (in KB) for the cache-overflow term of --icache-rotate-copies=-1. "
"Linux: effective bytes = N * 2 * 1024 (default 64 -> 128KB, ~2x typical L1 "
"I-cache); larger -> more copies loaded for the same per-kernel hot-path size. "
"Non-Linux: used directly as the extras count (no ELF parsing).")
("use-e", po::value<bool>()->default_value(false), "Use E.")
("use-gradient", po::value<bool>()->default_value(false), "Use gradient.")
("use-user-args", po::value<bool>()->default_value(false), "Use user argument structure as kernel input.")
("rotating-buffer-size", po::value<int32_t>()->default_value(0), "Size of rotating buffer in the unit of MB.")
("rotating-buffer-mode", po::value<int32_t>()->default_value(0), "Rotating mode.")
("output-amaxD", po::value<bool>()->default_value(false), "Output AmaxD.")
("timing-instrumentation", po::value<bool>()->default_value(false)->implicit_value(true), "Enable detailed timing instrumentation output to stderr.")
("rocprof-counter", vector_default_empty<std::string>(), "Rocprof counters.")
("metadata-layout", po::value<int32_t>()->default_value(0), "Sparse Metadata Layout")
;
// clang-format on
return options;
}
/** Dump parsed program options to a text file for comparison (Boost vs replacement).
* Set env TENSILE_DUMP_OPTIONS=1 to enable. Writes to /tmp/tensilelite_program_options_dump.txt */
void DumpProgramOptionsToFile(po::variables_map const& args, char const* path)
{
std::ofstream out(path);
if(!out)
{
std::cerr << "TENSILE_DUMP_OPTIONS: failed to open " << path << " for write\n";
return;
}
out << "# program_options dump (one key=value per line; vectors as size then "
"elements)\n";
#define DUMP_OPT(K, T) \
do \
{ \
if(args.count(K)) \
{ \
try \
{ \
out << (K) << "=" << args[(K)].as<T>() << "\n"; \
} \
catch(...) \
{ \
out << (K) << "=(as<" #T "> failed)\n"; \
} \
} \
} while(0)
#define DUMP_VEC(K, T) \
do \
{ \
if(args.count(K)) \
{ \
try \
{ \
auto const& v = args[(K)].as<std::vector<T>>(); \
out << (K) << ".size=" << v.size(); \
for(size_t i = 0; i < v.size(); ++i) \
out << " " << v[i]; \
out << "\n"; \
} \
catch(...) \
{ \
out << (K) << "=(vector as failed)\n"; \
} \
} \
} while(0)
#define DUMP_VECVEC(K) \
do \
{ \
if(args.count(K)) \
{ \
try \
{ \
auto const& v = args[(K)].as<std::vector<std::vector<size_t>>>(); \
out << (K) << ".size=" << v.size() << "\n"; \
for(size_t i = 0; i < v.size(); ++i) \
{ \
out << (K) << "[" << i << "]="; \
for(size_t j = 0; j < v[i].size(); ++j) \
out << (j ? " " : "") << v[i][j]; \
out << "\n"; \
} \
} \
catch(...) \
{ \
out << (K) << "=(vecvec failed)\n"; \
} \
} \
} while(0)
if(args.count("config-file"))
{
try
{
auto const& v = args["config-file"].as<std::vector<std::string>>();
out << "config-file.size=" << v.size() << "\n";
for(size_t i = 0; i < v.size(); ++i)
out << "config-file[" << i << "]=" << v[i] << "\n";
}
catch(...)
{
out << "config-file=(failed)\n";
}
}
DUMP_OPT("library-file", std::string);
if(args.count("code-object"))
{
try
{
auto const& v = args["code-object"].as<std::vector<std::string>>();
out << "code-object.size=" << v.size() << "\n";
for(size_t i = 0; i < v.size(); ++i)
out << "code-object[" << i << "]=" << v[i] << "\n";
}
catch(...)
{
out << "code-object=(failed)\n";
}
}
DUMP_OPT("performance-metric", PerformanceMetric);
DUMP_OPT("problem-identifier", std::string);
DUMP_OPT("type", rocisa::DataType);
DUMP_OPT("a-type", rocisa::DataType);
DUMP_OPT("b-type", rocisa::DataType);
DUMP_OPT("c-type", rocisa::DataType);
DUMP_OPT("d-type", rocisa::DataType);
DUMP_OPT("e-type", rocisa::DataType);
DUMP_OPT("amaxD-type", rocisa::DataType);
DUMP_OPT("alpha-type", rocisa::DataType);
DUMP_OPT("beta-type", rocisa::DataType);
DUMP_OPT("compute-input-type", rocisa::DataType);
DUMP_OPT("f32-xdl-math-op", rocisa::DataType);
DUMP_OPT("swizzle-tensor-a", bool);
DUMP_OPT("swizzle-tensor-b", bool);
DUMP_OPT("activation-compute-type", rocisa::DataType);
DUMP_OPT("high-precision-accumulate", bool);
DUMP_OPT("sparse", int);
DUMP_OPT("strided-batched", bool);
DUMP_OPT("grouped-gemm", bool);
DUMP_OPT("kernel-language", KernelLanguage);
DUMP_OPT("deterministic-mode", bool);
DUMP_OPT("init-seed", unsigned int);
DUMP_OPT("init-a", InitMode);
DUMP_OPT("init-b", InitMode);
DUMP_OPT("init-c", InitMode);
DUMP_OPT("init-d", InitMode);
DUMP_OPT("init-e", InitMode);
DUMP_OPT("init-gate", InitMode);
DUMP_OPT("init-alpha", InitMode);
DUMP_OPT("init-beta", InitMode);
DUMP_OPT("init-bias", InitMode);
DUMP_OPT("init-scaleA", InitMode);
DUMP_OPT("init-scaleB", InitMode);
DUMP_OPT("init-scaleC", InitMode);
DUMP_OPT("init-scaleD", InitMode);
DUMP_OPT("init-scaleAlphaVec", InitMode);
DUMP_OPT("pristine-on-gpu", bool);
DUMP_OPT("c-equal-d", bool);
DUMP_OPT("num-elements-to-validate", int);
DUMP_OPT("bounds-check", BoundsCheckMode);
DUMP_OPT("prune-mode", PruneSparseMode);
DUMP_OPT("device-idx", int);
DUMP_OPT("use-default-stream", bool);
DUMP_OPT("num-warmups", int);
DUMP_OPT("sync-after-warmups", bool);
DUMP_OPT("num-benchmarks", int);
DUMP_OPT("num-enqueues-per-sync", int);
DUMP_OPT("max-enqueues-per-sync", int);
DUMP_OPT("num-syncs-per-benchmark", int);
DUMP_OPT("skip-slow-solution-ratio", float);
DUMP_OPT("min-flops-per-sync", size_t);
DUMP_OPT("use-gpu-timer", bool);
DUMP_OPT("sleep-percent", int);
DUMP_OPT("hardware-monitor", bool);
DUMP_OPT("perf-l2-read-hits", double);
DUMP_OPT("perf-l2-write-hits", double);
DUMP_OPT("perf-l2-read-bw-mul", double);
DUMP_OPT("perf-read-efficiency", double);
DUMP_OPT("csv-export-extra-cols", bool);
DUMP_OPT("csv-merge-same-problems", bool);
DUMP_OPT("PrintWinnersOnly", bool);
DUMP_VECVEC("problem-size");
if(args.count("prob-sol-map"))
{
try
{
auto const& m = args["prob-sol-map"].as<std::map<int, int>>();
out << "prob-sol-map.size=" << m.size() << "\n";
for(auto const& p : m)
out << "prob-sol-map " << p.first << "=" << p.second << "\n";
}
catch(...)
{
out << "prob-sol-map=(failed)\n";
}
}
DUMP_VECVEC("a-strides");
DUMP_VECVEC("b-strides");
DUMP_VECVEC("c-strides");
DUMP_VECVEC("d-strides");
DUMP_VECVEC("e-strides");
DUMP_VECVEC("bias-strides");
DUMP_VECVEC("gate-strides");
DUMP_OPT("problem-start-idx", int);
DUMP_OPT("num-problems", int);
DUMP_OPT("solution-start-idx", int);
DUMP_OPT("num-solutions", int);
DUMP_OPT("best-solution", bool);
DUMP_OPT("results-file", std::string);
DUMP_OPT("log-file", std::string);
DUMP_OPT("log-file-append", bool);
DUMP_OPT("log-level", LogLevel);
DUMP_OPT("library-update-file", std::string);
DUMP_OPT("library-update-comment", bool);
DUMP_OPT("exit-on-error", bool);
DUMP_OPT("selection-only", bool);
DUMP_OPT("max-workspace-size", size_t);
DUMP_OPT("granularity-threshold", double);
DUMP_OPT("activation-type", ActivationType);
DUMP_OPT("activation-no-guard", bool);
if(args.count("activation-additional-args"))
{
try
{
auto const& v
= args["activation-additional-args"].as<std::vector<std::vector<double>>>();
out << "activation-additional-args.size=" << v.size() << "\n";
for(size_t i = 0; i < v.size(); ++i)
{
out << "activation-additional-args[" << i << "]=";
for(size_t j = 0; j < v[i].size(); ++j)
out << (j ? "," : "") << v[i][j];
out << "\n";
}
}
catch(...)
{
out << "activation-additional-args=(failed)\n";
}
}
DUMP_VEC("activation-enum-args", ActivationType);
DUMP_VEC("streamk-hybrid-mode", int);
DUMP_OPT("use-bias", int);
DUMP_OPT("bias-source", int);
DUMP_OPT("use-scaleAB", std::string);
DUMP_OPT("use-scaleCD", bool);
DUMP_OPT("use-scaleAlphaVec", int);
DUMP_VEC("bias-type-args", rocisa::DataType);
DUMP_OPT("use-gate-residual", bool);
DUMP_VEC("gate-type-args", rocisa::DataType);
DUMP_VEC("factor-dim-args", int);
DUMP_VEC("icache-flush-args", bool);
DUMP_OPT("use-e", bool);
DUMP_OPT("use-gradient", bool);
DUMP_OPT("use-user-args", bool);
DUMP_OPT("rotating-buffer-size", int32_t);
DUMP_OPT("rotating-buffer-mode", int32_t);
DUMP_OPT("icache-rotate-copies", int);
DUMP_OPT("icache-rotate-size", int);
DUMP_OPT("output-amaxD", bool);
DUMP_OPT("timing-instrumentation", bool);
#undef DUMP_OPT
#undef DUMP_VEC
#undef DUMP_VECVEC
std::cerr << "TENSILE_DUMP_OPTIONS: wrote " << path << "\n";
}
std::shared_ptr<Hardware> GetHardware(po::variables_map const& args)
{
int deviceCount = 0;
HIP_CHECK_EXC(hipGetDeviceCount(&deviceCount));
int deviceIdx = args["device-idx"].as<int>();
if(deviceIdx >= deviceCount)
throw std::runtime_error(concatenate(
"Invalid device index ", deviceIdx, " (", deviceCount, " total found.)"));
HIP_CHECK_EXC(hipSetDevice(deviceIdx));
return hip::GetCurrentDevice();
}
hipStream_t GetStream(po::variables_map const& args)
{
if(args["use-default-stream"].as<bool>())
return 0;
hipStream_t stream;
HIP_CHECK_EXC(hipStreamCreate(&stream));
return stream;
}
std::shared_ptr<MasterSolutionLibrary<ContractionProblemGemm>>
LoadSolutionLibrary(po::variables_map const& args)
{
auto filename = args["library-file"];
if(!filename.empty())
{
return std::dynamic_pointer_cast<MasterSolutionLibrary<ContractionProblemGemm>>(
LoadLibraryFile<ContractionProblemGemm>(filename.as<std::string>()));
}
auto embeddedLibrary
= std::dynamic_pointer_cast<MasterSolutionLibrary<ContractionProblemGemm>>(
EmbeddedLibrary<ContractionProblemGemm>::Get());
if(embeddedLibrary != nullptr)
return embeddedLibrary;
throw std::runtime_error("Client must be linked with an embedded library or "
"a library must be specified at runtime.");
}
void LoadCodeObjects(po::variables_map const& args, hip::SolutionAdapter& adapter)
{
auto const& filenames = args["code-object"].as<std::vector<std::string>>();
auto logLevel = args["log-level"].as<LogLevel>();
if(filenames.empty())
{
adapter.loadEmbeddedCodeObjects();
}
else
{
//only trigger exception when failed to load all code objects.
bool loaded = false;
hipError_t retError = hipSuccess;
for(auto const& filename : filenames)
{
hipError_t ret;
if(logLevel >= LogLevel::Verbose)
std::cout << "Loading " << filename << std::endl;
ret = adapter.loadCodeObjectFile(filename);
if(ret == hipSuccess)
loaded = true;
else
retError = ret;
}
if(!loaded)
HIP_CHECK_EXC(retError);
}
}
template <typename T>
T parse_num(std::string const& s)
{
T t{};
std::istringstream ss(s);
ss >> t;
if(!ss && !ss.eof())
throw std::runtime_error("Failed to parse number: " + s);
return t;
}
template <typename T>
std::vector<T> split_nums(std::string const& value)
{
std::vector<std::string> parts = po::split_string(value);
std::vector<T> rv;
rv.reserve(parts.size());
for(auto const& part : parts)
if(!part.empty())
rv.push_back(parse_num<T>(part));
return rv;
}
template <typename T>
void parse_arg_nums(po::variables_map& args, std::string const& name)
{
auto inValue = args[name].as<std::vector<std::string>>();
std::vector<std::vector<T>> outValue;
outValue.reserve(inValue.size());
for(auto const& str : inValue)
outValue.push_back(split_nums<T>(str));
args.at(name).value() = std::any(outValue);
}
void parse_arg_bools(po::variables_map& args, std::string const& name)
{
auto opts = args[name].as<std::vector<bool>>();
args.at(name).value() = std::any(opts);
}
void parse_arg_ints(po::variables_map& args, std::string const& name)
{
parse_arg_nums<size_t>(args, name);
}
void parse_arg_double(po::variables_map& args, std::string const& name)
{
parse_arg_nums<double>(args, name);
}
void parse_bias_type_args(po::variables_map& args, std::string const& name)
{
auto type = args[name].as<std::vector<rocisa::DataType>>();
args.at(name).value() = std::any(type);
}
void parse_activation_enum_args(po::variables_map& args, std::string const& name)
{
auto type = args[name].as<std::vector<ActivationType>>();
args.at(name).value() = std::any(type);
}
void parse_activation_int(po::variables_map& args, std::string const& name)
{
auto type = args[name].as<ActivationType>();
args.at(name).value() = std::any(type);
}
template <typename T>
void parse_arg_nums_map(po::variables_map& args, std::string const& name)
{
auto inValue = args[name].as<std::vector<std::string>>();
std::map<int, int> outValue;
for(auto const& str : inValue)
{
auto vec = split_nums<T>(str);
outValue[vec[0]] = vec[1];
}
args.at(name).value() = std::any(outValue);
}
void parse_arg_ints_map(po::variables_map& args, std::string const& name)
{
parse_arg_nums_map<int>(args, name);
}
void fix_data_types(po::variables_map& args)
{
auto type = args["type"].as<rocisa::DataType>();
// These types use the same data type for all inputs/outputs, so we allow
// using the overarching 'type' parameter.
if(type == rocisa::DataType::Float || type == rocisa::DataType::Double
|| type == rocisa::DataType::ComplexFloat || type == rocisa::DataType::ComplexDouble
|| type == rocisa::DataType::Int32)
{
args.at("a-type").value() = std::any(type);
args.at("b-type").value() = std::any(type);
args.at("c-type").value() = std::any(type);
args.at("d-type").value() = std::any(type);
args.at("alpha-type").value() = std::any(type);
args.at("beta-type").value() = std::any(type);
}
}
po::variables_map parse_args(int argc, const char* argv[])
{
auto options = all_options();
po::variables_map args;
po::store(po::parse_command_line(argc, argv, options), args);
po::notify(args);
if(args.count("help"))
{
std::cout << options << std::endl;
exit(1);
}
if(args.count("config-file"))
{
auto configFiles = args["config-file"].as<std::vector<std::string>>();
for(auto filename : configFiles)
{
std::cout << "loading config file " << filename << std::endl;
std::ifstream file(filename.c_str());
if(file.bad())
throw std::runtime_error(concatenate("Could not open ", filename));
po::store(po::parse_config_file(file, options), args);
}
}
#if !(TENSILELITE_CLIENT_ENABLE_ROCPROFSDK)
if(args["rocprof-counter"].as<std::vector<std::string>>().size())
{
throw std::runtime_error("rocprof-counter is provided but client is not built with -DTENSILELITE_CLIENT_ENABLE_ROCPROFSDK.");
}
#endif
fix_data_types(args);
parse_arg_ints(args, "problem-size");
parse_arg_ints(args, "a-strides");
parse_arg_ints(args, "b-strides");
parse_arg_ints(args, "c-strides");
parse_arg_ints(args, "d-strides");
parse_arg_ints(args, "e-strides");
parse_arg_ints(args, "bias-strides");
parse_bias_type_args(args, "bias-type-args");
parse_arg_ints(args, "gate-strides");
parse_bias_type_args(args, "gate-type-args");
parse_activation_int(args, "activation-type");
parse_activation_enum_args(args, "activation-enum-args");
parse_arg_double(args, "activation-additional-args");
parse_arg_bools(args, "icache-flush-args");
// std::cout << "Pasring parse_arg_ints_map()" << std::endl;
parse_arg_ints_map(args, "prob-sol-map");
return args;
}
#if defined(__linux__)
// Parse the AMDGPU ELF code object at `coPath` and return the smallest
// distance (in bytes) from any FUNC/GLOBAL kernel-entry symbol to its
// corresponding `label_GW_End` LOCAL symbol. This is a Tensile-specific
// proxy for "I-cache hot-path size of the smallest kernel": label_GW_End
// marks the first global-write-epilogue exit, which roughly bounds the
// code that a launch executes through during the main compute path.
//
// Returns 0 when the file can't be opened, isn't ELF64, parsing fails,
// or no FUNC+GLOBAL / label_GW_End symbol pairing is found.
//
// Linux-only (uses <elf.h>). Non-Linux build paths use the raw
// --icache-rotate-size value instead.
std::uintmax_t getMinKernelSizeToGwEnd(std::string const& coPath)
{
std::ifstream f(coPath, std::ios::binary);
if(!f)
return 0;
Elf64_Ehdr eh{};
f.read(reinterpret_cast<char*>(&eh), sizeof(eh));
if(!f
|| std::memcmp(eh.e_ident, ELFMAG, SELFMAG) != 0
|| eh.e_ident[EI_CLASS] != ELFCLASS64)
return 0;
// Read all section headers.
std::vector<Elf64_Shdr> shdrs(eh.e_shnum);
f.seekg(eh.e_shoff);
f.read(reinterpret_cast<char*>(shdrs.data()),
static_cast<std::streamsize>(eh.e_shnum * sizeof(Elf64_Shdr)));
if(!f)
return 0;
// Find .symtab and its linked string table (sh_link).
Elf64_Shdr const* symSh = nullptr;
for(auto const& sh : shdrs)
if(sh.sh_type == SHT_SYMTAB)
{
symSh = &sh;
break;
}
if(!symSh
|| symSh->sh_link >= shdrs.size()
|| symSh->sh_size == 0
|| symSh->sh_size % sizeof(Elf64_Sym) != 0)
return 0;
auto const& strSh = shdrs[symSh->sh_link];
// Read string table.
std::vector<char> strs(strSh.sh_size);
f.seekg(strSh.sh_offset);
f.read(strs.data(), static_cast<std::streamsize>(strSh.sh_size));
if(!f)
return 0;
// Read symbol table.
auto const symCount = symSh->sh_size / sizeof(Elf64_Sym);
std::vector<Elf64_Sym> syms(symCount);
f.seekg(symSh->sh_offset);
f.read(reinterpret_cast<char*>(syms.data()),
static_cast<std::streamsize>(symSh->sh_size));
if(!f)
return 0;
// Collect kernel entry addresses (FUNC + GLOBAL) and label_GW_End
// addresses (we match by name; some toolchains emit different binds).
std::vector<std::uint64_t> kernelStarts;
std::vector<std::uint64_t> gwEndAddrs;
for(auto const& s : syms)
{
if(s.st_name >= strs.size())
continue;
char const* name = &strs[s.st_name];
unsigned char type = ELF64_ST_TYPE(s.st_info);
unsigned char bind = ELF64_ST_BIND(s.st_info);
if(type == STT_FUNC && bind == STB_GLOBAL)
kernelStarts.push_back(s.st_value);
else if(std::strcmp(name, "label_GW_End") == 0)
gwEndAddrs.push_back(s.st_value);
}
if(kernelStarts.empty() || gwEndAddrs.empty())
return 0;
std::sort(kernelStarts.begin(), kernelStarts.end());
// For each label_GW_End, the owning kernel is the one with the
// largest start address <= this end address.
std::uintmax_t minSize = std::numeric_limits<std::uintmax_t>::max();
for(auto end : gwEndAddrs)
{
auto it = std::upper_bound(kernelStarts.begin(),
kernelStarts.end(),
end);
if(it == kernelStarts.begin())
continue;
std::uint64_t start = *(it - 1);
if(end <= start)
continue;
auto sz = static_cast<std::uintmax_t>(end - start);
if(sz < minSize)
minSize = sz;
}
return (minSize == std::numeric_limits<std::uintmax_t>::max())
? std::uintmax_t{0}
: minSize;
}
#endif // defined(__linux__)
} // namespace Client
} // namespace TensileLite
int main(int argc, const char* argv[])
{
using namespace TensileLite;
using namespace TensileLite::Client;
auto args = parse_args(argc, argv);
// Enable timing instrumentation if requested
g_timingInstrumentationEnabled = args["timing-instrumentation"].as<bool>();
// Set srand
unsigned int seed = args["init-seed"].as<unsigned int>();
if(seed == 0)
{
seed = time(NULL);
}
std::cout << std::endl << "srand seed is set to " << seed << std::endl << std::endl;
srand(seed);
ClientProblemFactory problemFactory(args);
initTimingBuffer();
calibrateTimingOverhead();
std::shared_ptr<Hardware> hardware;
hipStream_t stream;
{
ScopedTimer timer("hip_initialization");
hardware = GetHardware(args);
stream = GetStream(args);
}
std::shared_ptr<MasterSolutionLibrary<ContractionProblemGemm>> library;
{
ScopedTimer timer("library_loading");
library = LoadSolutionLibrary(args);
if(!library)
throw std::runtime_error("Failed to load solution library");
}
TensileLite::hip::SolutionAdapter adapter;
#if TENSILELITE_CLIENT_ENABLE_ROCPROFSDK
RocProfiler::getInstance().start();
#endif
{
ScopedTimer timer("code_object_loading");
LoadCodeObjects(args, adapter);
}
// I-cache rotation: load N extra independent hipModule_t copies of every
// --code-object so each launch can use a different copy (different device PC).
// --icache-rotate-copies=0 (default) = no extra copies, no rotation.
// --icache-rotate-copies=N (N>0) = load N extras (total = N+1 modules).
{
int icacheRotateCopies = args["icache-rotate-copies"].as<int>();
if(icacheRotateCopies > 0)
{
ScopedTimer timer("icache_rotate_extra_copies_loading");
auto const& filenames = args["code-object"].as<std::vector<std::string>>();
for(auto const& filename : filenames)
HIP_CHECK_EXC(adapter.loadCodeObjectFileExtraCopies(filename,
icacheRotateCopies));
std::cout << "Loaded " << icacheRotateCopies
<< " extra rotation copies of each --code-object for I-cache test"
<< " (total = " << (icacheRotateCopies + 1) << " modules)"
<< std::endl;
}
}
#if TENSILELITE_CLIENT_ENABLE_ROCPROFSDK
RocProfiler::getInstance().stop();
#endif
auto filename = args["library-file"].as<std::string>();
size_t directoryPos = filename.rfind('/');
std::string libraryDirectory = filename;
if(directoryPos != std::string::npos)
libraryDirectory.resize(directoryPos + 1);
else
libraryDirectory = '.';
{
ScopedTimer timer("lazy_loading_init");
auto result = adapter.initializeLazyLoading(hardware->archName(), libraryDirectory);
if(result != hipSuccess)
{
std::string str = "Lazy loading failed. (" + std::to_string(int(result)) + ").";
std::runtime_error(str.c_str());
}
}
auto problems = problemFactory.problems();
int firstProblemIdx = args["problem-start-idx"].as<int>();
int numProblems = args["num-problems"].as<int>();
if(numProblems < 0)
numProblems = problems.size();
int lastProblemIdx = firstProblemIdx + numProblems - 1;
int firstSolutionIdx = args["solution-start-idx"].as<int>();
int numSolutions = args["num-solutions"].as<int>();
bool gpuTimer = args["use-gpu-timer"].as<bool>();
bool runKernels = !args["selection-only"].as<bool>();
bool exitOnError = args["exit-on-error"].as<bool>();
bool groupedGemm = args["grouped-gemm"].as<bool>();
const auto& icacheFlushArgs = args["icache-flush-args"].as<std::vector<bool>>();
float skip_slow_solution_ratio = args["skip-slow-solution-ratio"].as<float>();
if(skip_slow_solution_ratio > 1.0 || skip_slow_solution_ratio < 0.0)
{
std::cout << "Invalid Skip Slow Solution Ratio: " << skip_slow_solution_ratio << std::endl;
std::cout << "Please Set Valid Ratio : (0.0 ~ 1.0)." << std::endl;
exit(1);
}
if(firstSolutionIdx < 0)
firstSolutionIdx = library->solutions.begin()->first;
if(numSolutions < 0)
{
auto iter = library->solutions.end();
iter--;
}
std::shared_ptr<DataInitialization> dataInit;
{
ScopedTimer timer("data_init_setup");
dataInit = std::make_shared<DataInitialization>(args, problemFactory);
}
std::shared_ptr<SolutionIterator> solutionIterator;
{
ScopedTimer timer("solution_iterator_setup");
solutionIterator = SolutionIterator::Default(library, hardware, args);
}
MetaRunListener listeners;
std::shared_ptr<BenchmarkTimer> benchmarkTimer;
float flushTimeMs{};
{
ScopedTimer timer("listener_setup");
listeners.addListener(dataInit);
listeners.addListener(solutionIterator);
listeners.addListener(std::make_shared<ProgressListener>(args));
if(runKernels)
{
bool hasIcacheFlush
= std::any_of(begin(icacheFlushArgs), end(icacheFlushArgs), [](auto i) { return i; });
flushTimeMs = hasIcacheFlush ? estimate_flush_kernel_time(stream, gpuTimer) : 0.f;
listeners.addListener(std::make_shared<ReferenceValidator>(args, dataInit));
benchmarkTimer = std::make_shared<BenchmarkTimer>(args, *hardware, flushTimeMs * 1000);
listeners.addListener(benchmarkTimer);
listeners.addListener(std::make_shared<HardwareMonitorListener>(args));
#if TENSILELITE_CLIENT_ENABLE_ROCPROFSDK
if (!args["rocprof-counter"].as<std::vector<std::string>>().empty())
listeners.addListener(Profiler::Default(args));
#endif
}
}
std::shared_ptr<MetaResultReporter> reporters;
{
ScopedTimer timer("reporter_setup");
reporters = std::make_shared<MetaResultReporter>();
reporters->addReporter(PerformanceReporter::Default(args));
// PerformanceReporter needs to be called before these two, or else values
// will be missing
reporters->addReporter(LogReporter::Default(args));
reporters->addReporter(ResultFileReporter::Default(args));
reporters->addReporter(LibraryUpdateReporter::Default(args));
if(args.count("log-file"))
{
std::string filename = args["log-file"].as<std::string>();
auto logFile = std::make_shared<std::ofstream>(
filename.c_str(), args["log-file-append"].as<bool>() ? std::ios::app : std::ios::out);
reporters->addReporter(LogReporter::Default(args, logFile, LogLevel::Normal));
}
listeners.setReporter(reporters);
}
// ReferenceValidator validator(args, dataInit);
// BenchmarkTimer timer(args);
reporters->report(ResultKey::ProblemCount, problemFactory.problems().size());
bool useUserArgs = args["use-user-args"].as<bool>();
void* dUA = nullptr;
void* dUAHost = nullptr;
if(Debug::Instance().getBenchmark())
{
std::stringstream ss;
static_cast<void>(benchmarkAllocation(ss));
std::cout << ss.str();
}
while(listeners.needMoreBenchmarkRuns())
{
listeners.preBenchmarkRun();
const auto flushGridSize = flush_grid_size();
for(auto icacheFlush : icacheFlushArgs)
{
benchmarkTimer->setIFlushTimeUs(icacheFlush ? flushTimeMs * 1000 : 0.f);
// I-cache rotation counter: rotationLaunchIdx
// for scope across all problem / iterations, accumulate continuously
// to avoid starting from copy 0 for each problem
size_t rotationLaunchIdx = 0;
for(int problemIdx = firstProblemIdx; problemIdx <= lastProblemIdx; problemIdx++)
{
auto problem = problems[problemIdx].get();
reporters->report(ResultKey::ProblemIndex, problemIdx);
reporters->report(ResultKey::ProblemProgress,
concatenate(problemIdx, "/", lastProblemIdx));
{
ScopedTimer timer("pre_problem");
listeners.preProblem(problem);
}
std::shared_ptr<ProblemInputs> inputs;
{
ScopedTimer timer("gpu_input_preparation");
inputs = dataInit->prepareGPUInputs(problem);
}
size_t warmupInvocations = listeners.numWarmupRuns();
size_t syncs = listeners.numSyncs();
size_t enq = listeners.numEnqueuesPerSync();
size_t maxRotatingBufferNum = std::max(warmupInvocations, syncs * enq);
std::vector<std::shared_ptr<ProblemInputs>> inputArr;
{
ScopedTimer timer("rotating_buffer_preparation");
inputArr = dataInit->prepareRotatingGPUOutput(
maxRotatingBufferNum, problem, inputs, stream);
static_cast<void>(hipDeviceSynchronize());
}
// I-cache rotation auto-load: when --icache-rotate-copies=-1,
// pick the larger of two extra-copy counts:
// 1) inputArr.size() - 1
// Aligns the code rotation cycle with DataInit's actual
// data buffer rotation cycle (inputArr.size() = 1 original
// + rotatingNum extras).
// 2) Cache-overflow term targeting ~128KB of L1 I-cache.
// On Linux: parse each .co's ELF symbol table to find the
// smallest kernel hot-path size K (kernel_start ->
// label_GW_End), then load (128KB / K) - 1 extras so the
// total rotated code (~128KB) overflows typical L1 I-cache.
// On non-Linux: <elf.h> is unavailable; fall back to
// args["icache-rotate-size"] as the raw extras count.
// Use adapter.numRotationModules()==1 as the "not yet loaded"
// guard so the load only fires on the first problem; subsequent
// problems reuse the same N.
{
int icacheArg = args["icache-rotate-copies"].as<int>();
if(icacheArg == -1 && adapter.numRotationModules() == 1)
{
auto const& filenames
= args["code-object"].as<std::vector<std::string>>();
// Term 1: align with data rotation cycle.
int extrasFromDataInit = static_cast<int>(inputArr.size()) - 1;
// Term 2: cache-overflow term.
#if defined(__linux__)
// K = min(kernel_start -> label_GW_End) across all .co.
std::uintmax_t K = 0;
for(auto const& filename : filenames)
{
std::uintmax_t sz = getMinKernelSizeToGwEnd(filename);
if(sz > 0 && (K == 0 || sz < K))
K = sz;
}
// kCacheBudgetBytes = icache-rotate-size * 2 * 1024.
// Default icache-rotate-size=64 -> 128 KB
// Loosely targets ~2x typical L1 I-cache.
int rotateSizeKB = args["icache-rotate-size"].as<int>();
if(rotateSizeKB < 0)
rotateSizeKB = 0;
std::uintmax_t kCacheBudgetBytes = std::uintmax_t(rotateSizeKB) * 2 * 1024;
int extrasFromCache = 0;
if(K == 0)
{
std::cerr << "[icache-rotate] warning: no label_GW_End "
<< "found in any --code-object; cache-based "
<< "term contributes 0" << std::endl;
}
else if(kCacheBudgetBytes > K)
{
extrasFromCache = static_cast<int>(kCacheBudgetBytes / K - 1);
}
#else
// <elf.h> unavailable on this platform; use the raw
// --icache-rotate-size value as the extras count.
int extrasFromCache
= static_cast<int>(args["icache-rotate-size"].as<int>());
#endif
int extras = std::max(extrasFromDataInit, extrasFromCache);
if(extras > 0)
{
ScopedTimer timer("icache_rotate_extra_copies_loading");
for(auto const& filename : filenames)
HIP_CHECK_EXC(adapter.loadCodeObjectFileExtraCopies(
filename, extras));
}
#if defined(__linux__)
std::cout << "[icache-rotate] auto extras = max("
<< extrasFromDataInit << " from inputArr.size()-1, "
<< extrasFromCache << " from "
<< kCacheBudgetBytes << "/" << K << ") = "
<< extras
<< " (total = " << (extras + 1) << " modules)"
<< std::endl;
#else
std::cout << "[icache-rotate] auto extras = max("
<< extrasFromDataInit << " from inputArr.size()-1, "
<< extrasFromCache << " from --icache-rotate-size) = "
<< extras
<< " (total = " << (extras + 1) << " modules)"
<< std::endl;
#endif
}
}
// The first per-solution iteration must re-upload inputs so that
// the upload happens after preSolution() and can read the picked
// solution's problemType.mxScaleFormat to pick the correct host
// upload layout for MX scale tensors. The extra upload is a no-op
// cost on non-MX problems.
bool resetInput = true;
while(solutionIterator->moreSolutionsInProblem())
{
std::shared_ptr<ContractionSolution> solution;
{
ScopedTimer timer("solution_selection");
solution = solutionIterator->getSolution();
}
if(solution == nullptr)
throw std::runtime_error("Could not find a solution");
{
ScopedTimer timer("pre_solution");
listeners.preSolution(solution.get());
}
if(solutionIterator->runCurrentSolution() && runKernels)
{
try
{
while(listeners.needMoreRunsInSolution())
{
if(resetInput)
{
ScopedTimer timer("gpu_input_reset");
auto inputs = dataInit->prepareGPUInputs(problem);
inputArr[0] = inputs;
}
resetInput = true;
std::vector<std::vector<KernelInvocation>> kernels;
{
ScopedTimer timer("kernel_solving");
for(size_t r = 0; r < inputArr.size(); r++)
{
auto kernel = useUserArgs
? solution->solveTensileGPU((*problem),
*inputArr[r],
*hardware,
&dUA,
&dUAHost,
nullptr,
0,
stream)
: solution->solve((*problem),
*inputArr[r],
*hardware,
nullptr,
0,
stream);
kernels.push_back(kernel);
}
}
size_t warmupInvocations = listeners.numWarmupRuns();
size_t warmupEventCount = kernels[0].size();
TimingEvents warmupStartEvents(warmupInvocations, warmupEventCount);
TimingEvents warmupStopEvents(warmupInvocations, warmupEventCount);
// I-cache rotation counter: increments per launchKernels call
// and selects which hipModule_t copy services the next launch.
// numRotationModules()==1 → no rotation (always copy 0).
int nRotationModules = adapter.numRotationModules();
auto rotateAndSelect = [&]() {
adapter.selectRotationCopy(
(int)(rotationLaunchIdx++ % (size_t)nRotationModules));
};
if(warmupInvocations > 0)
{
{
ScopedTimer timer("warmup_runs");
listeners.preWarmup();
HIP_CHECK_EXC(adapter.launchKernels(kernels[0],
stream,
warmupStartEvents[0],
warmupStopEvents[0]));
}
{
ScopedTimer timer("validate_warmups");
listeners.validateWarmups(
inputs, warmupStartEvents, warmupStopEvents);
}
{
ScopedTimer timer("warmup_runs");
for(int i = 1; i < warmupInvocations; i++)
{
size_t kIdx = i % kernels.size();
HIP_CHECK_EXC(adapter.launchKernels(kernels[kIdx],
stream,
warmupStartEvents[i],
warmupStopEvents[i]));
}
listeners.postWarmup(
warmupStartEvents, warmupStopEvents, stream);
}
}
#if TENSILELITE_CLIENT_ENABLE_ROCPROFSDK
TimingEvents ProfilerStartEvents(1, warmupEventCount);
TimingEvents ProfilerStopEvents(1, warmupEventCount);
listeners.preProfiler();
rotateAndSelect();
HIP_CHECK_EXC(adapter.launchKernels(kernels[warmupInvocations % kernels.size()],
stream,
ProfilerStartEvents[0],
ProfilerStopEvents[0]));
listeners.postProfiler();
#endif
size_t syncs = listeners.numSyncs();
size_t enq = listeners.numEnqueuesPerSync();
size_t eventCount = gpuTimer ? kernels[0].size() : 0;
{
ScopedTimer timer("benchmark_runs");
listeners.preSyncs();
if(enq)
for(int i = 0; i < syncs; i++)
{
TimingEvents startEvents(enq, eventCount);
TimingEvents stopEvents(enq, eventCount);
{
ScopedTimer kernelTimer("gpu_kernel_execution");
listeners.preEnqueues(stream);
for(int j = 0; j < enq; j++)
{
size_t kIdx = ((i * enq) + j) % kernels.size();
rotateAndSelect();
HIP_CHECK_EXC(adapter.launchKernels(
kernels[kIdx], stream, nullptr, nullptr));
if(icacheFlush)
{
hipLaunchKernelGGL(
flush_icache, flushGridSize, 64, 0, stream);
}
}
listeners.postEnqueues(startEvents, stopEvents, stream);
}
listeners.validateEnqueues(inputs, startEvents, stopEvents);
}
listeners.postSyncs();
}
if(useUserArgs)
{
solution->relaseDeviceUserArgs(dUA, dUAHost);
}
}
}
catch(std::runtime_error const& err)
{
reporters->report(ResultKey::Validation, "INVALID");
reporters->log(LogLevel::Error,
concatenate("Exception occurred: ", err.what(), "\n"));
}
}
{
ScopedTimer timer("post_solution");
listeners.postSolution();
}
if(exitOnError && listeners.error() > 0)
{
flushTimingBuffer();
// error range in shell is [0-255]
return std::min(listeners.error(), 255);
}
}
{
ScopedTimer timer("post_problem");
listeners.postProblem();
}
}
}
listeners.postBenchmarkRun();
}
{
ScopedTimer timer("finalize_report");
listeners.finalizeReport();
}
// Flush all buffered timing records to stderr.
// Timed with raw chrono instead of ScopedTimer because ScopedTimer pushes
// its record into the buffer on destruction — after flushTimingBuffer()
// has already drained and cleared it, so the record would be lost.
{
auto flushStart = TimingClock::now();
flushTimingBuffer();
auto flushMs = std::chrono::duration<double, std::milli>(
TimingClock::now() - flushStart).count();
std::clog << "TIMING:flush_timing_buffer:" << flushMs << "\n";
}
// error range in shell is [0-255]
return std::min(listeners.error(), 255);
}