Query Language: Thicket Tutorial

Thicket is a python-based toolkit for Exploratory Data Analysis (EDA) of parallel performance data that enables performance optimization and understanding of applications’ performance on supercomputers. It bridges the performance tool gap between being able to consider only a single instance of a simulation run (e.g., single platform, single measurement tool, or single scale) and finding actionable insights in multi-dimensional, multi-scale, multi-architecture, and multi-tool performance datasets.

1. Import Necessary Packages

To explore the structure and various capabilities of thicket components, we begin by importing necessary packages. These include python extensions and thicket’s statistical functions.

[1]:
import re

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display
from IPython.display import HTML
import hatchet as ht

import thicket as tt

display(HTML("<style>.container { width:80% !important; }</style>"))
[2]:
# Disable the Pandas 3 and Numpy Warnings for now
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=FutureWarning)

2. Read in Performance Profiles

For this notebook, we select profiles generated on Lawrence Livermore National Lab (LLNL) machine, lassen. We create a thicket object generated with the same block size of 128.

[3]:
problem_sizes = [
    "1048576",
    "2097152",
    "4194304",
    "8388608"
]
lassen1 = [f"../data/lassen/clang10.0.1_nvcc10.2.89_{x}/1/Base_CUDA-block_128.cali" for x in problem_sizes]
lassen2 = [f"../data/lassen/clang10.0.1_nvcc10.2.89_1048576/1/Base_CUDA-block_256.cali"]

# generate thicket(s)
th_lassen = tt.Thicket.from_caliperreader(lassen1, disable_tqdm=True)

3. Thicket Query Language

Use the Query Language

Thicket’s query language provides users the capability to select or query specific nodes based on the call tree component in thicket. The nodes in the performance data and statistics table are updated as well to reflect which nodes are remaining in the call tree.

[4]:
print("Initial call tree:")
print(th_lassen.tree("Total time"))
Initial call tree:
  _____ _     _      _        _
 |_   _| |__ (_) ___| | _____| |_
   | | | '_ \| |/ __| |/ / _ \ __|
   | | | | | | | (__|   <  __/ |_
   |_| |_| |_|_|\___|_|\_\___|\__|  v2024.1.0

1.781 RAJAPerf
├─ 0.007 Algorithm
│  ├─ 0.002 Algorithm_MEMCPY
│  ├─ 0.002 Algorithm_MEMSET
│  └─ 0.003 Algorithm_REDUCE_SUM
├─ 0.185 Apps
│  ├─ 0.007 Apps_DEL_DOT_VEC_2D
│  ├─ 0.039 Apps_ENERGY
│  ├─ 0.004 Apps_FIR
│  ├─ 0.035 Apps_HALOEXCHANGE
│  ├─ 0.005 Apps_HALOEXCHANGE_FUSED
│  ├─ 0.014 Apps_LTIMES
│  ├─ 0.014 Apps_LTIMES_NOVIEW
│  ├─ 0.008 Apps_NODAL_ACCUMULATION_3D
│  ├─ 0.048 Apps_PRESSURE
│  ├─ 0.006 Apps_VOL3D
│  └─ 0.004 Apps_ZONAL_ACCUMULATION_3D
├─ 0.358 Basic
│  ├─ 0.009 Basic_COPY8
│  ├─ 0.017 Basic_DAXPY
│  ├─ 0.017 Basic_DAXPY_ATOMIC
│  ├─ 0.012 Basic_IF_QUAD
│  ├─ 0.028 Basic_INIT3
│  ├─ 0.042 Basic_INIT_VIEW1D
│  ├─ 0.042 Basic_INIT_VIEW1D_OFFSET
│  ├─ 0.020 Basic_MULADDSUB
│  ├─ 0.021 Basic_NESTED_INIT
│  ├─ 0.127 Basic_PI_ATOMIC
│  ├─ 0.003 Basic_PI_REDUCE
│  ├─ 0.002 Basic_REDUCE3_INT
│  ├─ 0.016 Basic_REDUCE_STRUCT
│  └─ 0.003 Basic_TRAP_INT
├─ 0.386 Lcals
│  ├─ 0.062 Lcals_DIFF_PREDICT
│  ├─ 0.023 Lcals_EOS
│  ├─ 0.048 Lcals_FIRST_DIFF
│  ├─ 0.006 Lcals_FIRST_MIN
│  ├─ 0.048 Lcals_FIRST_SUM
│  ├─ 0.049 Lcals_GEN_LIN_RECUR
│  ├─ 0.034 Lcals_HYDRO_1D
│  ├─ 0.023 Lcals_HYDRO_2D
│  ├─ 0.047 Lcals_INT_PREDICT
│  ├─ 0.003 Lcals_PLANCKIAN
│  └─ 0.045 Lcals_TRIDIAG_ELIM
├─ 0.583 Polybench
│  ├─ 0.006 Polybench_2MM
│  ├─ 0.009 Polybench_3MM
│  ├─ 0.037 Polybench_ADI
│  ├─ 0.026 Polybench_ATAX
│  ├─ 0.037 Polybench_FDTD_2D
│  ├─ 0.206 Polybench_FLOYD_WARSHALL
│  ├─ 0.006 Polybench_GEMM
│  ├─ 0.007 Polybench_GEMVER
│  ├─ 0.026 Polybench_GESUMMV
│  ├─ 0.022 Polybench_HEAT_3D
│  ├─ 0.077 Polybench_JACOBI_1D
│  ├─ 0.102 Polybench_JACOBI_2D
│  └─ 0.022 Polybench_MVT
└─ 0.261 Stream
   ├─ 0.034 Stream_ADD
   ├─ 0.043 Stream_COPY
   ├─ 0.108 Stream_DOT
   ├─ 0.043 Stream_MUL
   └─ 0.034 Stream_TRIAD

Legend (Metric: Total time Min: 0.00 Max: 1.78 indices: {'profile': 1814734126})
1.60 - 1.78
1.25 - 1.60
0.89 - 1.25
0.54 - 0.89
0.18 - 0.54
0.00 - 0.18

name User code     Only in left graph     Only in right graph

Example Query 1: Find a Subgraph with a Specific Root

This example shows how to find a subtree starting with a specific root. More specifically, the query in this example finds a subtree rooted at the node with the name “Stream” followed by all nodes down to the leaf nodes.

NOTE: A DeprecationWarning is generated when using “old-style” queries (i.e., queries with QueryMatcher) if you have Hatchet>=2023.1.0 installed.

[5]:
query_ex1 = (
    ht.QueryMatcher()
    .match (
        ".",
        lambda row: row["name"].apply(
            lambda x: re.match(
                "Stream", x
            )
            is not None
        ).all()
    )
    .rel("*")
)

# applying the first query on the lassen thicket
th_ex1 = th_lassen.query(query_ex1)
print(th_ex1.tree("Total time"))
  _____ _     _      _        _
 |_   _| |__ (_) ___| | _____| |_
   | | | '_ \| |/ __| |/ / _ \ __|
   | | | | | | | (__|   <  __/ |_
   |_| |_| |_|_|\___|_|\_\___|\__|  v2024.1.0

0.261 Stream
├─ 0.034 Stream_ADD
├─ 0.043 Stream_COPY
├─ 0.108 Stream_DOT
├─ 0.043 Stream_MUL
└─ 0.034 Stream_TRIAD

Legend (Metric: Total time Min: 0.03 Max: 0.26 indices: {'profile': 1814734126})
0.24 - 0.26
0.19 - 0.24
0.15 - 0.19
0.10 - 0.15
0.06 - 0.10
0.03 - 0.06

name User code     Only in left graph     Only in right graph

Example Query 2: Find All Paths Ending with a Specific Node

This example shows how to find all paths of a GraphFrame ending with a specific node. More specifically, the queries in this example can be used to find paths ending with a node named “Stream”.

[6]:
query_ex2 = (
    ht.QueryMatcher()
    .match("*")
    .rel(
        ".",
        lambda row: row["name"].apply(
            lambda x: re.match(
                "Stream", x
            )
            is not None
        ).all()
    )
)

# applying the second query on the lassen thicket
th_ex2 = th_lassen.query(query_ex2)
print(th_ex2.tree("Total time"))
  _____ _     _      _        _
 |_   _| |__ (_) ___| | _____| |_
   | | | '_ \| |/ __| |/ / _ \ __|
   | | | | | | | (__|   <  __/ |_
   |_| |_| |_|_|\___|_|\_\___|\__|  v2024.1.0

1.781 RAJAPerf
└─ 0.261 Stream
   ├─ 0.034 Stream_ADD
   ├─ 0.043 Stream_COPY
   ├─ 0.108 Stream_DOT
   ├─ 0.043 Stream_MUL
   └─ 0.034 Stream_TRIAD

Legend (Metric: Total time Min: 0.03 Max: 1.78 indices: {'profile': 1814734126})
1.61 - 1.78
1.26 - 1.61
0.91 - 1.26
0.56 - 0.91
0.21 - 0.56
0.03 - 0.21

name User code     Only in left graph     Only in right graph

Example Query 3: Find All Paths with Specific Starting and Ending Nodes

This example shows how to find all call paths starting with and ending with specific nodes. More specifically, the query in this example finds paths starting with a node named “Stream” and ending with a node named “Stream_MUL”.

[7]:
query_ex3 = (
    ht.QueryMatcher()
    .match(
        ".",
        lambda row: row["name"].apply(
            lambda x: re.match(
                "Stream", x
            )
            is not None
        ).all()
    )
    .rel("*")
    .rel(
        ".",
        lambda row: row["name"].apply(
            lambda x: re.match(
                "Stream_MUL", x
            )
            is not None
        ).all()
    )
)

# applying the third query on the lassen thicket
th_ex3 = th_lassen.query(query_ex3)
print(th_ex3.tree("Total time"))
  _____ _     _      _        _
 |_   _| |__ (_) ___| | _____| |_
   | | | '_ \| |/ __| |/ / _ \ __|
   | | | | | | | (__|   <  __/ |_
   |_| |_| |_|_|\___|_|\_\___|\__|  v2024.1.0

0.261 Stream
└─ 0.043 Stream_MUL

Legend (Metric: Total time Min: 0.04 Max: 0.26 indices: {'profile': 1814734126})
0.24 - 0.26
0.20 - 0.24
0.15 - 0.20
0.11 - 0.15
0.06 - 0.11
0.04 - 0.06

name User code     Only in left graph     Only in right graph

Example Query 4: Find All Nodes for a Particular Software Library

This example shows how to find all call paths representing a specific software library. This example is simply a variant of finding a subtree with a given root shown in Example Query 1. The example query below can be adapted to find the nodes for a subset of the MPI library, for example. In our example, we look for subtrees rooted at PolyBench_2MM, Basic_DAXPY, and Apps_ENERGY.

[8]:
api_entrypoints = [
    "Polybench_2MM",
    "Basic_DAXPY",
    "Apps_ENERGY",
]

query_ex4 = (
    ht.QueryMatcher()
    .match(
        ".",
        lambda row: row["name"].apply(
            lambda x: x in api_entrypoints
        ).all()
    )
    .rel("*")
)

# applying the fourth query on the lassen thicket
th_ex4 = th_lassen.query(query_ex4)
print(th_ex4.tree("Total time"))
  _____ _     _      _        _
 |_   _| |__ (_) ___| | _____| |_
   | | | '_ \| |/ __| |/ / _ \ __|
   | | | | | | | (__|   <  __/ |_
   |_| |_| |_|_|\___|_|\_\___|\__|  v2024.1.0

0.039 Apps_ENERGY
0.017 Basic_DAXPY
0.006 Polybench_2MM

Legend (Metric: Total time Min: 0.01 Max: 0.04 indices: {'profile': 1814734126})
0.04 - 0.04
0.03 - 0.04
0.02 - 0.03
0.02 - 0.02
0.01 - 0.02
0.01 - 0.01

name User code     Only in left graph     Only in right graph

Example Query 5: Find All Paths through a Specific Node

This example shows how to find all call paths that pass through a specific node. More specifically, the query below finds all paths that pass through a node named “Stream”.

[9]:
query_ex5 = (
    ht.QueryMatcher()
    .match("*")
    .rel(
        ".",
        lambda row: row["name"].apply(
            lambda x: re.match(
                "Stream", x
            )
            is not None
        ).all()
    )
    .rel("*")
)

# applying the fifth query on the lassen thicket
th_ex5 = th_lassen.query(query_ex5)
print(th_ex5.tree("Total time"))
  _____ _     _      _        _
 |_   _| |__ (_) ___| | _____| |_
   | | | '_ \| |/ __| |/ / _ \ __|
   | | | | | | | (__|   <  __/ |_
   |_| |_| |_|_|\___|_|\_\___|\__|  v2024.1.0

1.781 RAJAPerf
└─ 0.261 Stream
   ├─ 0.034 Stream_ADD
   ├─ 0.043 Stream_COPY
   ├─ 0.108 Stream_DOT
   ├─ 0.043 Stream_MUL
   └─ 0.034 Stream_TRIAD

Legend (Metric: Total time Min: 0.03 Max: 1.78 indices: {'profile': 1814734126})
1.61 - 1.78
1.26 - 1.61
0.91 - 1.26
0.56 - 0.91
0.21 - 0.56
0.03 - 0.21

name User code     Only in left graph     Only in right graph