Global Performance Tuning (Attention)#

This example shows how to use TensorRT’s Global Performance Tuning (GPT) from Torch-TensorRT: discover knobs, sweep a small route space on a simple multi-head attention module, and apply the winning build_route.

Requirements: TensorRT with GPT enabled. This feature is available since TensorRT 11.1 and is currently not available in TensorRT-RTX or Windows. The script exits early if GPT is not available.

Imports and model#

[ ]:
import json
import os
import tempfile

import torch
import torch.nn as nn
import torch_tensorrt
from torch_tensorrt.dynamo import get_all_build_routes, is_global_perf_tuning_available


class SimpleAttention(nn.Module):
    """Minimal self-attention block suitable for a short GPT demo."""

    def __init__(self, embed_dim: int = 64, num_heads: int = 4) -> None:
        super().__init__()
        self.attn = nn.MultiheadAttention(
            embed_dim, num_heads, batch_first=True, bias=True
        )
        self.norm = nn.LayerNorm(embed_dim)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        y, _ = self.attn(x, x, x, need_weights=False)
        return self.norm(x + y)


def main() -> None:
    torch.manual_seed(0)
    if not torch.cuda.is_available():
        raise SystemExit("CUDA is required for this example.")
    if not is_global_perf_tuning_available():
        raise SystemExit(
            "Global Performance Tuning is not available on this TensorRT build "
            "(IBuilderConfig.build_route / all_build_routes)."
        )

    batch, seq, embed = 1, 32, 64
    model = SimpleAttention(embed_dim=embed, num_heads=4).eval().cuda().half()
    example_inputs = [
        torch.randn((batch, seq, embed), device="cuda", dtype=torch.float16)
    ]

    # Discover knobs (``trtexec --helpBuildRoute``).
    knobs = get_all_build_routes()
    print(f"tuner_version={knobs.get('tuner_version')}")
    print(f"num_knobs={len(knobs.get('tuner_options', []))}")

    tune_expr = (
        "-match_ragged_mha=[on|off] "
        "-kgen:codegen:cuda_tile=[0|1|2|3] "
        "-cask_fusion:num_tactics=[10|20] "
        "-peep:max=[1|2]"
    )
    print(f"tune_build_routes={tune_expr}")

    # Sweep routes (``trtexec --tuneBuildRoutes``).
    cache_base = os.path.join(
        tempfile.gettempdir(), "torch_trt_attention_tune_example.jsonl"
    )
    cache_dir = os.path.dirname(cache_base) or "."
    cache_stem = os.path.splitext(os.path.basename(cache_base))[0]
    # Clear cache if you don't want to continue tuning
    # for name in os.listdir(cache_dir):
    #     if name.startswith(cache_stem) and name.endswith(".jsonl"):
    #         os.remove(os.path.join(cache_dir, name))
    print(f"tuning_cache_file base={cache_base}")

    optimized = torch_tensorrt.compile(
        model,
        ir="dynamo",
        arg_inputs=example_inputs,
        min_block_size=1,
        tune_build_routes=tune_expr,  # tuning_continue=True,
        tuning_search="mixed",
        accuracy_threshold=0.01,
        accuracy_algorithm="cos",
        tuning_cache_file=cache_base,
    )

    with torch.no_grad():
        ref = model(*example_inputs)
        out = optimized(*example_inputs)

    cos = torch.nn.functional.cosine_similarity(
        ref.flatten().float(), out.flatten().float(), dim=0
    ).item()
    print(f"cosine_similarity(torch_eager, tuned_trt)={cos:.6f}")

    partition_caches = sorted(
        os.path.join(cache_dir, name)
        for name in os.listdir(cache_dir)
        if name.startswith(cache_stem + ".") and name.endswith(".jsonl")
    )
    assert partition_caches, f"expected per-partition cache under {cache_base}"
    cache_path = partition_caches[0]
    print(f"partition cache_path={cache_path}")

    with open(cache_path, "r", encoding="utf-8") as f:
        lines = [ln.strip() for ln in f if ln.strip()]

    header = json.loads(lines[0])
    print("cache header keys:", sorted(header.keys()))
    print(f"recorded iterations: {len(lines) - 1}")
    for line in lines[1:]:
        row = json.loads(line)
        print(
            f"  iter={row['iter']} crash={row['crash']} "
            f"gpu_time={row.get('gpu_time')} "
            f"route={row.get('build_route')}"
        )

    best_route = ""
    best_time = None
    for line in lines[1:]:
        row = json.loads(line)
        trial_time = row.get("gpu_time")
        if row.get("crash") or trial_time is None:
            continue
        if best_time is None or trial_time < best_time:
            best_time = trial_time
            best_route = row["build_route"]

    print(f"best cached route={best_route!r} gpu_time_ms={best_time}")
    if best_route:
        pinned = torch_tensorrt.compile(
            model,
            ir="dynamo",
            arg_inputs=example_inputs,
            require_full_compilation=True,
            min_block_size=1,
            build_route=best_route,
        )
        with torch.no_grad():
            pinned_out = pinned(*example_inputs)
        print(
            "pinned route max abs err vs torch eager:",
            (pinned_out.float() - ref.float()).abs().max().item(),
        )


if __name__ == "__main__":
    main()