Skip to content

Semantic analysis agent and evals - #951

Open
amoskvic wants to merge 56 commits into
mainfrom
staging/semantic
Open

amoskvic wants to merge 56 commits into
mainfrom
staging/semantic

Conversation

@amoskvic

Copy link
Copy Markdown
Contributor

Overview

This PR introduces a revamped semantic analysis approach (fallback for the comparative graph mode, when there is no graph capture), as well as a new type of eval (partial eval): evaluation based on a partial execution of the general agentic workflow, with pass/fail determined by quality metrics derived from a gold standard.

General approach

The philosophy was to try to keep the changes to the main orchestrator to the absolute minimum. When the orchestrator detects the fallback condition (comparative + graph, but no capture info), a semantic subagent is invoked. The semantic subagent produces a diff_stats file using an LLM-driven analysis, with an analogous structure to a regular diff_stats. After that, the main orchestrator picks up from there (the new --precomputed_diff_stats_csv flag on generate_perf_report_pytorch.py / generate_perf_report_pytorch_inference.py (skips the internal TraceDiff and loads the semantic diff_stats.csv directly)).

Structure

The evals live in agent_evals/Analysis/partial_tests/, a new folder alongside the existing full-orchestrator eval harness. The main entry point is agent_evals/Analysis/partial_tests/run_partial_tests.sh. It invokes the semantic-comparison-agent workflow on the DECODE-only trace pairs listed in partial_test_cases.csv (one row per model: DeepSeek-R1, Qwen3-30B-A3B), then compares the resulting diff_stats.csv with the gold standard one (analysis_output_ref/semantic_purity_gold_diff_stats.csv, obtained by running the usual comparative mode with capture info on the same DECODE trace pair). Helper functions in agent_evals/Analysis/eval_utils/compare_lca_partitions.py (shared with tests/test_compare_lca_partitions.py) are primarily for computing the quality metrics to compare two different diff_stats files; partial_tests/eval_utils/semantic_partition_scripted_evals.py records per-run metrics (informational), and partial_tests/eval_utils/semantic_purity_aggregate.py is the actual pass/fail gate (averages strict_forward across repeats against a per-model floor).

The semantic agent logic itself lives in TraceLens/Agent/Analysis/skills/analysis-orchestrator/agents/semantic-comparison-agent.md (plus its three sub-agents: kernel-stem-preprocessing-agent.md, kernel-unification-agent.md, kernel-coherence-agent.md), with helper python functions in TraceLens/Agent/Analysis/semantic_analyses/ (extract_trace_data.py, extract_tree_context.py, pattern_finder.py, build_semantic_labels.py, kernel_unification.py, kernel_coherence.py, generate_semantic_diff.py, match_and_compare.py) and TraceLens/Agent/Analysis/utils/classify_kernels.py.

Validation

The changes were validated using the approach described above, with the current solution strongly outperforming the previous semantic analysis approach and a number of simpler baselines (e.g. random kernel grouping). One limitation: as of the time of the creation of this PR, the tests were run from a local agent, not using the on-cluster docker. This extra verification step is in progress.

Extra notes:

  • Removed dead code superseded by this approach: the old block-alignment harmonization
    agent (semantic-harmonization-agent.md) and its python backend (harmonization.py).
  • On the semantic-purity gate floors: set below the worst single observed run
    with a small margin (see semantic_purity_aggregate.py docstring).

kyle-hoffmeyer and others added 30 commits June 3, 2026 14:47
…unts mismatch

AMD capture traces sometimes record extra HIP dispatch API calls
(hipExtModuleLaunchKernel, hipLaunchKernel) that get consolidated
during graph optimization, causing a count mismatch vs graph kernels.
Previously the merge was skipped entirely for that execution root,
leaving its kernels as flat children of hipGraphLaunch with no cpu_op
ancestry.

