fix(kernel): stop sentinel strings from short-circuiting source resolution - #1107
Open
zhanglei-amd wants to merge 3 commits into
Open
fix(kernel): stop sentinel strings from short-circuiting source resolution#1107zhanglei-amd wants to merge 3 commits into
zhanglei-amd wants to merge 3 commits into
Conversation
zhanglei-amd
requested review from
a team,
devalshahamd and
tsrikris
as code owners
August 6, 2026 07:09
5 tasks
zhanglei-amd
force-pushed
the
fix/zhanglei/kernel-source-resolution
branch
2 times, most recently
from
August 6, 2026 10:39
467e564 to
dbb5985
Compare
chaojhou
approved these changes
Aug 6, 2026
CI E2E report — ❌ Failed
|
zhanglei-amd
force-pushed
the
fix/zhanglei/kernel-source-resolution
branch
from
August 6, 2026 14:56
300cfb1 to
fbd7c7c
Compare
Collaborator
|
@zhanglei-amd Note that @Ahmedhasssan-aig is developing an alternate to the JSON lookup #1113 |
zhanglei-amd
force-pushed
the
fix/zhanglei/kernel-source-resolution
branch
3 times, most recently
from
August 7, 2026 03:39
717e0ca to
40b6fed
Compare
…sult auditable TraceLens writes a placeholder into source_file whenever it cannot resolve a launcher -- "Not found" for every Synthetic Op, "AITER (vendor)" for vendor kernels. Downstream code tested that field for truthiness, so a placeholder counted as a resolved source: the grep fallback was skipped and classify_patchability rejected the candidate with "source not under a reusable framework root: Not found". This hit the hottest kernels precisely because they are the ones TraceLens cannot attribute. Resolution now runs as a ladder -- curated dictionary, trace-derived launcher, name grep, then two model tiers -- with placeholders normalized to empty before any of it, since every later tier is gated on an empty source_file. Trace-derived resolution walks kernel correlation to runtime launch to the enclosing python_function frame. Three properties make it trustworthy: * Deterministic. Profiler timestamps are microsecond-granular, so adjacent frames on a fast call chain share a ts, and ordering by start alone left the tie to trace write order. Ordering by (ts, -dur) lets nesting decide. A tie at the top of the frame vote now leaves the kernel unresolved rather than taking whichever the counter saw first: a plurality is not agreement. * Process-aware. Kernels, launches and frames are keyed with pid. A merged multi-process trace restarts correlation and thread ids per rank, so without it one rank's launch overwrites another's and a kernel binds to a different process's launcher. * Bounded. Kernels launched from C++ or only replayed from a graph never resolve, so "stop once everything resolves" would read an entire capture folder at two streaming passes per file. Cap the files read, stop when one adds nothing, and size the probe map by launches rather than by every correlated runtime event. Both model tiers run unconditionally and differ in authority. The fallback sees a candidate only after all three deterministic tiers come up empty and only above 5% GPU share, and may return one of the exact grep shortlist strings and nothing else. The review tier exists because the deterministic tiers do not fail by coming up empty, they fail by coming up confidently wrong: across historical sessions only 59% of verifiable resolutions mention the kernel they claim to define, and aten::fill_ alone resolves to four unrelated business files, each a real root-resident path passing every mechanical check. A tier gated on an empty source_file can never see any of them, so this one inspects the whole table and may keep, rewrite or unresolve any entry -- bounded by a rewritten path having to exist on disk under a known framework root, and by every revision recording previous_source_file and previous_method. Failures are recorded rather than swallowed. Both tiers stay fail-soft but stamp a structured source_resolution_reason and log; per-file resolver errors reach the caller through a file_errors list instead of surfacing as an ordinary "0 resolved"; and the resolved/total tally goes to the run log. The result is now its own artifact. kernel_source_resolution.json records, per hot kernel, the identity, GPU share, resolved location and deciding tier behind a schema_version consumers can gate on. It is an audit view, not the stage contract -- kernel_candidates.json remains that, since it alone carries shapes, eligibility, backends and task groups. Review revisions reach the pipeline because apply_resolution_entries_to_candidates() folds them back onto the candidates and re-runs classification, which is what decides dispatch. One caveat worth stating: SOURCE_EXTENSIONS stays narrow, matching what source_type_for() can classify. Widening it also widens grep admission, and an unclassifiable suffix lands as source_type="unknown" while still competing in _rank_paths -- a /csrc/ file then outranks the sibling .py and flips a routable candidate non-routable. PATH_SHAPED_EXTENSIONS carries the wider set and feeds only the placeholder gate. Also included, unrelated to the feature: the DeepSeek-only preflight test inherited the host environment, so a CI runner with OPENAI_BASE_URL set tripped the cross-provider check and failed a test whose premise is that only DeepSeek is configured. It reproduces on a clean origin/main worktree by exporting that variable, so it is a pre-existing sensitivity, but it was blocking this PR's checks.
zhanglei-amd
force-pushed
the
fix/zhanglei/kernel-source-resolution
branch
from
August 7, 2026 05:13
1b0f5c3 to
726ab20
Compare
Which file implements a kernel is a question about the running configuration, not just about file names. The same MoE operator dispatches to different implementations under --moe-runner-backend triton and aiter, and a model shown only forty lines of each candidate cannot tell those apart. Both tiers now receive a context block carrying the model's config (architecture, quantization, expert layout), the serving flags and environment that select a backend, and the framework roots in play. The review tier additionally shows each candidate's launcher call site, which is often the only thing separating a kernel from the business file that merely calls it. Three properties keep this from becoming a liability. Environment values are admitted by an explicit allowlist of path-selecting names, with a secret-name pattern as a backstop, so a credential cannot ride along even if the allowlist is edited carelessly. Each section is truncated independently and the whole block is capped. Every section degrades to omitted, so a missing config or an unreadable root list narrows the context rather than failing the tier.
The tests pulled in _llm_source_context and _trace_launcher_resolver with both 'from X import' and 'import X as Y', which CodeQL flags as py/import-and-import-from. The module object was only ever needed to reach a module attribute, so use monkeypatch's string target form where an attribute is patched, and import _MAX_CONTEXT_CHARS directly where the constant is merely read.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
TraceLens writes a placeholder into
source_filewhenever it cannot resolve alauncher —
Not foundfor every Synthetic Op,AITER (vendor)for vendorkernels,
N/Aelsewhere. Downstream code tested that field for truthiness, so aplaceholder counted as a resolved source: the grep fallback was skipped and
classify_patchabilityrejected the candidate with "source not under areusable framework root: Not found".
This hit the hottest kernels precisely because they are the ones TraceLens
cannot attribute — hand-written Triton kernels launched straight from Python
have no
cpu_opparent, so they surface as Synthetic Ops.Resolution ladder
Placeholders are normalized to empty before any of it, since every later tier
is gated on an empty
source_file.looks_like_source_path()replaces thetruthiness test, and a rejected value is preserved as
source_file_rejectedrather than silently dropped. Bare runtime API names (
hipModuleLaunchKerneland friends) are excluded from grep, which would otherwise match unrelated call
sites.
Trace-derived resolution
_trace_launcher_resolverwalks kernel correlation → runtime launch → theenclosing
python_functionframe. Three properties make it trustworthy:Deterministic. Profiler timestamps are microsecond-granular, so adjacent
frames on a fast call chain share a
ts. Ordering by start alone left the tieto trace write order — the same call chain resolved differently depending on
which frame was serialised first. Ordering by
(ts, -dur)lets nesting decide.Process-aware. Kernels, launches and frames are keyed with
pid. A mergedmulti-process trace restarts correlation and thread ids per rank, so without it
one rank's launch overwrites another's and a kernel binds to a different
process's launcher.
Bounded. Kernels launched from C++ or only replayed from a graph never
resolve, so "stop once everything resolves" would read an entire capture folder
at two streaming passes per file. The file count is capped, scanning stops when
a file adds nothing, and the probe map is sized by launches rather than by every
correlated runtime event.
A tie at the top of the frame vote now leaves the kernel unresolved rather than
picking whichever the counter saw first — a plurality is not agreement, and the
grep tier is a better answer than an arbitrary one.
The two model tiers
Both run unconditionally; there is no environment switch. They differ in scope
and authority, and
docs/reference/environment-variables.mddocuments eachseparately.
Fallback sees a candidate only after all three deterministic tiers come up
empty, and only above 5% GPU share. It may return one of the exact grep
shortlist strings and nothing else; an invented path is rejected, as is anything
below 0.7 confidence.
Review exists because the deterministic tiers do not fail by coming up
empty — they fail by coming up confidently wrong. Measured across historical
sessions, only 59% of verifiable resolutions mention the kernel they claim to
define, and
aten::fill_alone resolves to four unrelated business files, eacha real root-resident path passing every mechanical check. A tier gated on an
empty
source_filecan never see any of them, so this one inspects the wholetable and may keep, rewrite or unresolve any entry. Two guard rails bound it: a
rewritten path must exist on disk under a known framework root, and every
revision records
previous_source_fileandprevious_methodso a bad review isreversible.
Neither tier can fail a run. No model configured, a gateway error, a timeout or
an unparseable reply all leave the deterministic result standing.
Observability
Every non-trivial outcome is recorded as
source_resolution_reason, so a skipis distinguishable from a failure:
llm_fallback_skipped: gpu_pct ...llm_fallback_no_shortlistllm_fallback_declined: ...llm_fallback_error: ...trace_resolver_error: ...rejected_non_path_sentinelPer-file resolver errors reach the caller through a
file_errorslist insteadof surfacing as an ordinary "0 resolved". The
resolved/totaltally goes to therun log, where an operator actually looks.
New artifact
kernel_source_resolution.jsonrecords, per hot kernel, the identity, GPUshare, resolved location and deciding tier, behind a
schema_versionconsumerscan gate on. Previously this existed only as fields mutated onto candidate rows
by a dozen functions, with no single place to read "where does this kernel live,
and how do we know".
Scope: it is an audit view, not the stage contract.
kernel_candidates.jsonremains the contract, since it alone carries shapes, eligibility, backends and
task groups. Review-tier revisions reach the pipeline because
apply_resolution_entries_to_candidates()folds them back onto the candidatesand re-runs classification — a rewrite changes
source_type, which decideswhether the kernel is dispatched at all.
On a completed session the artifact reported 37 entries, 36 resolved: 29 by
grep, 6 by the trace tier, 1 by the curated dictionary.
Files
New:
common/kernel_source_contract.py— versioned artifact schema and validationtools/_trace_launcher_resolver.pytools/_llm_source_fallback.pytools/_llm_source_review.pytests/test_trace_launcher_resolver.pytests/test_source_resolution_guards.pytests/test_source_resolution_artifact.pytests/test_llm_source_fallback.pyModified:
tools/tracelens_analysis.pytools/tracelens_skill_runner.pydocs/reference/environment-variables.mdinference_optimizer/tests/test_setup_cli.py— unrelated CI fix, see belowTesting
Full kernel-agent suite: 1321 passed. The one failure
(
test_bypass_report.py::test_render_surfaces_source_dispatchability_and_task_groups)reproduces on a clean
origin/mainworktree and is unrelated.The
test_setup_cli.pychange is a CI fix, not part of this feature. TheDeepSeek-only preflight test inherited the host environment, so a runner with
OPENAI_BASE_URLset tripped the cross-provider check and failed a test whosepremise is that only DeepSeek is configured. It reproduces on a clean
origin/mainworktree by exporting that variable, so it is a pre-existingsensitivity rather than a regression — but it was blocking this PR's checks.
Not included
Roofline patch-set repairs and the SGLang patch vendoring were part of an
earlier revision of this branch and have been removed. Consumer-side
consolidation is also out of scope:
request_handlers.pystill carries its owncopy of the source-path classification logic, which can disagree with the
producer's. Unifying that touches eight consumers and belongs in its own PR.