Skip to content

Commit ab34d43

Browse files
FindHaometa-codesync[bot]
authored andcommitted
Add info CLI Subcommand (#210)
Summary: This PR adds the `info` subcommand to query kernel information from NDJSON trace files. It automatically detects raw logs and parses them, enabling users to list kernels and their launches without manual parsing. ## Changes - **`info/cli.py`** (NEW): - `_add_info_args()`: Add CLI arguments for info subcommand - `info_command()`: Main command implementation - Auto-detects raw logs (no launch_diff events) and parses them automatically - Supports listing all kernels or launches for a specific kernel - Provides fuzzy matching suggestions when kernel not found - **`info/parse_helper.py`** (NEW): - `parse_and_compress_raw_log()`: Parse and compress raw log files - Reuses `parse_single_file()` and `gzip_single_file()` from parse module - Handles file naming for various input formats (.ndjson, .ndjson.gz, .bin.ndjson) - **`info/kernel_query.py`**: - `list_launches_for_kernel()`: List all launches for a specific kernel with grid info - `find_similar_kernels()`: Fuzzy matching using `difflib.get_close_matches()` - `list_kernels_fast()`: Optimized listing using `launch_diff` events when available - Merges kernels with same name (sums up launches) - Falls back to `list_kernels()` for raw logs - **`info/__init__.py`**: - Export new functions: `list_launches_for_kernel`, `find_similar_kernels`, `list_kernels_fast` - **`cli.py`**: - Add `info` subcommand integration - Import `_add_info_args` and `info_command` from `info.cli` - Update help examples - **`tests/test_tritonparse.py`**: - `test_info_kernel_query_functions()`: Combined unit test for 3 query functions - `test_info_list_kernels()`: Integration test for listing all kernels - `test_info_kernel_launches()`: Integration test for listing kernel launches - `test_info_kernel_not_found()`: Integration test for error handling with suggestions ## Usage ```bash # List all kernels with launch counts tritonparseoss info trace.ndjson # Output example: # Kernels in trace.ndjson: # fused_op_kernel 4 launches (id: 0-3) # matmul_kernel 1553 launches (id: 0-1552) # List launches for a specific kernel tritonparseoss info trace.ndjson --kernel matmul_kernel # Output example: # Launches for 'matmul_kernel': # id= 0 line 123 grid=[128, 1, 1] # id= 1 line 456 grid=[256, 1, 1] # ... # Works with raw logs (auto-parses) tritonparseoss info raw_trace.ndjson # Automatically parses and compresses, then queries ``` ## Features - **Auto-parsing**: Automatically detects and parses raw logs (no launch_diff events) - **Compression**: Parsed files are automatically compressed using parse module's gzip functionality - **Performance**: Uses `launch_diff` events for fast kernel listing when available - **Error handling**: Suggests similar kernel names when kernel not found - **Temporary files**: Parsed files saved to temp directory (not cleaned up, user can reuse) ## Testing - 1 combined unit test covering 3 query functions - 3 integration tests covering CLI functionality - All tests use real data from test fixtures - Tests verify both fast path (launch_diff) and slow path (full traversal) ## Notes - Temporary directories are not cleaned up (as per design requirement) - Parsed files are compressed (.ndjson.gz) for consistency with parse module - Kernel name matching is case-sensitive (exact match only) - Fuzzy matching uses 0.6 cutoff threshold for similarity Pull Request resolved: #210 Test Plan: ```bash % python -m unittest tests.test_tritonparse.TestTritonparseCPU.test_info_kernel_query_functions tests.test_tritonparse.TestTritonparseCPU.test_info_list_kernels tests.test_tritonparse.TestTritonparseCPU.test_info_kernel_launches tests.test_tritonparse.TestTritonparseCPU.test_info_kernel_not_found -v test_info_kernel_query_functions (tests.test_tritonparse.TestTritonparseCPU.test_info_kernel_query_functions) Test info module kernel query functions. ... ok test_info_list_kernels (tests.test_tritonparse.TestTritonparseCPU.test_info_list_kernels) Integration test: info command lists all kernels. ... ok test_info_kernel_launches (tests.test_tritonparse.TestTritonparseCPU.test_info_kernel_launches) Integration test: info command lists launches for specific kernel. ... ok test_info_kernel_not_found (tests.test_tritonparse.TestTritonparseCPU.test_info_kernel_not_found) Integration test: info command handles kernel not found. ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.708s OK ``` Reviewed By: wychi Differential Revision: D88171129 Pulled By: FindHao fbshipit-source-id: ff464052f8d80819912b65551a6d87ba8a184fa2
1 parent 3b40ee8 commit ab34d43

File tree

6 files changed

+420
-0
lines changed

6 files changed

+420
-0
lines changed

tests/test_tritonparse.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,114 @@ def test_reproduce_launch_id_out_of_range(self):
614614
finally:
615615
self.cleanup_temp_reproduce_dir(temp_dir)
616616

617+
def test_info_kernel_query_functions(self):
618+
"""Test info module kernel query functions."""
619+
from tritonparse.info.kernel_query import (
620+
find_similar_kernels,
621+
list_kernels,
622+
list_kernels_fast,
623+
list_launches_for_kernel,
624+
)
625+
from tritonparse.tools.prettify_ndjson import load_ndjson
626+
627+
gz_file = self._get_test_ndjson_file()
628+
events = load_ndjson(gz_file)
629+
630+
# Test list_launches_for_kernel
631+
launches = list_launches_for_kernel(events, "fused_op_kernel")
632+
self.assertGreater(len(launches), 0)
633+
self.assertEqual(launches[0].launch_id, 0)
634+
self.assertIsInstance(launches[0].grid, list)
635+
636+
# Test list_launches_for_kernel with non-existent kernel
637+
with self.assertRaises(ValueError) as cm:
638+
list_launches_for_kernel(events, "nonexistent_kernel")
639+
self.assertIn("not found", str(cm.exception))
640+
641+
# Test find_similar_kernels
642+
similar = find_similar_kernels(events, "fused_op", n=3)
643+
self.assertGreater(len(similar), 0)
644+
self.assertIn("fused_op_kernel", similar)
645+
646+
similar = find_similar_kernels(events, "fused_op_kernel", n=3)
647+
self.assertIn("fused_op_kernel", similar)
648+
649+
similar = find_similar_kernels(events, "xyz_abc_123", n=3)
650+
self.assertEqual(len(similar), 0)
651+
652+
# Test list_kernels_fast (should use launch_diff and match list_kernels)
653+
kernels_fast = list_kernels_fast(events)
654+
self.assertGreater(len(kernels_fast), 0)
655+
656+
kernels_slow = list_kernels(events)
657+
fast_dict = {k.name: k.total_launches for k in kernels_fast}
658+
slow_dict = {k.name: k.total_launches for k in kernels_slow}
659+
self.assertEqual(fast_dict, slow_dict)
660+
661+
def test_info_list_kernels(self):
662+
"""Integration test: info command lists all kernels."""
663+
import sys
664+
from io import StringIO
665+
666+
from tritonparse.info.cli import info_command
667+
668+
gz_file = self._get_test_ndjson_file()
669+
670+
# Capture stdout
671+
old_stdout = sys.stdout
672+
sys.stdout = captured_output = StringIO()
673+
674+
try:
675+
info_command(str(gz_file), kernel_name=None)
676+
output = captured_output.getvalue()
677+
self.assertIn("Kernels in", output)
678+
self.assertIn("launches", output)
679+
finally:
680+
sys.stdout = old_stdout
681+
682+
def test_info_kernel_launches(self):
683+
"""Integration test: info command lists launches for specific kernel."""
684+
import sys
685+
from io import StringIO
686+
687+
from tritonparse.info.cli import info_command
688+
689+
gz_file = self._get_test_ndjson_file()
690+
691+
# Capture stdout
692+
old_stdout = sys.stdout
693+
sys.stdout = captured_output = StringIO()
694+
695+
try:
696+
info_command(str(gz_file), kernel_name="fused_op_kernel")
697+
output = captured_output.getvalue()
698+
self.assertIn("Launches for 'fused_op_kernel'", output)
699+
self.assertIn("id=", output)
700+
self.assertIn("line", output)
701+
finally:
702+
sys.stdout = old_stdout
703+
704+
def test_info_kernel_not_found(self):
705+
"""Integration test: info command handles kernel not found."""
706+
import sys
707+
from io import StringIO
708+
709+
from tritonparse.info.cli import info_command
710+
711+
gz_file = self._get_test_ndjson_file()
712+
713+
# Capture stdout
714+
old_stdout = sys.stdout
715+
sys.stdout = captured_output = StringIO()
716+
717+
try:
718+
with self.assertRaises(ValueError):
719+
info_command(str(gz_file), kernel_name="nonexistent_kernel")
720+
output = captured_output.getvalue()
721+
self.assertIn("not found", output)
722+
finally:
723+
sys.stdout = old_stdout
724+
617725

618726
class TestTritonparseCUDA(unittest.TestCase):
619727
"""CUDA tests (require GPU)"""

tritonparse/cli.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from importlib.metadata import PackageNotFoundError, version
55

66
from .common import is_fbcode
7+
from .info.cli import _add_info_args, info_command
78
from .reproducer.cli import _add_reproducer_args
89
from .reproducer.orchestrator import reproduce
910
from .utils import _add_parse_args, unified_parse
@@ -31,6 +32,8 @@ def main():
3132
"Examples:\n"
3233
f" {prog_name} parse /path/to/logs --out parsed_output\n"
3334
f" {prog_name} reproduce /path/to/trace.ndjson --line 1 --out-dir repro_output\n"
35+
f" {prog_name} info /path/to/trace.ndjson\n"
36+
f" {prog_name} info /path/to/trace.ndjson --kernel matmul_kernel\n"
3437
),
3538
formatter_class=argparse.RawDescriptionHelpFormatter,
3639
)
@@ -60,6 +63,14 @@ def main():
6063
_add_reproducer_args(repro_parser)
6164
repro_parser.set_defaults(func="reproduce")
6265

66+
# info subcommand
67+
info_parser = subparsers.add_parser(
68+
"info",
69+
help="Query kernel information from trace file",
70+
)
71+
_add_info_args(info_parser)
72+
info_parser.set_defaults(func="info")
73+
6374
args = parser.parse_args()
6475

6576
if args.func == "parse":
@@ -89,6 +100,8 @@ def main():
89100
kernel_import=args.kernel_import,
90101
replacer=replacer,
91102
)
103+
elif args.func == "info":
104+
info_command(input_path=args.input, kernel_name=args.kernel)
92105
else:
93106
raise RuntimeError(f"Unknown command: {args.func}")
94107

tritonparse/info/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@
1111

1212
from tritonparse.info.kernel_query import (
1313
find_launch_index_by_kernel,
14+
find_similar_kernels,
1415
KernelSummary,
1516
LaunchInfo,
1617
list_kernels,
18+
list_kernels_fast,
19+
list_launches_for_kernel,
1720
)
1821

1922
__all__ = [
2023
"KernelSummary",
2124
"LaunchInfo",
2225
"list_kernels",
26+
"list_kernels_fast",
27+
"list_launches_for_kernel",
2328
"find_launch_index_by_kernel",
29+
"find_similar_kernels",
2430
]

tritonparse/info/cli.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
3+
"""
4+
CLI implementation for the info subcommand.
5+
6+
This module provides command-line interface for querying kernel information
7+
from NDJSON trace files.
8+
"""
9+
10+
import argparse
11+
import tempfile
12+
from typing import Optional
13+
14+
from tritonparse.info.kernel_query import (
15+
find_similar_kernels,
16+
list_kernels_fast,
17+
list_launches_for_kernel,
18+
)
19+
from tritonparse.info.parse_helper import parse_and_compress_raw_log
20+
from tritonparse.tools.prettify_ndjson import load_ndjson
21+
22+
23+
def _add_info_args(parser: argparse.ArgumentParser) -> None:
24+
"""Add arguments for the info subcommand."""
25+
parser.add_argument(
26+
"input",
27+
help="Path to ndjson/ndjson.gz/.bin.ndjson file",
28+
)
29+
parser.add_argument(
30+
"--kernel",
31+
type=str,
32+
default=None,
33+
help="Kernel name to list launches for",
34+
)
35+
36+
37+
def info_command(input_path: str, kernel_name: Optional[str] = None) -> None:
38+
"""
39+
Main function for the info command.
40+
41+
Args:
42+
input_path: Path to ndjson file
43+
kernel_name: Optional kernel name to list launches for
44+
"""
45+
# 1. Load and detect type
46+
events = load_ndjson(input_path)
47+
has_launch_diff = any(e.get("event_type") == "launch_diff" for e in events)
48+
49+
# 2. If no launch_diff, auto-parse
50+
if not has_launch_diff:
51+
print(
52+
f"Input file '{input_path}' appears to be raw log (no launch_diff events)."
53+
)
54+
print("Parsing automatically to generate launch_diff events...")
55+
56+
temp_dir = tempfile.mkdtemp(prefix="tritonparse_info_")
57+
58+
try:
59+
# Parse and compress (reuses parse module's functions)
60+
parsed_file = parse_and_compress_raw_log(
61+
input_path,
62+
output_dir=temp_dir,
63+
split_inductor_compilations=False,
64+
verbose=False,
65+
)
66+
67+
# Load compressed file (load_ndjson supports .ndjson.gz)
68+
events = load_ndjson(parsed_file)
69+
70+
print(f"✓ Parsed and compressed file: {parsed_file}")
71+
print(f" (Temporary directory: {temp_dir})")
72+
except Exception as e:
73+
raise RuntimeError(f"Failed to parse input file '{input_path}': {e}") from e
74+
else:
75+
print(f"Using parsed trace file: {input_path}")
76+
77+
# 3. Process query
78+
if kernel_name:
79+
# List launches for specific kernel
80+
try:
81+
launches = list_launches_for_kernel(events, kernel_name)
82+
print(f"\nLaunches for '{kernel_name}':")
83+
print("-" * 60)
84+
for launch in launches:
85+
grid_str = str(launch.grid) if launch.grid else "N/A"
86+
print(
87+
f" id={launch.launch_id:3d} line {launch.line_index:5d} grid={grid_str}"
88+
)
89+
except ValueError as e:
90+
error_msg = str(e)
91+
print(f"\nError: {error_msg}")
92+
# Try to suggest similar kernels
93+
try:
94+
similar = find_similar_kernels(events, kernel_name, n=3)
95+
if similar:
96+
print("\nDid you mean one of these?")
97+
all_kernels = list_kernels_fast(
98+
events
99+
) # Use fast path for consistency
100+
kernel_dict = {k.name: k for k in all_kernels}
101+
for name in similar:
102+
count = kernel_dict[name].total_launches
103+
print(f" - {name} ({count} launches)")
104+
print("\nUse 'tritonparseoss info <file>' to list all kernels.")
105+
except Exception:
106+
pass # Ignore errors in suggestion
107+
raise
108+
else:
109+
# List all kernels
110+
kernels = list_kernels_fast(events)
111+
print(f"\nKernels in {input_path}:")
112+
print("-" * 60)
113+
for kernel in kernels:
114+
if kernel.total_launches > 0:
115+
max_id = kernel.total_launches - 1
116+
print(
117+
f" {kernel.name:30s} {kernel.total_launches:3d} launches "
118+
f"(id: 0-{max_id})"
119+
)
120+
else:
121+
print(f" {kernel.name:30s} {kernel.total_launches:3d} launches")

0 commit comments

Comments
 (0)