Adds _align_capture_to_graph which scans forward through capture
dispatch events to match each graph kernel by name, discarding any
unmatched extras. verify_subtree_events now returns a (success,
aligned_capture_events) tuple so the caller can proceed with the
aligned subset when counts differ but names align.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
@amoskvic

amoskvic commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Update

  • Addressed all code/prompt quality comments.
  • Added unit tests.
  • Stripped away lots of code that was either outdated or scaffolding for future cross-framework comparisons.
  • Streamlined control flow, removing the overly complicated & costly machinery for "detecting" the need for semantic analysis.
  • Removed unnecessary trace loads.

Some things to be addressed in the future:

  • The pattern finder needs to be replaced with the updated SILO team implementation (if it proves usable for the task).
  • The pattern finder is currently excluded from unit testing as it should be replaced in the future.
  • Trace splitting should be deduplicated and replaced with the main trace split implementation. This might require modifications to the trace split functionality/api, and I'd like compartmentalize it into a separate follow-up PR. This PR, while large, is self-contained, and, for the most part, doesn't modify existing behavior outside of semantic analysis. I think it'd be good to keep it this way.

A note on comments:
There are a few (very few) comment threads I left unresolved where input would be helpful.

The rest is resolved. When I didn't write out a justification, the reasons for resolving them were either "the file/function in question is stripped away entirely," or "the code is improved and the issue fixed".

- If **Graph replay + capture**, ask for the **Capture Folder Path(s)**:
- `standalone`: one folder → `<capture_folder_path_1>`. Ask: "Please provide the full path to the graph capture traces folder"
- `comparative`: one folder per trace → `<capture_folder_path_1>` (primary/trace1) and `<capture_folder_path_2>` (comparison/trace2). Ask: "Please provide the graph capture traces folder for the primary trace and for the comparison trace."
- **Comparative + graph replay** (do not abort): collect capture folders for both traces when available. If capture is not available, the comparison uses the semantic path (see Step 0.5).

@tsrikris tsrikris Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"do not abort" seems redundant

I would instead reframe this section as, keeping 2 as is, something like
3. Graph replay only (<inference_exec_mode> = graph_replay_only):

  • standalone: Abort
  • comparative: Semantic analysis


## Step 0.5: Comparison Method (comparative only)

For `standalone`, skip this step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be removable with the prev. comment

## Step 1: Generate Performance Report

Use **`<analysis_mode>`** to determine which CLI tool to run and then **`<comparison_scope>`** to determine arguments.
Use **`<analysis_mode>`** to determine which CLI tool to run and then **`<comparison_scope>`** (and, for comparative, **`<comparison_method>`** from Step 0.5) to determine arguments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use <analysis_mode> to determine which CLI tool to run and then <comparison_scope> to determine arguments. can stay as is


## Step 1.5: Semantic Comparative Ordering (`<comparison_method>` = `semantic` only)

When `<comparison_scope>` = `comparative` and `<comparison_method>` = `semantic`, run Step 1 in this order:

@tsrikris tsrikris Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<inference_exec_mode> = graph_replay_only and scope comparative directly implies semantic, would just simplify the verbiage here

'''
Step 1.5: Semantic Comparative Ordering (<inference_exec_mode> = graph_replay_only )

  1. ....
  2. ...

'''

- **GPU Graph Replay**: raw trace JSON contains `hipGraphLaunch` or `cudaGraphLaunch`.
- **Default mode** (analysis_mode = `default`): Inform the user with `[DIAG:trace_quality:GPU_GRAPH_REPLAY]` that GPU graph replay was detected and that the default analysis mode supports typical PyTorch traces. **Abort** -- do not retry or continue.
- **Inference mode** (analysis_mode = `inference`): Graph launches are expected and supported if graph capture folder is provided, do not abort. If inference_exec_mode is `eager` (no capture folder was provided), continue.
- **Comparative scope**: graph replay is **supported**; the comparison method (tracediff+capture vs semantic) was set in Step 0.5 from capture availability, with no trace classification. Ensure capture folders were collected for both traces when available.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would just remove 692-696. Seems redundant with all that's been added

