Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions include/gpufl/backends/amd/amd_capture_capabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,21 +172,59 @@ CaptureCapabilitiesEvent BuildAmdCaptureCapabilitiesEvent(
? "not_requested"
: (!input.trace_configured
? "skipped"
: (input.dropped_trace_records > 0 ? "partial" : "enabled")),
: (input.dropped_trace_records > 0 ||
input.trace_buffer_flush_failures > 0 ||
input.dropped_client_records > 0
? "partial"
: "enabled")),
input.trace_configured ? "rocprofiler_buffer_tracing" : "disabled",
!input.trace_configured
? (tracing_requested ? "rocprofiler_buffer_tracing_unavailable"
: "not_selected")
: (input.dropped_trace_records > 0
? "rocprofiler_records_dropped"
: ""),
: (input.trace_buffer_flush_failures > 0
? "rocprofiler_buffer_flush_failed"
: (input.dropped_trace_records > 0
? "rocprofiler_records_dropped"
: (input.dropped_client_records > 0
? "gpufl_activity_queue_full"
: ""))),
!input.trace_configured
? "ROCprofiler trace-buffer delivery was not active."
: (input.dropped_trace_records > 0
? "ROCprofiler reported " +
std::to_string(input.dropped_trace_records) +
" dropped trace record(s); this session is incomplete."
: "ROCprofiler reported no dropped trace records."));
: (input.trace_buffer_flush_failures > 0
? "ROCprofiler trace-buffer flush failed " +
std::to_string(input.trace_buffer_flush_failures) +
" time(s); buffered activity may be incomplete or in the wrong segment."
: (input.dropped_trace_records > 0
? "ROCprofiler reported " +
std::to_string(input.dropped_trace_records) +
" dropped trace record(s); this session is incomplete."
: (input.dropped_client_records > 0
? "GPUFlight dropped " +
std::to_string(input.dropped_client_records) +
" trace record(s) because its activity queue was full."
: "ROCprofiler and GPUFlight reported complete trace delivery."))));

AddCapability(
event, "scope_correlation", tracing_requested,
!tracing_requested
? "not_requested"
: (!input.trace_configured
? "skipped"
: (input.scope_correlation_failures > 0 ? "partial" : "enabled")),
input.trace_configured ? "rocprofiler_external_correlation" : "disabled",
!input.trace_configured
? (tracing_requested ? "rocprofiler_buffer_tracing_unavailable"
: "not_selected")
: (input.scope_correlation_failures > 0
? "rocprofiler_scope_correlation_failed"
: ""),
!input.trace_configured
? "ROCprofiler scope correlation was not active."
: (input.scope_correlation_failures > 0
? "ROCprofiler scope correlation failed " +
std::to_string(input.scope_correlation_failures) +
" time(s); some trace rows may have no user scope."
: "ROCprofiler accepted every GPUFlight scope-correlation push and pop."));

return event;
}
Expand Down
3 changes: 3 additions & 0 deletions include/gpufl/backends/amd/amd_capture_capabilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ struct AmdCaptureCapabilityInput {
uint64_t memcpy_rows = 0;
uint64_t profiling_sample_rows = 0;
uint64_t dropped_trace_records = 0;
uint64_t dropped_client_records = 0;
uint64_t trace_buffer_flush_failures = 0;
uint64_t unattributed_trace_records = 0;
uint64_t scope_correlation_failures = 0;
};

