|
18 | 18 | logger.addHandler(logging.NullHandler()) |
19 | 19 |
|
20 | 20 |
|
| 21 | +@contextmanager |
| 22 | +def locked_clocks(device: int = 0, clock_mhz: int | None = None): |
| 23 | + """Lock GPU SM clocks for stable benchmarking. |
| 24 | +
|
| 25 | + Uses ``sudo nvidia-smi -lgc`` to lock and ``sudo nvidia-smi -rgc`` to |
| 26 | + reset. Will prompt for a password in interactive terminals. |
| 27 | +
|
| 28 | + Args: |
| 29 | + device: CUDA device index. |
| 30 | + clock_mhz: SM clock frequency in MHz. If None, locks to the GPU's max SM clock. |
| 31 | + """ |
| 32 | + import subprocess |
| 33 | + |
| 34 | + if clock_mhz is None: |
| 35 | + clock_mhz = int( |
| 36 | + subprocess.check_output( |
| 37 | + [ |
| 38 | + "nvidia-smi", |
| 39 | + "-i", |
| 40 | + str(device), |
| 41 | + "--query-gpu=clocks.max.sm", |
| 42 | + "--format=csv,noheader,nounits", |
| 43 | + ], |
| 44 | + text=True, |
| 45 | + ).strip() |
| 46 | + ) |
| 47 | + |
| 48 | + subprocess.check_call( |
| 49 | + ["sudo", "nvidia-smi", "-i", str(device), "-lgc", f"{clock_mhz},{clock_mhz}"] |
| 50 | + ) |
| 51 | + logger.info(f"Locked GPU {device} SM clocks to {clock_mhz} MHz") |
| 52 | + try: |
| 53 | + yield clock_mhz |
| 54 | + finally: |
| 55 | + subprocess.call(["sudo", "nvidia-smi", "-i", str(device), "-rgc"]) |
| 56 | + logger.info(f"Reset GPU {device} SM clocks") |
| 57 | + |
| 58 | + |
21 | 59 | def lazy_import_error(error_msg: str): |
22 | 60 | """Decorator that allows functions with imports to be defined without the dependency""" |
23 | 61 |
|
@@ -60,40 +98,48 @@ class ProfileConfig: |
60 | 98 |
|
61 | 99 |
|
62 | 100 | def benchmark_torch_function_in_microseconds(func: Callable, *args, **kwargs) -> float: |
63 | | - # warmup |
64 | | - for _ in range(5): |
65 | | - func(*args, **kwargs) |
66 | | - t0 = benchmark.Timer( |
67 | | - stmt="func(*args, **kwargs)", |
68 | | - globals={"args": args, "kwargs": kwargs, "func": func}, |
69 | | - ) |
70 | | - return t0.adaptive_autorange(min_run_time=0.1).median * 1e6 |
| 101 | + lock = kwargs.pop("LOCK_CLOCKS", False) |
| 102 | + ctx = locked_clocks() if lock else nullcontext() |
| 103 | + with ctx: |
| 104 | + for _ in range(5): |
| 105 | + func(*args, **kwargs) |
| 106 | + t0 = benchmark.Timer( |
| 107 | + stmt="func(*args, **kwargs)", |
| 108 | + globals={"args": args, "kwargs": kwargs, "func": func}, |
| 109 | + ) |
| 110 | + return t0.adaptive_autorange(min_run_time=0.1).median * 1e6 |
71 | 111 |
|
72 | 112 |
|
73 | 113 | def benchmark_cuda_function_in_microseconds(func: Callable, *args, **kwargs) -> float: |
74 | 114 | """Thin wrapper around do_bench_using_profiling. |
75 | 115 |
|
76 | | - Accepts NUM_ITERS as a kwarg but removes it before calling func so it |
77 | | - never leaks into the benchmarked callable. |
| 116 | + Accepts NUM_ITERS, IS_VETTED_BENCHMARKING, and lock_clocks as kwargs but |
| 117 | + removes them before calling func so they never leak into the benchmarked callable. |
78 | 118 | """ |
79 | 119 | num_iters = kwargs.pop("NUM_ITERS", 100) |
80 | 120 | is_vetted_benchmarking = kwargs.pop("IS_VETTED_BENCHMARKING", False) |
81 | | - no_args = lambda: func(*args, **kwargs) |
82 | | - time = do_bench_using_profiling( |
83 | | - no_args, rep=num_iters, is_vetted_benchmarking=is_vetted_benchmarking |
84 | | - ) |
85 | | - return time * 1e3 |
| 121 | + lock = kwargs.pop("LOCK_CLOCKS", False) |
| 122 | + ctx = locked_clocks() if lock else nullcontext() |
| 123 | + with ctx: |
| 124 | + no_args = lambda: func(*args, **kwargs) |
| 125 | + return ( |
| 126 | + do_bench_using_profiling( |
| 127 | + no_args, rep=num_iters, is_vetted_benchmarking=is_vetted_benchmarking |
| 128 | + ) |
| 129 | + * 1e3 |
| 130 | + ) |
86 | 131 |
|
87 | 132 |
|
88 | 133 | @lazy_import_error("This function requires Triton. Please install it with: pip install triton") |
89 | 134 | def benchmark_cuda_function_in_microseconds_triton(func: Callable, *args, **kwargs) -> float: |
90 | 135 | """Thin wrapper around do_bench""" |
91 | | - from triton.testing import do_bench # Python caches this automatically |
92 | | - |
93 | | - no_args = lambda: func(*args, **kwargs) |
94 | | - time = do_bench(no_args) |
| 136 | + from triton.testing import do_bench |
95 | 137 |
|
96 | | - return time * 1e3 |
| 138 | + lock = kwargs.pop("LOCK_CLOCKS", False) |
| 139 | + ctx = locked_clocks() if lock else nullcontext() |
| 140 | + with ctx: |
| 141 | + no_args = lambda: func(*args, **kwargs) |
| 142 | + return do_bench(no_args) * 1e3 |
97 | 143 |
|
98 | 144 |
|
99 | 145 | def profile_function( |
|
0 commit comments