Skip to content

Commit 0887059

Browse files
committed
feat(validators): RoCE NET variant for nccl-all-reduce-bw (AICR_NCCL_FABRIC), default EFA
Adds a ConnectX RoCE path to the nccl-all-reduce-bw-net test, selected by AICR_NCCL_FABRIC=roce (default efa — every existing EFA recipe is unchanged). Fabric-keyed and accelerator-agnostic: the RoCE NET template lives at testdata/roce/{service}/ and is shared across EKS RoCE nodes rather than per-accelerator, so it does not depend on any specific accelerator constant. When fabric=roce, the NET test: - uses NCCL's built-in IB/verbs transport over the ConnectX RoCE devices (NCCL_IB_HCA=rocep, NCCL_NET_PLUGIN=none to bypass the bundled aws-ofi EFA plugin), on a CUDA-13 pytorch image (sm_103-capable; the EFA hpc-cloud image is CUDA 12 + EFA-only); - claims RoCE NICs via a DRA ResourceClaimTemplate (roce.networking.k8s.aws) instead of the vpc.amazonaws.com/efa extended resource; - disables HPC-X UCC/HCOLL and forces the ob1 PML over TCP (their team-create / UCX PML segfault during MPI_Init on this image — MPI is only bootstrap). The transport assertion (verifyTransportFromLogs) is unchanged: it already accepts any non-Socket NET plugin, so 'Using network IB' passes. AICR_NCCL_FABRIC is forwarded to the NET check pod via buildEnv (the test runs in-Job), scoped to nccl-all-reduce-bw-net. Validated end-to-end on a ConnectX-RoCE GB300 cluster: NCCL IB over 8 rocep* RoCE devices (GPUDirect RDMA), ~387 GB/s peak busbw. The env knob is interim; snapshot-based fabric auto-detection is tracked in #1413. Refs #1413, #1410.
1 parent dc23abf commit 0887059

9 files changed

Lines changed: 774 additions & 33 deletions

File tree

docs/user/validation.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,21 @@ ones) that match the target fabric:
4848
| Check | Transport | When it's selected |
4949
|---|---|---|
5050
| `nccl-all-reduce-bw` | Auto-detect (whatever NCCL picks) | H100/H200 on EKS, H100 on GKE, and B200/GB200 on self-managed clusters (`service=any`). Preserves the pre-variant behavior. |
51-
| `nccl-all-reduce-bw-net` | NET (EFA on EKS) | GB200 + EKS. Asserts EFA actually carried traffic — catches silent fallback to Socket when the NVIDIA driver is missing `NVreg_GrdmaPciTopoCheckOverride=1`. |
51+
| `nccl-all-reduce-bw-net` | NET (EFA on EKS by default; ConnectX RoCE via `AICR_NCCL_FABRIC=roce`) | GB200 + EKS. Asserts EFA actually carried traffic — catches silent fallback to Socket when the NVIDIA driver is missing `NVreg_GrdmaPciTopoCheckOverride=1`. |
5252
| `nccl-all-reduce-bw-nvls` | NVLS (MNNVL across an NVL72 IMEX domain) | GB200 + EKS, and GB200 + OKE. Asserts the NVLS communicator actually initialized — catches silent fallback to EFA (EKS) or Socket (OKE) when the IMEX domain is misconfigured. |
5353

