Skip to content

sanitizers: a ConSan finding names neither its kernel nor its offset, and its code is the raw numeric kind, so a race cannot be attributed #451

Description

@vivekkhandelwal1

Summary

A ConSan Finding in a sanitizer_report.json cannot say which kernel it is about, cannot say where in that kernel, and its code is a raw numeric enum rather than a name. Across a run of every daily-* recipe producing 160 findings:

field populated
code_object 160/160
severity 160/160
code 160/160, but every ConSan value is the literal "1"
kernel_name 0/160
entry_offset 0/160

So every ConSan finding cites as consan:1, and none of them names a kernel or an offset. Waitcheck, in the same reports and through the same model, emits the symbolic wait_hazard — so the shape being asked for here already exists in the sibling tool.

Three related observations follow. They are filed together because they share one complaint: the information needed to attribute a finding exists in the run and does not reach the finding.

1. code is ConSan's numeric kind, and the symbolic fallback is unreachable

consan.py:178:

code=fields.get("kind", "record_replay_conflict"),

ConSan's auto-replay diagnostic always carries kind=1, so code is always "1" and the symbolic record_replay_conflict default never applies. The discriminating information is elsewhere on the same record: severity (race) and the instruction pair in the metadata. Compare consan.py:135, where the Waitcheck path defaults to wait_hazard and produces a name.

A consumer grouping findings by code therefore gets one bucket for all ConSan races, and a numeric one whose meaning is not discoverable from the report.

2. kernel_name is unpopulated, though the name is in the same log stream

consan.py:180 does try:

kernel_name=fields.get("kernel"),

but the ConSan MOI auto replay diagnostic record carries no kernel= field. It carries first_owner=0 second_owner=1 — owner indices, not names:

[rocjitsu-dbi-hooks] ConSan MOI auto replay diagnostic reader=14024592 index=0 kind=1
code_object=fnv1a64:244edc5b44244e79 report_generation=2 generation=2 epoch=0
first_owner=0 second_owner=1 first_inst=0x8 second_inst=0x28 first_lds_known=true
first_lds=[0,4) second_lds=[0,4) first_kind=2 second_kind=1
first_lane_mask=0x1 second_lane_mask=0x1

The name is nonetheless present in the very same log, on a record sharing the reader join key:

[rocjitsu-dbi-hooks] ConSan kernel reader=14024592 name=_Z14lds_race_2wavePj

reader=14024592 on both. So this is resolvable in the parser by joining on reader, without any upstream change. It is also recoverable one level up in the report already — kernel_results[].identity.name and backend.selected_kernel do give real names (consan_lds_race_2wave, gemm_f32_ss, gemm_NT_M256_N4096_K1024) — but that attribution is per scenario, not per finding, so it cannot distinguish two kernels in one object.

entry_offset is not populated at all: the constructor never sets it, even though the record carries first_inst=0x8 and second_inst=0x28, which are the offsets of the two conflicting accesses. An access pair arguably wants two offsets rather than the model's single entry_offset, which may be why it was left alone; if so, that is worth deciding explicitly rather than leaving the field empty.

One part of this may be better fixed upstream. Having ConSan put the kernel name (or resolved owner_names) directly on the diagnostic record would be cleaner than a join, and ConSan already emits owner_names= on its sync-event records, so the plumbing exists there. Either side fixes it; the join is available here today.

3. The same findings are written to two places, so a naive count doubles

consan.py:689-704 assigns the identical consan.findings tuple to both the nested KernelCheckResult.findings (line 693) and the enclosing CheckResult.findings (line 703). In the serialized report the same 64 finding objects therefore appear twice, and a consumer that walks both — the natural thing to do, since Waitcheck does put per-kernel findings only on the kernel results — reads 64 findings as 128.

The parser itself already dedupes on dedupe_key at consan.py:188, so the duplication is introduced by report assembly rather than by parsing. Whether this is intentional denormalisation for convenience or an oversight is a maintainer's call, but the report does not say which, so every consumer has to guess.

Why it matters

A finding that cannot name its kernel or its offset supports a verdict and nothing more. "There is a race in this code object" is not something a human or a tool can act on; "there is a race between the accesses at 0x8 and 0x28 in lds_race_2wave" is. The operator-facing half of this system is exactly the second thing, so attribution granularity is the property that decides whether a ConSan result can become a root cause or only a red light.

The volume makes this concrete rather than theoretical. The two-wave LDS race reproducer produces 64 findings, and they are one race: identical kind, identical first_inst/second_inst pair, differing only in lane mask and LDS byte range, one record per lane. Deduplicating on the tuple that identifies a race site collapses all 64 to 1, and across the whole nine-recipe run 160 findings collapse to 4 sites. Per-finding granularity is currently reporting volume, not information — and because the one field that could group them (code) is constant, the report gives a consumer no way to discover that the 64 are one thing.

Environment

Slurm node cv350-rck-g03-c17-08, partition meta64, gfx950, ROCm 7.0.2.2. RocJITsu prebuilt bundle f92da4cb3c5b3612db3752a36f4f3d0d3e9ff768 (rocm-systems Actions run 33641388794). aorta at ba73dab. Nine daily-* recipes run as .github/workflows/sanitizers-nightly.yml runs them; counts above are over all findings from that run.

Suggested remedies

Not prescribing one, and the three parts are independent.

  1. code: prefer a symbolic name and keep the numeric kind in metadata, e.g. map known kind values to names and fall back to record_replay_conflict_kind_<n> for unknown ones. Keeps the citation vocabulary readable and stays honest about kinds the parser has not seen.
  2. kernel_name / entry_offset: resolve the name by joining the diagnostic's reader to the ConSan kernel reader=... name=... record, and decide explicitly what entry_offset means for a two-access conflict — either populate it from first_inst or record both offsets in a shape built for a pair.
  3. Duplication: pick one home for findings and document it, or leave both and say in the schema that the nested list is a view of the enclosing one so consumers stop double-counting.

If only one is done, 2 is the one that changes what the report can be used for; 1 and 3 are cheap and mostly protect consumers from mistakes.

Possible relation to rocm-systems#10966 — offered as an observation

ROCm/rocm-systems#10966 is open and its recent history covers barrier sites being discovered but not patched under the entry-point allowlist. Coarse attribution here and unpatched barrier sites there both live near ConSan's lowering and record path, so they may share a cause. I have not established that and am not claiming it — the evidence in this issue is entirely about fields that are absent from a record whose siblings carry them, which is consistent with a reporting gap alone. Noting it only so whoever picks this up can check the two together rather than discovering the overlap later.

Discovery context

Found while building a labelled corpus of real sanitizer runs for RL post-training. The corpus cites findings by a namespaced code, in the manner of a detector ID, which is what made a vocabulary of one visible; and it needed per-finding kernel attribution to stratify examples, which is what made the empty fields matter rather than merely be noticeable.

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

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions