RDMA and CUDA#
This guide describes the current source. See Build for provider dependencies, Architecture for the code layout and Qualification for executed hardware profiles.
Set RdmaConfig.disable_gpu_direct_rdma and log_slice_affinity in the
immutable typed or JSON configuration. JSON switches require booleans.
Disabling GPU Direct while explicitly requiring RdmaCudaMemory::Direct
returns a configuration error. Affinity diagnostics log each selected NIC pair
and slice length through the log facade at debug level.
Build and select a path#
The default crate supports CPU/TCP without a native SDK. The independent rdma
and cuda features enable runtime-loaded libibverbs and CUDA Driver API bindings.
RDMA install resolves libibverbs.so.1 once and retains it for the process lifetime;
a CPU-only engine in that build does not load it. A missing library or required
export returns Unavailable naming libibverbs.so.1. Runtime RDMA still needs
the host provider and devices. All engine, protocol,
progress and resource owners are Rust. A small C shim only queries port rates
from the installed libibverbs headers.
A request names Transport::Tcp, Transport::Rdma or Transport::Nvlink, or
uses Transport::Unspec for transport selection. Each
peer’s memory policy then picks the backing:
Memory and policy |
Payload path |
|---|---|
Host, |
Application range ↔ RC RDMA ↔ peer registered range |
Host, |
Application bytes ↔ private registered host staging ↔ RDMA |
NVIDIA device, |
Device ↔ owned host staging ↔ RDMA |
NVIDIA device, |
Device MR ↔ RC RDMA ↔ peer registered range |
NVIDIA device over TCP |
Device ↔ owned host staging ↔ socket |
Host over TCP |
Application bytes ↔ owned host buffers ↔ socket |
A failed direct MR registration returns an error; it never switches to staging. Same-host GPU sharing uses the CUDA IPC transport. TCP and staged RDMA share the CUDA staging pool, and remote staging adds Pin/Delegate/Unpin over the same control channel.
GPU Direct registration routes#
With cuda_memory: "direct", set rdma.gpu_direct_registration to auto
(default), dmabuf or peermem. The typed field takes
GpuDirectRegistration::{Auto, Dmabuf, Peermem}. auto prefers dma-buf when
CUDA advertises DMA_BUF_SUPPORTED and both export and verbs symbols exist;
otherwise it uses peermem only when a supported kernel module is loaded. An explicit
route with missing prerequisites returns Unavailable. The default CUDA memory
policy remains staged; a failed explicit Direct registration does not change
that policy or retry a device pointer as host memory. An attempted export or
native registration failure is returned without trying another route.
Peermem detection follows NCCL’s module probe:
any one of /sys/kernel/mm/memory_peers/nv_mem/version,
/sys/kernel/mm/memory_peers/nv_mem_nc/version or
/sys/module/nvidia_peermem/version must exist. This covers MLNX_OFED’s
nv_peer_mem and NVIDIA’s nvidia_peermem; a module directory alone is not
evidence of support.
cuMemGetHandleForAddressRange exports the containing allocation. Its base and
export size are host-page aligned; a legacy allocation’s length rounds up to a
host page. VMM uses the owned, fully mapped extent and keeps its existing
gpuDirectRDMACapable flag. The MR retains the exact requested length, with
offset = address - allocation_base and iova = address. This meets the
CUDA export contract
and the verbs offset/IOVA rule.
It follows NCCL’s host-page registration geometry,
while keeping OrbiTE’s exact-range grant authority.
Export flags are zero on every driver and platform, preserving CUDA’s default
mapping. NCCL’s mapping flag
forces PCIe only for its ncclTopoGdrModePci mode:
topology selection
requires a local GPU→CPU PATH_C2C and a GPU→NIC distance other than PATH_P2C.
x86 PCIe and Grace CPU-attached NIC paths use zero. OrbiTE lacks this C2C/PCI
classification, so forced PCIe mapping is not implemented. Grace mapping
and route verification remain an installed-gate limit; an export error fails
closed rather than trying peermem.
The optional ibv_reg_dmabuf_mr symbol is resolved from loaded libibverbs;
older rdma-core needs no new link-time symbol. Linux
takes its own dma-buf reference
for the MR. OrbiTE conservatively retains the fd too, under the existing bounded
MR/grant budgets, until deregistration succeeds. It then closes the fd exactly
once. A failed deregistration keeps the MR, fd, allocation, context and charges;
a failed fd close is sticky and retains the remaining owners. Neither destroy
is retried. Shared MRs, private grant PDs and opt-in GPU-source reuse all use
this same registration boundary.
Debug diagnostics report registration ID, length and route, without keys or
addresses. RdmaStats::{dmabuf_registrations, peermem_registrations} counts
successful native registrations. RemoteRegion::gpu_direct_routes() reports
routes observed for that published region through Describe; it is evidence,
not a release receipt or proof that a future route is usable. The optional
metrics exporter exposes both counters. Both installed route gates
remain Implemented; installed profile unqualified.
Device, port, GID and memory topology discovery#
Set EngineConfig.rdma to an RdmaConfig:
Configuration |
Behavior |
|---|---|
|
Discover permitted devices, ports and valid GIDs |
|
Strictly pin that device, port 1 and GID index 0 |
|
|
|
Filter devices before opening; a nonempty whitelist wins |
|
Restrict dispatch to stable NIC IDs (up to 64) |
|
CPU NUMA hint overriding the owner’s location |
|
Reject known cross-NUMA NICs (default |
|
Inline TENT matrix JSON; wins over |
|
TENT matrix or native |
|
TENT |
Discovery prefers active routes and keeps usable paused ports. Each context owns its device, protection domain (PD), key history and address; a failed slot never renumbers other NIC IDs. Explicit pins never fall back to another device, port or GID.
With cuda as well, discovery lists CUDA-visible devices without creating
contexts. Each cuda:N entry carries its PCI bus ID, optional NUMA node and
three GPU-to-RNIC tiers: nearest known PCI bridge, other same-NUMA NICs, then the
rest. Missing sysfs distance never counts as nearest. No CUDA driver leaves the
host inventory usable.
DeviceSelector compiles location priorities once. Each request supplies its
owner’s location (cpu:N, cuda:N or unknown *). Selection intersects the
mask, healthy contexts, compatible peer paths and strict-NUMA eligibility, then
picks the lowest estimated service time. A matching NIC name or address family
does not prove physical reachability, and strict locality cannot be overridden
by a mask. See qualification.
Bandwidth-aware RDMA selection#
Smart selection, following TENT’s DeviceSelector, is on by default. For each
eligible NIC:
score = (charged_inflight_bytes + slice_bytes) / selection_bandwidth_bytes_per_second
* numa_tier_weight
A single slice takes the lowest score, with rotating ties. A multi-slice parent
follows TENT’s selectMultiPath: each NIC gets floor(weight / total_weight * slice_count) slices with weight = 1 / (score + 1e-12), and leftovers go to the
best NIC. Tier weights default to [1, 5, 10]. Every 100th multi-slice parent
probes eligible NICs round-robin; probes never reach failed rails. Set
scheduling.smart_selection = false for plain round-robin within the first tier.
All of a parent’s byte charges commit under one selector lock and move to each
grant’s DeviceLease; abandonment returns only unconsumed charges. Eligibility
rules stay hard limits: weights can favor an idle lower-ranked NIC but never
admit an excluded one.
Port rates come from ibv_query_port at initialization and on each monitor
scan, preferring the extended speed encoding (including XDR) and the optional
ibv_query_port_speed LAG rate. A failed query keeps the last effective value.
Unknown or out-of-range rates use default_bandwidth_gbps (400). Topology
attributes rdma.link_speed_mbps and rdma.effective_speed_mbps expose the
result. See qualification.
Each NIC keeps two independent estimates, both clamped to 0.1–10× the seed:
Selection EWMA: successful payload bytes over the time from first post to last payload CQE (old-value weight 0.01).
Transmit bandwidth: completion bytes over time with posted payload debt outstanding (old-value weight 0.9, 10 ms updates, 50 ms window).
Failed or uncertain posts invalidate their samples; possible native debt stays
charged until completion or drain. TransferEngine::nic_load_stats() returns
these estimates (bytes/s), link rates, charged, posted and completed bytes for
initialized NICs without probing devices. They are local requester observations.
Selection adds no driver query or heap allocation per slice. See
bandwidth and
multipath evidence.
Completion and transport retry#
RDMA reports a replayable READ failure only after all affected QPs and WRs drain and their loans are released. A CQE error followed by failed retirement retains the backing and admission. A partially posted WRITE keeps its unknown effect even after local QP retirement. A transport switch consumes the same physical owner’s retry budget. See transport attempts.
Request priority and bandwidth arbitration#
request.with_priority(...) selects High (the default, as in TENT), Medium
or Low. TCP and NVLink accept every priority without scheduling by it. On
RDMA, the effective priority follows ordinary grants, multirail slices and
remote staging CROSS work into the worker queues.
Each RDMA worker collects ready payloads from all its contexts and posts High, then Medium, then Low, FIFO within a tier. Requests within a batch keep their order, posted DMA is never preempted, and completion, receive, MW and cleanup work bypass the priority queue. A full QP does not block another ready QP.
With RdmaConfig::deadline_bw_arbitration = true (default false), candidates
sharing a NIC and priority are ordered by
(posted_bytes + unposted_grant_bytes) / transmit_bandwidth / remaining_deadline.
As in TENT, the first 64 picks are greedy and the tail uses a fixed backlog with
FIFO ties; unknown bandwidth keeps FIFO. This orders work; it is not a rate cap
or a deadline guarantee. OrbiTE groups by local NIC across peers because its
workers share one CQ budget, whereas TENT also groups by remote segment. Sorting
buffers are reused, with no per-dispatch allocation. See
qualification.
Admission uses the same DeadlineMlu formula before posting. It counts
dispatched, unfinished RDMA payload bytes and uses the sum of current transmit
estimates for healthy NICs allowed by the physical owner’s mask. An effective
link rate supplies the initial estimate. Measured transmit bandwidth can
subsequently replace it. If an eligible NIC has no rate or transmit sample,
or recovery owns the route snapshot, admission leaves the owner eligible.
The RDMA worker continues to arbitrate its own posted backlog. A pre-dispatch
drop creates no grant, connection or WR. See
queue configuration.
Custom topology and priority matrices#
priority_matrix accepts TENT’s classic matrix. Each row is
[preferred NIC names, available NIC names] (rank 0, rank 1):
let rdma = {
let mut config = orbite::transport::rdma::RdmaConfig::default();
config.priority_matrix = Some(
r#"{
"cpu:0": [["mlx5_0"], ["mlx5_1"]],
"cuda:0": [["mlx5_1"], ["mlx5_0"]]
}"#
.into(),
);
config
};
Names must exist in the discovered inventory after filters; cuda:N uses the
CUDA-visible ordinal. Only listed NICs are eligible for a row, an empty rank
disables a location, and masks or strict NUMA can only narrow the candidates. An
explicit * row covers unlisted locations; otherwise the classic matrix
synthesizes * from all named NICs.
custom_json_path also accepts TENT’s native format:
{
"nics": [{"name": "mlx5_1", "type": 0}, {"name": "mlx5_0", "type": 0}],
"mems": [{"name": "cuda:0", "type": 1,
"device_list": {"rank0": [0], "rank1": [1], "rank2": []}}]
}
Indices refer to the file’s nics order. Memory type, when given, must match
discovery (0 host, 1 CUDA, 4 for *). JSON never replaces discovered
PCI/NUMA metadata or capabilities. Both inputs are limited to 1 MiB, and invalid
explicit configuration fails initialization (TENT warns and falls back to
autodiscovery). Ranks are frozen after initialization. See
qualification.
Eager publication and cross-rail slice completion#
With host Direct policy, registration prepares MRs on every usable context,
including paused ports, before the range becomes visible: one local-only MR, or
for remote access a local-only MR plus a separate LOCAL_WRITE|MW_BIND parent.
Neither parent key is exported. Visibility commits only after every MR is ready;
failure retires the new copies, and failed rollback retains its MR, backing and
budget. Final unregister hides the range while admitted work keeps its MR borrow
through drain. Each grant locks its exact range, so disjoint ranges proceed and
conflicting CPU or DMA access is rejected across aliases.
Slicing follows TENT: a 64 KiB base block, at most 64 slices for host-local READ and 32 for WRITE or device-local READ, adaptive blocks for large requests and a short tail folded into its predecessor. Each slice selects its own rail. The source reserves the whole parent’s grant and receive capacity before publishing authority, and the parent succeeds only after every slice and its source receipt complete. Earlier slices may already have changed bytes, so a multirail WRITE is not atomic.
Connections, grants and completion#
Peers share a fixed-width reusable QP array per local context, remote
process/PD/NIC, role and capability; width is the smaller num_lanes. Each cold
builder reserves a slot before creating a QP, and waiters never hold store locks
across I/O. Concurrent grants share published QPs. If either peer stages, or the
source uses direct GPU memory, connections are per grant. Every RDMA phase binds
process and connection incarnations, the exact request and a grant generation.
For host or staged source memory, the source binds a one-use Type-2B memory
window to the exact range and direction and posts a completion receive before
publishing the grant; Type-2B support is required. The PD keeps a bounded key
history so an old key cannot regain authority, parking exhausted MW indices up
to max_window_key_indices.
The requester posts READ/WRITE work and sends a fenced SEND_WITH_INV after
all payload WRs complete. signal_interval defaults to 1. A larger interval
signals every Nth WR and the actual post tail, capped at half the SQ depth
(minimum one). Each possible flush still needs one CQ slot per outstanding WR.
RdmaStats::payload_send_cqes counts physical payload receipts, while
completed_reads and completed_writes count successful WRs. The source accepts completion only when its
RQ receives the exact 141-byte binding and invalidated key, then cleans the MW
and grant, sends ACK and publishes READY. RdmaQp tracks exact outstanding WR
identities, so old or duplicate completions cannot repay current debt. Only an
explicit pre-authority cache miss permits a reconnect; ambiguous writes are never
replayed. This protocol is OrbiTE’s own, not TENT wire compatibility.
Workers, CQ lanes and budgets#
One transport-level Workers pool serves all contexts. num_lanes sets the data
worker count and SQ/RQ CQ pairs per context; a reused QP stays on its lane.
Workers bind to allowed NUMA CPUs. RdmaProgressMode::Polling is the default;
EventDriven arms both CQs and wakes on native events. One Monitor consumes
device and CQ events and scans contexts each second.
Budget |
Default |
Charged resource |
|---|---|---|
|
64 |
Physical QPs, including retained connections |
|
64 |
Whole-parent grant reservations |
|
16 |
Unresolved send WRs per QP |
|
1024 |
Each CQ’s capacity |
|
64 MiB each |
Active direct loans and private staging |
|
64 / 64 MiB |
Full-view MR copies across contexts |
|
65,536 |
MW key history per PD |
Creating, closing and retained resources stay charged. These limits govern admission; they are not throughput or fairness guarantees.
The HCA supplies initiator and responder READ limits for each new RC QP.
max_rd_atomic and max_dest_rd_atomic default to 16. A requested zero
stays zero. A positive request uses at most the reported limit, or one when
the HCA reports zero. Data grants authorize only the requested READ/WRITE
direction. last_connected_qp records the local HCA limits, atomic support
and applied depths after both QP transitions succeed.
For wildcard memory the requester prefers the configured healthy direct peer even if the peer lists it after another NIC. Slice planning refreshes local paths after reading the peer’s table and preserves the request’s device mask.
Failure, drain and resource ownership#
A failed post releases only a proven unaccepted suffix. An unknown bad_wr,
invalid CQE, timeout or CQ error keeps the work charged and fences admission;
healthy siblings still finish their own cleanup. Parent cleanup has one
30-second destruction window, separate from the data deadline, and a timeout
proves nothing. Failed native teardown keeps its resources, backing and charges
and is never retried by another close or Drop. A fully drained progress owner can
be replaced while keeping the context’s PD and MRs.
TransferEngine::close(timeout) fences admission, waits for accepted work,
retires connections and MRs, drains CQs and workers, then destroys PDs and
contexts; a later close can keep waiting. close_source and close_requester
are directional and do not tear down PDs or contexts. Error::Unquiesced means
physical completion or destruction is unproven, so batch capacity, registrations
and DMA loans stay retained. See native cleanup and
async batches.
Rail health#
The requester tracks errors per peer epoch and local/remote NIC pair. The
registered memory’s path table, static rail_topology (all and direct),
the policy device mask and current health determine which NIC can carry a
slice. An empty allowed set fails before payload. Port generations from the
existing monitor fence old observations. QP retirement and grant drain still
belong to the original CQ lane.
Defaults are three failures inside ten seconds, a 30-second cooldown and a
one-second exploratory probe interval. Set RdmaConfig::rail_error_threshold,
rail_error_window, rail_cooldown and rail_probe_interval to change them.
Zero probe interval allows an immediate trial. Looking up a mask never arms a
probe. Only one admitted trial can run on a paused pair. Early failure leaves
the cooldown intact. Expiry admits one half-open trial. A failed half-open
trial doubles the cooldown up to five minutes. Only a current trial’s positive
receipt reopens the pair. Cancelling an unposted trial returns its slot.
RdmaStats exposes errors, open pairs, probes and recoveries. NIC load stats
include each local NIC’s rail errors, probes and open-pair count. A process
retains at most max_rail_pairs pair identities. An exhausted table preserves
active and unproven owners and returns ResourceExhausted. See the
installed verification queue.
Periodic monitor recovery#
The monitor queries selected routes even without traffic. A changed or inactive
address fences the route; old builders, QPs and CQs must drain before a validated
route is published again. Recovery keeps the PD, key history and MRs.
monitor_ticks, route_queries, route_recoveries and progress_recoveries
count scans, queries, resumed routes and reclaimed lanes. Fatal device events and
retained cleanup failures stay fenced.
Initially paused contexts and GID reselection#
An initially inactive port with valid GID metadata keeps a paused context and
PD. Host Direct registration still prepares its MRs, while path selection
rejects that route and healthy rails stay eligible. On recovery, automatic GID
configuration (gid_index: None) reranks valid GIDs on the same device and port;
Some(index) stays pinned. This follows TENT’s RdmaContext::enable and
refreshAddress; hotplug and cross-rail retry are out of scope.
The focused native gate runs on one authorized mlx5 RoCE RNIC with real
PD/MR/QP/CQ operations and an overridden port state; it changes no physical port
and is not a two-host or physical-loss test. Choose an ORBITE_INITIAL_GID
different from the automatically preferred index:
ORBITE_RDMA_LOCK=/path/to/authorized-rnic.lock \
ORBITE_SHARED_DEVICE=mlx5_0 ORBITE_SHARED_PORT=1 ORBITE_INITIAL_GID=0 \
CC=gcc CXX=g++ cargo +stable test --locked --features rdma \
transport::rdma::context::tests::native_context_recovery::native_context_recovery_preserves_pd_mrs_and_strict_gid_selection \
-- --exact --ignored --nocapture --test-threads=1 \
> /path/to/external-artifacts/context-recovery.log 2>&1
See qualification.
NVIDIA memory ownership#
CudaMemory::allocate owns device memory and its primary-context reference.
Unsafe CudaMemory::from_raw_parts adopts an existing allocation after
validating type, context, ordinal and extent; host and managed pointers are
rejected, and aliases must share one Arc<CudaMemory>. CUDA batch registration
uses the same transactional rules as host registration.
cuda::allocate_pinned_host returns owned pinned HostMemory. The feature
loads Driver API symbols from libcuda.so.1 at runtime and needs no nvcc,
libcudart or other CUDA libraries to build.
Callers exclude conflicting external access before handoff. GPU producers either
finish their work first or use the readiness handoff below. Failed copy
synchronization, context restore or unpin keeps the owner, visible through
cuda::retained_resources.
Producer-stream readiness#
Unsafe CudaMemory::record_producer_stream(stream, owner) captures work already
queued on the producer’s stream. Call it on the producer thread before handing
the allocation to a transfer. owner must keep the stream and all producer
inputs alive until completion, without an ownership cycle. The stream must
belong to the allocation’s native context; default-stream handles resolve on the
calling thread, and graph-capturing streams are rejected.
cuda/readiness.rs::CallerSyncEvent belongs to the allocation and is shared by
its aliases. The first local copy, staging loan, GPUDirect DMA loan or IPC access
waits for the recorded generation once, with cuEventSynchronize. That wait
blocks the CPU and cannot be interrupted by a request deadline; it is not a
device-side stream dependency. An unproven record, wait or context restore fences
the allocation and keeps its producer inputs. TENT’s CallerSyncEventPool /
syncWithCallerStream waits on cudaStreamPerThread; OrbiTE accepts the
caller’s explicit stream and carries the generation across threads and
transports. Consumers start after the engine’s completion receipt; consumer
stream chaining is not exposed. See
qualification.
Pinned host producer handoff#
With cuda, unsafe HostMemory::record_producer_stream(stream, owner) accepts
memory from cuda::allocate_pinned_host under the same contract; ordinary or
external host memory is rejected. The stream must belong to the allocation’s own
context. CPU read/write, staging, host RDMA and local pinned IPC access consume
readiness before touching payload, and final cuMemFreeHost or
cuMemHostUnregister waits for any pending generation. Internal staging buffers
without a producer create no event. See
qualification.
Copy completion and reuse#
Each CudaMemory lazily owns one nonblocking stream and completion event.
Staging copies are asynchronous, record the event after the copy and wait for it
before exposing host bytes or releasing staging. If submission or recording
fails, OrbiTE drains that stream; if drain is unproven, it returns Unquiesced
and keeps the allocation, staging and handles. Destroying an event or stream is
cleanup, not completion evidence.
Run the byte/guard gate only on allocated, locked GPUs:
cargo build --locked --release --features cuda --example cuda_copy
# Ordinals refer to the selected process-visible devices.
target/release/examples/cuda_copy 0 1
It checks three unaligned sizes with eight concurrent callers for four rounds each. It covers local staging only.
GPUDirect RDMA ownership#
Build with --features rdma,cuda, set MLX5_SCATTER_TO_CQE=0 before the process
starts, and select RdmaConfig.cuda_memory = RdmaCudaMemory::Direct.
Initialization rejects any other setting, because mlx5 scatter-to-CQE can copy
small READ results with the CPU, which is incompatible with device pointers
(provider guidance). There is no
automatic fallback to staging.
Before registration, OrbiTE sets CU_POINTER_ATTRIBUTE_SYNC_MEMOPS and checks
GPUDirect write-ordering support. LocalBufferManager publishes one local-only
GPU MR per context under the owning CUDA context, and requester slices borrow
these persistent MRs. Allocation-wide DmaLease ranges reject overlapping copies
or grants and never expose GPU pointers as CPU slices.
A GPU source can reuse a connected QP and its local-only GPU MR when every
advertised rail can create a hardware-revocable key over that MR.
Set RdmaConfig::gpu_source_reuse to true to opt in. The default uses the
private grant path qualified on H20. Changing that default requires the
installed reuse gate to pass. On mlx5, OrbiTE probes the
actual parent using DevX, indirect KLM translation and named key indices. Each
grant binds a fresh key to its QPN, exact byte range and READ or WRITE right.
The source verifies the device’s key context and KLM before publishing it.
Firmware that requires the wildcard QPN fails the registration probe. A full key
is never reused on that PD. Key history shares the bounded window-key budget.
The source releases the range after the matching receive-with-invalidate CQE, bound DONE, key destruction and CUDA write visibility. Uncertain revocation, destruction or visibility retains the owners and admission. With reuse disabled or an unsupported parent/provider, a grant has a private PD, exact MR and QP. Its ordinary fenced SEND is followed by QP/MR/PD teardown before ACK. Both paths avoid binding ordinary memory windows to GPU MRs. Provider memory window limits, mlx5 programming reference.
The hardware_peer gate accepts ORBITE_RDMA_CUDA_MEMORY=direct and optional
ORBITE_RDMA_SLICE_BYTES (1, 17, 32 or 64). Its passive observer records MR
flags, QP PDs and payload WR addresses, which gdr.py checks against the
declared allocations and full buffers:
CC=gcc CXX=g++ python3 tests/hardware/build_installed.py --consumer gpu \
--toolchain stable --output /external/installed-gpu-stable
python3 -B tests/hardware/run.py --profile /external/gpu-profile.json \
--binary /external/installed-gpu-stable/installed/bin/orbite-hardware-peer \
--installation /external/installed-gpu-stable/manifest.json \
--gdr-observer /external/installed-gpu-stable/liborbite_cq_fault.so \
--output /external/runs/gpu-direct-001
Use --toolchain 1.85.0 and a new output directory for the MSRV build. Each case
declares cuda_memory: "direct", transport: "rdma" and cpu/cuda:0 memory
kinds with at least one GPU. Small cases use payload: 4096; the two-rail
folded-tail case adds rails, both NIC locks, payload: 8388768,
rdma_block_size: 1048576 and expected_grants: 12. Run both directions and
both rdma_events values, and keep failed runs. Set gpu_source_reuse: true
on GPU-source cases to require key and QP reuse. The verifier matches each
payload rkey to its source receive CQE on the parent PD and checks both peers’
full buffers and guards. See the verification queue.
Run a native profile#
Collect fresh host, provider and device inventory first, then pass an external
JSON profile to tests/hardware/run.py:
python3 tests/hardware/run.py --profile /external/profile.json \
--binary /external/build/release/examples/hardware_peer \
--output /external/runs/run-001
A profile contains ssh_config, rnic, gid_index, nodes and cases. Each
node has an SSH alias, exact hostname and boot_id, a bind address and its
nic_lock; GPU cases add gpu_lock and gpu_uuid, and optional env freezes
library paths. Each case sets source, requester, source_kind,
requester_kind (cpu, pinned:0 or cuda:0), transport, payload and
seed, with optional host_memory overrides. Multirail and installed-host modes
need two physical rails and a nic_locks map; see the
installed host recipe and
native cleanup recipe.
The controller checks host identity and binary hashes, deploys to a unique
/var/tmp/orbite-runs directory and records provider mappings. Supervisors hold
resource locks until waitpid confirms their children exited; a missed reap or
missing receipt fails cleanup. The runner never changes NIC configuration. Keep
runs and failed controls outside the checkout.
Reference#
The frozen TENT reference is e1ea5d31bb5e46ff2df9f90e7c2fbaf52fcdee8c; see the
owner mapping. Exact-range MW
authorization, source receipts and failed-cleanup retention are OrbiTE
contracts. The
pre-cleanup guide
keeps the earlier stage-specific recipes and evidence locations.