Skip to content

telemetry/doca: share one timestamp per export batch (NIX-1625)#1952

Open
e-eygin wants to merge 4 commits into
ai-dynamo:mainfrom
e-eygin:eeygin/nix-1625-doca-batch-timestamp
Open

telemetry/doca: share one timestamp per export batch (NIX-1625)#1952
e-eygin wants to merge 4 commits into
ai-dynamo:mainfrom
e-eygin:eeygin/nix-1625-doca-batch-timestamp

Conversation

@e-eygin

@e-eygin e-eygin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What?

Make the DOCA telemetry exporter stamp every sample produced in a single
drain/export cycle with one shared timestamp, instead of reading the DOCA clock
2–3 times per event.

  • Adds a scoped withBatch(body) callback to nixlTelemetryExporter
    (src/api/cpp/telemetry/telemetry_exporter.h), backed by a private RAII guard
    that drives two no-op private virtual hooks, onBatchBegin() / onBatchEnd()
    (NVI idiom). Default no-ops leave the Prometheus / BUFFER / NOP exporters
    unchanged.
  • nixlTelemetry::flushPendingEvents() runs the whole drain — the queued events
    plus the synthetic AGENT_TELEMETRY_EVENTS_DROPPED event — inside one
    withBatch.
  • The DOCA exporter overrides the hooks to read the DOCA clock once per batch
    (a lazy currentTimestamp() backed by inBatch_ + std::optional<uint64_t> batchTimestamp_) and passes that timestamp explicitly to the counter / error
    counter / gauge append helpers.
  • Adds gtests: one asserting all non-histogram samples in a batch share a single
    timestamp (even when events are spaced past DOCA's millisecond resolution),
    and one asserting a standalone exportEvent (no batch) still stamps each event
    freshly.

No metric names, labels, event types, buffer format, or TELEMETRY_VERSION
change; the Prometheus scrape output (values, series, labels) is unchanged.

Why?

Every append helper (appendCounterSample, appendErrorCounterSample,
appendGaugeSample) previously called docaTimestamp() independently, so a
single event emitting a gauge + counter read the clock twice and a full drain of
N events triggered ~2N–3N reads — each a slightly different value for samples
that logically belong to the same flush instant. All events in one drain
represent the same snapshot moment, so they should share one timestamp: it is
cheaper (one clock read per batch) and semantically consistent.

Tracking: NIX-1625. This is PR 1 of 2; the companion NIX-1626 (constant
agent_name label) rebases onto this.

How?

The batch boundary is an RAII-backed callback rather than manually-paired
begin/end calls: withBatch(body) opens a private BatchScope guard, runs
body, and the guard's destructor closes the batch — so the boundary is always
balanced and there is no naked scope for callers to mismanage. The begin/end
hooks are private virtuals (NVI), and BatchScope is a private implementation
detail, so withBatch is the only entry point.

The DOCA exporter fills batchTimestamp_ lazily on the first sample of a batch
(zero cost for an empty batch) and returns it for every subsequent sample; a
standalone exportEvent outside any batch still reads a fresh timestamp per
event. Histograms are unaffected — they are timestamped at histogram_flush via
HISTOGRAM_TIMESTAMP_UPDATE, not at observe time. Thread-safety is unchanged:
each nixlTelemetry drains on its own single-thread asio pool, so inBatch_ /
batchTimestamp_ are only ever touched by one thread.

Testing: DOCA suite (doca_test, doca_nixl_test, histogram_parity_test)
3/3 pass; telemetry gtests 60/60 pass; clang-format-19 clean; commit is
DCO-signed.

Summary by CodeRabbit

  • New Features
    • Added batch-scoped telemetry exporting with automatic begin/end lifecycle handling, including correct behavior for nested batch scopes.
  • Bug Fixes
    • Ensured metrics written during the same batch export use a single consistent timestamp.
    • Kept separate, fresh timestamps for events exported outside a batch.
    • Exported both queued and dropped-event metrics within the same batch context.
  • Tests
    • Added end-to-end tests validating shared batch timestamps, per-event timestamps when unbatched, and nested batch timestamp semantics.

Add a scoped withBatch(body) callback to nixlTelemetryExporter, backed by
a private RAII guard driving no-op private virtual onBatchBegin/onBatchEnd
hooks (NVI). flushPendingEvents() runs the whole drain (queued events plus
the synthetic AGENT_TELEMETRY_EVENTS_DROPPED event) inside one withBatch.

The DOCA exporter overrides the hooks to read the DOCA clock once per batch
(lazy currentTimestamp with inBatch_ + optional batchTimestamp_) instead of
2-3 reads per event, so every sample in a drain shares one timestamp. The
append helpers take an explicit timestamp; histograms are unaffected (they
are timestamped at histogram_flush). Standalone exportEvent (no batch) still
gets a fresh timestamp per event.

No metric names, labels, event types, buffer format, or TELEMETRY_VERSION
change; scrape output is unchanged. Adds gtests covering the shared-batch
timestamp and the standalone path.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
@e-eygin
e-eygin requested a review from a team as a code owner July 16, 2026 06:09
@copy-pr-bot

copy-pr-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

Copy link
Copy Markdown

👋 Hi e-eygin! Thank you for contributing to ai-dynamo/nixl.

Your PR reviewers will review your contribution then trigger the CI to test your changes.

🚀

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Telemetry exports now support RAII-managed batch scopes. Pending events and dropped-event metrics share a batch, while the DOCA exporter uses one timestamp per batch and fresh timestamps for standalone exports. Tests cover batched, standalone, and nested scopes.

Changes

Telemetry batching and timestamp consistency

Layer / File(s) Summary
Batch execution contract
src/api/cpp/telemetry/telemetry_exporter.h
Adds withBatch, RAII lifecycle handling, nested-scope tracking, and overridable batch hooks.
Flush batch integration
src/core/telemetry/telemetry.cpp
Wraps queued event exports and dropped-event emission in one batch scope.
DOCA timestamp propagation and validation
src/plugins/telemetry/doca/..., test/doca-telemetry/telemetry_doca_nixl_test.cpp
Caches batch timestamps, passes explicit timestamps to metric appenders, and tests batched, standalone, and nested exports.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Telemetry
  participant Exporter
  participant DOCA
  participant Metrics
  Telemetry->>Exporter: withBatch(flush exports)
  Exporter->>DOCA: onBatchBegin()
  Telemetry->>DOCA: exportEvent(event)
  DOCA->>Metrics: write samples with cached timestamp
  Telemetry->>DOCA: exportDroppedEvents()
  Exporter->>DOCA: onBatchEnd()
Loading

Possibly related PRs

  • ai-dynamo/nixl#1919: Changes the dropped-event export path that intersects with the new batching integration.

Suggested reviewers: colinnv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: sharing one timestamp per DOCA export batch.
Description check ✅ Passed The description follows the template with clear What, Why, and How sections and covers the key implementation details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/cpp/telemetry/telemetry_exporter.h`:
- Line 87: Rename the nested type BatchScope to batchScope and update all
references to this type throughout the surrounding telemetry exporter code,
preserving its existing behavior and API usage.

In `@src/plugins/telemetry/doca/doca_exporter.cpp`:
- Around line 371-379: Update nixlTelemetryDocaExporter::onBatchBegin and
onBatchEnd to track batch nesting depth, initializing inBatch_ and
batchTimestamp_ only on the 0→1 transition and clearing them only on the 1→0
transition; preserve the outer batch timestamp while nested withBatch scopes are
active. Add a regression test covering nested batch scopes and verifying all
outer-scope events share the original timestamp.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: d04a9654-32ac-476d-887a-3074dab53610

📥 Commits

Reviewing files that changed from the base of the PR and between b392820 and 8302691.

📒 Files selected for processing (5)
  • src/api/cpp/telemetry/telemetry_exporter.h
  • src/core/telemetry/telemetry.cpp
  • src/plugins/telemetry/doca/doca_exporter.cpp
  • src/plugins/telemetry/doca/doca_exporter.h
  • test/doca-telemetry/telemetry_doca_nixl_test.cpp

Comment thread src/api/cpp/telemetry/telemetry_exporter.h Outdated
Comment thread src/plugins/telemetry/doca/doca_exporter.cpp
@e-eygin e-eygin self-assigned this Jul 16, 2026
…afe batches

Rename the nested guard BatchScope -> batchScope to follow the repo
lowerCamelCase type-naming rule (docs/CodeStyle.md).

Make withBatch nesting-safe in the base: track a batch depth and fire
onBatchBegin/onBatchEnd only on the outermost 0->1 and 1->0 transitions, so
an inner scope's exit no longer resets a still-active outer batch's shared
timestamp. Add a DOCA regression test asserting events before, inside, and
after a nested scope all carry the one outer-batch timestamp.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/cpp/telemetry/telemetry_exporter.h (1)

87-118: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Coordinate a telemetry ABI bump. Adding onBatchBegin()/onBatchEnd() and batchDepth_ changes nixlTelemetryExporter’s vtable and object layout, so prebuilt or out-of-tree exporters can break when loaded. Keep the ABI stable, or gate this behind a major-version/rebuild plan.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/cpp/telemetry/telemetry_exporter.h` around lines 87 - 118, Update
nixlTelemetryExporter and batchScope so the batching support does not change the
existing exporter ABI or object layout. Remove or isolate the virtual
onBatchBegin()/onBatchEnd() additions and batchDepth_ storage using an
explicitly coordinated major-version/rebuild mechanism, preserving compatibility
for prebuilt and out-of-tree exporters.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/cpp/telemetry/telemetry_exporter.h`:
- Around line 79-84: Add a Doxygen-style block comment immediately before the
public withBatch template describing that it executes the supplied callback,
supports nested batching scopes, and guarantees the associated lifecycle hooks
are invoked appropriately. Keep the implementation unchanged.

In `@test/doca-telemetry/telemetry_doca_nixl_test.cpp`:
- Around line 645-666: Update the metric validation in the test around the
series iteration and counted assertion to require exactly the three expected
non-histogram series from before, inside, and after nesting. Identify each
expected series by name, verify it is present and has a timestamp, and assert
all three timestamps equal the shared outer batch timestamp instead of relying
on EXPECT_GE(counted, 2u).

---

Outside diff comments:
In `@src/api/cpp/telemetry/telemetry_exporter.h`:
- Around line 87-118: Update nixlTelemetryExporter and batchScope so the
batching support does not change the existing exporter ABI or object layout.
Remove or isolate the virtual onBatchBegin()/onBatchEnd() additions and
batchDepth_ storage using an explicitly coordinated major-version/rebuild
mechanism, preserving compatibility for prebuilt and out-of-tree exporters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9f7d4619-13a2-41f4-9dc1-737af810968b

📥 Commits

Reviewing files that changed from the base of the PR and between 8302691 and b18b7ae.

📒 Files selected for processing (2)
  • src/api/cpp/telemetry/telemetry_exporter.h
  • test/doca-telemetry/telemetry_doca_nixl_test.cpp

Comment thread src/api/cpp/telemetry/telemetry_exporter.h
Comment thread test/doca-telemetry/telemetry_doca_nixl_test.cpp Outdated
Add a Doxygen block to the public withBatch() describing callback execution,
exception-safe batch lifecycle, and outermost-only nested-scope hook semantics.

Tighten NestedBatchScopesShareOuterTimestamp: assert the three series produced
before, inside, and after the nested scope (agent_tx_bytes / agent_rx_bytes /
agent_memory_registered_last_bytes) are each present and all share the outer
batch timestamp, instead of the loose EXPECT_GE(counted, 2) check.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/cpp/telemetry/telemetry_exporter.h (1)

121-127: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve the exporter ABI. These additions change the vtable and base-class layout, so out-of-tree telemetry exporters built against the old header will break. Move the batch state/hooks out of the exported base class, or gate this behind an explicit ABI-major bump and rebuild plan.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/cpp/telemetry/telemetry_exporter.h` around lines 121 - 127, Preserve
the ABI of the telemetry exporter base class by removing onBatchBegin(),
onBatchEnd(), and batchDepth_ from the exported class layout, then implement
batch tracking through a non-exported mechanism or existing extension point. Do
not alter the virtual table or base-class size unless this change is explicitly
accompanied by an ABI-major bump and rebuild plan.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/cpp/telemetry/telemetry_exporter.h`:
- Around line 79-87: Add parameter-level Doxygen documentation to the withBatch
comment: include `@tparam` Body for the template parameter and `@param` body for the
callback argument. Keep the existing `@brief` and batch-lifecycle description
unchanged, and do not add an `@return` tag because withBatch returns void.

---

Outside diff comments:
In `@src/api/cpp/telemetry/telemetry_exporter.h`:
- Around line 121-127: Preserve the ABI of the telemetry exporter base class by
removing onBatchBegin(), onBatchEnd(), and batchDepth_ from the exported class
layout, then implement batch tracking through a non-exported mechanism or
existing extension point. Do not alter the virtual table or base-class size
unless this change is explicitly accompanied by an ABI-major bump and rebuild
plan.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 0f123ef8-ff36-4dd9-889b-3ce36602f71d

📥 Commits

Reviewing files that changed from the base of the PR and between b18b7ae and b555158.

📒 Files selected for processing (2)
  • src/api/cpp/telemetry/telemetry_exporter.h
  • test/doca-telemetry/telemetry_doca_nixl_test.cpp

Comment thread src/api/cpp/telemetry/telemetry_exporter.h
Complete the public withBatch() documentation with parameter-level Doxygen
tags per the repo public-API convention; no @return since it returns void.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/cpp/telemetry/telemetry_exporter.h (1)

124-130: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Coordinate the telemetry exporter ABI change. nixlTelemetryExporter is a plugin-facing base class, so adding onBatchBegin()/onBatchEnd() changes its vtable and batchDepth_ changes its object layout. That breaks binary compatibility for out-of-tree exporters built against the previous header; add a version bump/rebuild plan or avoid new virtual slots.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/cpp/telemetry/telemetry_exporter.h` around lines 124 - 130, The
plugin-facing nixlTelemetryExporter ABI is broken by the new virtual
onBatchBegin/onBatchEnd slots and batchDepth_ member. Avoid adding virtual
methods or instance data to the existing base class, or coordinate an explicit
ABI version bump and rebuild plan for all out-of-tree exporters, preserving
compatibility with exporters built against the previous header.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/api/cpp/telemetry/telemetry_exporter.h`:
- Around line 124-130: The plugin-facing nixlTelemetryExporter ABI is broken by
the new virtual onBatchBegin/onBatchEnd slots and batchDepth_ member. Avoid
adding virtual methods or instance data to the existing base class, or
coordinate an explicit ABI version bump and rebuild plan for all out-of-tree
exporters, preserving compatibility with exporters built against the previous
header.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 373cb95c-b22b-4892-a13f-37fe000b53da

📥 Commits

Reviewing files that changed from the base of the PR and between b555158 and 2cca182.

📒 Files selected for processing (1)
  • src/api/cpp/telemetry/telemetry_exporter.h

@e-eygin

e-eygin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@ColinNV @ovidiusm please review

@e-eygin

e-eygin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

/build

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu · commit 0ef06af5

TL;DR: Both parallel Docker image builds failed at apt-get install gdb gdb-multiarch because ports.ubuntu.com was unreachable ("Network is unreachable" / "connection timed out"). This is a transient network/infra failure, not a code bug — retry the build.

Full analysis

Summary: The "Compiling NIXL Docker Image for DL" stages (node 159 = ucx-master, node 162 = ucx-v1.22.x) both failed at Dockerfile STEP 15/17 during apt-get install.

Root cause: apt could not download gdb, gdb-multiarch, and their dependencies from the public Ubuntu ARM mirror ports.ubuntu.com. Every mirror address failed — IPv6 (2620:2d:4002:1::10a/b/c, "Network is unreachable") and IPv4 (91.189.91.102/103/104, "connection timed out") — causing apt-get to exit 100 and the podman build RUN to fail. Notably the internal Artifactory base-image pull and packages.microsoft.com both succeeded, so this is an environmental network problem reaching the external Ubuntu mirror, not a defect in commit 0ef06af5 or the Dockerfile. Both parallel builds failing with the identical symptom at the same time confirms an infra outage rather than a PR-specific issue.

Implicated commit: none — infrastructure/network failure, not a code change (the failing apt-get install gdb gdb-multiarch line is pre-existing in .ci/dockerfiles/Dockerfile.gpu-test).

File: .ci/dockerfiles/Dockerfile.gpu-test:34-38 (the RUN apt-get update && apt-get install ... gdb gdb-multiarch step)

Suggested fix: Re-run the build; the failure should clear once mirror connectivity is restored. To harden against recurrence: (1) point apt at a reliable internal/proxied Ubuntu mirror instead of the public ports.ubuntu.com, and/or (2) pre-install gdb/gdb-multiarch in the base image (nixl-ci-dl-gpu-base-...) so the per-build apt fetch isn't on the critical path, and/or (3) add apt retry options (Acquire::Retries) to tolerate transient network blips. No source-code fix is warranted.

Related: none

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 0ef06af5

TL;DR: The expansion_fault_contraction elastic EP test failed because when rank 2 was intentionally killed mid dispatch/combine, buffer teardown hit a CUDA "illegal memory access," so the surviving/killed workers exited with code 1 instead of the expected clean -SIGTERM; fix the fault-path buffer teardown in the SIGTERM handler so a killed rank tears down GPU/UCX resources without an illegal access. (A second Docker-build stage failed separately on a transient ports.ubuntu.com network outage — infra flake, unrelated to the code.)

Full analysis

Summary: Stage "Run DL EP elastic tests" (#340, node 223) failed on plan expansion_fault_contraction.json: RuntimeError: Worker processes failed: worker 1 (exit code 1), worker 2 (exit code 1), worker 3 (exit code 1).

Root cause: During the fault-injection phase, rank 2 was intentionally killed ("[rank 2] Killing rank during dispatch/combine" → "SIGTERM (15) received … releasing rank and exiting"). Teardown then hit CUDA warning: an illegal memory access was encountered (function destroyEvent) on multiple ranks, followed by UCX cuMemFree_v2/cuStreamDestroy_v2 failures and "WARNING: destroy() was not called before NIXL_EP buffer destruction." Because the buffer destruction in the SIGTERM handler faulted, the processes exited with code 1 instead of terminating via the default SIGTERM handler (-signal.SIGTERM), which main() treats as an unexpected failure (elastic.py:50 only tolerates exit codes 0 and -SIGTERM). This is a real teardown/fault-tolerance bug, not a timeout — the log shows continuous activity from 07:21:57 to 07:22:09 with no multi-minute gaps.

Implicated commit: unknown for the exact regression, but the fault path is handled by handle_sigterm (buffer.destroy()) introduced/last touched around commit 2bdcdb5 (Itay Alroy, "nixl_ep: Handle planned SIGTERM in elastic test", #1500); the buffer teardown itself is in examples/device/ep/nixl_ep/buffer.py, last major refactor a394341 (Itay Alroy, #1693). Confirm against PR #1952's own diff.

File: examples/device/ep/tests/elastic/elastic.py:78-80 (SIGTERM handler calling buffer.destroy() on a killed rank) and the underlying EP buffer teardown in examples/device/ep/nixl_ep/buffer.py; failure surfaced at elastic.py:50 / elastic.py:692.

Suggested fix: Make the killed-rank teardown crash-safe: in handle_sigterm, guard buffer.destroy() so an in-flight dispatch/combine kernel doesn't cause an illegal memory access (e.g. synchronize/cancel outstanding GPU work before freeing device buffers and UCX endpoints, or catch and swallow teardown errors and then exit via the default SIGTERM path). Ensure the killed rank actually exits via signal.raise_signal(SIGTERM) so its exit code is -SIGTERM (tolerated at elastic.py:50) rather than 1. Separately, retry/mirror the apt step in stage 168 (or use a network-independent base image) to eliminate the transient ports.ubuntu.com unreachable failure.

Related: #1500 (planned SIGTERM handling), #1693 (rank/expert refactor), #1584 (preserve CUDA graphs across scale up/down); PR under test #1952. No existing issue found for this illegal-memory-access-on-teardown signature.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-gpu · commit 0ef06af5

TL;DR: The "Run CPP tests" stage failed with exit code 42 because CUDA failed to initialize on the test node mizu03 (cuDeviceGetCount() failed: initialization error), which made UCX report "GPU memory is not supported" — a warning the gtest harness treats as a failure. Fix the node's CUDA/driver environment; this is not a code regression in PR #1952.

Full analysis

Summary: 69/154 CPP gtests failed on node mizu03; 68 are exit-code-42 "unexpected NIXL warning" failures and 1 (HardwareWarningTest.NoWarningWhenIbAndCudaSupported) is a real exit-code-1 failure — all stemming from a broken CUDA context on the GPU test host.

Root cause: Every test process logs cuda_ctx.c:52 UCX ERROR cuDeviceGetCount(&num_devices) failed: initialization error, and then ucx_utils.cpp:639 emits 8 NVIDIA GPU(s) were detected, but UCX CUDA support was not found! GPU memory is not supported. The gtest wrapper flags any unexpected warning and returns exit code 42. CUDA driver initialization is failing on mizu03 (GPUs are visible via NVML but the CUDA driver API can't init — a classic driver/runtime mismatch, MIG/cgroup, or transient driver state issue), so UCX builds with no cuda/ib transports. HardwareWarningTest.NoWarningWhenIbAndCudaSupported then hard-fails: it expects CUDA+IB support but UCX throws Failed to create UCX context: No such device (no usable transports/devices asked ib,cuda). This is environmental — the telemetry/tracing commits in recent history are unrelated to CUDA init.

Implicated commit: none — not a code regression from PR #1952 (commit 0ef06af); this is a test-node CUDA environment failure on mizu03.

File: src/.../ucx_utils.cpp:639 (warning source) and the failing assert in HardwareWarningTest.NoWarningWhenIbAndCudaSupported — but neither is the root cause; the fault is the CUDA driver state on mizu03.

Suggested fix: Repair/reset the CUDA driver environment on test node mizu03 (verify nvidia-smi, driver↔CUDA-runtime version match, container GPU/--gpus and NVIDIA_VISIBLE_DEVICES passthrough, and that no stale MIG/cgroup config blocks cuDeviceGetCount), then re-run the pipeline. Once CUDA initializes, UCX will pick up the cuda/ib transports and both the exit-42 warnings and the NoWarningWhenIbAndCudaSupported failure will clear. If mizu03 is persistently bad, drain it from the CI pool. No code change to PR #1952 is warranted.

Related: none (no matching issue for the cuDeviceGetCount ... initialization error signature).

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-build-wheel · commit 0ef06af5

TL;DR: The wheel built fine, but the "Build sanity image" stage failed because apt install git inside contrib/Dockerfile.vllm couldn't reach ports.ubuntu.com (network unreachable) — an infra/network issue, so git had "no installation candidate" and the docker build exited 100.

Full analysis

Summary: Stage 470 "Build sanity image" (aarch64 vLLM) failed during docker build -f contrib/Dockerfile.vllm; the RUN apt update && apt install -y curl git step exited with code 100.

Root cause: During the image build, apt update could not reach the Ubuntu ports mirror: repeated Cannot initiate the connection to ports.ubuntu.com:80 ... (101: Network is unreachable) and connection timed out for both IPv6 and IPv4. Because the package index for jammy/jammy-updates/etc. failed to download, apt fell back to a stale/empty index and apt install -y git failed with E: Package 'git' has no installation candidateexit status 100. This is a transient network/DNS/egress problem on the aarch64 build node (note the NVIDIA CUDA and Launchpad mirrors resolved fine, but ports.ubuntu.com — including its IPv6 addresses — was unreachable), not a source-code bug. (The x86_64 sanity image, stage 477, built successfully.) The earlier "FAILURE" on stage 332 is just the pipeline's normal parallel-stop bookkeeping ("pipline stop on build_helper_vllm"), not the real failure.

Implicated commit: unknown — not a code regression. The Dockerfile step originates from b594eb0b00a0 (NirWolfer, "ci: add vLLM/SGLang NIXL disaggregation sanity testing", #1777), but the failure is environmental.

File: contrib/Dockerfile.vllm:22-25 (the apt update && apt install -y curl git layer)

Suggested fix: Re-run the build first — this is almost certainly a transient egress outage to ports.ubuntu.com on the aarch64 node. If it recurs, harden the step so CI doesn't depend on live Ubuntu mirrors: (a) point the aarch64 builders' apt config at a reachable internal/proxy mirror for ports.ubuntu.com, and/or force IPv4 (-o Acquire::ForceIPv4=true) since the IPv6 routes are unreachable; or (b) bake curl/git into the vllm-nixl-base image so the sanity image build doesn't run apt install at all. Do not treat this as a code change to the PR.

Related: PR #1777 (added this sanity-image build); PR under test #1952. No matching issue found for the ports.ubuntu.com network failure.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 0ef06af5

TL;DR: The "Run DL EP elastic tests" stage failed because the surviving ranks in the expansion_fault_contraction plan crashed with a CUDA "illegal memory access" when peer rank 2 was deliberately killed mid dispatch/combine — a real fault-tolerance bug in the low-latency NVLink EP kernel, not a timeout or the build_helper hiccup.

Full analysis

Summary: Stage 203 (Run DL EP elastic tests) failed: expansion_fault_contraction.json phase 2 kills rank 2, and the surviving worker ranks (1, 2, 3) exited with code 1 after CUDA warning: an illegal memory access was encountered, causing RuntimeError: Worker processes failed.

Root cause: In the fault-injection phase (plan [0, 1, -2, 3]), rank 2 receives the planned SIGTERM correctly, but the low-latency NVLink dispatch/combine path on the surviving ranks continues to access the departed peer's CUDA IPC buffer/endpoint. This produces an illegal GPU memory access (destroyEvent, then UCX cuMemFree_v2/cuStreamDestroy_v2 on device_ep all fail), so workers crash with exit code 1 instead of tolerating the peer loss. The harness (elastic.py lines 49–55) only excuses exit codes 0 and -SIGTERM, so a code-1 crash is a genuine failure. Timestamps are continuous (11:29:12→11:29:15) — this is a crash, not a hang/timeout. The build_helper failure in stage 125 (ucx-v1.22.x) was retried successfully in stage 149 and is not the blocking failure.

Implicated commit: Most likely [REDACTED:Hex High Entropy String] "nixl_ep: Optimize low-latency NVLink path (#1751)" (Itay Alroy) — the most recent change to the LL NVLink dispatch/combine kernel exercised by this UCX-CUDA-IPC path. (Not definitively proven from logs alone; needs confirmation against the PR #1952 diff.)

File: examples/device/ep/csrc/kernels/nixl_ep_ll.cu (LL NVLink dispatch/combine peer-buffer access); crash surfaces via examples/device/ep/tests/elastic/elastic.py:692 with plan examples/device/ep/tests/elastic/expansion_fault_contraction.json:4 ([0, 1, -2, 3]).

Suggested fix: In the LL NVLink dispatch/combine kernel, guard peer-buffer/IPC-endpoint accesses so a rank marked failed/departed (the -2 entry) is skipped rather than dereferenced, and ensure the GPU-timeout/abort path releases the peer's IPC mapping before it is freed. Concretely: validate that each target peer is still active before issuing NVLink loads/stores in the combine step, and confirm PR #1751's peer-connection teardown doesn't leave a dangling device_ep handle after a mid-transfer contraction. As a triage step, re-run expansion_fault_contraction.json against the commit just before #1751 to confirm the regression window.

Related: PR #1751 (LL NVLink path), PR #1500 (planned-SIGTERM handling in elastic test), PR #1846 (forcibly close UCP endpoints — possibly relevant to endpoint teardown on fault).

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 0b4be7b7-1df4-49df-90b3-cf93355949b7 in the triage console for the audit trail.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-gpu · commit 0ef06af5

TL;DR: The Jenkins build compiled NIXL and pushed the test image successfully; both failing stages died from cluster infrastructure faults — a compute-node failure on mizu02 killed the running srun job, and the build-helper agent lost its remoting channel so the SLURM re-allocation timed out. This is not a code defect in PR #1952; the fix is to retry the build on a healthy node.

Full analysis

Summary: "Run CPP tests" and "Allocate Environment" stages failed due to SLURM/Jenkins-agent infrastructure faults, not test or build errors.

Root cause: In "Run CPP tests" (node 220), the docker image import via pyxis was interrupted by srun: error: Node failure on mizu02; the SLURM job 65404 was then reported Invalid job id specified / Expired or invalid job 65404. In "Allocate Environment" (node 200), the Jenkins build-helper pod dropped its channel (hudson.remoting.ChannelClosedException) and the retried salloc -p mizu failed with salloc: error: Unable to allocate resources: Connection timed out. The NIXL compile, unit-test link, image build and push all completed successfully beforehand — the failure is entirely in the compute-node/scheduler layer.

Implicated commit: none — not code-related (commit [REDACTED:Hex High Entropy String] is not implicated)

File: none (infrastructure: SLURM node mizu02, mizu partition, Jenkins build-helper agent nixl-ci-gpu-buildhelper-2792-8287d-691w1)

Suggested fix: Retry the build; it should pass on a healthy node. Have infra investigate the health of node mizu02 (drain it if the node failure recurs) and the mizu-partition controller connectivity that produced the salloc "Connection timed out". No source change is needed for this PR. Optionally, consider making the pipeline resilient to transient Node failure / agent-channel drops by auto-rescheduling on a different node rather than failing the build.

Related: none

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 15c01638-a68d-48f0-81d8-2c1d8e391f07 in the triage console for the audit trail.

@e-eygin

e-eygin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 2cca182

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-gpu · commit 0ef06af5

TL;DR: The "Run Nixlbench tests" stage was killed at the 15-min mark (exit 143) because the UCCL WRITE DRAM→DRAM --check_consistency benchmark hung indefinitely at startup — a hang in the UCCL backend, not a slow test, so raising the timeout is not the fix.

Full analysis

Summary: Jenkins stage 337 "Run Nixlbench tests" (nixl-ci-gpu #2805, v1.22.x pipeline) was aborted with exit code 143 after a ~15-minute silent hang.

Root cause: A process hang, not a wall-clock shortfall. The last application output is at 16:51:18.476 — "Engine destroyed" / "Error/HUP on connection: 1.1.101.4:37771", ending the UCCL WRITE DRAM→VRAM run. The loop then began the next iteration, run_nixlbench_two_workers_etcd --backend UCCL --op_type WRITE --initiator_seg_type DRAM --target_seg_type DRAM --check_consistency, but not a single line was emitted for it (no "Connecting to ETCD", no "Creating Engine"). The log is then completely silent for ~15 minutes until 17:06:40 "Sending interrupt signal to process" → "script returned exit code 143". Because it's run under parallel --line-buffer --halt now,fail=1, one worker hanging at engine creation/peer rendezvous stalls the whole invocation with no output. The UCCL backend is also the suspect: its throughput across all runs is pathological (~0.03 GB/s, ~450µs latency vs 2–5 GB/s for UCX), indicating UCCL is only marginally functional on this node and prone to stalling during connection setup.

Implicated commit: Not definitively identifiable from logs; UCCL backend behavior last substantially changed in [REDACTED:Hex High Entropy String] "UCCL: Simplify and Optimize for batch transfers (#1271)" (Pravein Govindan Kannan). The two-worker ETCD launch timing was touched in 1cca7d3801c3 (#1560, ovidiusm). The hang itself is in the UCCL engine startup/rendezvous path, not the shell script.

File: .gitlab/test_nixlbench.sh (the run_nixlbench_two_workers_etcd UCCL loop) — hang occurs inside ./bin/nixlbench UCCL engine creation before any output; underlying code is the UCCL backend engine init/connect path.

Suggested fix: Do not raise the stage timeout. Instead: (1) wrap each nixlbench invocation in a per-test timeout (e.g. timeout 120 parallel ...) so a single hung UCCL worker fails fast and surfaces which combination hung instead of silently consuming the whole stage budget; (2) investigate the UCCL backend engine-creation/peer-connect path for a deadlock when the initiator and target are both DRAM (the DRAM→DRAM UCCL WRITE is the exact combination that hung) — likely a missing timeout/retry in the UCCL connection rendezvous, consistent with the ~0.03 GB/s degraded UCCL performance seen throughout. Capture a stack trace (gdb/py-spy) of the hung nixlbench process next time via the crash-export hook added in #1800.

Related: #1271 (UCCL optimize batch transfers), #1560 (sleep to prevent both nixlbench processes becoming clients), #1800 (export container on test failure for crash debugging).

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 5686bde8-66db-4650-8662-560116ff8e50 in the triage console for the audit trail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants