blob: fc4973b53979ca28f9dabca4f5a73f4dd4d34223 [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 <cstring>
#include "iree/base/api.h"
#include "iree/hal/api.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"
namespace iree::hal {
namespace {
using ::iree::testing::status::IsOk;
using ::iree::testing::status::StatusIs;
using ::testing::Eq;
using ::testing::Ne;
//===----------------------------------------------------------------------===//
// Builder initialization tests
//===----------------------------------------------------------------------===//
// Tests basic builder initialization.
TEST(TopologyBuilder, Initialize) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 4);
// Verify device count was set.
EXPECT_EQ(builder.topology.device_count, 4);
// Verify self-edges were initialized with optimal scheduling settings.
for (uint32_t i = 0; i < 4; ++i) {
uint32_t idx = i * 4 + i;
EXPECT_TRUE(builder.edges_set[idx]);
EXPECT_EQ(iree_hal_topology_edge_wait_mode(builder.topology.edges[idx].lo),
IREE_HAL_TOPOLOGY_INTEROP_MODE_NATIVE);
EXPECT_EQ(
iree_hal_topology_edge_signal_mode(builder.topology.edges[idx].lo),
IREE_HAL_TOPOLOGY_INTEROP_MODE_NATIVE);
EXPECT_EQ(iree_hal_topology_edge_link_class(builder.topology.edges[idx].lo),
IREE_HAL_TOPOLOGY_LINK_CLASS_SAME_DIE);
}
// Verify self-edges have native handle types in the interop word.
for (uint32_t i = 0; i < 4; ++i) {
uint32_t idx = i * 4 + i;
EXPECT_EQ(iree_hal_topology_edge_semaphore_import_types(
builder.topology.edges[idx].hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_NATIVE);
EXPECT_EQ(iree_hal_topology_edge_buffer_import_types(
builder.topology.edges[idx].hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_NATIVE);
}
}
//===----------------------------------------------------------------------===//
// Edge setting tests
//===----------------------------------------------------------------------===//
// Tests setting edges in the builder.
TEST(TopologyBuilder, SetEdges) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 2);
// Set self-edges (already initialized, but we can re-set them).
iree_hal_topology_edge_t self_edge = iree_hal_topology_edge_make_self();
IREE_ASSERT_OK(iree_hal_topology_builder_set_edge(&builder, 0, 0, self_edge));
IREE_ASSERT_OK(iree_hal_topology_builder_set_edge(&builder, 1, 1, self_edge));
// Set cross-device edges with custom scheduling and interop settings.
iree_hal_topology_edge_t cross_edge =
iree_hal_topology_edge_make_cross_driver();
cross_edge.lo = iree_hal_topology_edge_set_wait_mode(
cross_edge.lo, IREE_HAL_TOPOLOGY_INTEROP_MODE_COPY);
cross_edge.lo = iree_hal_topology_edge_set_signal_mode(
cross_edge.lo, IREE_HAL_TOPOLOGY_INTEROP_MODE_COPY);
cross_edge.lo = iree_hal_topology_edge_set_link_class(
cross_edge.lo, IREE_HAL_TOPOLOGY_LINK_CLASS_NVLINK_IF);
// Set interop handle types for the cross-device edge.
cross_edge.hi = iree_hal_topology_edge_set_semaphore_import_types(
cross_edge.hi, IREE_HAL_TOPOLOGY_HANDLE_TYPE_OPAQUE_FD);
cross_edge.hi = iree_hal_topology_edge_set_buffer_import_types(
cross_edge.hi, IREE_HAL_TOPOLOGY_HANDLE_TYPE_DMA_BUF);
IREE_ASSERT_OK(
iree_hal_topology_builder_set_edge(&builder, 0, 1, cross_edge));
IREE_ASSERT_OK(
iree_hal_topology_builder_set_edge(&builder, 1, 0, cross_edge));
// Build and get topology.
iree_hal_topology_t topology;
IREE_ASSERT_OK(iree_hal_topology_builder_finalize(&builder, &topology));
// Check the scheduling word in the topology.
EXPECT_EQ(topology.device_count, 2);
EXPECT_EQ(iree_hal_topology_edge_wait_mode(topology.edges[0 * 2 + 0].lo),
IREE_HAL_TOPOLOGY_INTEROP_MODE_NATIVE);
EXPECT_EQ(iree_hal_topology_edge_wait_mode(topology.edges[0 * 2 + 1].lo),
IREE_HAL_TOPOLOGY_INTEROP_MODE_COPY);
// Check the interop word survived the finalize copy.
EXPECT_EQ(iree_hal_topology_edge_semaphore_import_types(
topology.edges[0 * 2 + 1].hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_OPAQUE_FD);
EXPECT_EQ(
iree_hal_topology_edge_buffer_import_types(topology.edges[0 * 2 + 1].hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_DMA_BUF);
}
// Tests that both words of an edge are preserved through set_edge and finalize.
TEST(TopologyBuilder, FullEdgePreservation) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 2);
// Build a fully-populated cross-device edge.
iree_hal_topology_edge_t edge = iree_hal_topology_edge_make_cross_driver();
edge.lo = iree_hal_topology_edge_set_wait_mode(
edge.lo, IREE_HAL_TOPOLOGY_INTEROP_MODE_IMPORT);
edge.lo = iree_hal_topology_edge_set_signal_mode(
edge.lo, IREE_HAL_TOPOLOGY_INTEROP_MODE_COPY);
edge.lo = iree_hal_topology_edge_set_capability_flags(
edge.lo, IREE_HAL_TOPOLOGY_CAPABILITY_P2P_COPY |
IREE_HAL_TOPOLOGY_CAPABILITY_REMOTE_DMA);
edge.lo = iree_hal_topology_edge_set_wait_cost(edge.lo, 3);
edge.lo = iree_hal_topology_edge_set_copy_cost(edge.lo, 7);
edge.lo = iree_hal_topology_edge_set_link_class(
edge.lo, IREE_HAL_TOPOLOGY_LINK_CLASS_FABRIC);
edge.hi = iree_hal_topology_edge_set_semaphore_import_types(
edge.hi, IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
edge.hi = iree_hal_topology_edge_set_semaphore_export_types(
edge.hi, IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
edge.hi = iree_hal_topology_edge_set_buffer_import_types(
edge.hi, IREE_HAL_TOPOLOGY_HANDLE_TYPE_RDMA_MR |
IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
edge.hi = iree_hal_topology_edge_set_buffer_export_types(
edge.hi, IREE_HAL_TOPOLOGY_HANDLE_TYPE_RDMA_MR |
IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
IREE_ASSERT_OK(iree_hal_topology_builder_set_edge(&builder, 0, 1, edge));
IREE_ASSERT_OK(iree_hal_topology_builder_set_edge(&builder, 1, 0, edge));
iree_hal_topology_t topology;
IREE_ASSERT_OK(iree_hal_topology_builder_finalize(&builder, &topology));
// Verify the entire scheduling word survived.
iree_hal_topology_edge_t result = topology.edges[0 * 2 + 1];
EXPECT_EQ(iree_hal_topology_edge_wait_mode(result.lo),
IREE_HAL_TOPOLOGY_INTEROP_MODE_IMPORT);
EXPECT_EQ(iree_hal_topology_edge_signal_mode(result.lo),
IREE_HAL_TOPOLOGY_INTEROP_MODE_COPY);
EXPECT_EQ(iree_hal_topology_edge_capability_flags(result.lo),
IREE_HAL_TOPOLOGY_CAPABILITY_P2P_COPY |
IREE_HAL_TOPOLOGY_CAPABILITY_REMOTE_DMA);
EXPECT_EQ(iree_hal_topology_edge_wait_cost(result.lo), 3);
EXPECT_EQ(iree_hal_topology_edge_copy_cost(result.lo), 7);
EXPECT_EQ(iree_hal_topology_edge_link_class(result.lo),
IREE_HAL_TOPOLOGY_LINK_CLASS_FABRIC);
// Verify the entire interop word survived.
EXPECT_EQ(iree_hal_topology_edge_semaphore_import_types(result.hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
EXPECT_EQ(iree_hal_topology_edge_semaphore_export_types(result.hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
EXPECT_EQ(iree_hal_topology_edge_buffer_import_types(result.hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_RDMA_MR |
IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
EXPECT_EQ(iree_hal_topology_edge_buffer_export_types(result.hi),
IREE_HAL_TOPOLOGY_HANDLE_TYPE_RDMA_MR |
IREE_HAL_TOPOLOGY_HANDLE_TYPE_SHM);
}
//===----------------------------------------------------------------------===//
// Validation tests
//===----------------------------------------------------------------------===//
// Tests that validation fails when not all edges are set.
TEST(TopologyBuilder, ValidationFailsIncomplete) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 2);
// Clear one of the edges_set flags to simulate missing edge.
// Self-edges [0,0] and [1,1] are set by initialize.
// Don't set cross-edges [0,1] and [1,0].
builder.edges_set[0 * 2 + 1] = false; // mark [0,1] as not set
builder.edges_set[1 * 2 + 0] = false; // mark [1,0] as not set
iree_hal_topology_t topology;
iree_status_t status =
iree_hal_topology_builder_finalize(&builder, &topology);
EXPECT_THAT(status, StatusIs(iree::StatusCode::kInvalidArgument));
iree_status_ignore(status);
}
// Tests that validation fails for asymmetric link classes.
TEST(TopologyBuilder, ValidationFailsAsymmetricLinks) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 2);
// Set asymmetric cross-device edges (different link classes).
iree_hal_topology_edge_t edge1 = iree_hal_topology_edge_make_cross_driver();
edge1.lo = iree_hal_topology_edge_set_link_class(
edge1.lo, IREE_HAL_TOPOLOGY_LINK_CLASS_NVLINK_IF);
iree_hal_topology_edge_t edge2 = iree_hal_topology_edge_make_cross_driver();
edge2.lo = iree_hal_topology_edge_set_link_class(
edge2.lo, IREE_HAL_TOPOLOGY_LINK_CLASS_PCIE_SAME_ROOT);
IREE_ASSERT_OK(iree_hal_topology_builder_set_edge(&builder, 0, 1, edge1));
IREE_ASSERT_OK(iree_hal_topology_builder_set_edge(&builder, 1, 0, edge2));
iree_hal_topology_t topology;
iree_status_t status =
iree_hal_topology_builder_finalize(&builder, &topology);
EXPECT_THAT(status, StatusIs(iree::StatusCode::kInvalidArgument));
iree_status_ignore(status);
}
// Tests that self-edges must have NATIVE wait mode.
TEST(TopologyBuilder, ValidationFailsNonNativeSelfEdge) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 1);
// Try to set a self-edge with non-NATIVE wait mode.
iree_hal_topology_edge_t bad_self = iree_hal_topology_edge_make_self();
bad_self.lo = iree_hal_topology_edge_set_wait_mode(
bad_self.lo, IREE_HAL_TOPOLOGY_INTEROP_MODE_COPY);
iree_status_t status =
iree_hal_topology_builder_set_edge(&builder, 0, 0, bad_self);
EXPECT_THAT(status, StatusIs(iree::StatusCode::kInvalidArgument));
iree_status_ignore(status);
}
//===----------------------------------------------------------------------===//
// NUMA node tests
//===----------------------------------------------------------------------===//
// Tests NUMA node assignment.
TEST(TopologyBuilder, NumaNodes) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 4);
// Assign NUMA nodes.
IREE_ASSERT_OK(iree_hal_topology_builder_set_numa_node(&builder, 0, 0));
IREE_ASSERT_OK(iree_hal_topology_builder_set_numa_node(&builder, 1, 0));
IREE_ASSERT_OK(iree_hal_topology_builder_set_numa_node(&builder, 2, 1));
IREE_ASSERT_OK(iree_hal_topology_builder_set_numa_node(&builder, 3, 1));
// Set cross-device edges (self-edges already set by initialize).
for (uint32_t i = 0; i < 4; ++i) {
for (uint32_t j = 0; j < 4; ++j) {
if (i != j) {
iree_hal_topology_edge_t cross =
iree_hal_topology_edge_make_cross_driver();
// Same NUMA node = lower cost (costs are 4-bit, max 15).
uint8_t cost =
(builder.topology.numa_nodes[i] == builder.topology.numa_nodes[j])
? 5
: 12;
cross.lo = iree_hal_topology_edge_set_copy_cost(cross.lo, cost);
IREE_ASSERT_OK(
iree_hal_topology_builder_set_edge(&builder, i, j, cross));
}
}
}
iree_hal_topology_t topology;
IREE_ASSERT_OK(iree_hal_topology_builder_finalize(&builder, &topology));
// Check NUMA nodes.
EXPECT_EQ(topology.numa_nodes[0], 0);
EXPECT_EQ(topology.numa_nodes[1], 0);
EXPECT_EQ(topology.numa_nodes[2], 1);
EXPECT_EQ(topology.numa_nodes[3], 1);
// Check that intra-NUMA transfers have lower cost.
EXPECT_EQ(iree_hal_topology_edge_copy_cost(topology.edges[0 * 4 + 1].lo),
5); // same NUMA
EXPECT_EQ(iree_hal_topology_edge_copy_cost(topology.edges[0 * 4 + 2].lo),
12); // different NUMA
}
//===----------------------------------------------------------------------===//
// Topology query tests
//===----------------------------------------------------------------------===//
// Tests querying edges from a finalized topology.
TEST(TopologyBuilder, QueryEdges) {
iree_hal_topology_builder_t builder;
iree_hal_topology_builder_initialize(&builder, 3);
// Set cross-device edges with varying configurations.
for (uint32_t i = 0; i < 3; ++i) {
for (uint32_t j = 0; j < 3; ++j) {
if (i != j) {
iree_hal_topology_edge_t edge =
iree_hal_topology_edge_make_cross_driver();
// Give each edge a unique copy cost for identification.
edge.lo =
iree_hal_topology_edge_set_copy_cost(edge.lo, (uint8_t)(i * 3 + j));
// Set link class consistently for symmetry validation.
edge.lo = iree_hal_topology_edge_set_link_class(
edge.lo, IREE_HAL_TOPOLOGY_LINK_CLASS_PCIE_SAME_ROOT);
IREE_ASSERT_OK(
iree_hal_topology_builder_set_edge(&builder, i, j, edge));
}
}
}
iree_hal_topology_t topology;
IREE_ASSERT_OK(iree_hal_topology_builder_finalize(&builder, &topology));
// Query each edge and verify the copy cost encodes the (i,j) pair.
for (uint32_t i = 0; i < 3; ++i) {
for (uint32_t j = 0; j < 3; ++j) {
iree_hal_topology_edge_t queried =
iree_hal_topology_query_edge(&topology, i, j);
if (i == j) {
// Self-edges have zero copy cost.
EXPECT_EQ(iree_hal_topology_edge_copy_cost(queried.lo), 0);
} else {
EXPECT_EQ(iree_hal_topology_edge_copy_cost(queried.lo), i * 3 + j);
}
}
}
}
} // namespace
} // namespace iree::hal