"""Nsight Compute metric set and the roofline quantities derived from it.
The metric names follow Nvidia's own roofline recipe: floating-point work is
counted from the executed SASS instructions (an FMA counts twice), memory
traffic is read at a selectable level of the hierarchy, and the two ceilings are
*measured* rather than looked up in a datasheet — every ``.peak_sustained``
metric is a per-cycle rate that becomes an absolute rate once multiplied with
the clock frequency that unit actually ran at during the kernel.
"""
from __future__ import annotations
# --------------------------------------------------------------------------- #
# Metrics requested from ncu
# --------------------------------------------------------------------------- #
#: Wall-clock duration and the clock rates the ceilings are scaled with.
TIMING_METRICS = [
"gpu__time_duration.sum",
"sm__cycles_elapsed.avg",
"sm__cycles_elapsed.avg.per_second",
]
#: Executed floating-point SASS instructions, per precision and operation.
FLOP_METRICS = [
f"sm__sass_thread_inst_executed_op_{precision}{operation}_pred_on.sum"
for precision in ("f", "d", "h")
for operation in ("add", "mul", "fma")
]
#: Tensor-core instructions; reported for context, never counted as FLOPs.
TENSOR_METRICS = ["sm__inst_executed_pipe_tensor.sum"]
#: Bytes moved and the achievable bytes/cycle, per level of the hierarchy.
MEMORY_METRICS = [
"dram__bytes.sum",
"dram__bytes.sum.peak_sustained",
"dram__cycles_elapsed.avg.per_second",
"lts__t_bytes.sum",
"lts__t_bytes.sum.peak_sustained",
"lts__cycles_elapsed.avg.per_second",
"l1tex__t_bytes.sum",
"l1tex__t_bytes.sum.peak_sustained",
"l1tex__cycles_elapsed.avg.per_second",
]
#: Achievable floating-point instructions per cycle (the compute ceilings).
PEAK_METRICS = [
"sm__sass_thread_inst_executed_op_ffma_pred_on.sum.peak_sustained",
"sm__sass_thread_inst_executed_op_dfma_pred_on.sum.peak_sustained",
"sm__sass_thread_inst_executed_op_hfma_pred_on.sum.peak_sustained",
]
#: Launch configuration, useful to explain a point that sits far below the roof.
LAUNCH_METRICS = [
"launch__grid_size",
"launch__block_size",
"launch__waves_per_multiprocessor",
"sm__throughput.avg.pct_of_peak_sustained_elapsed",
"gpu__dram_throughput.avg.pct_of_peak_sustained_elapsed",
]
#: Every metric collected by ``ppbcc profile``.
ROOFLINE_METRICS = (
TIMING_METRICS
+ FLOP_METRICS
+ TENSOR_METRICS
+ MEMORY_METRICS
+ PEAK_METRICS
+ LAUNCH_METRICS
)
# --------------------------------------------------------------------------- #
# Precisions and memory levels
# --------------------------------------------------------------------------- #
#: Precision key -> (ncu instruction infix, human-readable label).
PRECISIONS = {
"fp32": ("f", "FP32"),
"fp64": ("d", "FP64"),
"fp16": ("h", "FP16"),
}
#: Memory level key -> (ncu counter base, human-readable label).
MEMORY_LEVELS = {
"dram": ("dram__bytes", "DRAM"),
"l2": ("lts__t_bytes", "L2"),
"l1": ("l1tex__t_bytes", "L1/TEX"),
}
#: ``dram__bytes`` is clocked by ``dram__cycles_elapsed``, the others by the
#: unit that shares their name prefix.
MEMORY_LEVEL_CLOCK = {
"dram": "dram__cycles_elapsed.avg.per_second",
"l2": "lts__cycles_elapsed.avg.per_second",
"l1": "l1tex__cycles_elapsed.avg.per_second",
}
# --------------------------------------------------------------------------- #
# Units
# --------------------------------------------------------------------------- #
#: Units of the derived roofline columns, as written into the CSV header.
DERIVED_UNITS = {
"Grid Size": "blocks",
"Block Size": "threads/block",
"Duration": "s",
"FLOP": "FLOP",
"FLOP FP32": "FLOP",
"FLOP FP64": "FLOP",
"FLOP FP16": "FLOP",
"Memory Traffic": "Byte",
"Arithmetic Intensity": "FLOP/Byte",
"Performance": "FLOP/s",
"Peak Performance": "FLOP/s",
"Peak Bandwidth": "Byte/s",
}
[docs]
def column_unit(column: str) -> str | None:
"""Return the unit of a profiling column.
Raw metrics follow Nsight Compute's naming scheme, from which their unit
follows (checked against ``IMetric.unit()`` of the reports): ``.sum`` of
bytes or instructions is a count, ``.peak_sustained`` the same per clock
cycle, ``.per_second`` a clock rate, ``pct_of_*`` a percentage.
Args:
column: Column name without a unit.
Returns:
The unit, or ``None`` for identifying and textual columns.
"""
if column in DERIVED_UNITS:
return DERIVED_UNITS[column]
if "__" not in column:
return None
if column == "gpu__time_duration.sum":
return "ns"
if "pct_of_peak" in column:
return "%"
if column.endswith(".per_second"):
return "cycle/s"
if column.startswith("launch__"):
return {"launch__grid_size": "blocks", "launch__block_size": "threads/block"}.get(
column, "1"
)
quantity = "Byte" if "bytes" in column else "inst" if "inst" in column else "cycle"
return f"{quantity}/cycle" if column.endswith(".peak_sustained") else quantity
[docs]
def flop_columns(precision: str) -> tuple[str, str, str]:
"""Return the add/mul/fma metric names of one precision.
Args:
precision: Key of :data:`PRECISIONS` (``fp32``, ``fp64`` or ``fp16``).
Returns:
The ``add``, ``mul`` and ``fma`` metric names, in that order.
"""
infix = PRECISIONS[precision][0]
return tuple( # type: ignore[return-value]
f"sm__sass_thread_inst_executed_op_{infix}{operation}_pred_on.sum"
for operation in ("add", "mul", "fma")
)
[docs]
def peak_flop_column(precision: str) -> str:
"""Return the ``peak_sustained`` FMA metric name of one precision.
Args:
precision: Key of :data:`PRECISIONS`.
Returns:
The metric name holding the achievable FMA instructions per cycle.
"""
infix = PRECISIONS[precision][0]
return (
f"sm__sass_thread_inst_executed_op_{infix}fma_pred_on.sum.peak_sustained"
)