Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions projects/rccl/test/host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function(rccl_define_micro_source_lists)
ras-test.cc
ras-diagnostics-common-test.cc # src/ras/diagnostics_checks_common.cc
ras-collectives-test.cc # src/ras/collectives.cc
ras-diagnostics-env-test.cc # src/ras/diagnostics_env.cc
tuning-test.cc # src/tuning/tuning.cc
tuning-general-test.cc
ras-client-test.cc
Expand Down Expand Up @@ -748,6 +749,7 @@ rccl_add_micro_binary(rccl-UnitTestsMicro
# collectives_tmp.cc, not collectives.cc: src/CMakeLists.txt lists src/collectives.cc first,
# so add_file_unique() renames this one. Re-check after any source-list reorder.
COLLECTIVES_CC_PATH="${RCCL_HOST_HIPIFY_DIR}/src/ras/collectives_tmp.cc"
RAS_DIAGNOSTICS_ENV_CC_PATH="${RCCL_HOST_HIPIFY_DIR}/src/ras/diagnostics_env.cc"
# graph/tuning.cc keeps the basename, so add_file_unique() renames this one.
TUNING_CC_PATH="${RCCL_HOST_HIPIFY_DIR}/src/tuning/tuning_tmp.cc"
TUNING_GENERAL_CC_PATH="${RCCL_HOST_HIPIFY_DIR}/src/tuning/tuning_general.cc"
Expand Down
27 changes: 27 additions & 0 deletions projects/rccl/test/host/MICROTEST_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ export colliding non-`static` symbols; otherwise a unit needs its own binary:
allocation fails after request rewriting. Enable it with the production fix;
it is excluded from normal host-test runs. To reproduce explicitly, run
`rccl-UnitTestsMicro --gtest_also_run_disabled_tests --gtest_filter=RasCollectivesMicrotest.DISABLED_NetSendCollReq_CommsPayloadAllocationFailureKeepsRequestForwardable`.
- `ras/diagnostics_env.cc` (`RAS_DIAGNOSTICS_ENV_CC_PATH`, from
`ras-diagnostics-env-test.cc`); suite `RasDiagnosticsEnvMicrotest.*`.
Covers NCCL environment collection, filtering, truncation, aggregation,
mismatch reporting, and reporter errors.
Comment thread
aduforest marked this conversation as resolved.
Resolved
- `ras/client.cc` (`RAS_CLIENT_CC_PATH`, from `ras-client-test.cc`); suite
`RasClientMicrotest.*`. With
`NCCL_RAS_CLIENT` defined, `ras_internal.h` reduces to four macros, so this
Expand Down Expand Up @@ -746,3 +750,26 @@ cmake --build build -j"$(nproc)"

Disable coverage instrumentation for the standalone host-only test binaries
with `-DHOST_TEST_COVERAGE=OFF`.

### Shared RAS diagnostic fixtures

`fakes/ras_registry_test_support.h` owns test installation/reset of the
`ras.cc` communicator registry. Both operations hold `ncclCommsMutex` and
clear `ncclCommsSorted`; installation validates allocation before replacing
the old registry and preserves explicit vacant slots. The RAS and diagnostics
suites use the same helper. `fakes/ras_diagnostics_test_support.h` shares the
owned communicator and recording reporter used by the diagnostic suites.

`SetMicroEnviron` in `fakes/env_fakes.{h,cc}` replaces the scripted environment
for enumeration tests, preserving input order and malformed entries while
making valid entries visible through `micro_getenv` and `getenv`. While that
override is active, `SetMicroEnv` and `SetMicroEnvAbsent` also update `environ`.
`ClearMicroEnv`/`ResetEnvFakes` restore the original process environment and
clear the lookup map. Map-only tests retain the existing fallback behavior
unless they opt into the enumeration override.

`DISABLED_Summarize_HashCollisionsReportFullCommIdentity` tracks AICOMRCCL-2743.
The environment reporters currently omit host and process hashes, so distinct
communicators can appear identical. The disabled regression specifies the full
identity for consistent, mismatch, and mismatch-summary reports. Enable it
with the separate production fix; enabled tests still pin correct grouping.
68 changes: 67 additions & 1 deletion projects/rccl/test/host/fakes/env_fakes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,34 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <unistd.h>

#include "nccl.h"

namespace {
struct MicroEnvironState {
bool active = false;
char** original = nullptr;
std::vector<std::string> entries;
std::vector<char*> pointers;
};

MicroEnvironState& microEnvironState() {
static MicroEnvironState state;
return state;
}

void PublishMicroEnviron() {
auto& state = microEnvironState();
state.pointers.clear();
for (auto& entry : state.entries) state.pointers.push_back(entry.data());
state.pointers.push_back(nullptr);
environ = state.pointers.data();
}

// A nullopt entry means "absent". Unmapped names read as unset via micro_getenv, real via the getenv interposer.
std::unordered_map<std::string, std::optional<std::string>>& microEnvMap() {
static std::unordered_map<std::string, std::optional<std::string>> m;
Expand Down Expand Up @@ -54,11 +78,53 @@ void SetMicroEnv(const char* name, const char* value) {
if (name == nullptr) return;
if (value == nullptr) microEnvMap()[name] = std::nullopt;
else microEnvMap()[name] = value;
auto& state = microEnvironState();
if (state.active) {
const std::string prefix = std::string(name) + "=";
std::vector<std::string> entries;
bool replaced = false;
for (const auto& entry : state.entries) {
if (entry.compare(0, prefix.size(), prefix) != 0) {
entries.push_back(entry);
} else if (!replaced && value != nullptr) {
entries.push_back(prefix + value);
replaced = true;
}
}
if (!replaced && value != nullptr) entries.push_back(prefix + value);
state.entries = std::move(entries);
PublishMicroEnviron();
}
}

void SetMicroEnvAbsent(const char* name) { SetMicroEnv(name, nullptr); }

void ClearMicroEnv() { microEnvMap().clear(); }
void SetMicroEnviron(const std::vector<std::string>& entries) {
auto& state = microEnvironState();
if (!state.active) state.original = environ;
state.active = true;
state.entries = entries;
auto& values = microEnvMap();
values.clear();
for (const auto& entry : entries) {
const auto separator = entry.find('=');
if (separator != std::string::npos)
values.emplace(entry.substr(0, separator), entry.substr(separator + 1));
}
PublishMicroEnviron();
}

void ClearMicroEnv() {
auto& state = microEnvironState();
if (state.active) {
environ = state.original;
state.active = false;
state.original = nullptr;
state.entries.clear();
state.pointers.clear();
}
microEnvMap().clear();
}

void ResetEnvFakes() { ClearMicroEnv(); }

Expand Down
4 changes: 4 additions & 0 deletions projects/rccl/test/host/fakes/env_fakes.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#ifndef RCCL_TEST_HOST_ENV_FAKES_H_
#define RCCL_TEST_HOST_ENV_FAKES_H_

#include <string>
#include <vector>

// Strict: a name the fixture has not scripted reads as unset, so no test can
// depend on the ambient environment. ncclGetEnv routes here.
const char* micro_getenv(const char* name);
Expand All @@ -29,6 +32,7 @@ const char* micro_getenv(const char* name);
// fall through to the real getenv.
void SetMicroEnv(const char* name, const char* value);
void SetMicroEnvAbsent(const char* name);
void SetMicroEnviron(const std::vector<std::string>& entries);
void ClearMicroEnv();

// Named like every other per-TU reset so a Reset*Fakes() chain reads as one list
Expand Down
55 changes: 55 additions & 0 deletions projects/rccl/test/host/fakes/ras_diagnostics_test_support.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef RCCL_TEST_HOST_RAS_DIAGNOSTICS_TEST_SUPPORT_H_
#define RCCL_TEST_HOST_RAS_DIAGNOSTICS_TEST_SUPPORT_H_

#include <memory>
#include <string>
#include <vector>

#include "comm.h"
#include "ras/diagnostics.h"
#include "fakes/ras_registry_test_support.h"

namespace ras_test {

struct OwnedComm {
OwnedComm(uint64_t commHash, uint64_t hostHash, uint64_t pidHash, int rank, bool peerInfoValid = true)
: comm(std::make_unique<ncclComm>()), peers(std::make_unique<ncclPeerInfo[]>(1)) {
comm->commHash = commHash;
comm->peerInfo = peers.get();
comm->peerInfoValid = peerInfoValid;
comm->rank = rank;
comm->nRanks = 8;
comm->cudaDev = rank + 10;
comm->nvmlDev = rank + 20;
comm->busId = 0x1000 + rank;
comm->localRank = rank % 4;
comm->localRanks = 4;
peers[0].hostHash = hostHash;
peers[0].pidHash = pidHash;
}

std::unique_ptr<ncclComm> comm;
std::unique_ptr<ncclPeerInfo[]> peers;
};

struct ReporterState {
std::vector<std::string> lines;
ncclResult_t emitResult = ncclSuccess;
int calls = 0;
int failCall = 0;
};

inline ncclResult_t CaptureReport(void* target, const char* line) {
auto* state = static_cast<ReporterState*>(target);
state->lines.emplace_back(line);
++state->calls;
return state->failCall == 0 || state->calls == state->failCall ? state->emitResult : ncclSuccess;
}

inline rasDiagnosticsReporter MakeReporter(ReporterState* state) {
return rasDiagnosticsReporter{CaptureReport, nullptr, state};
}

}

#endif
34 changes: 34 additions & 0 deletions projects/rccl/test/host/fakes/ras_registry_test_support.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef RCCL_TEST_HOST_RAS_REGISTRY_TEST_SUPPORT_H_
#define RCCL_TEST_HOST_RAS_REGISTRY_TEST_SUPPORT_H_

#include <cstdlib>
#include <mutex>
#include <vector>

#include <gtest/gtest.h>
#include "ras/ras_internal.h"

namespace ras_test {

inline void InstallNcclComms(const std::vector<ncclComm*>& comms) {
std::lock_guard<std::mutex> lock(ncclCommsMutex);
auto** replacement = static_cast<ncclComm**>(std::calloc(comms.empty() ? 1 : comms.size(), sizeof(ncclComm*)));
ASSERT_NE(nullptr, replacement);
std::free(ncclComms);
ncclComms = replacement;
nNcclComms = static_cast<int>(comms.size());
ncclCommsSorted = false;
for (size_t index = 0; index < comms.size(); ++index) ncclComms[index] = comms[index];
}

inline void ResetNcclComms() {
std::lock_guard<std::mutex> lock(ncclCommsMutex);
std::free(ncclComms);
ncclComms = nullptr;
nNcclComms = 0;
ncclCommsSorted = false;
}

}

#endif
66 changes: 13 additions & 53 deletions projects/rccl/test/host/ras-diagnostics-common-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "alloc.h"
#include "checks.h"
#include "comm.h"
#include "fakes/ras_diagnostics_test_support.h"
#include "compiler.h"
#include "ras/diagnostics_checks_common.h"
#include "ras/ras_internal.h"
Expand Down Expand Up @@ -58,26 +59,10 @@ struct TestPayload {
uint32_t marker;
};

struct OwnedComm {
OwnedComm(uint64_t commHash, uint64_t hostHash, uint64_t pidHash, int rank, bool peerInfoValid = true)
: comm(std::make_unique<ncclComm>()), peers(std::make_unique<ncclPeerInfo[]>(1)) {
comm->commHash = commHash;
comm->peerInfo = peers.get();
comm->peerInfoValid = peerInfoValid;
comm->rank = rank;
comm->nRanks = 8;
comm->cudaDev = rank + 10;
comm->nvmlDev = rank + 20;
comm->busId = 0x1000 + rank;
comm->localRank = rank % 4;
comm->localRanks = 4;
peers[0].hostHash = hostHash;
peers[0].pidHash = pidHash;
}

std::unique_ptr<ncclComm> comm;
std::unique_ptr<ncclPeerInfo[]> peers;
};
using ras_test::OwnedComm;
using ras_test::ReporterState;
using ras_test::CaptureReport;
using ras_test::InstallNcclComms;

std::vector<rasDiagnosticsCommSnapshot> g_fillSnapshots;
int g_fillCalls = 0;
Expand All @@ -102,23 +87,8 @@ ncclResult_t FillTestPayload(const rasDiagnosticsCommSnapshot* snapshot, void* c
return ncclSuccess;
}

struct ReporterState {
std::vector<std::string> lines;
ncclResult_t emitResult = ncclSuccess;
};

ncclResult_t CaptureReport(void* target, const char* line) {
auto* state = static_cast<ReporterState*>(target);
state->lines.emplace_back(line);
return state->emitResult;
}

void ResetDiagnosticsCommonState() {
std::lock_guard<std::mutex> lock(ncclCommsMutex);
std::free(ncclComms);
ncclComms = nullptr;
nNcclComms = 0;
ncclCommsSorted = false;
ras_test::ResetNcclComms();
g_allocationCalls = 0;
g_failAllocationCall = 0;
g_allocationCounts.clear();
Expand All @@ -128,16 +98,6 @@ void ResetDiagnosticsCommonState() {
g_fillObservedUnlockedMutex = true;
}

void InstallComms(std::initializer_list<ncclComm*> comms) {
std::lock_guard<std::mutex> lock(ncclCommsMutex);
std::free(ncclComms);
ncclComms = static_cast<ncclComm**>(std::calloc(comms.size(), sizeof(*ncclComms)));
ASSERT_NE(nullptr, ncclComms);
nNcclComms = static_cast<int>(comms.size());
int index = 0;
for (ncclComm* comm : comms) ncclComms[index++] = comm;
}

rasDiagnosticsContext UnfilteredContext() {
rasDiagnosticsContext ctx{};
ctx.hasCommFilter = false;
Expand Down Expand Up @@ -307,7 +267,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectRejectsStrideRoundedPastIntMax) {
TEST_F(RasDiagnosticsCommonMicrotest, CollectRejectsCombinedRecordBytesPastIntMax) {
OwnedComm first(1, 2, 3, 0);
OwnedComm second(4, 5, 6, 1);
InstallComms({first.comm.get(), second.comm.get()});
InstallNcclComms({first.comm.get(), second.comm.get()});
const rasDiagnosticsContext ctx = UnfilteredContext();
rasDiagnosticsLocalData data{};
const size_t payloadSize = static_cast<size_t>(INT_MAX) / 2;
Expand All @@ -323,7 +283,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectRejectsCombinedRecordBytesPastIntMa
TEST_F(RasDiagnosticsCommonMicrotest, CollectAcceptsRecordBytesExactlyAtIntMaxLimit) {
OwnedComm first(1, 2, 3, 0);
OwnedComm second(4, 5, 6, 1);
InstallComms({first.comm.get(), second.comm.get()});
InstallNcclComms({first.comm.get(), second.comm.get()});
const rasDiagnosticsContext ctx = UnfilteredContext();
rasDiagnosticsLocalData data{};
const size_t payloadSize = 1000000000;
Expand All @@ -341,7 +301,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectAcceptsRecordBytesExactlyAtIntMaxLi
TEST_F(RasDiagnosticsCommonMicrotest, CollectReturnsEmptyForNullInvalidAndFilteredComms) {
OwnedComm invalid(10, 20, 30, 0, false);
OwnedComm mismatch(11, 20, 30, 1);
InstallComms({nullptr, invalid.comm.get(), mismatch.comm.get()});
InstallNcclComms({nullptr, invalid.comm.get(), mismatch.comm.get()});
const rasDiagnosticsContext ctx = FilteredContext(10, 20, 30);
rasDiagnosticsLocalData data{reinterpret_cast<char*>(1), 2, 3, 4};

Expand All @@ -363,7 +323,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectFiltersCommsAndBuildsAlignedRecords
first.comm->nRanks = 0x55667788;
second.comm->rank = 0x22334455;
second.comm->nRanks = 0x66778899;
InstallComms({first.comm.get(), nullptr, invalid.comm.get(), mismatch.comm.get(), second.comm.get(), nullptr});
InstallNcclComms({first.comm.get(), nullptr, invalid.comm.get(), mismatch.comm.get(), second.comm.get(), nullptr});
const rasDiagnosticsContext ctx = FilteredContext(10, 20, 30);
rasDiagnosticsLocalData data{};

Expand Down Expand Up @@ -407,7 +367,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectFiltersCommsAndBuildsAlignedRecords

TEST_F(RasDiagnosticsCommonMicrotest, CollectPropagatesSnapshotAllocationFailure) {
OwnedComm owned(1, 2, 3, 0);
InstallComms({owned.comm.get()});
InstallNcclComms({owned.comm.get()});
const rasDiagnosticsContext ctx = UnfilteredContext();
rasDiagnosticsLocalData data{};
g_failAllocationCall = 1;
Expand All @@ -421,7 +381,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectPropagatesSnapshotAllocationFailure

TEST_F(RasDiagnosticsCommonMicrotest, CollectPropagatesRecordAllocationFailure) {
OwnedComm owned(1, 2, 3, 0);
InstallComms({owned.comm.get()});
InstallNcclComms({owned.comm.get()});
const rasDiagnosticsContext ctx = UnfilteredContext();
rasDiagnosticsLocalData data{};
g_failAllocationCall = 2;
Expand All @@ -435,7 +395,7 @@ TEST_F(RasDiagnosticsCommonMicrotest, CollectPropagatesRecordAllocationFailure)

TEST_F(RasDiagnosticsCommonMicrotest, CollectPropagatesFillFailureWithoutPublishingPartialData) {
OwnedComm owned(1, 2, 3, 0);
InstallComms({owned.comm.get()});
InstallNcclComms({owned.comm.get()});
const rasDiagnosticsContext ctx = UnfilteredContext();
rasDiagnosticsLocalData data{};
g_failFillCall = 1;
Expand Down
Loading
Loading