Skip to content

[Bug]: the incident accumulator keeps only the first error code per (watch, GPU), so a second code on the same GPU is silently dropped #1795

Description

@lfriedman-netllama

Prerequisites

  • I searched existing issues
  • I can reproduce this issue

Bug Description

Split out of #1791 at @lalitadithya's request. #1793 fixes the suppression path; this is the underlying data loss that remains once suppression is out of the picture.

When DCGM reports two or more incidents for the same (watch, GPU), only the first error code survives. Later incidents contribute their message but their code is discarded, so the reported code depends on DCGM's iteration order.

health-monitors/gpu-health-monitor/gpu_health_monitor/dcgm_watcher/dcgm.py:643

if accumulator_key not in gpu_failures_accumulator:
    gpu_failures_accumulator[accumulator_key] = {"code": error_code, "messages": []}

gpu_failures_accumulator[accumulator_key]["messages"].append(error_msg)   # :647

Consolidation at :653 then writes a single ErrorDetails, and types.ErrorDetails holds exactly one code plus one message — so the structure cannot represent two codes even if the loop wanted to.

Why it matters

The code is what drives remediation. The messages are merged, so an operator reading the event text may see both problems, but the recommendedAction mapping keys off the single surviving code. A GPU reporting, say, DCGM_FR_FABRIC_PROBE_STATE and DCGM_FR_NVLINK_DOWN under the NVLINK watch is actioned as whichever DCGM happened to return first.

It also makes the reported code non-deterministic across polls if DCGM's ordering is not stable, which would show up as an event flapping between codes on a GPU whose hardware state has not changed.

We have not been bitten by this in production — our GPU 0 case was the suppression interaction, which #1793 fixes. This is the latent half.

Suggested fix

Two options, in increasing order of correctness and cost:

  1. Key the accumulator on (watch, gpu, code). Each distinct code becomes its own incident. Cheap, but entity_failures is dict[gpu_id, ErrorDetails], so it cannot hold two entries for one GPU — this needs the container to change too.
  2. Make entity_failures hold a list per GPU, i.e. dict[int, list[ErrorDetails]]. Correct, and it is the shape the data actually has, but it touches every consumer of HealthDetails.

A cheaper interim that removes the non-determinism without restructuring: pick the surviving code deterministically rather than by arrival — for example the highest-severity code, or the first in a documented precedence order — and say so in a comment. That does not fix the loss, but it makes behaviour predictable and reviewable.

Test worth having

One GPU, one watch, two unsuppressed incidents with different codes, asserted in both arrival orders — the reported code should be the same either way. That test fails today for one of the two orders whichever precedence you pick, which is the point.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions