Files
Atul Kulkarni 142860442a Enable MPI support to execute MPI specific unit/functional tests (#1996)
* Added MPI support to execute unit/functional tests

Update node and process validation
Updated node detection count and modified validation method
Update validation logic to include max procs and nodes

* Address review comments

* Fix warnings

* Added a new NET transport test and clean up

* Added MPI test logging mechanism

* Decoupled GTest framework

* Added Net IB functional tests

* Updated with resource guards

* Added NET IB tests and refactored code

* Update P2pWorkflow test

* Update documentation

* Add MPI_TESTS_ENABLED guard to the file

* Fix Shm and NetIB tests

* Applied refactoring and cleanup

* Replaced BufferGuard with AutoGuard

* Modified test debug logging

* Use macro to reduce NcclTypeTraits code duplication

- Replace repetitive template specializations with a single
  DEFINE_NCCL_TYPE_TRAIT macro
- Use stringification operator (#) to auto-generate type name strings
- Add #undef to keep macro from polluting namespace
- Makes adding new type mappings trivial

* Unify buffer initialization with generic pattern function

- Remove initializeBufferWithCustomPattern
- Make initializeBufferWithPattern generic with PatternFunc template param
- Now single function handles all patterns via lambda injection
- Updated all test files to use lambdas for pattern generation
- Pattern logic now visible at call site (self-documenting)

* Unify buffer verification with pluggable pattern function

- Remove verifyBufferWithCustomCheck
- Make verifyBufferData generic with PatternFunc template param
- Single function handles all verification patterns via lambda injection
- Updated all test files to use lambdas
- Better defaults: num_samples=0 means verify all elements
- Pattern logic now visible at call site (self-documenting)

* Docs: Add DeviceBufferHelpers section to MPITestRunner.md

- Document new refactored buffer initialization/verification API
- Explain pluggable pattern functions with lambda examples
- Show type mapping and automatic float/int comparison
- Include migration guide from old API to new unified functions
- Demonstrate best practices with real-world examples
- Reference recent refactoring commits (macro-based type traits)

* Docs: Update documentation and examples

- Update on DeviceBufferHelpers
- Update examples using DeviceBufferHelpers methods, e.g. data verification

* Address review comment.

- Replace manual pattern generation loop with initializeBufferWithPattern call
- Use downloadBuffer to get host copy instead of manual hipMemcpy

* Remove non-existent dependency

* Remove duplicate testcase

* Code cleanup in test files

* Moved common constants to base class

[ROCm/rccl commit: 29e1567b95]
2025-12-06 16:05:37 -06:00

296 строки
11 KiB
C++

/*************************************************************************
* Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/
#ifndef TRANSPORT_MPI_BASE_HPP
#define TRANSPORT_MPI_BASE_HPP
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "rccl/rccl.h"
#include "gtest/gtest.h"
#ifdef MPI_TESTS_ENABLED
#include "MPITestBase.hpp"
#include "MPIEnvironment.hpp"
#include "TestChecks.hpp"
#include "ResourceGuards.hpp"
#include "comm.h"
#include "core.h"
#include "device.h"
#include "graph.h"
#include "graph/topo.h"
#include "nccl_common.h"
#include "transport.h"
using namespace RCCLTestGuards;
// Transport-specific RAII deleters
namespace RCCLTestGuards
{
struct TransportSendResourceDeleter
{
ncclTransport* transport;
explicit TransportSendResourceDeleter(ncclTransport* t = nullptr) : transport(t) {}
void operator()(ncclConnector* connector) const
{
if(connector && transport)
{
transport->send.free(connector);
}
}
};
struct TransportRecvResourceDeleter
{
ncclTransport* transport;
explicit TransportRecvResourceDeleter(ncclTransport* t = nullptr) : transport(t) {}
void operator()(ncclConnector* connector) const
{
if(connector && transport)
{
transport->recv.free(connector);
}
}
};
using TransportSendResourceGuard = ResourceGuard<ncclConnector*, TransportSendResourceDeleter>;
using TransportRecvResourceGuard = ResourceGuard<ncclConnector*, TransportRecvResourceDeleter>;
class TransportResourceGuard
{
private:
ncclConnector* send_connector_;
ncclConnector* recv_connector_;
ncclTransport* transport_;
public:
TransportResourceGuard(ncclConnector* send, ncclConnector* recv, ncclTransport* transport)
: send_connector_(send), recv_connector_(recv), transport_(transport)
{}
~TransportResourceGuard()
{
if(recv_connector_ && transport_)
{
transport_->recv.free(recv_connector_);
}
if(send_connector_ && transport_)
{
transport_->send.free(send_connector_);
}
}
TransportResourceGuard(const TransportResourceGuard&) = delete;
TransportResourceGuard& operator=(const TransportResourceGuard&) = delete;
TransportResourceGuard(TransportResourceGuard&&) = delete;
TransportResourceGuard& operator=(TransportResourceGuard&&) = delete;
};
inline TransportSendResourceGuard makeTransportSendGuard(ncclConnector* connector,
ncclTransport* transport)
{
return TransportSendResourceGuard(connector, TransportSendResourceDeleter(transport));
}
inline TransportRecvResourceGuard makeTransportRecvGuard(ncclConnector* connector,
ncclTransport* transport)
{
return TransportRecvResourceGuard(connector, TransportRecvResourceDeleter(transport));
}
} // namespace RCCLTestGuards
extern struct ncclTransport p2pTransport;
extern struct ncclTransport netTransport;
extern struct ncclTransport shmTransport;
// ============================================================================
// Transport Test Constants
// ============================================================================
namespace TransportTestConstants
{
// Buffer size constants (common across P2P, SHM, NET tests)
inline constexpr size_t kDefaultBufferSize = 1024 * sizeof(float); // 4096 bytes
inline constexpr size_t kSmallBufferSize = 256;
inline constexpr size_t kMediumBufferSize = 16384; // 16 KB
inline constexpr size_t kLargeBufferSize = 135168; // ~132 KB
inline constexpr size_t kVeryLargeBufferSize = 256 * 1024 * 1024; // 256 MB
inline constexpr size_t kCEMemcpyBufferSize = 256 * 1024 * 1024; // 256 MB (for CE tests)
// Pattern generation constants
inline constexpr int kDefaultPatternMultiplier = 1000; // Standard rank-based patterns
inline constexpr int kSmallPatternMultiplier = 100; // Smaller patterns (memcpy tests)
inline constexpr int kLargePatternMultiplier = 1000000; // Large buffer patterns
inline constexpr int kPatternModulo = 10000; // Wraparound patterns
inline constexpr int kBytePatternModulo = 256; // uint8_t wraparound
// Validation constants
inline constexpr size_t kMaxValidationElements = 100; // Number of elements to validate
inline constexpr size_t kMinValidationSamples = 100; // Minimum samples for validation
inline constexpr size_t kValidationStride = 1000; // Stride for sampling validation
inline constexpr int kMaxErrorsToReport = 10; // Max errors to display
// Test iteration constants
inline constexpr int kMultipleTransferCount = 5; // Number of sequential transfers
} // namespace TransportTestConstants
// Common test configuration
struct TransportTestConfig
{
int world_rank{0};
int world_size{0};
int peer_rank{0};
ncclComm_t nccl_comm{nullptr};
hipStream_t stream{nullptr};
};
// Base class for transport tests with common functionality
// Inherits from MPITestBase to get validation capabilities
class TransportTestBase : public MPITestBase
{
protected:
TransportTestConfig config;
// Transport connectors (can be used for P2P or NET)
ncclConnector send_connector = {};
ncclConnector recv_connector = {};
// Track which transport type is initialized
enum class TransportType
{
None,
P2P,
SHM,
Network
};
TransportType initialized_transport = TransportType::None;
// Core NCCL components
struct ncclComm* comm_handle = nullptr;
ncclPeerInfo* local_peer_info = nullptr;
ncclPeerInfo* remote_peer_info = nullptr;
ncclTopoGraph* topology_graph = nullptr;
// RAII guards for automatic resource cleanup
// These are managed by helper methods and cleaned up automatically
std::vector<DeviceBufferAutoGuard> buffer_guards_;
std::vector<NcclRegHandleGuard> reg_handle_guards_;
// Setup and teardown
void SetUp() override;
void TearDown() override;
// Override createTestCommunicator to also update config
ncclResult_t createTestCommunicator() override;
// Set transport type and initialize connectors
void setTransportType(TransportType type);
// Buffer allocation (unguarded - for manual management)
void allocateAndInitBuffers(void** send_buffer,
void** recv_buffer,
size_t send_bytes,
size_t recv_bytes);
// Buffer allocation with automatic RAII guards
// store_in_base=true: Guards stored in base class, cleanup at test end
// store_in_base=false: Guards returned, caller controls cleanup scope
std::pair<DeviceBufferAutoGuard, DeviceBufferAutoGuard> allocateAndInitBuffersGuarded(void** send_buffer,
void** recv_buffer,
size_t send_bytes,
size_t recv_bytes,
bool store_in_base = true);
// Buffer registration (unguarded - for manual management)
void preRegisterBuffers(void* send_buffer,
void* recv_buffer,
size_t send_bytes,
size_t recv_bytes,
void** send_reg_handle,
void** recv_reg_handle);
// Buffer registration with automatic RAII guards
// store_in_base=true: Guards stored in base class, cleanup at test end
// store_in_base=false: Guards returned, caller controls cleanup scope
std::pair<NcclRegHandleGuard, NcclRegHandleGuard>
preRegisterBuffersGuarded(void* send_buffer,
void* recv_buffer,
size_t send_bytes,
size_t recv_bytes,
void** send_reg_handle,
void** recv_reg_handle,
bool store_in_base = true);
};
// ============================================================================
// Generic Stream Synchronization Helpers
// ============================================================================
/**
* @brief Generic stream synchronization helper
*
* Synchronizes a HIP stream and returns the error code. This function is
* marked [[nodiscard]] to ensure callers check the return value.
*
* @param stream HIP stream to synchronize
* @param rank MPI rank (for error reporting, currently unused but allows
* future enhancement with rank-specific error messages)
* @return hipError_t Result of hipStreamSynchronize
*
* Usage examples:
* - Manual error checking: hipError_t err = syncStream(stream, rank);
* - With HIPCHECK macro: HIPCHECK(syncStream(stream, rank));
* - With assertion macro: ASSERT_STREAM_SYNC(stream, rank);
*/
[[nodiscard]] inline hipError_t syncStream(hipStream_t stream, int rank = 0)
{
return hipStreamSynchronize(stream);
}
/**
* @def ASSERT_STREAM_SYNC
* @brief Macro to assert stream synchronization succeeds
*
* Convenience macro that combines syncStream() with ASSERT_EQ to provide
* clean, consistent stream synchronization checks in tests.
*
* @param stream HIP stream to synchronize
* @param rank MPI rank for error reporting
*
* Example: ASSERT_STREAM_SYNC(config.stream, config.world_rank);
*/
#define ASSERT_STREAM_SYNC(stream, rank) \
ASSERT_EQ(hipSuccess, syncStream(stream, rank)) \
<< "Rank " << rank << ": Stream synchronization failed"
/**
* @def ASSERT_STREAM_SYNC_MPI
* @brief MPI-aware stream synchronization assertion
*
* Uses ASSERT_MPI_EQ to ensure all ranks synchronize before failing.
* This prevents deadlocks when one rank fails while others are waiting
* in collective operations.
*
* @param stream HIP stream to synchronize
* @param rank MPI rank for error reporting
*
* Example: ASSERT_STREAM_SYNC_MPI(config.stream, config.world_rank);
*
* @note Prefer this version in multi-rank tests to avoid hangs
*/
#define ASSERT_STREAM_SYNC_MPI(stream, rank) ASSERT_MPI_EQ(hipSuccess, syncStream(stream, rank))
#endif // MPI_TESTS_ENABLED
#endif // TRANSPORT_MPI_BASE_HPP