CaptureCapabilitiesEvent BuildAmdCaptureCapabilitiesEvent(
Expand Down
53 changes: 53 additions & 0 deletions include/gpufl/backends/amd/amd_dispatch_collection_gate.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

#include <atomic>

#include "gpufl/core/monitor.hpp"

namespace gpufl::amd {

// Lock-free collection gate read from ROCprofiler's dispatch callback. In
// Always mode the running session supplies a counter profile for every
// dispatch. WindowOnly supplies no profile until a deep window opens, which
// ROCprofiler defines as "collect no counters for this dispatch."
class AmdDispatchCollectionGate {
public:
void configure(const DeepArmMode mode) {
window_only_ = mode == DeepArmMode::WindowOnly;
running_.store(false, std::memory_order_relaxed);
window_active_.store(false, std::memory_order_relaxed);
}

void start() { running_.store(true, std::memory_order_release); }

void stop() {
running_.store(false, std::memory_order_release);
window_active_.store(false, std::memory_order_release);
}

void openWindow() {
window_active_.store(true, std::memory_order_release);
}

void closeWindow() {
window_active_.store(false, std::memory_order_release);
}

bool armed() const {
if (!running_.load(std::memory_order_acquire)) return false;
return !window_only_ ||
window_active_.load(std::memory_order_acquire);
}

bool collectDispatch(const bool window_claimed_launch) const {
if (!running_.load(std::memory_order_acquire)) return false;
return !window_only_ || window_claimed_launch;
}

private:
bool window_only_ = false;
std::atomic<bool> running_{false};
std::atomic<bool> window_active_{false};
};

} // namespace gpufl::amd
11 changes: 11 additions & 0 deletions include/gpufl/backends/amd/amd_profiling_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ bool AmdRequestNeedsDeviceCounting(const ProfilingEngine engine) {
engine == ProfilingEngine::Deep;
}

std::optional<uint32_t> ResolveAmdDispatchDeviceId(
const uint64_t configured_agent_handle,
const uint32_t configured_device_id,
const uint64_t dispatch_agent_handle) {
if (configured_agent_handle == 0 || dispatch_agent_handle == 0 ||
configured_agent_handle != dispatch_agent_handle) {
return std::nullopt;
}
return configured_device_id;
}

AmdResolvedProfilingPlan ResolveAmdProfilingPlan(
const ProfilingEngine requested,
const AmdProfilingSupport& support) {
Expand Down
10 changes: 10 additions & 0 deletions include/gpufl/backends/amd/amd_profiling_policy.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <cstdint>
#include <optional>
#include <string>

#include "gpufl/core/monitor.hpp"
Expand Down Expand Up @@ -41,4 +43,12 @@ bool AmdRequestNeedsDispatchCounting(ProfilingEngine engine);
bool AmdRequestNeedsPcSampling(ProfilingEngine engine);
bool AmdRequestNeedsDeviceCounting(ProfilingEngine engine);

// Dispatch counting is currently configured for one GPU agent. Resolve only
// records from that agent so a secondary GPU can never be mislabeled as device
// zero (or as the configured primary device).
std::optional<uint32_t> ResolveAmdDispatchDeviceId(
uint64_t configured_agent_handle,
uint32_t configured_device_id,
uint64_t dispatch_agent_handle);

} // namespace gpufl::amd
8 changes: 8 additions & 0 deletions include/gpufl/backends/amd/engine/amd_profiling_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class AmdProfilingEngine {
/// Returns false if the hardware/driver doesn't support this engine.
virtual bool initialize(rocprofiler_context_id_t context,
rocprofiler_agent_id_t gpu_agent,
uint32_t gpu_device_id,
const MonitorOptions& opts) = 0;

/// Begin profiling (context is already started).
Expand All @@ -37,6 +38,13 @@ class AmdProfilingEngine {
/// True once this engine has emitted at least one profiling sample.
virtual bool hasData() const = 0;

/// True once the context-bound service and its counter configuration are
/// ready for a deep window to arm.
virtual bool isPrepared() const = 0;

/// Point-in-time state used by deep-window audit rows before disarming.
virtual bool isArmed() const = 0;

/// Scope hooks - engines may filter collection to scoped regions.
virtual void onScopeStart(const char* /*name*/) {}
virtual void onScopeStop(const char* /*name*/) {}
Expand Down
64 changes: 52 additions & 12 deletions include/gpufl/backends/amd/engine/dispatch_counter_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
#include <cstdio>
#include <cstring>

#include "gpufl/backends/amd/amd_profiling_policy.hpp"

#include "gpufl/core/common.hpp"
#include "gpufl/core/debug_logger.hpp"
#include "gpufl/core/deep_window.hpp"
#include "gpufl/core/deep_window_rules.hpp"
#include "gpufl/core/monitor.hpp"

namespace gpufl::amd {
Expand Down Expand Up @@ -54,8 +59,12 @@ bool CheckStatus(rocprofiler_status_t status, const char* call) {

bool DispatchCounterEngine::initialize(const rocprofiler_context_id_t context,
const rocprofiler_agent_id_t gpu_agent,
const MonitorOptions& /*opts*/) {
const uint32_t gpu_device_id,
const MonitorOptions& opts) {
context_ = context;
gpu_agent_ = gpu_agent;
gpu_device_id_ = gpu_device_id;
collection_gate_.configure(opts.deep_arm_mode);

if (!discoverCounters(gpu_agent)) {
GFL_LOG_ERROR("[DispatchCounterEngine] No counters discovered");
Expand Down Expand Up @@ -83,24 +92,36 @@ bool DispatchCounterEngine::initialize(const rocprofiler_context_id_t context,
}

void DispatchCounterEngine::start() {
// Context start is handled by the backend
// Context start is handled by the backend. The collection gate decides
// whether callbacks receive a profile immediately (Always) or only while
// a deep window is active (WindowOnly).
collection_gate_.start();
}

void DispatchCounterEngine::stop() {
// Context stop is handled by the backend
collection_gate_.stop();
}

void DispatchCounterEngine::drain() {
// Callback mode delivers data synchronously - nothing to drain
}

void DispatchCounterEngine::shutdown() {
if (config_valid_) {
rocprofiler_destroy_counter_config(config_id_);
config_valid_ = false;
collection_gate_.stop();
if (config_valid_.exchange(false, std::memory_order_acq_rel)) {
(void) CheckStatus(rocprofiler_destroy_counter_config(config_id_),
"rocprofiler_destroy_counter_config");
}
}

void DispatchCounterEngine::onScopeStart(const char*) {
collection_gate_.openWindow();
}

void DispatchCounterEngine::onScopeStop(const char*) {
collection_gate_.closeWindow();
}

bool DispatchCounterEngine::discoverCounters(const rocprofiler_agent_id_t agent) {
struct DiscoveryCtx {
DispatchCounterEngine* engine;
Expand Down Expand Up @@ -208,19 +229,36 @@ bool DispatchCounterEngine::createCounterConfig(const rocprofiler_agent_id_t age
GFL_LOG_DEBUG("[DispatchCounterEngine] - ", name);
}

config_valid_ = true;
config_valid_.store(true, std::memory_order_release);
return true;
}

void DispatchCounterEngine::dispatchCallback(
rocprofiler_dispatch_counting_service_data_t /*dispatch_data*/,
rocprofiler_dispatch_counting_service_data_t dispatch_data,
rocprofiler_counter_config_id_t* config,
rocprofiler_user_data_t* /*user_data*/,
void* callback_data) {
auto* engine = static_cast<DispatchCounterEngine*>(callback_data);
if (engine && engine->config_valid_ && config) {
if (!engine) return;

// ROCprofiler treats a callback that supplies no profile as an explicit
// "collect no counters for this dispatch" decision. Clear the output so
// WindowOnly stays cheap outside the window. Claim the budget first: the
// Nth dispatch still receives the profile, while later callbacks reject
// collection immediately without running teardown on this callback path.
const bool window_claimed_launch = DeepWindow::OnLaunch();
const auto device_id = ResolveAmdDispatchDeviceId(
engine->gpu_agent_.handle, engine->gpu_device_id_,
dispatch_data.dispatch_info.agent_id.handle);
if (config) *config = {};
if (config && device_id.has_value() &&
engine->collection_gate_.collectDispatch(window_claimed_launch)) {
*config = engine->config_id_;
}

if (detail::DeepWindowRules::WantsLaunchFeed()) {
detail::DeepWindowRules::NoteKernelLaunch(detail::GetTimestampNs());
}
}

void DispatchCounterEngine::recordCallback(
Expand All @@ -232,8 +270,10 @@ void DispatchCounterEngine::recordCallback(
auto* engine = static_cast<DispatchCounterEngine*>(callback_data);
if (!engine || !record_data || record_count == 0) return;

const auto& info = dispatch_data.dispatch_info;
(void)info; // reserved for future agent_id → device_id resolution
const auto device_id = ResolveAmdDispatchDeviceId(
engine->gpu_agent_.handle, engine->gpu_device_id_,
dispatch_data.dispatch_info.agent_id.handle);
if (!device_id.has_value()) return;
const auto corr_id = dispatch_data.correlation_id.internal;
const int64_t now_ns =
static_cast<int64_t>(dispatch_data.start_timestamp);
Expand Down Expand Up @@ -268,7 +308,7 @@ void DispatchCounterEngine::recordCallback(
ProfileSampleInput s;
s.ts_ns = now_ns;
s.corr_id = static_cast<uint32_t>(corr_id & 0xFFFFFFFF);
s.device_id = 0; // TODO: resolve from agent_id
s.device_id = *device_id;
s.sample_kind = 1; // sass_metric
s.metric_name = counter_name;
s.metric_value = static_cast<uint64_t>(rec.counter_value);
Expand Down
15 changes: 14 additions & 1 deletion include/gpufl/backends/amd/engine/dispatch_counter_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <rocprofiler-sdk/counters.h>
#include <rocprofiler-sdk/dispatch_counting_service.h>

#include "gpufl/backends/amd/amd_dispatch_collection_gate.hpp"
#include "gpufl/backends/amd/engine/amd_profiling_engine.hpp"

namespace gpufl::amd {
Expand All @@ -24,12 +25,21 @@ class DispatchCounterEngine final : public AmdProfilingEngine {

bool initialize(rocprofiler_context_id_t context,
rocprofiler_agent_id_t gpu_agent,
uint32_t gpu_device_id,
const MonitorOptions& opts) override;
void start() override;
void stop() override;
void drain() override;
void shutdown() override;
bool hasData() const override { return sample_count_.load() > 0; }
bool isPrepared() const override {
return config_valid_.load(std::memory_order_acquire);
}
bool isArmed() const override {
return isPrepared() && collection_gate_.armed();
}
void onScopeStart(const char* name) override;
void onScopeStop(const char* name) override;

private:
/// Counter metadata for resolving record IDs to human-readable names.
Expand All @@ -56,8 +66,11 @@ class DispatchCounterEngine final : public AmdProfilingEngine {
void* callback_data);

rocprofiler_context_id_t context_{};
rocprofiler_agent_id_t gpu_agent_{};
uint32_t gpu_device_id_ = 0;
rocprofiler_counter_config_id_t config_id_{};
bool config_valid_ = false;
std::atomic<bool> config_valid_{false};
AmdDispatchCollectionGate collection_gate_;

mutable std::mutex counter_mu_;
std::unordered_map<uint64_t, CounterInfo> counter_info_; // counter_id.handle → info
Expand Down
Loading
Loading