Skip to content

v0.6.0

Latest

Choose a tag to compare

@FindHao FindHao released this 17 Sep 20:05
· 10 commits to main since this release
  • 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 MonacoCodePanel core 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=monaco opt-in, flipped to default, then the legacy renderers were deleted outright — CodeViewer.tsx (799 lines), CodeComparisonView.tsx (660 lines), the highlightEvents bus, LegacyRulerAdapter and 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_loc only understood MLIR #loc directives, while LLVM IR records locations as !dbg !N references into !DILocation metadata — so the parser silently matched nothing and source_mappings shipped without an llir key. New extract_llvm_dbg_mappings() / _parse_llvm_dbg registers a llvm_dbg parser on the base adapter (LLVM is not vendor-specific) and both NVIDIA and AMD LLIR stage descriptors point at it. inlinedAt chains 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 new TestStageParserContract guards 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: MLIR extract_loc_definitions gains a second pass that walks each callsite's caller chain to the outermost frame; SASS _iter_sass_instructions now keeps the last inline frame (nvdisasm emits one //## File comment per frame, innermost first) instead of discarding the call site; and three .loc defects were repaired — inlined .loc directives were never matched by the regex, inlined_at named 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. Stateless fnmatch matching was extracted into a shared tritonparse/kernel_filter.py so trace-time and parse-time semantics stay identical, and parse/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 explicit unified_parse() parameter, and the TRITONPARSE_KERNEL_ALLOWLIST environment variable, with forwarding verified through the CLI and the context manager.

  • 🔬 Inductor Example Trace and Reproducible Example Generation: torch.compile produces most traces in the wild, yet nothing in the repo showed one. This release adds tritonparse.tools.generate_examples plus make 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, predated roofline / 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 and pt_info frame attribution. The welcome screen now offers both examples from a BUNDLED_EXAMPLES list, and kernels generated by torch.compile get an inductor badge.

  • 📉 Trace Size Guardrails: A launch event's function is an opaque backend handle documented as None and 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), violated launch.schema.json, and stretched the viewer page to hundreds of thousands of pixels. Fixed in three layers: summarize_launch_function gates the field by size at the writer (documented shapes pass through; short strings kept; anything larger collapses to <bytes: N bytes omitted>, measured with len() so a multi-MB repr is never materialized); summarize_oversized_strings() applies the same guard at parse time for traces already on disk, placed after compute_launch_event_hash so group hashes stay byte-identical; and a new TruncatedValue component collapses over-long values in the viewer with a Show more toggle, 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_dbg in parse/ir_parser.py, registered as llvm_dbg on the base adapter. Handles plain !DILocation, multi-level inlinedAt chains, !dbg pointing at a DISubprogram, and non-LLVM input. New TestLLVMDebugMappings and TestStageParserContract suites.
  • Inlined-code attribution across MLIR, PTX, AMDGCN and SASS (#435): inlined_at_file / inlined_at_line are now written by every stage and read by create_python_mapping, tested with is not None rather 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 derived amd_buffer_ops field so users can tell directly whether AMD buffer ops are enabled rather than inferring it from raw op counts. enabled is true only when every global memory op in AMDGCN is a buffer op; status distinguishes all_buffer / partial / none / unknown, with per-op counts and a human-readable reason. Emitted by both the adapter-driven analyzer and the legacy fallback; ir_analysis.schema.json and the website updated to match.

🎨 Website: Monaco Migration

  • Monaco core : MonacoCodePanel.tsx (285 lines) plus monaco-setup.ts, highlightMath.ts, irGrammars.ts (174 lines) and registerIrLanguages.ts — ESM loader, inline worker, panel core and IR languages.
  • Comparison V2: CodeComparisonViewV2 three-panel component with a dedicated fixture page, and monaco/comparisonMapping.ts carrying 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, V1 CodeComparisonView.tsx, highlightEvents bus, LegacyRulerAdapter and the Prism mapLanguageToHighlighter adapter 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: Wrap off/on for the single IR view and the comparison view (all three panels), persisted via the same wrap URL 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 writes localStorage.
  • 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> (defer is ignored on src-less inline scripts, so a head-placed bundle ran before #root existed), 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:none instead of unmounting (mount/unmount of the Monaco diff editor dominated every switch); Monaco's automaticLayout was replaced with a manual ResizeObserver that skips zero-size notifications and passes explicit container dimensions, removing ~5s of relayouts per switch; and the onDidLayoutChange→applyWrap update/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_THRESHOLD to 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 shared EMPTY_HIGHLIGHTED_LINES constant and CodeViewInner now 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.py extracts comma-separated allowlist parsing and fnmatch matching, with the existing structured_logging wrappers and trace-time behavior left intact.
  • Complete-group filtering during parse: parse/kernel_selection.py builds 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.validation shipped schemas for only 5 of the 8 event types, and validate_record() passes unknown types through untouched — so every autotune record in every trace was silently unvalidated. Schemas for the raw autotune record and the derived autotune_analysis / autotune_summary records were transcribed from their producers and registered in _SCHEMA_FILES.
  • pt_info attempt field fix: _determine_output_fname() read pt_info.get("attempt_id", 0), but nothing writes attempt_id — the writer emits attempt. Split-by-frame output filenames now carry the real attempt number.
  • Remote-input seam for tritonparse diff (#431, #432): New diff/core/input_resolver.py introduces a ResolvedInput(local_path, display, json_url) container plus resolve_input() and is_remote(). OSS accepts local paths only; the seam lets deployments plug in their own fetch/upload backend. tritonparse diff also gained --no-url, --no-share and --max-urls (default 10, 0 for 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_function gates 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.json widens function to ["integer", "string", "null"].
  • Parse-side summarization: summarize_oversized_strings() recursively replaces any string over MAX_VALUE_CHARS (4096) with <N chars omitted>, returning a new object so callers that hash the original are unaffected. Applied at ingest in parse_single_rank and to both sames and distribution values 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 the EXCLUDED_FIELDS filter the multi-launch path applies.
  • Viewer truncation: TruncatedValue with 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. asm is no longer rendered — it duplicates IR the compilation event already stores verbatim in file_content.

🤖 AI Module

  • Codex client: CodexClient for 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 MuseClient shelling out to muse exec headless mode, then upgraded to --json event parsing — final text from the terminal event with a delta-concat fallback, session id from the event stream, model id from run.model.configured, and real streaming in chat_stream via argv Popen yielding run.output.delta chunks. The strict single-turn prompt validation and CLI failure formatting shared with CodexClient were extracted into module-level helpers.
  • Provider selection: New create_llm_client factory (claude / codex / muse, default claude) wired into AIDiffAnalyzer and AICompatFixer. --ai-provider added to compat-build, and --ai-model / --ai-provider added to the diff command, 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 the muse provider enforces this at the prompt level only.

🔴 AMD / ROCm

  • MI350 CI job (#427): New test-rocm-nightly leg in the Tests workflow for AMD MI350 (gfx950). Host-direct run (ROCm needs /dev/kfd passthrough), torch nightly rocm7.1 plus an upstream Triton nightly pin via the shared resolver, reusing install-project.sh and run-tests.sh. Gated on workflow_dispatch while stabilizing. Also adds tests/cpu/test_device_arch.py and tests/cpu/test_disasm.py arch helpers.
  • Buffer-ops regression coverage (#428): A realistic AMDGCN-block fixture in TestAmdBufferOpsStatus locking line-based counting semantics (CPU-runnable), plus tests/gpu/test_amd_buffer_ops.py — an AMD-only e2e asserting a well-formed amd_buffer_ops payload on live gfx9xx.

🧰 Tracing & Configuration

  • init(enable_full_python_source=...): extract_python_source_info() captures only the kernel's function definition unless TRITON_FULL_PYTHON_SOURCE=1 is exported, which is actively wrong for nested kernels — a thin triton.jit wrapper 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 an init() keyword mirroring the existing enable_sass_dump.
  • TritonParseManager reaches every init() 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_source and compression are now declared with init()'s own types and defaults and forwarded unchanged.
  • Launch tracing reaches inductor's JIT hook gate: torch._inductor.config.run_jit_post_compile_hook is read from the environment once at torch import time but checked dynamically at compile time, so init(enable_trace_launch=True) only flipped tritonparse's own module global — inductor traces contained compilations and zero launches. init() now sets both TORCHINDUCTOR_RUN_JIT_POST_COMPILE_HOOK=1 for 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 caught CalledProcessError, OSError and UnicodeDecodeError around info.derive_func(source_path). When nvdisasm is missing, triton.knobs raises a plain RuntimeError, which escaped and aborted the entire trace write — a run produced no raw_logs at all even though every stage except SASS succeeded. Containment widened to except Exception, scoped to the derive_func call 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 of triton.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.tsx replaces the blocking alert() 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, with buildShareUrl() 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: diffWordWrap and both per-side wordWrapOverride fields were hardcoded to "on", so Wrap=off did nothing; hideUnchangedRegions: undefined does not reset a previously enabled value via updateOptions, 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=1 or ignore_ws=0 keep 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 Test push or manual run on main, 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 to import.meta.dirname for 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 discover imports every test*.py before running suites, and tests/test_add.py enabled launch tracing at import time, leaving TRITON_TRACE_LAUNCH set and failing test_init_without_launch_leaves_inductor_alone in 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, including monaco-editor 0.55.1→0.56.0, react/react-dom 19.2.7→19.2.8, vite 8.1.2→8.2.0, eslint 10.6.0→10.8.0, typescript-eslint 8.62.1→8.65.0, tailwindcss 4.3.2→4.3.3 and assorted type packages. A dompurify advisory was fixed and redundant/dead dependencies dropped. typescript is deliberately held at ^6.0.3: typescript-eslint declares peer typescript@">=4.8.4 <6.1.0" and no published version supports TS 7 yet. update_deps.sh itself was fixed and gained a --peer flag that reports ignored incompatible updates explicitly.
  • postcss 8.5.16 → 8.5.25 (#420), @humanfs/node 0.16.7 → 0.16.8 (#424).

Compatibility Notes

  • Website: the Prism renderer is gone. Monaco is now the only IR renderer. The ?renderer=prism escape hatch that existed during the migration has been removed along with the legacy components.
  • File Diff URL params word_level and ignore_ws are 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 raw asm or function values out of parsed output, they will now be elided. Nothing in the Python code or the web viewer reads either one.
  • launch.schema.json widens function from ["integer", "null"] to ["integer", "string", "null"] to accommodate short non-integer backend handles.
  • Split-by-frame output filenames may change. _determine_output_fname() now reads pt_info["attempt"] instead of the nonexistent attempt_id, so the a{attempt} segment carries the real attempt number rather than always 0.
  • Traces gain an llir key in source_mappings. Consumers that assumed a fixed set of mapped stages should tolerate the new key and the new llir_lines entries on other stages.
  • Inlined-helper mappings move. IR generated by tl.cdiv, tl.sum, tl.dot and 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 optional kernel_allowlist parameter and init() gains an optional enable_full_python_source parameter; both default to preserving prior behavior.

Upgrade Guidance

  1. Standard upgrade:

    pip install --upgrade tritonparse
  2. 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
  3. Filter a large trace down to the kernels you care about. Patterns are comma-separated fnmatch globs, 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")
  4. Capture the full Python source for nested or wrapper kernels. If your triton.jit entry 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):
        ...
  5. 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>
  6. 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