Severity
Critical scientific correctness — independent trace artifacts on one run can be fused into one timing/correlation region, producing host-to-device matches and gaps that never existed in any trace
Validated against main at 0a60b288342cd96e2608302fa9fd2917e59d7e58.
Audit only; no implementation change is included.
Summary
AcceleratorRecipes resolves a run to all of its trace artifact IDs and queries normalized trace.event rows across the whole scope.
The query does not select or retain artifact_id. Region grouping uses only:
cycle-like artifact role, when present
phase
and ignores every other artifact role.
Within a region, runtime→kernel correlation uses only the stringified correlation_id:
runtime_correlation_ids = {str(value["correlation_id"]) ...}
...
str(kernel["correlation_id"]) in runtime_correlation_ids
Timing summaries likewise combine start_ns/duration_ns from all selected artifacts and treat them as one clock domain.
This is invalid for independent trace files. Correlation IDs are meaningful within a captured activity stream; numeric equality across two independent artifacts does not establish a relationship. Trace timestamps also need one clock/session mapping before subtraction.
A concrete production path creates exactly this state: inference profiling preserves each trace file as a separate import run with role inference_profile, then copies all registrations into one parent profiling run. The accelerator recipe ignores that role, so stage-separated or multiple profiler outputs can merge under the same phase or <unscoped> region.
Direct code evidence
The recipe drops artifact identity at the query boundary
rows = snapshot.execute(
"SELECT name, value_json, context FROM observations WHERE ("
+ where
+ ") AND kind = 'trace.event' ORDER BY observation_id",
parameters,
).fetchall()
|
status="partial", |
|
reason="runtime_or_accelerator_tracks_missing", |
|
) |
|
if not ( |
|
coverage["runtime_launches"] |
|
and coverage["accelerator_kernels"] |
|
and ( |
|
comparison_coverage is None |
|
or ( |
|
comparison_coverage["runtime_launches"] |
|
and comparison_coverage["accelerator_kernels"] |
|
) |
|
) |
|
) |
|
else available_availability() |
|
) |
|
), |
|
) |
|
|
|
def _accelerator_launch_regions( |
|
self, |
|
snapshot: Snapshot, |
|
input_id: str, |
|
*, |
|
phase: str | None, |
|
limit: int, |
resolve_evidence_scope() may select several artifact IDs for a run, but the row consumer cannot tell which artifact produced an event.
Only cycle-prefixed roles survive grouping
artifact_role = str(value.get("artifact_role") or "")
cycle = (
artifact_role
if artifact_role.startswith(("cycle_", "partial_cycle_"))
else None
)
region = f"{cycle}/{phase}" if cycle is not None else phase
Every role such as:
primary
inference_profile
prefill_profile
decode_profile
collapses into the same phase bucket unless the provider separately injects a distinct phase.
Correlation is a bare-ID set membership test
runtime_correlation_ids = {
str(value["correlation_id"])
for value in (*direct, *graph)
if value.get("correlation_id") not in {None, ""}
}
...
correlated_kernel_count=sum(
str(item["correlation_id"]) in runtime_correlation_ids
for _, item in kernels
if item.get("correlation_id") not in {None, ""}
)
https://github.com/morluto/flameox/blob/0a60b288342cd96e2608302fa9fd2917e59d7e58/src/flameox/analysis/recipe_accelerator.py#L380-L460
Artifact, process, context, device, and provider/session identity do not participate.
Timing also crosses the erased boundary
The same merged event list is used for:
region_start_ns
region_end_ns
region_duration_ns
runtime launch gaps
per-stream idle gaps
Events are grouped into runtime tracks and accelerator streams only after artifact/session identity has been lost.
Inference profiling creates multiple independent trace artifacts on one parent run
InferenceProfilingService._preserve() imports every discovered trace candidate separately and assigns all of them:
kind=ArtifactKind.EXECUTION_TRACE
role="inference_profile"
It returns multiple artifact run IDs.
_canonical_registrations() then copies every source registration onto the parent profiling run:
for artifact_run_id in artifact_run_ids:
source = self.runs.read(artifact_run_id)
registrations.extend(
registration.model_copy(
update={"registration_id": new_id(), "run_id": parent_run_id}
)
for registration in source.artifacts
)
https://github.com/morluto/flameox/blob/0a60b288342cd96e2608302fa9fd2917e59d7e58/src/flameox/application/inference_profiling.py
The parent run's evidence scope therefore includes every independently extracted trace.
The SGLang plan explicitly says it captures separate prefill/decode traces, and generic directory discovery can preserve several .json, .json.gz, .pftrace, or .sqlite trace candidates.
Deterministic false-correlation proof
Let parent run R register two independently captured traces, A and B. Both registrations have role inference_profile, and both normalized events use phase decode.
Trace A
{
"artifact_id": "A",
"name": "cudaLaunchKernel",
"category": "cuda_runtime",
"start_ns": 1000,
"duration_ns": 10,
"process": "P1",
"context": "C1",
"correlation_id": "7"
}
A contains no kernel with correlation 7.
Trace B
{
"artifact_id": "B",
"name": "projection_kernel",
"category": "kernel",
"start_ns": 0,
"duration_ns": 5,
"process": "P2",
"context": "C2",
"device": "1",
"stream": "9",
"correlation_id": "7"
}
B contains no runtime launch with correlation 7.
Current analysis over R:
region = decode for both events
runtime_correlation_ids = {"7"}
kernel B correlation "7" is in that set
correlated_kernel_count = 1
coverage.host_to_device_correlation = True
No host-to-device correlation exists. The launch and kernel came from different artifacts, processes, contexts, and capture sessions.
CUPTI's correlation ID contract links API/activity records inside the collected activity stream; it does not make integer 7 a global cross-file identifier. Official CUPTI documentation describes the ID as the value carried by the API call and the activity records generated by that call:
https://docs.nvidia.com/cupti/main/main.html
Deterministic false-timing proof
Using the same two traces:
A clock origin: event at 1000..1010
B clock origin: event at 0..5
Current region summary returns:
region_start_ns = 0
region_end_ns = 1010
region_duration_ns = 1010
There was no 1,010 ns region in either trace. The value is the span between two unrelated clock origins.
If A and B each contain kernels with the same (device, context, stream) strings, _positive_gaps() sorts them together and invents cross-file idle intervals.
Same-artifact scope still needs more than a bare integer
Even inside one report, multi-process/system-wide traces and provider-specific correlation namespaces require the exact documented key. Normalized rows already carry fields such as process, thread, context, device, and track. The recipe discards them for correlation matching.
The correct key may differ by provider/profile, for example:
artifact/session + process + correlation ID
artifact/session + context + correlation ID
provider-owned explicit flow/link identity
It must be qualified rather than guessed universally.
Additional consequences
- Trace-event counts from separate captures are added as if they were one execution.
- Kernel-name counts can double-count repeated stage traces.
- Phase annotations with identical names do not prove common clock or repeated-work semantics.
correlation_ids=True and host_to_device_correlation=True can overstate coverage.
- Comparisons can attribute multi-artifact composition differences to launch behavior.
- A user cannot reconstruct which artifact contributed a reported region because artifact identity is absent from
AcceleratorLaunchRegion.
Violated invariant
Every timing subtraction, gap computation, stream grouping, and correlation join must be performed only within one qualified trace/session namespace unless an explicit provider mapping proves cross-session alignment.
At minimum:
artifact/session identity
provider/profile identity
clock domain
process/context scope
correlation namespace
must participate in the grouping/join contract.
Proposed direction
1. Preserve artifact/session identity in normalized rows and query models
Select and retain artifact_id, registration role, extractor profile, provider, and clock identity for every event.
2. Produce summaries per trace session by default
Region identity should include the trace artifact/session, or the API should require one artifact input. Multi-artifact aggregation must be an explicit higher-level operation with a declared alignment rule.
3. Use provider-qualified correlation keys
Define exact join keys for Perfetto, Nsight Systems, rocprofv3, and other providers based on maintained native semantics. Unknown/incomplete keys remain uncorrelated.
4. Never subtract unaligned clocks
Gap and region-duration calculations must require one clock domain. Cross-trace comparison should compare durations/aggregates, not absolute timestamps, unless synchronization evidence exists.
5. Preserve inference stages explicitly
Stage-separated prefill/decode traces need distinct typed roles/stage identities in registration and analysis. A generic inference_profile label is insufficient.
Acceptance criteria
Relationship to existing issues
Severity
Critical scientific correctness — independent trace artifacts on one run can be fused into one timing/correlation region, producing host-to-device matches and gaps that never existed in any trace
Validated against
mainat0a60b288342cd96e2608302fa9fd2917e59d7e58.Audit only; no implementation change is included.
Summary
AcceleratorRecipesresolves a run to all of its trace artifact IDs and queries normalizedtrace.eventrows across the whole scope.The query does not select or retain
artifact_id. Region grouping uses only:and ignores every other artifact role.
Within a region, runtime→kernel correlation uses only the stringified
correlation_id:Timing summaries likewise combine
start_ns/duration_nsfrom all selected artifacts and treat them as one clock domain.This is invalid for independent trace files. Correlation IDs are meaningful within a captured activity stream; numeric equality across two independent artifacts does not establish a relationship. Trace timestamps also need one clock/session mapping before subtraction.
A concrete production path creates exactly this state: inference profiling preserves each trace file as a separate import run with role
inference_profile, then copies all registrations into one parent profiling run. The accelerator recipe ignores that role, so stage-separated or multiple profiler outputs can merge under the same phase or<unscoped>region.Direct code evidence
The recipe drops artifact identity at the query boundary
flameox/src/flameox/analysis/recipe_accelerator.py
Lines 160 to 185 in 0a60b28
resolve_evidence_scope()may select several artifact IDs for a run, but the row consumer cannot tell which artifact produced an event.Only cycle-prefixed roles survive grouping
Every role such as:
collapses into the same phase bucket unless the provider separately injects a distinct phase.
Correlation is a bare-ID set membership test
https://github.com/morluto/flameox/blob/0a60b288342cd96e2608302fa9fd2917e59d7e58/src/flameox/analysis/recipe_accelerator.py#L380-L460
Artifact, process, context, device, and provider/session identity do not participate.
Timing also crosses the erased boundary
The same merged event list is used for:
Events are grouped into runtime tracks and accelerator streams only after artifact/session identity has been lost.
Inference profiling creates multiple independent trace artifacts on one parent run
InferenceProfilingService._preserve()imports every discovered trace candidate separately and assigns all of them:It returns multiple artifact run IDs.
_canonical_registrations()then copies every source registration onto the parent profiling run:https://github.com/morluto/flameox/blob/0a60b288342cd96e2608302fa9fd2917e59d7e58/src/flameox/application/inference_profiling.py
The parent run's evidence scope therefore includes every independently extracted trace.
The SGLang plan explicitly says it captures separate prefill/decode traces, and generic directory discovery can preserve several
.json,.json.gz,.pftrace, or.sqlitetrace candidates.Deterministic false-correlation proof
Let parent run R register two independently captured traces, A and B. Both registrations have role
inference_profile, and both normalized events use phasedecode.Trace A
{ "artifact_id": "A", "name": "cudaLaunchKernel", "category": "cuda_runtime", "start_ns": 1000, "duration_ns": 10, "process": "P1", "context": "C1", "correlation_id": "7" }A contains no kernel with correlation 7.
Trace B
{ "artifact_id": "B", "name": "projection_kernel", "category": "kernel", "start_ns": 0, "duration_ns": 5, "process": "P2", "context": "C2", "device": "1", "stream": "9", "correlation_id": "7" }B contains no runtime launch with correlation 7.
Current analysis over R:
No host-to-device correlation exists. The launch and kernel came from different artifacts, processes, contexts, and capture sessions.
CUPTI's correlation ID contract links API/activity records inside the collected activity stream; it does not make integer 7 a global cross-file identifier. Official CUPTI documentation describes the ID as the value carried by the API call and the activity records generated by that call:
https://docs.nvidia.com/cupti/main/main.html
Deterministic false-timing proof
Using the same two traces:
Current region summary returns:
There was no 1,010 ns region in either trace. The value is the span between two unrelated clock origins.
If A and B each contain kernels with the same
(device, context, stream)strings,_positive_gaps()sorts them together and invents cross-file idle intervals.Same-artifact scope still needs more than a bare integer
Even inside one report, multi-process/system-wide traces and provider-specific correlation namespaces require the exact documented key. Normalized rows already carry fields such as process, thread, context, device, and track. The recipe discards them for correlation matching.
The correct key may differ by provider/profile, for example:
It must be qualified rather than guessed universally.
Additional consequences
correlation_ids=Trueandhost_to_device_correlation=Truecan overstate coverage.AcceleratorLaunchRegion.Violated invariant
Every timing subtraction, gap computation, stream grouping, and correlation join must be performed only within one qualified trace/session namespace unless an explicit provider mapping proves cross-session alignment.
At minimum:
must participate in the grouping/join contract.
Proposed direction
1. Preserve artifact/session identity in normalized rows and query models
Select and retain
artifact_id, registration role, extractor profile, provider, and clock identity for every event.2. Produce summaries per trace session by default
Region identity should include the trace artifact/session, or the API should require one artifact input. Multi-artifact aggregation must be an explicit higher-level operation with a declared alignment rule.
3. Use provider-qualified correlation keys
Define exact join keys for Perfetto, Nsight Systems, rocprofv3, and other providers based on maintained native semantics. Unknown/incomplete keys remain uncorrelated.
4. Never subtract unaligned clocks
Gap and region-duration calculations must require one clock domain. Cross-trace comparison should compare durations/aggregates, not absolute timestamps, unless synchronization evidence exists.
5. Preserve inference stages explicitly
Stage-separated prefill/decode traces need distinct typed roles/stage identities in registration and analysis. A generic
inference_profilelabel is insufficient.Acceptance criteria
correlated_kernel_count=0.Relationship to existing issues