@@ -0,0 +1,137 @@
<!--
Copyright (c) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to change copyright dates to 2026

```json
{
"context_renames": {
"(platform 1 name):6": "qkv_projection",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback for follow up PR: Create a util function for this along with a validator, JSON generation leads to token wastage.

@@ -0,0 +1,75 @@
###############################################################################
# Copyright (c) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright flag

def parse_filename_metadata(filepath: str) -> Dict[str, Any]:
"""
Parse isl, osl, conc, tp from trace filename.
Example: mi355_tp1_isl1024_osl8_conc4_opt_asm64x256.pt.trace.json.gz

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend removing trace name

return result, kernels


def _write_split_regions(split_result, trace_path, output_dir): # pragma: no cover

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarifying question: are all intermediates for semantic stored inside a separate directory? Just making sure it's inside a sub-dir in the results directory of the agent

return n


def load_json(path):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there's a top level DataLoader class for opening traces

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be merged with another file? Or do you prefer leaving it separate



def _load(path):
return load_json(path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant load? Can directly call helper function

Comment thread .coveragerc
TraceLens/PerfModel/benchmarking/*
TraceLens/PerfModel/origami_helper.py
TraceLens/PerfModel/run_perf_model.py
# pattern_finder.py is slated for replacement by an external package; omit until then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for follow up PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending aligning file nomenclature with other set of evals

@@ -0,0 +1,8 @@
# Expanded fixtures -- regenerated from fixtures/*.tar.gz by run_partial_tests.sh.
# Only the tarballs under fixtures/ are committed (matches the e2e_tests_* convention).
/semantic_purity_deepseek_r1/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Results of tests should be in a separate directory. Check other evals directory for structure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go a level above in the .gitignore under evals directory.

<!--
Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.

See LICENSE for license information.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend merging this with the README in evals. Two levels of README seems messy. Can come in a follow up PR.

# below the worst healthy run and ~10x above collapse, so ordinary bucketing
# variance never false-alarms while any real collapse fails hard.
MIN_SHARED_BUCKETS = {
"semantic_purity_deepseek_r1": 10,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? Adding a new trace would require updating this then. Would suggest just a single value

CANDIDATE_RELPATH = os.path.join("tracediff_output", "diff_stats.csv")


def _write(results_path: str, rows: list[dict]) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming there is no such function inside the eval_utils?

semantic_partition_scripted_evals.py records per-run LCA-partition purity
metrics but does not gate on them (see that module's docstring) -- the
semantic-bucketing method has real run-to-run variance (observed on
Qwen3-30B-A3B), so a single run is not a reliable regression signal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specific comment needs to be removed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Receommend renaming all these as test_analysis_agent_semantic)...

@tsrikris tsrikris left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional comments/clarifications added.

@amoskvic could you please take a look?

  1. Addressing comments on code
  2. Synching changes to staging_agent's latest PR
  3. PR also requires updating the main TraceLens Agent README and the docs

@Ahmedhasssan-aig could you review the evals directory?
@kyle-hoffmeyer @ajassani on the Reporting and split_trace_annotation and a general review too

Comment on lines +1324 to +1325
"--comparison_json_path; unlike --comparison_json_path it may be "
"combined with --capture_folder."

@kyle-hoffmeyer kyle-hoffmeyer Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring is false. --comparison_json_path can be used with --capture_folder

type=str,
default=None,
help=(
"Path to a precomputed TraceDiff-schema diff_stats.csv (e.g. from "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the schema of the CSV output of semantic analysis is the same as TraceDiff, then maybe we generalize this to just say "Path to precomputed TraceDiff output". This would be useful in general for developers who have TraceDiff output and want to generate a comparative report without running through TraceDiff again.
I'd also change flag name to be more interpretable. Maybe "precomputed_tracediff_csv"?

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.

6 participants