Skip to content

gpu: tier selection — one setting, three values, and off means off - #90

Merged
dpsoft merged 1 commit into
mainfrom
feat/tier-selection
Aug 26, 2026
Merged

gpu: tier selection — one setting, three values, and off means off#90
dpsoft merged 1 commit into
mainfrom
feat/tier-selection

Conversation

@dpsoft

@dpsoft dpsoft commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Task 11 of the GPU PC sampling plan.

PERFAGENT_GPU_PC_SAMPLING = off (default) | continuous | serialized, with 0/1/2 still accepted on both ends since container specs already carry them. One parser per side — gpu/tier.go and shim/core/pctier.h — with a test that reads the header from Go and pins that both accept the same six spellings. Surfaced as --gpu-pc-sampling on cmd/gpu-cuda-profile.

One field, not two

TimelineConfig.SerializedSampling bool was replaced by TimelineConfig.PCSampling PCSamplingTier, not joined by it: two fields that can disagree about which tier is running is how a profile discloses one thing and does another.

The tier rides out on Snapshot.PCSampling, because "Tier A asked for, no window arrived" and "Tier A never asked for" are different facts — and inferring them from SamplingWindowsReceived == 0 gets them backwards.

Why exclusivity is process-wide, not just forbidden

COLLECTION_MODE is a single per-CUcontext CUPTI attribute. A process could set different modes on different contexts — but which context a kernel lands on is the application's choice, not the profiler's, so a "both" mode would produce a profile whose attribution quality varied along an axis the operator can neither see nor control. Worse than either tier alone.

Refused in three shapes — one value naming both tiers, flag and environment naming different tiers, unknown value — each a startup error that falls closed to off and carries the COLLECTION_MODE reason rather than the rule alone. "Both" is deliberately parseable so the refusal is reachable, and both orderings are tested, since a first-token parser answers differently for each.

The acknowledgement gate and the standing warning

serialized requires --gpu-pc-sampling-acknowledge-perturbation, and the refusal text itself names all three perturbations. joinhealth then prints a four-line standing warning on every render — including on a Tier A snapshot where nothing went wrong, which is exactly the profile whose reader has no other way to learn the tier was on. Counted separately from anomalies, with len(lines)-1 == warnings + anomalies asserted.

All three perturbations, because an operator told only the first will misread the other two: GPU kernel durations inside a burst are inflated and marked gpu_serialized="true"; CPU and off-CPU samples during a burst are distorted and carry no marking at all — serialization inflates precisely the sync wait off-CPU profiling exists to measure; and Tier A is unavailable where CUDA graphs are in use.

Off means off, asserted at the probe site

shim/stub/pc_tier_test.cc patches the four PC-sampling probe nops with int3 — what a uprobe does — and counts traps. Tier off ⇒ zero fires with the stub's PC knobs turned up, while launch/exec probes must still fire so an inert producer cannot pass. The continuous and serialized passes fire those same sites, so the negatives are not vacuous. Mutation-checked: removing the stub's tier gate makes it fail and name the probe.

This required the stub to honour the tier at all — it is now the outer gate over its own knobs, which no existing test sets.

Two defects found in already-merged code

The "unknown" anomaly was guarded by SamplingWindowsReceived > 0, which suppressed it in the worst case: Tier A selected, not one window received, every execution unknown. unknown is unreachable in the other tiers, so the guard only hid the case that needed it most. It now names which cause applies rather than disappearing.

anomalousSnapshot() carried the three serialization counters with PCSampling off — a combination that cannot occur — and would have quietly stopped exercising the warning a real Tier A run carries.

Verification

make -C shim / test (incl. new pctier_test and pc_tier_test 8/8) / check-fpless / check-cubin-defer / nvidia; go build/vet; the four-package test list; -race -count=4; golangci-lint 0 issues. No ABI, BPF or .o churn; Tier A/B behaviour, cubin capture, the CubinView guard and the MODULE_UNLOAD_STARTING drain untouched.

Cannot verify (CapEff: 0, no GPU — no CUpti_ path here has run): the adapter's tier parse ordering relative to CONTEXT_CREATED, g_pc_tier_refused on hardware, and that an explicit =off wins in a real injected process's environ.

PERFAGENT_GPU_PC_SAMPLING becomes off | continuous | serialized (0 | 1 | 2
still accepted, since container specs already carry them), read by both
producers through one parser in shim/core/pctier.h and by the agent through
gpu/tier.go, and surfaced on cmd/gpu-cuda-profile as --gpu-pc-sampling.
TimelineConfig.SerializedSampling is replaced by TimelineConfig.PCSampling
rather than joined by it: two fields that can disagree about which tier is
running is how a profile ends up disclosing one thing and doing another.

The tiers cannot run together, and the reason is what is written down.
COLLECTION_MODE is a single per-CUcontext CUPTI attribute, so a process could
set different modes on different contexts - but which context a kernel lands
on is the application's choice, not the profiler's, so "both" would produce
one profile whose attribution quality varied along an axis the operator can
neither see nor control. Naming two tiers is therefore a startup error in all
three shapes it can take: one value naming both, the flag and the environment
naming different ones, and an unknown value in either. Every refusal falls
CLOSED to off and explains itself; none of them picks a tier. The value is
parsed rather than rejected as syntax on purpose - "both" has to be
expressible for the refusal to be reachable, and a parser that took the first
token of "continuous,serialized" would be the silent pick the rule forbids.

Because Tier A perturbs the workload it measures, "serialized" is refused
unless --gpu-pc-sampling-acknowledge-perturbation says so, and joinhealth
carries a standing warning for the whole run rather than once at startup - it
stands even on a Tier A snapshot in which nothing went wrong, which is exactly
the profile whose reader has no other way to learn the tier was on. The
warning names all three perturbations, because an operator told only about the
first will misread the other two: they will see gpu_serialized="true" on the
GPU samples, conclude the marked ones are the perturbed ones, and then trust
an off-CPU profile whose synchronization waits are inflated by this very
mechanism and carry no marking at all. The third is that Tier A is unavailable
where CUDA graphs are in use. A limitation an operator is told about is a
limitation; one they discover from a misleading profile is a defect.

Off means off, and it is asserted where a uprobe would assert it.
shim/stub/pc_tier_test.cc reads its own .note.stapsdt, patches the four
PC-sampling probe nops with int3 and counts the traps: with the tier off, and
with the stub's own PC knobs turned up, not one of them may fire, while the
launch and exec probes must - so an inert producer cannot make it pass. The
continuous and serialized passes fire those same sites, so the negative passes
are not vacuous. This needed the stub to honour the tier at all; it is now the
outer gate over the stub's own knobs, which no existing test sets.

One anomaly strengthened while in there: the "unknown" clause was guarded by
SamplingWindowsReceived > 0, which suppressed it in the worst available case -
Tier A selected, not one window received, every execution unknown. Unknown is
unreachable in the other two tiers, so the guard hid the case that most needed
raising; it now names which cause applies instead of disappearing.

Verified: make -C shim {,test,check-fpless,check-cubin-defer,nvidia}; go build
./... && go vet ./...; go test ./gpu/ ./gpuprobe/ ./internal/... -count=1; go
test ./gpu/ ./gpuprobe/ -race -count=4; golangci-lint 0 issues. The off-means-
off test was mutation-checked: removing the stub's tier gate makes it fail and
name the probe that fired. No CUpti_ path here has run - CapEff: 0, no GPU.
@dpsoft
dpsoft merged commit 6f7cdf0 into main Aug 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant