Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/client/include/TimingInstrumentation.hpp
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

285 lines
11 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#pragma once
#include <charconv>
#include <chrono>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
namespace TensileLite
{
namespace Client
{
// Global flag to enable/disable timing instrumentation output
// Set via command line: --timing-instrumentation
inline bool g_timingInstrumentationEnabled = false;
// Per-call overhead measured once at startup via calibrateTimingOverhead().
// Single-threaded — no atomic needed.
inline double g_calibratedPerCallOverheadMs = 0;
// ---- Deferred I/O buffer ------------------------------------------------
//
// During measurement, timing data is captured as raw structs with no string
// formatting or I/O. All formatting and writing happens in flushTimingBuffer().
//
// Note: single-threaded. The benchmark loop in main.cpp is sequential.
struct TimingRec
{
const char* category;
double durationMs;
};
// Context buffers: separate from the main timing buffer.
// posInTimingBuffer records g_timingBuffer.size() at push time so that
// flushTimingBuffer() can merge context lines into the correct position.
struct ContextEntry
{
size_t posInTimingBuffer;
size_t M, N, K, batchCount;
std::string typeA, typeD;
};
struct GroupedContextEntry
{
size_t posInTimingBuffer;
size_t index, totalGemms;
size_t M, N, K, batchCount;
std::string typeA, typeD;
};
// Main buffer: timing records only (hot path, ~2.5M entries, 16 bytes each).
inline std::vector<TimingRec> g_timingBuffer;
// Context buffers: cold path, few thousand entries each.
inline std::vector<ContextEntry> g_contextBuffer;
inline std::vector<GroupedContextEntry> g_groupedContextBuffer;
// Reserve buffer capacity upfront. Real workloads produce ~2M+ records.
// Only allocates when timing is enabled to avoid penalizing normal runs.
inline void initTimingBuffer(size_t capacity = 2'500'000)
{
if(g_timingInstrumentationEnabled)
g_timingBuffer.reserve(capacity);
}
// ---- Formatting helpers (used only during flush) ------------------------
inline char* fmtOne(char* p, char* end, const char* s)
{
auto len = std::strlen(s);
auto avail = static_cast<size_t>(end - p);
if(len > avail)
len = avail;
std::memcpy(p, s, len);
return p + len;
}
inline char* fmtOne(char* p, char* end, const std::string& s)
{
return fmtOne(p, end, s.c_str());
}
inline char* fmtOne(char* p, char* end, size_t v)
{
auto result = std::to_chars(p, end, v);
return result.ptr;
}
inline char* fmtOne(char* p, char* end, double v)
{
auto result = std::to_chars(p, end, v);
return result.ptr;
}
// Format args into a stack buffer and write as a single line to std::clog
template<typename... Args>
inline void writeLine(Args&&... args)
{
char buf[256];
char* p = buf;
char* const end = buf + sizeof(buf) - 1;
((p = fmtOne(p, end, args)), ...);
*p++ = '\n';
std::clog.write(buf, p - buf);
}
// Format and write all buffered records, then clear the buffers.
// Context entries are merged into the output at their recorded positions
// to preserve the interleaving expected by analyze_timing.py.
inline void flushTimingBuffer()
{
// Emit calibrated instrumentation overhead as a timing record.
// g_timingBuffer.size() now correctly counts only TimingRec entries.
if(g_timingInstrumentationEnabled && g_calibratedPerCallOverheadMs > 0)
{
double overheadMs = g_calibratedPerCallOverheadMs * g_timingBuffer.size();
g_timingBuffer.push_back(TimingRec{"timing_overhead", overheadMs});
}
// Two-pointer merge: emit context lines at their recorded positions.
size_t ctxIdx = 0;
size_t grpIdx = 0;
for(size_t i = 0; i < g_timingBuffer.size(); i++)
{
while(ctxIdx < g_contextBuffer.size()
&& g_contextBuffer[ctxIdx].posInTimingBuffer <= i)
{
auto& c = g_contextBuffer[ctxIdx++];
writeLine("TIMING_CONTEXT:M=", c.M, ",N=", c.N, ",K=", c.K,
",batch=", c.batchCount,
",typeA=", c.typeA, ",typeD=", c.typeD);
}
while(grpIdx < g_groupedContextBuffer.size()
&& g_groupedContextBuffer[grpIdx].posInTimingBuffer <= i)
{
auto& g = g_groupedContextBuffer[grpIdx++];
writeLine("TIMING_CONTEXT_GROUPED:index=", g.index,
",total=", g.totalGemms,
",M=", g.M, ",N=", g.N, ",K=", g.K,
",batch=", g.batchCount,
",typeA=", g.typeA, ",typeD=", g.typeD);
}
writeLine("TIMING:", g_timingBuffer[i].category, ":",
g_timingBuffer[i].durationMs);
}
// Emit any trailing context entries (after all timing records).
while(ctxIdx < g_contextBuffer.size())
{
auto& c = g_contextBuffer[ctxIdx++];
writeLine("TIMING_CONTEXT:M=", c.M, ",N=", c.N, ",K=", c.K,
",batch=", c.batchCount,
",typeA=", c.typeA, ",typeD=", c.typeD);
}
while(grpIdx < g_groupedContextBuffer.size())
{
auto& g = g_groupedContextBuffer[grpIdx++];
writeLine("TIMING_CONTEXT_GROUPED:index=", g.index,
",total=", g.totalGemms,
",M=", g.M, ",N=", g.N, ",K=", g.K,
",batch=", g.batchCount,
",typeA=", g.typeA, ",typeD=", g.typeD);
}
g_timingBuffer.clear();
g_contextBuffer.clear();
g_groupedContextBuffer.clear();
std::clog.flush();
}
using TimingClock = std::chrono::high_resolution_clock;
// Simple RAII timer that records timing on destruction.
// Output format: TIMING:<category>:<duration_ms>
//
// Timing records are buffered in g_timingBuffer during measurement.
// Call flushTimingBuffer() to format and write all records to std::clog.
class ScopedTimer
{
public:
using clock = TimingClock;
// category must be a string literal or have static storage duration
ScopedTimer(const char* category)
{
if(g_timingInstrumentationEnabled)
{
m_category = category;
m_start = clock::now();
}
}
~ScopedTimer()
{
if(g_timingInstrumentationEnabled)
{
auto end = clock::now();
double durationMs
= std::chrono::duration<double, std::milli>(end - m_start).count();
g_timingBuffer.push_back(TimingRec{m_category, durationMs});
}
}
// Get elapsed time without stopping
double elapsedMs() const
{
auto now = clock::now();
auto duration = std::chrono::duration<double, std::milli>(now - m_start);
return duration.count();
}
private:
const char* m_category = nullptr;
std::chrono::time_point<clock> m_start;
};
// Measure per-call ScopedTimer overhead once at startup.
// Must be called after initTimingBuffer().
inline void calibrateTimingOverhead()
{
if(!g_timingInstrumentationEnabled)
return;
// Pre-touch all reserved pages so calibration runs on faulted memory,
// matching the conditions of real workloads.
size_t cap = g_timingBuffer.capacity();
for(size_t i = 0; i < cap; i++)
g_timingBuffer.push_back(TimingRec{"_warmup", 0});
g_timingBuffer.clear();
constexpr int kWarmup = 1000;
constexpr int kIter = 100000;
for(int i = 0; i < kWarmup; i++)
{ ScopedTimer t("_calibrate"); }
g_timingBuffer.clear();
auto t0 = TimingClock::now();
for(int i = 0; i < kIter; i++)
{ ScopedTimer t("_calibrate"); }
auto t1 = TimingClock::now();
g_calibratedPerCallOverheadMs
= std::chrono::duration<double, std::milli>(t1 - t0).count() / kIter;
g_timingBuffer.clear();
}
// Report a timing value directly (for GPU timings already measured)
inline void reportTiming(const char* category, double ms)
{
if(g_timingInstrumentationEnabled)
{
g_timingBuffer.push_back(TimingRec{category, ms});
}
}
// Report problem context for correlation (single GEMM).
// Buffered separately from timing records; merged during flush.
inline void reportProblemContext(size_t M, size_t N, size_t K, size_t batchCount,
const std::string& typeA, const std::string& typeD)
{
if(g_timingInstrumentationEnabled)
{
g_contextBuffer.push_back(
ContextEntry{g_timingBuffer.size(), M, N, K, batchCount, typeA, typeD});
}
}
// Report problem context for grouped GEMM (multiple GEMMs batched together).
// Buffered separately from timing records; merged during flush.
inline void reportGroupedProblemContext(size_t index, size_t totalGemms,
size_t M, size_t N, size_t K, size_t batchCount,
const std::string& typeA, const std::string& typeD)
{
if(g_timingInstrumentationEnabled)
{
g_groupedContextBuffer.push_back(GroupedContextEntry{
g_timingBuffer.size(), index, totalGemms, M, N, K, batchCount, typeA, typeD});
}
}
} // namespace Client
} // namespace TensileLite