Repository navigation
- Date range: 2026-07-14 — 2026-09-16
- Scope: Major feature release - Monaco-based IR viewer rewrite, LLIR source mapping, inlined-helper source-mapping correctness fix, parse-time kernel allowlist, a published inductor example trace, AMD MI350 CI plus buffer-ops analysis, and multi-provider LLM client support. 171 files changed, ~20.4k insertions / ~3.0k deletions.
Highlights
-
🎨 Monaco IR Viewer Rewrite: The entire IR viewing stack moved from Prism-based rendering to Monaco (~9.2k insertions across 48 website files). New
MonacoCodePanelcore with an ESM loader and inline worker, custom IR grammars and language registration (irGrammars.ts,registerIrLanguages.ts,monacoLanguage.ts), a shared overview ruler with side-strip geometry, and a three-panel comparison component (CodeComparisonViewV2). Landed behind a?renderer=monacoopt-in, flipped to default, then the legacy renderers were deleted outright —CodeViewer.tsx(799 lines),CodeComparisonView.tsx(660 lines), thehighlightEventsbus,LegacyRulerAdapterand the Prism adapter are all gone. Ships with a committed e2e harness, fixture pages, unit tests for the comparison mapping math, a perf matrix runner, and a worker-delivery probe. -
🗺️ LLIR Source Mapping (#434): Closes a long-standing gap (issue #61) where the LLIR stage was registered as mappable but never actually mapped.
_parse_generic_loconly understood MLIR#locdirectives, while LLVM IR records locations as!dbg !Nreferences into!DILocationmetadata — so the parser silently matched nothing andsource_mappingsshipped without anllirkey. Newextract_llvm_dbg_mappings()/_parse_llvm_dbgregisters allvm_dbgparser on the base adapter (LLVM is not vendor-specific) and both NVIDIA and AMD LLIR stage descriptors point at it.inlinedAtchains are walked to the root frame so library code is not attributed to the user's file. Because the cross-stage join is generic, this also yields LLIR↔TTIR, LLIR↔TTGIR, and LLIR↔PTX/SASS (NVIDIA) or LLIR↔AMDGCN (ROCm) in both directions at no extra cost. A newTestStageParserContractguards the bug class: a mappable stage must name a parser that can actually read its syntax. -
🎯 Inlined Triton Helper Source Mapping Fix (#435): Inlined helpers —
tl.cdiv,tl.sum,tl.dot— were filed under the callee line in a Triton library file instead of the kernel line the user wrote, making the generated arithmetic unreachable from the source pane. Fixed across every stage: MLIRextract_loc_definitionsgains a second pass that walks each callsite's caller chain to the outermost frame; SASS_iter_sass_instructionsnow keeps the last inline frame (nvdisasm emits one//## Filecomment per frame, innermost first) instead of discarding the call site; and three.locdefects were repaired — inlined.locdirectives were never matched by the regex,inlined_atnamed the immediate caller rather than the root, and.loc <file> 0 <col>(DWARF for "no line information") invented a bogus mapping to python line 0. The reverse direction is fixed too: clicking inlined IR now highlights the user's line. -
🔍 Parse-Time Kernel Allowlist (
--kernel-allowlist): Filter a trace down to specific kernels at parse time, not just at trace time. Statelessfnmatchmatching was extracted into a sharedtritonparse/kernel_filter.pyso trace-time and parse-time semantics stay identical, andparse/kernel_selection.py(222 lines) builds a rank-local hash/autotune-session index and uses its transitive closure to filter complete kernel event groups — including fake compilations and aliases across PIDs — before any expensive analysis runs. Exposed as a CLI flag, an explicitunified_parse()parameter, and theTRITONPARSE_KERNEL_ALLOWLISTenvironment variable, with forwarding verified through the CLI and the context manager. -
🔬 Inductor Example Trace and Reproducible Example Generation:
torch.compileproduces most traces in the wild, yet nothing in the repo showed one. This release addstritonparse.tools.generate_examplesplusmake regen-examples/make regen-examples-install, making example-trace regeneration a recorded command rather than something done by hand — the previously published example was captured on Triton 3.6, predatedroofline/ir_stages/ SASS, and embedded ~9,500 absolute paths from a developer checkout into a public site. Both examples were then regenerated: the refreshed Triton example (byte-identical filenames, so every published URL keeps resolving) and a new inductor example covering both kernel shapes inductor emits — a fused pointwise kernel and persistent reductions — across two compile frames, with the full IR chain through SASS andpt_infoframe attribution. The welcome screen now offers both examples from aBUNDLED_EXAMPLESlist, and kernels generated bytorch.compileget an inductor badge. -
📉 Trace Size Guardrails: A launch event's
functionis an opaque backend handle documented asNoneand reported by CUDA/HIP as an integer address — but on one accelerator backend the value is the entire compiled binary, ~3.35M characters per launch event. On a real trace this single field was 254 MB of a 258 MB file (98% of the trace), violatedlaunch.schema.json, and stretched the viewer page to hundreds of thousands of pixels. Fixed in three layers:summarize_launch_functiongates the field by size at the writer (documented shapes pass through; short strings kept; anything larger collapses to<bytes: N bytes omitted>, measured withlen()so a multi-MBrepris never materialized);summarize_oversized_strings()applies the same guard at parse time for traces already on disk, placed aftercompute_launch_event_hashso group hashes stay byte-identical; and a newTruncatedValuecomponent collapses over-long values in the viewer with aShow moretoggle, refusing to expand beyond 100,000 chars because painting a multi-MB string as one text node freezes the tab.
Changes by Area
🗺️ Source Mapping & IR Analysis
- LLIR debug-metadata parser (#434):
extract_llvm_dbg_mappings()/_parse_llvm_dbginparse/ir_parser.py, registered asllvm_dbgon the base adapter. Handles plain!DILocation, multi-levelinlinedAtchains,!dbgpointing at aDISubprogram, and non-LLVM input. NewTestLLVMDebugMappingsandTestStageParserContractsuites. - Inlined-code attribution across MLIR, PTX, AMDGCN and SASS (#435):
inlined_at_file/inlined_at_lineare now written by every stage and read bycreate_python_mapping, tested withis not Nonerather than truthiness so a line of 0 does not silently re-file the entry under the callee.inlined_at_*is recorded only when the walk reaches a non-callsite frame, so a broken or cyclic chain leaves the entry plain instead of mis-attributing it. - Overview ruler for mapped-line highlights (#433): Slim overview rulers on the left IR, right IR, and Python viewers, synchronized with mapped-line updates without re-rendering the highlighted document. Highlights are preserved while a panel is hidden and restored on remount, stale highlights are cleared when the document or source mapping changes, and markers are keyboard accessible and clickable. Fixes issue #422.
- AMD buffer-ops enablement (
ir_analysis): New derivedamd_buffer_opsfield so users can tell directly whether AMD buffer ops are enabled rather than inferring it from raw op counts.enabledis true only when every global memory op in AMDGCN is a buffer op;statusdistinguishesall_buffer/partial/none/unknown, with per-op counts and a human-readablereason. Emitted by both the adapter-driven analyzer and the legacy fallback;ir_analysis.schema.jsonand the website updated to match.
🎨 Website: Monaco Migration
- Monaco core :
MonacoCodePanel.tsx(285 lines) plusmonaco-setup.ts,highlightMath.ts,irGrammars.ts(174 lines) andregisterIrLanguages.ts— ESM loader, inline worker, panel core and IR languages. - Comparison V2:
CodeComparisonViewV2three-panel component with a dedicated fixture page, andmonaco/comparisonMapping.tscarrying the mapping math with strict-integer filtering and unit tests. - Legacy removal: After Monaco became the default for both single and comparison views, the old
CodeViewer.tsx,CodeViewer.css, V1CodeComparisonView.tsx,highlightEventsbus,LegacyRulerAdapterand the PrismmapLanguageToHighlighteradapter were deleted. No renderer branches remain. - Cross-frame click sync: Line-verified scroll-then-click synchronization, text-derived click points, and strict python-line validation that refuses to forge a line number via
Number(). - Word wrap toggle:
Wrapoff/on for the single IR view and the comparison view (all three panels), persisted via the samewrapURL param as File Diff. Click mapping stays exact while wrapped because positions are logical lines. Default stays off to preserve the measured perf baselines. - Scroll tip toast (#436): The comparison view's persistent in-flow tip banner became a fixed overlay toast that auto-hides after 6 seconds and takes no layout space, with
role="status"for screen readers. Auto-hide does not persist; only a manual dismiss writeslocalStorage. - Overview ruler layout: Folded to a side-only layout, native Monaco overview ruler dropped in favor of a transparent empty strip.
- Test and perf tooling: Committed e2e harness with fixture pages, a perf matrix runner covering open/scroll and hot-reopen scenarios with real-rAF boundaries, tab and first-frame observers, a worker delivery probe with request-reply matching, and codicon font delivery with keyboard coverage.
- Standalone build fix: Inlined bundles moved before
</body>(deferis ignored on src-less inline scripts, so a head-placed bundle ran before#rootexisted), a replacer function prevents$-pattern substitution from corrupting bundle bytes, and a byte-integrity self-check fails the build loudly.
⚡ Website Performance
- Multi-second view-switch freezes fixed: Tab switches froze the page for 5–12s. Three independent causes fixed together — visited tabs now stay mounted and hide with
display:noneinstead of unmounting (mount/unmount of the Monaco diff editor dominated every switch); Monaco'sautomaticLayoutwas replaced with a manualResizeObserverthat skips zero-size notifications and passes explicit container dimensions, removing ~5s of relayouts per switch; and theonDidLayoutChange→applyWrapupdate/layout feedback loop was dropped. Measured on the same trace pair: File Diff → IR Code 12.2s → 0.7s, IR Code → File Diff ~13s → 1.2s. - IR virtualization reverted, memo chain fixed: Lowering
LARGE_FILE_THRESHOLDto 100KB virtualized all real IR files and broke two behaviors that assume every line is mounted (stale highlight stacking on Python-line clicks, janky scrolling). Reverted to 10MB with a note explaining why render-skipping breaks programmatic far jumps to mapped lines. Separately,highlightedLines={[]}became a sharedEMPTY_HIGHLIGHTED_LINESconstant andCodeViewInnernow memoizes its panel descriptors, eliminating ~6.5s of re-tokenization per tab switch. A deferred-mount loading placeholder was added for large comparisons.
🔍 Parsing & Filtering
- Stateless kernel filter module:
tritonparse/kernel_filter.pyextracts comma-separated allowlist parsing andfnmatchmatching, with the existingstructured_loggingwrappers and trace-time behavior left intact. - Complete-group filtering during parse:
parse/kernel_selection.pybuilds a rank-local hash/autotune-session index and filters complete event groups, preserving the unfiltered path and emitting bounded no-match diagnostics. - Pipeline threading: Filtered parse results are aggregated across ranks; parsing fails only when no rank emits a kernel group; empty ranks are omitted from published output; the OSS allowlist is normalized once; and an existing output directory is preserved until parsing succeeds.
- Autotune event schemas:
tritonparse.validationshipped schemas for only 5 of the 8 event types, andvalidate_record()passes unknown types through untouched — so every autotune record in every trace was silently unvalidated. Schemas for the rawautotunerecord and the derivedautotune_analysis/autotune_summaryrecords were transcribed from their producers and registered in_SCHEMA_FILES. pt_infoattempt field fix:_determine_output_fname()readpt_info.get("attempt_id", 0), but nothing writesattempt_id— the writer emitsattempt. Split-by-frame output filenames now carry the real attempt number.- Remote-input seam for
tritonparse diff(#431, #432): Newdiff/core/input_resolver.pyintroduces aResolvedInput(local_path, display, json_url)container plusresolve_input()andis_remote(). OSS accepts local paths only; the seam lets deployments plug in their own fetch/upload backend.tritonparse diffalso gained--no-url,--no-shareand--max-urls(default 10,0for no limit) for controlling File Diff link output, and the IR stage a link opens on is chosen by largest absolute line delta rather than a fixed guess.
📉 Trace Size & Payload Hygiene
- Writer-side cap on
function:summarize_launch_functiongates by size before the value enters the trace. A single warning per process names the type and size, rather than one warning per launch.launch.schema.jsonwidensfunctionto["integer", "string", "null"]. - Parse-side summarization:
summarize_oversized_strings()recursively replaces any string overMAX_VALUE_CHARS(4096) with<N chars omitted>, returning a new object so callers that hash the original are unaffected. Applied at ingest inparse_single_rankand to bothsamesanddistributionvalues in_generate_launch_diff. Also fixes the single-launch shortcut in_generate_launch_diff, which returned_unflatten_dict(_flatten_dict(event))directly and so skipped theEXCLUDED_FIELDSfilter the multi-launch path applies. - Viewer truncation:
TruncatedValuewith per-call-site thresholds (200 chars in the responsive metadata grid, 1000 in the full-width long-field list); only the preview is handed to React while collapsed.asmis no longer rendered — it duplicates IR the compilation event already stores verbatim infile_content.
🤖 AI Module
- Codex client:
CodexClientfor non-interactive, read-only analysis — prompts sent through stdin, system guidance mapped to invocation-scoped developer instructions, mutable agent features disabled, and retries limited to transient capacity failures. Shell-free argv execution. - Muse client: One-shot
MuseClientshelling out tomuse execheadless mode, then upgraded to--jsonevent parsing — final text from the terminal event with a delta-concat fallback, session id from the event stream, model id fromrun.model.configured, and real streaming inchat_streamvia argvPopenyieldingrun.output.deltachunks. The strict single-turn prompt validation and CLI failure formatting shared withCodexClientwere extracted into module-level helpers. - Provider selection: New
create_llm_clientfactory (claude/codex/muse, defaultclaude) wired intoAIDiffAnalyzerandAICompatFixer.--ai-provideradded tocompat-build, and--ai-model/--ai-provideradded to thediffcommand, which previously had no model selection at all. All defaults stay on Claude, so no behavior change. A READ-ONLY instruction was added to the diff analysis system prompt, with the caveat documented that themuseprovider enforces this at the prompt level only.
🔴 AMD / ROCm
- MI350 CI job (#427): New
test-rocm-nightlyleg in the Tests workflow for AMD MI350 (gfx950). Host-direct run (ROCm needs/dev/kfdpassthrough), torch nightly rocm7.1 plus an upstream Triton nightly pin via the shared resolver, reusinginstall-project.shandrun-tests.sh. Gated onworkflow_dispatchwhile stabilizing. Also addstests/cpu/test_device_arch.pyandtests/cpu/test_disasm.pyarch helpers. - Buffer-ops regression coverage (#428): A realistic AMDGCN-block fixture in
TestAmdBufferOpsStatuslocking line-based counting semantics (CPU-runnable), plustests/gpu/test_amd_buffer_ops.py— an AMD-only e2e asserting a well-formedamd_buffer_opspayload on live gfx9xx.
🧰 Tracing & Configuration
init(enable_full_python_source=...):extract_python_source_info()captures only the kernel's function definition unlessTRITON_FULL_PYTHON_SOURCE=1is exported, which is actively wrong for nested kernels — a thintriton.jitwrapper whose body is a single call captures 26 lines containing none of the real kernel, while the full capture yields ~3,500 lines. Exporting an env var is awkward for programmatic callers, so this adds aninit()keyword mirroring the existingenable_sass_dump.TritonParseManagerreaches everyinit()keyword: The manager exposed an arbitrary subset, forcing callers who needed the rest to abandon the parse/cleanup lifecycle.enable_trace_launch_within_profiling,enable_more_tensor_information,enable_sass_dump,enable_full_python_sourceandcompressionare now declared withinit()'s own types and defaults and forwarded unchanged.- Launch tracing reaches inductor's JIT hook gate:
torch._inductor.config.run_jit_post_compile_hookis read from the environment once at torch import time but checked dynamically at compile time, soinit(enable_trace_launch=True)only flipped tritonparse's own module global — inductor traces contained compilations and zero launches.init()now sets bothTORCHINDUCTOR_RUN_JIT_POST_COMPILE_HOOK=1for future subprocess/spawn workers and the live torch config attribute for this process. Lazy import keeps the no-top-level-torch invariant; missing or old torch is a no-op. - Optional derived-artifact failures are contained:
_extract_file_content_adapter_driven()only caughtCalledProcessError,OSErrorandUnicodeDecodeErroraroundinfo.derive_func(source_path). Whennvdisasmis missing,triton.knobsraises a plainRuntimeError, which escaped and aborted the entire trace write — a run produced noraw_logsat all even though every stage except SASS succeeded. Containment widened toexcept Exception, scoped to thederive_funccall only, so failures in required extraction stages still surface. - Reproducer fix for kernels defined inside a function:
get_kernel_info()trimmed captured source by slicing at the character offset oftriton.jit. For a module-level kernel that offset is column 0 and the slice is a no-op, but for a kernel defined inside a function or class — how most of this repo's own tests and any factory-built kernel are written — slicing mid-line stripped indentation from the decorator line only, producing invalid Python.
🖥️ File Diff & Sharing
- Share button toast: New
ShareButton.tsxreplaces the blockingalert()with an auto-dismissing success/error toast and a textarea fallback when the async clipboard API is unavailable. A Share button was added to the File Diff view, withbuildShareUrl()shared by the debounced history sync and the header button so the copied link is never stale; it is disabled with a tooltip when either side is loaded from a local file. - Wrap and only-changed options fixed:
diffWordWrapand both per-sidewordWrapOverridefields were hardcoded to"on", so Wrap=off did nothing;hideUnchangedRegions: undefineddoes not reset a previously enabled value viaupdateOptions, so Only-changes could not be turned back off; and with Wrap=off the horizontal scrollbar stayed hidden at size 0, clipping long lines. - Dead options removed: The Word-level checkbox (never read — Monaco has no word-vs-line diff mode) and the Ignore-whitespace toggle (only ever controlled leading/trailing whitespace, the Monaco default) were removed along with their share-link params. Legacy links carrying
word_level=1orignore_ws=0keep loading; the params are ignored. The Context input is now disabled unless Only-changes is on.
🏗️ Infrastructure & CI
- Pages deploy gating (#425): GitHub Pages deployment now runs only after a successful
Website Build Testpush or manual run onmain, and checks out the exact SHA that passed validation. Website test path filters include deployment workflow changes, and superseded website validation runs are cancelled to prevent stale deployments. Also switches the Vite config toimport.meta.dirnamefor compatibility with the upcoming native config loader default. - OSS lint stabilization (#419): Pinned the standalone Ruff CLI and explicitly preserved the pre-0.16 default rule set so upstream default-rule changes cannot silently redefine OSS lint policy. Formatting and linting targets were separated, and check-only lint now runs a complete Ruff check instead of
--diff, which implies fix-only and can miss non-fixable diagnostics. - Test isolation fixes: OSS
unittest discoverimports everytest*.pybefore running suites, andtests/test_add.pyenabled launch tracing at import time, leavingTRITON_TRACE_LAUNCHset and failingtest_init_without_launch_leaves_inductor_alonein both pip and nightly jobs (#426). Separately, an AI-forwarding test that patches a module absent from the OSS tree now skips cleanly instead of erroring out all three GPU CI legs. - Pyre mode headers removed across the package and tests.
📦 Dependencies
- Website dependency refreshes (#421, #423): Two rounds of bumps via
website/scripts/update_deps.sh --major, includingmonaco-editor0.55.1→0.56.0,react/react-dom19.2.7→19.2.8,vite8.1.2→8.2.0,eslint10.6.0→10.8.0,typescript-eslint8.62.1→8.65.0,tailwindcss4.3.2→4.3.3 and assorted type packages. Adompurifyadvisory was fixed and redundant/dead dependencies dropped.typescriptis deliberately held at^6.0.3:typescript-eslintdeclarespeer typescript@">=4.8.4 <6.1.0"and no published version supports TS 7 yet.update_deps.shitself was fixed and gained a--peerflag that reports ignored incompatible updates explicitly. postcss8.5.16 → 8.5.25 (#420),@humanfs/node0.16.7 → 0.16.8 (#424).
Compatibility Notes
- Website: the Prism renderer is gone. Monaco is now the only IR renderer. The
?renderer=prismescape hatch that existed during the migration has been removed along with the legacy components. - File Diff URL params
word_levelandignore_wsare no longer honored. Existing share links still load; the params are simply ignored. Whitespace at line start/end is always ignored, and intra-line highlighting is always on. - Parsed output now summarizes oversized strings. Any string over 4096 characters in a launch payload is replaced with
<N chars omitted>. Launch event group hashes are unchanged (summarization happens after hashing), but if you consume rawasmorfunctionvalues out of parsed output, they will now be elided. Nothing in the Python code or the web viewer reads either one. launch.schema.jsonwidensfunctionfrom["integer", "null"]to["integer", "string", "null"]to accommodate short non-integer backend handles.- Split-by-frame output filenames may change.
_determine_output_fname()now readspt_info["attempt"]instead of the nonexistentattempt_id, so thea{attempt}segment carries the real attempt number rather than always0. - Traces gain an
llirkey insource_mappings. Consumers that assumed a fixed set of mapped stages should tolerate the new key and the newllir_linesentries on other stages. - Inlined-helper mappings move. IR generated by
tl.cdiv,tl.sum,tl.dotand friends is now filed under the kernel line that called the helper rather than the library line inside the helper. This is the intended behavior, but it changes which Python line a given IR line maps to. - No Python API removals.
unified_parse()gains an optionalkernel_allowlistparameter andinit()gains an optionalenable_full_python_sourceparameter; both default to preserving prior behavior.
Upgrade Guidance
-
Standard upgrade:
pip install --upgrade tritonparse
-
Re-parse existing traces to pick up LLIR and inlined-helper mappings. Source mappings are computed at parse time, so previously parsed output will not gain them:
tritonparseoss parse ./logs --out ./parsed_output --overwrite
-
Filter a large trace down to the kernels you care about. Patterns are comma-separated
fnmatchglobs, and matching pulls in the complete event group (compilation, autotune session, launches) for each kernel:tritonparseoss parse ./logs --out ./parsed_output --kernel-allowlist "matmul_kernel,triton_poi_*"from tritonparse.parse.utils import unified_parse unified_parse(source="./logs", out="./parsed_output", kernel_allowlist="matmul_kernel")
-
Capture the full Python source for nested or wrapper kernels. If your
triton.jitentry point is a thin wrapper around the real kernel body, the default capture misses the body entirely:import tritonparse.structured_logging as tsl tsl.init("./logs", enable_full_python_source=True)
The same options are now reachable through the context manager:
from tritonparse.context_manager import TritonParseManager with TritonParseManager(enable_trace_launch=True, enable_full_python_source=True): ...
-
Select a non-default LLM provider for AI-assisted analysis (defaults are unchanged, so this is opt-in):
tritonparseoss diff before.ndjson after.ndjson --ai-provider codex --ai-model <model-id>
-
Regenerate example traces if you maintain a fork with its own fixtures:
make regen-examples # writes to ./example_output_regen make regen-examples-install # overwrites the checked-in examples