blob: a201cec1401b7827115db0557694e4bee96c43ad [file]
// Copyright 2025 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/hal/drivers/amdgpu/system.h"
#include "iree/hal/drivers/amdgpu/executable.h"
#include "iree/hal/drivers/amdgpu/util/topology.h"
//===----------------------------------------------------------------------===//
// Memory Pools and Regions
//===----------------------------------------------------------------------===//
// Unfortunately HSA has regions (the original concept) and then memory pools
// (the new hotness) and the API uses each in different places. They roughly
// represent the same underlying memory but do so differently enough that we
// can't interchange them and have to basically treat them as independent
// concepts everywhere.
// NOTE: we could do the filtering inline in the iteration callback but that
// requires moving a lot more state into the user data struct and means we can't
// emit iree_status_t errors. Instead we iterate over all and then do things in
// local for-loops.
typedef struct iree_hal_amdgpu_hsa_memory_pool_list_t {
// Number of valid entries in |values|.
iree_host_size_t count;
// Fixed-capacity memory-pool list populated by HSA iteration callbacks.
hsa_amd_memory_pool_t values[32];
} iree_hal_amdgpu_hsa_memory_pool_list_t;
static hsa_status_t iree_hal_amdgpu_iterate_hsa_memory_pool(
hsa_amd_memory_pool_t memory_pool, void* user_data) {
iree_hal_amdgpu_hsa_memory_pool_list_t* pool_list =
(iree_hal_amdgpu_hsa_memory_pool_list_t*)user_data;
if (pool_list->count >= IREE_ARRAYSIZE(pool_list->values)) {
return HSA_STATUS_ERROR_OUT_OF_RESOURCES;
}
pool_list->values[pool_list->count++] = memory_pool;
return HSA_STATUS_SUCCESS;
}
static iree_status_t iree_hal_amdgpu_system_populate_host_memory_pools(
const iree_hal_amdgpu_libhsa_t* libhsa, hsa_agent_t host_agent,
iree_hal_amdgpu_host_memory_pools_t* host_memory_pools) {
IREE_TRACE_ZONE_BEGIN(z0);
// Iterate all memory pools on the CPU agent.
iree_hal_amdgpu_hsa_memory_pool_list_t all_memory_pools = {
.count = 0,
};
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hsa_amd_agent_iterate_memory_pools(
IREE_LIBHSA(libhsa), host_agent,
iree_hal_amdgpu_iterate_hsa_memory_pool, &all_memory_pools));
bool fine_pool_is_kernarg = false;
for (iree_host_size_t i = 0; i < all_memory_pools.count; ++i) {
hsa_amd_memory_pool_t pool = all_memory_pools.values[i];
// Filter to the global segment only.
hsa_region_segment_t segment = 0;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hsa_amd_memory_pool_get_info(IREE_LIBHSA(libhsa), pool,
HSA_AMD_MEMORY_POOL_INFO_SEGMENT,
&segment));
if (segment != HSA_REGION_SEGMENT_GLOBAL) continue;
// Only care about accessible-by-all.
bool accessible_by_all = false;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0,
iree_hsa_amd_memory_pool_get_info(
IREE_LIBHSA(libhsa), pool,
HSA_AMD_MEMORY_POOL_INFO_ACCESSIBLE_BY_ALL, &accessible_by_all));
if (!accessible_by_all) continue;
// Must be able to allocate. This should be true for any pool we query that
// matches the other flags. Workgroup-private pools won't have this set.
bool alloc_allowed = false;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0,
iree_hsa_amd_memory_pool_get_info(
IREE_LIBHSA(libhsa), pool,
HSA_AMD_MEMORY_POOL_INFO_RUNTIME_ALLOC_ALLOWED, &alloc_allowed));
if (!alloc_allowed) continue;
// Coarse-grained pools are used for write-once/read-many data.
// Kernarg-init pools are used for dispatch argument storage. Fine-grained
// pools are used for host/device shared state that requires atomics.
uint32_t global_flag = 0;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hsa_amd_memory_pool_get_info(
IREE_LIBHSA(libhsa), pool,
HSA_AMD_MEMORY_POOL_INFO_GLOBAL_FLAGS, &global_flag));
const bool is_kernarg =
global_flag & HSA_AMD_MEMORY_POOL_GLOBAL_FLAG_KERNARG_INIT;
const bool is_fine =
global_flag & HSA_AMD_MEMORY_POOL_GLOBAL_FLAG_FINE_GRAINED;
if ((global_flag & HSA_AMD_MEMORY_POOL_GLOBAL_FLAG_COARSE_GRAINED) &&
!host_memory_pools->coarse_pool.handle) {
host_memory_pools->coarse_pool = pool;
}
if (is_kernarg && !host_memory_pools->kernarg_pool.handle) {
host_memory_pools->kernarg_pool = pool;
}
if (is_fine && (!host_memory_pools->fine_pool.handle ||
(fine_pool_is_kernarg && !is_kernarg))) {
host_memory_pools->fine_pool = pool;
fine_pool_is_kernarg = is_kernarg;
}
}
if (!host_memory_pools->coarse_pool.handle) {
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_make_status(IREE_STATUS_NOT_FOUND,
"no accessible-by-all + coarse-grained shared "
"memory pool is available in the system"));
}
if (!host_memory_pools->fine_pool.handle) {
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_make_status(IREE_STATUS_NOT_FOUND,
"no accessible-by-all + fine-grained shared "
"memory pool is available in the system"));
}
if (!host_memory_pools->kernarg_pool.handle) {
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_make_status(IREE_STATUS_NOT_FOUND,
"no accessible-by-all + kernarg-init shared "
"memory pool is available in the system"));
}
IREE_TRACE_ZONE_END(z0);
return iree_ok_status();
}
// NOTE: we could do the filtering inline in the iteration callback but that
// requires moving a lot more state into the user data struct and means we can't
// emit iree_status_t errors. Instead we iterate over all and then do things in
// local for-loops.
typedef struct iree_hal_amdgpu_hsa_region_list_t {
// Number of valid entries in |values|.
iree_host_size_t count;
// Fixed-capacity region list populated by HSA iteration callbacks.
hsa_region_t values[32];
} iree_hal_amdgpu_hsa_region_list_t;
static hsa_status_t iree_hal_amdgpu_iterate_hsa_region(hsa_region_t region,
void* user_data) {
iree_hal_amdgpu_hsa_region_list_t* region_list =
(iree_hal_amdgpu_hsa_region_list_t*)user_data;
if (region_list->count >= IREE_ARRAYSIZE(region_list->values)) {
return HSA_STATUS_ERROR_OUT_OF_RESOURCES;
}
region_list->values[region_list->count++] = region;
return HSA_STATUS_SUCCESS;
}
static iree_status_t iree_hal_amdgpu_system_populate_host_regions(
const iree_hal_amdgpu_libhsa_t* libhsa, hsa_agent_t host_agent,
iree_hal_amdgpu_host_memory_pools_t* host_memory_pools) {
IREE_TRACE_ZONE_BEGIN(z0);
// Iterate all regions on the CPU agent.
iree_hal_amdgpu_hsa_region_list_t all_regions = {
.count = 0,
};
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hsa_agent_iterate_regions(IREE_LIBHSA(libhsa), host_agent,
iree_hal_amdgpu_iterate_hsa_region,
&all_regions));
for (iree_host_size_t i = 0; i < all_regions.count; ++i) {
hsa_region_t region = all_regions.values[i];
// Filter to the global segment only.
hsa_region_segment_t segment = 0;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hsa_region_get_info(IREE_LIBHSA(libhsa), region,
HSA_REGION_INFO_SEGMENT, &segment));
if (segment != HSA_REGION_SEGMENT_GLOBAL) continue;
// Must be able to allocate. This should be true for any pool we query that
// matches the other flags. Workgroup-private pools won't have this set.
bool alloc_allowed = false;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hsa_region_get_info(IREE_LIBHSA(libhsa), region,
HSA_REGION_INFO_RUNTIME_ALLOC_ALLOWED,
&alloc_allowed));
if (!alloc_allowed) continue;
// Only want fine-grained so we can use atomics.
hsa_region_global_flag_t global_flag = 0;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0,
iree_hsa_region_get_info(IREE_LIBHSA(libhsa), region,
HSA_REGION_INFO_GLOBAL_FLAGS, &global_flag));
if (global_flag & HSA_REGION_GLOBAL_FLAG_FINE_GRAINED) {
if (!host_memory_pools->fine_region.handle) { // first only
host_memory_pools->fine_region = region;
}
}
}
iree_status_t status = iree_ok_status();
if (!host_memory_pools->fine_region.handle) {
status = iree_make_status(IREE_STATUS_NOT_FOUND,
"no accessible-by-all + fine-grained shared "
"memory region is available in the system");
}
IREE_TRACE_ZONE_END(z0);
return status;
}
//===----------------------------------------------------------------------===//
// iree_hal_amdgpu_system_t
//===----------------------------------------------------------------------===//
static void iree_hal_amdgpu_system_deinitialize(
iree_hal_amdgpu_system_t* system);
static iree_status_t iree_hal_amdgpu_system_initialize(
const iree_hal_amdgpu_libhsa_t* libhsa,
const iree_hal_amdgpu_topology_t* topology,
iree_hal_amdgpu_system_options_t options, iree_allocator_t host_allocator,
iree_hal_amdgpu_system_t* out_system) {
IREE_ASSERT_ARGUMENT(libhsa);
IREE_ASSERT_ARGUMENT(out_system);
IREE_TRACE_ZONE_BEGIN(z0);
out_system->host_allocator = host_allocator;
// Ensure all GPU agents in the topology support compatible ISAs. They should
// all be the same today but in the future if we start allowing heterogeneous
// (even if just lightly) we'll want to catch issues here.
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hal_amdgpu_verify_device_isa_commonality(libhsa, topology));
// Query and validate the system information.
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hal_amdgpu_system_info_query(libhsa, &out_system->info));
// Initialize the platform source used for profile clock correlation.
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hal_amdgpu_device_clock_source_initialize(
&out_system->device_clock_source));
// Copy the libhsa symbol table and retain HSA for the lifetime of the system.
// The caller may destroy the provided libhsa after this call returns.
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_hal_amdgpu_libhsa_copy(libhsa, &out_system->libhsa));
// Copy the topology - today it's a plain-old-data struct and we can just
// memcpy it. This is an implementation detail, though, and in the future if
// it allocates anything we'll need to make sure this retains the allocations
// or does a deep copy.
memcpy(&out_system->topology, topology, sizeof(out_system->topology));
// Preserved as the information may be used by components of the system.
out_system->options = options;
// Initialize the device library, which will load the builtin executable and
// fail if we don't have a supported arch.
iree_status_t status = iree_hal_amdgpu_device_library_initialize(
&out_system->libhsa, topology, host_allocator,
&out_system->device_library);
// Find common/shared memory pools.
if (iree_status_is_ok(status)) {
for (iree_host_size_t host_ordinal = 0;
host_ordinal < topology->cpu_agent_count; ++host_ordinal) {
iree_hal_amdgpu_host_memory_pools_t* host_memory_pools =
&out_system->host_memory_pools[host_ordinal];
status = iree_hal_amdgpu_system_populate_host_memory_pools(
libhsa, topology->cpu_agents[host_ordinal], host_memory_pools);
if (!iree_status_is_ok(status)) break;
status = iree_hal_amdgpu_system_populate_host_regions(
&out_system->libhsa, topology->cpu_agents[host_ordinal],
host_memory_pools);
if (!iree_status_is_ok(status)) break;
}
}
if (!iree_status_is_ok(status)) {
iree_hal_amdgpu_system_deinitialize(out_system);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
static void iree_hal_amdgpu_system_deinitialize(
iree_hal_amdgpu_system_t* system) {
IREE_ASSERT_ARGUMENT(system);
IREE_TRACE_ZONE_BEGIN(z0);
// Unload the device library - no references to it should remain.
iree_hal_amdgpu_device_library_deinitialize(&system->device_library);
// Release platform clock-sampling state before unloading HSA.
iree_hal_amdgpu_device_clock_source_deinitialize(
&system->device_clock_source);
// This may unload HSA if we were the last retainer in the process.
iree_hal_amdgpu_libhsa_deinitialize(&system->libhsa);
memset(system, 0, sizeof(*system));
IREE_TRACE_ZONE_END(z0);
}
iree_status_t iree_hal_amdgpu_system_allocate(
const iree_hal_amdgpu_libhsa_t* libhsa,
const iree_hal_amdgpu_topology_t* topology,
iree_hal_amdgpu_system_options_t options, iree_allocator_t host_allocator,
iree_hal_amdgpu_system_t** out_system) {
IREE_ASSERT_ARGUMENT(libhsa);
IREE_ASSERT_ARGUMENT(out_system);
IREE_TRACE_ZONE_BEGIN(z0);
*out_system = NULL;
iree_hal_amdgpu_system_t* system = NULL;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_allocator_malloc(
host_allocator,
sizeof(*system) + topology->cpu_agent_count *
sizeof(system->host_memory_pools[0]),
(void**)&system));
iree_status_t status = iree_hal_amdgpu_system_initialize(
libhsa, topology, options, host_allocator, system);
if (iree_status_is_ok(status)) {
*out_system = system;
} else {
iree_allocator_free(host_allocator, system);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
void iree_hal_amdgpu_system_free(iree_hal_amdgpu_system_t* system) {
if (!system) return;
iree_allocator_t host_allocator = system->host_allocator;
IREE_TRACE_ZONE_BEGIN(z0);
iree_hal_amdgpu_system_deinitialize(system);
iree_allocator_free(host_allocator, system);
IREE_TRACE_ZONE_END(z0);
}