54+
The `-net` check defaults to the AWS EFA fabric. On a ConnectX **RoCE** cluster
55+
(e.g. DGXC GB300 `p6e-gb300r`), set `AICR_NCCL_FABRIC=roce` in the `aicr
56+
validate` environment to run the NET test over NCCL's built-in IB/verbs
57+
transport across `roce.networking.k8s.aws` DRA devices instead. The value is
58+
scoped to the `-net` check only; unset (or `efa`) leaves every existing recipe
59+
on the EFA path unchanged, and any other value is rejected. The RoCE runtime
60+
image installs `openssh-server` at startup, so the GPU nodes need apt egress;
61+
on an air-gapped cluster the RoCE NET test cannot bootstrap. This env override is
62+
interim — snapshot-based fabric auto-detection (and removing the runtime
63+
package install once a CUDA-13 image ships sshd) is tracked in
64+
[NVIDIA/aicr#1413](https://github.com/NVIDIA/aicr/issues/1413).
65+
5466
GB200/EKS recipes (both `training` and `inference` intents) enable `-net` and
5567
`-nvls` together rather than the auto-detect variant, because those nodes
5668
expose two inter-node fabrics simultaneously and a single auto-detect test

pkg/validator/v1/job_plan_internal.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ const (
5252

5353
requireScopedInferenceGatewayEnv = "AICR_REQUIRE_SCOPED_INFERENCE_GATEWAY"
5454

55+
// ncclAllReduceBWNetCheckName is the catalog name of the NCCL all-reduce NET
56+
// check. Used to scope AICR_NCCL_FABRIC forwarding to that validator only.
57+
ncclAllReduceBWNetCheckName = "nccl-all-reduce-bw-net"
58+
59+
// ncclFabricEnv selects the NET fabric (efa default | roce). Forwarded to
60+
// the NET check pod so the in-Job validator can observe it. This is the
61+
// orchestrator (forwarding) end; the validator-pod (reading) end defines the
62+
// same literal as ncclFabricEnv in
63+
// validators/performance/nccl_all_reduce_bw_constraint.go — keep the two in
64+
// sync. The split mirrors the other forwarded validator envs (HF_TOKEN,
65+
// AICR_REQUIRE_SCOPED_INFERENCE_GATEWAY, AICR_INFERENCE_PERF_NO_CLEANUP): the
66+
// pod binary is a separate package that does not import this one.
67+
ncclFabricEnv = "AICR_NCCL_FABRIC"
68+
5569
// inferencePerfNoCleanupEnv, when truthy, makes the inference-perf validator
5670
// leave its namespace/DGD/workers/frontend/AIPerf Job in place after the run
5771
// for post-mortem inspection. Forwarded only to the inference-perf pod.
@@ -155,6 +169,14 @@ func buildEnv(
155169
env = append(env, corev1.EnvVar{Name: requireScopedInferenceGatewayEnv, Value: v})
156170
}
157171

172+
// Forward the NCCL fabric selector to the nccl-all-reduce-bw-net check pod.
173+
// The NET test runs inside the Job, so it can't observe the CLI environment
174+
// unless forwarded here. Unset (default) leaves the check on EFA; scoped to
175+
// the NET check so unrelated validator pods don't carry it.
176+
if v, ok := os.LookupEnv(ncclFabricEnv); ok && v != "" && entry.Name == ncclAllReduceBWNetCheckName {
177+
env = append(env, corev1.EnvVar{Name: ncclFabricEnv, Value: v})
178+
}
179+
158180
// Forward the inference-perf no-cleanup debug toggle into that validator pod.
159181
// Cleanup runs inside the Job, so it can't see the CLI process environment
160182
// unless the orchestrator carries the value across. Scoped to the
@@ -175,7 +197,7 @@ func buildEnv(
175197
// forwarded value (k8s takes the last duplicate), breaking that trust
176198
// boundary.
177199
for _, e := range entry.Env {
178-
if e.Name == hfTokenEnvVar || e.Name == requireScopedInferenceGatewayEnv || e.Name == inferencePerfNoCleanupEnv {
200+
if e.Name == hfTokenEnvVar || e.Name == requireScopedInferenceGatewayEnv || e.Name == inferencePerfNoCleanupEnv || e.Name == ncclFabricEnv {
179201
continue
180202
}
181203
env = append(env, corev1.EnvVar{Name: e.Name, Value: e.Value})

pkg/validator/v1/job_plan_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,85 @@ func TestBuildJobPlan_ForwardsInferencePerfNoCleanupEnv(t *testing.T) {
522522
})
523523
}
524524

525+
// TestBuildJobPlan_ForwardsNCCLFabricEnv verifies the NET fabric selector is
526+
// carried from the CLI process into the nccl-all-reduce-bw-net validator Job
527+
// (where ncclFabric() reads it), only that validator, and that a catalog-pinned
528+
// value can never shadow or substitute for the forwarded one. Unlike the
529+
// no-cleanup toggle, the value is forwarded verbatim (the validator validates it).
530+
func TestBuildJobPlan_ForwardsNCCLFabricEnv(t *testing.T) {
531+
build := func(entry ValidatorEntry) map[string]string {
532+
plan, err := BuildJobPlan(entry, "run-1", "ns", "1.0.0", "abc123", "sa", nil, nil, nil, "", "", nil)
533+
if err != nil {
534+
t.Fatalf("BuildJobPlan error: %v", err)
535+
}
536+
m := make(map[string]string)
537+
for _, e := range plan.Env {
538+
m[e.Name] = e.Value
539+
}
540+
return m
541+
}
542+
543+
netEntry := ValidatorEntry{Name: ncclAllReduceBWNetCheckName, Phase: "performance", Image: "img:v1", Timeout: time.Minute}
544+
545+
t.Run("forwarded verbatim to nccl-all-reduce-bw-net", func(t *testing.T) {
546+
t.Setenv(ncclFabricEnv, "roce")
547+
if got := build(netEntry)[ncclFabricEnv]; got != "roce" {
548+
t.Errorf("%s env = %q, want roce", ncclFabricEnv, got)
549+
}
550+
})
551+
t.Run("empty value omitted", func(t *testing.T) {
552+
t.Setenv(ncclFabricEnv, "")
553+
if _, present := build(netEntry)[ncclFabricEnv]; present {
554+
t.Errorf("%s should not be in Job env when empty", ncclFabricEnv)
555+
}
556+
})
557+
t.Run("not forwarded to other validators", func(t *testing.T) {
558+
t.Setenv(ncclFabricEnv, "roce")
559+
other := ValidatorEntry{Name: InferencePerfCheckName, Phase: "performance", Image: "img:v1", Timeout: time.Minute}
560+
if _, present := build(other)[ncclFabricEnv]; present {
561+
t.Errorf("%s must not be forwarded to a non-NET validator", ncclFabricEnv)
562+
}
563+
})
564+
565+
// values collects every occurrence of the env var (not just the last) so we
566+
// can assert the catalog value is dropped, not merely shadowed.
567+
values := func(entry ValidatorEntry) []string {
568+
plan, err := BuildJobPlan(entry, "run-1", "ns", "1.0.0", "abc123", "sa", nil, nil, nil, "", "", nil)
569+
if err != nil {
570+
t.Fatalf("BuildJobPlan error: %v", err)
571+
}
572+
var got []string
573+
for _, e := range plan.Env {
574+
if e.Name == ncclFabricEnv {
575+
got = append(got, e.Value)
576+
}
577+
}
578+
return got
579+
}
580+
581+
t.Run("catalog value cannot override forwarded value", func(t *testing.T) {
582+
t.Setenv(ncclFabricEnv, "roce")
583+
entry := ValidatorEntry{
584+
Name: ncclAllReduceBWNetCheckName, Phase: "performance", Image: "img:v1", Timeout: time.Minute,
585+
Env: []EnvVar{{Name: ncclFabricEnv, Value: "efa"}},
586+
}
587+
if got := values(entry); len(got) != 1 || got[0] != "roce" {
588+
t.Errorf("%s env = %v, want exactly [roce] (catalog value must be dropped)", ncclFabricEnv, got)
589+
}
590+
})
591+
592+
t.Run("catalog value alone cannot select fabric", func(t *testing.T) {
593+
t.Setenv(ncclFabricEnv, "")
594+
entry := ValidatorEntry{
595+
Name: ncclAllReduceBWNetCheckName, Phase: "performance", Image: "img:v1", Timeout: time.Minute,
596+
Env: []EnvVar{{Name: ncclFabricEnv, Value: "roce"}},
597+
}
598+
if got := values(entry); len(got) != 0 {
599+
t.Errorf("%s env = %v, want none (catalog must not select fabric without shell env)", ncclFabricEnv, got)
600+
}
601+
})
602+
}
603+
525604
func TestBuildJobPlanWithDefaults(t *testing.T) {
526605
// Test with minimal entry (no custom resources, no tolerations, no node selector)
527606
entry := ValidatorEntry{

recipes/validators/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Applied by `catalog.Load` (`pkg/validator/catalog/catalog.go`) in order:
4747
| Name | Description | Timeout |
4848
|------|-------------|---------|
4949
| `nccl-all-reduce-bw` | Verify NCCL All Reduce Bus Bandwidth meets threshold | 30m |
50-
| `nccl-all-reduce-bw-net` | Verify NCCL All Reduce Bus Bandwidth on the NET transport (EFA on EKS) | 30m |
50+
| `nccl-all-reduce-bw-net` | Verify NCCL All Reduce Bus Bandwidth on the NET transport (EFA on EKS; ConnectX RoCE via `AICR_NCCL_FABRIC=roce`) | 30m |
5151
| `nccl-all-reduce-bw-nvls` | Verify NCCL All Reduce Bus Bandwidth on the NVLS transport (MNNVL across an NVL72 IMEX domain) | 30m |
5252

5353
### Conformance Phase

0 commit comments

Comments
 (0)