Skip to content

Commit d9b28e8

Browse files
committed
[TRTLLM-14024][fix] Address nvMMH integration review findings
Keep split-K admission filtering when restoring unmatched swap orientations. Pin the process-wide NVMMH configuration while engines remain active, reject conflicting updates atomically, and release ownership during cleanup or GC. Equivalent policies reuse the installed snapshot. Cover engine configuration wiring and lifecycle, unmatched-swap admission, and deterministic complete swizzle annotations with regression tests. Add docstrings for changed helpers and correct the documented Blackwell BF16 field support. Validation: 43 autotuner tests and 2 Rubin MXFP8 scheduler tests passed on Hecate SM107. The four policy/admission cases passed again after the final identity-preservation adjustment. The LLM args golden manifest is unchanged. Signed-off-by: peaceh <103117813+peaceh-nv@users.noreply.github.com>
1 parent 94c57e6 commit d9b28e8

7 files changed

Lines changed: 265 additions & 45 deletions

File tree

‎docs/source/torch/adding_custom_kernels.md‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ This is wired up for the following dense GEMM runners. Each builds its validated
268268
|---|---|---|
269269
| SM100/SM103 | NVFP4 | `tile`, `cluster` (prune); `swizzle`, `cta_order` (annotate only) |
270270
| SM100/SM103 | blockwise FP8 | `tile`, `cluster` |
271-
| SM100/SM103 | BF16 | all five |
272-
| SM107 (Rubin) | block-scaled NVFP4, incl. the uGPU in-place variant | all five |
271+
| SM100/SM103 | BF16 | `tile`, `cluster`, `swizzle`, `cta_order` |
272+
| SM107 (Rubin) | block-scaled NVFP4 | all five |
273273
| SM107 (Rubin) | block-scaled MXFP8 | all five |
274274
| SM107 (Rubin) | per-tensor FP8 | `tile`, `cluster`, `cta_order` |
275275
| SM107 (Rubin) | BF16 | all five |
@@ -319,6 +319,12 @@ autotuner_nvmmh_config:
319319
max_tactics: 5 # Top-K signatures per modeled problem/orientation.
320320
```
321321
322+
All active model engines in one process must use the same effective policy,
323+
including the disabled default. Creating an engine with a conflicting policy
324+
or changing that policy through `configure_nvmmh()` raises `ValueError` without
325+
replacing the current configuration. Identical policies can be reused. After
326+
the last engine is cleaned up or garbage-collected, a new policy can be installed.
327+
322328
Compatible validated runner-specific tactics that NVMMH cannot represent are
323329
retained automatically and empirically profiled. The BF16 and SM107
324330
block-scaled runners supplement their validated preferred/fallback-cluster

‎tensorrt_llm/_torch/autotuner.py‎

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import json
1212
import math
1313
import os
14+
import threading
1415
import time
16+
import weakref
1517
from abc import ABC, abstractmethod
1618
from dataclasses import dataclass, field
1719
from functools import lru_cache
@@ -172,6 +174,7 @@ class NvMMHConfig:
172174
max_tactics: int = 5
173175

174176
def __post_init__(self) -> None:
177+
"""Validate policy values and normalize aliases and coupled search fields."""
175178
if not isinstance(self.enabled, bool):
176179
raise ValueError("NvMMHConfig.enabled must be a bool")
177180
if isinstance(self.max_tactics,
@@ -360,9 +363,11 @@ class CuteDSLTunableRunner(TunableRunner):
360363
"""Marker base for CuTe DSL runners that prefer CUPTI timing."""
361364

362365
def use_torch_profiler(self, tactic: Any) -> bool:
366+
"""Prefer CUDA activity timing for every CuTe DSL tactic."""
363367
return True
364368

365369
def profiling_timer_cache_key(self) -> Optional[str]:
370+
"""Separate CUPTI-ranked tactics from legacy timer cache entries."""
366371
return _TORCH_PROFILER_TIMER_KEY
367372

368373

@@ -659,6 +664,7 @@ def get_cache_key(
659664
apply_map_to_tuning_buckets: bool = True,
660665
timer_key_override: Optional[str] = None,
661666
) -> Tuple:
667+
"""Combine operation, runner, shapes, search policy, and timing backend."""
662668
cache_key = (
663669
custom_op,
664670
runner.__class__.__name__,
@@ -1061,12 +1067,15 @@ class AutoTuner:
10611067
_CUDA_GRAPH_DELAY_MICRO_SECS = 100
10621068
_instance = None
10631069
_nvmmh_config = NvMMHConfig()
1070+
_nvmmh_policy_lock = threading.RLock()
1071+
_nvmmh_policy_owners: weakref.WeakSet = weakref.WeakSet()
10641072
# CUPTI availability is process-wide. Keep the failure sticky across
10651073
# AutoTuner instances so every tactic does not repeat the same failed
10661074
# profiler initialization before falling back to CUDA events.
10671075
_torch_profiler_unavailable = False
10681076

10691077
def __init__(self, warmup=2, repeat=10, stream_delay_micro_secs=1000):
1078+
"""Initialize profiling settings, distributed state, statistics, and caches."""
10701079
# Increase log level for AutoTuner associated logger`
10711080
self._log_level_to_info = os.getenv(
10721081
"TLLM_AUTOTUNER_LOG_LEVEL_DEBUG_TO_INFO", '0') == '1'
@@ -1136,31 +1145,57 @@ def configure_nvmmh(
11361145
is replaced, so an invalid update leaves the previous policy intact.
11371146
Every ``AutoTuner`` instance shares this process-wide policy, which is
11381147
intentionally persistent across ``autotune`` contexts, instance
1139-
construction, and profiling-cache clears.
1148+
construction, and profiling-cache clears. Active model engines pin
1149+
this policy: a differing update raises without changing any state.
11401150
"""
1141-
keyword_values = (
1142-
enabled,
1143-
fields,
1144-
max_tactics,
1145-
)
1146-
if config is not None:
1147-
if any(value is not None for value in keyword_values):
1148-
raise ValueError(
1149-
"Pass either config or NVMMH keyword settings, not both")
1150-
if not isinstance(config, NvMMHConfig):
1151-
raise TypeError("config must be an NvMMHConfig")
1152-
new_config = config
1153-
else:
1154-
current = AutoTuner._nvmmh_config
1155-
new_config = NvMMHConfig(
1156-
enabled=current.enabled if enabled is None else enabled,
1157-
fields=current.fields if fields is None else fields,
1158-
max_tactics=(current.max_tactics
1159-
if max_tactics is None else max_tactics),
1151+
with AutoTuner._nvmmh_policy_lock:
1152+
keyword_values = (
1153+
enabled,
1154+
fields,
1155+
max_tactics,
11601156
)
1157+
if config is not None:
1158+
if any(value is not None for value in keyword_values):
1159+
raise ValueError(
1160+
"Pass either config or NVMMH keyword settings, not both"
1161+
)
1162+
if not isinstance(config, NvMMHConfig):
1163+
raise TypeError("config must be an NvMMHConfig")
1164+
new_config = config
1165+
else:
1166+
current = AutoTuner._nvmmh_config
1167+
new_config = NvMMHConfig(
1168+
enabled=current.enabled if enabled is None else enabled,
1169+
fields=current.fields if fields is None else fields,
1170+
max_tactics=(current.max_tactics
1171+
if max_tactics is None else max_tactics),
1172+
)
1173+
1174+
if new_config == AutoTuner._nvmmh_config:
1175+
return AutoTuner._nvmmh_config
1176+
if AutoTuner._nvmmh_policy_owners:
1177+
raise ValueError(
1178+
"All active model engines in one process must use the same "
1179+
"autotuner_nvmmh_config; clean up existing engines before "
1180+
"changing the policy.")
1181+
AutoTuner._nvmmh_config = new_config
1182+
return new_config
1183+
1184+
def _acquire_nvmmh_policy(self, owner: object, config: NvMMHConfig) -> None:
1185+
"""Pin one policy until the owning engine is cleaned up or collected.
1186+
1187+
Weak ownership also releases failed, partially initialized engines.
1188+
Registering an owner and validating its policy share the configuration
1189+
lock, so concurrent engine construction cannot replace a pinned policy.
1190+
"""
1191+
with AutoTuner._nvmmh_policy_lock:
1192+
self.configure_nvmmh(config)
1193+
AutoTuner._nvmmh_policy_owners.add(owner)
11611194

1162-
AutoTuner._nvmmh_config = new_config
1163-
return new_config
1195+
def _release_nvmmh_policy(self, owner: object) -> None:
1196+
"""Release an engine's policy pin without resetting the current policy."""
1197+
with AutoTuner._nvmmh_policy_lock:
1198+
AutoTuner._nvmmh_policy_owners.discard(owner)
11641199

11651200
class TacticsCapture:
11661201
"""Object returned by capture() that can be iterated to get all tactic combinations.
@@ -1688,6 +1723,7 @@ def _is_torch_profiler_unavailable_error(error: Exception) -> bool:
16881723

16891724
@classmethod
16901725
def _disable_torch_profiler(cls, error: Exception) -> None:
1726+
"""Latch a process-wide CUPTI failure and report the CUDA-event fallback."""
16911727
cls._torch_profiler_unavailable = True
16921728
logger.warning_once(
16931729
"[Autotuner] torch.profiler/CUPTI is unavailable; falling back "
@@ -1732,6 +1768,7 @@ def _profile_single_kernel(
17321768
use_torch_profiler = runner.use_torch_profiler(tactic)
17331769

17341770
def pure_profile(stream: torch.cuda.Stream, repeat: int):
1771+
"""Measure repeated launches on one stream using the selected timer."""
17351772
graph = torch.cuda.CUDAGraph() if use_cuda_graph else None
17361773

17371774
with torch.cuda.stream(stream):
@@ -1751,6 +1788,7 @@ def pure_profile(stream: torch.cuda.Stream, repeat: int):
17511788
self._dist.tp_barrier()
17521789

17531790
def launch_profiled_work():
1791+
"""Replay the captured batch or launch the same batch eagerly."""
17541792
if use_cuda_graph:
17551793
graph.replay()
17561794
else:
@@ -1805,12 +1843,15 @@ def launch_profiled_work():
18051843
end_ts = torch.empty(1, dtype=torch.int64, device="cuda")
18061844

18071845
def record_start():
1846+
"""Record the start of the measured interval on the profiling stream."""
18081847
record_global_timer(start_ts.data_ptr(), stream)
18091848

18101849
def record_end():
1850+
"""Record the end of the measured interval on the profiling stream."""
18111851
record_global_timer(end_ts.data_ptr(), stream)
18121852

18131853
def elapsed_time():
1854+
"""Return the recorded profiling interval in milliseconds."""
18141855
# GPU %globaltimer counts in ns; convert to ms to match
18151856
# the units of torch.cuda.Event.elapsed_time().
18161857
return (end_ts.item() - start_ts.item()) / 1e6
@@ -1819,12 +1860,15 @@ def elapsed_time():
18191860
end_evt = torch.cuda.Event(enable_timing=True)
18201861

18211862
def record_start():
1863+
"""Record the start of the measured interval on the profiling stream."""
18221864
start_evt.record()
18231865

18241866
def record_end():
1867+
"""Record the end of the measured interval on the profiling stream."""
18251868
end_evt.record()
18261869

18271870
def elapsed_time():
1871+
"""Return the recorded profiling interval in milliseconds."""
18281872
return start_evt.elapsed_time(end_evt)
18291873

18301874
# Delay the profiled kernel launch to eliminate affects of host time overhead in profiling.

‎tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ def unique_id(self):
586586
)
587587

588588
def tactic_search_cache_key(self):
589+
"""Return the active NVMMH search policy discriminator for profiling caches."""
589590
return _cutedsl_nvmmh_tactic_search_cache_key()
590591

591592
def __hash__(self):
@@ -603,6 +604,7 @@ def get_valid_tactics(
603604
**kwargs,
604605
) -> List[Tuple[int, int]]:
605606
# Early exit: Check SM version - CuteDSL NVFP4 only supports SM 100 and SM 103
607+
"""Enumerate supported Blackwell NVFP4 tactics and apply optional NVMMH guidance."""
606608
if (sm_version := get_sm_version()) not in (100, 103):
607609
logger.debug(
608610
f"CuteDSL: SM version {sm_version} is not supported. "
@@ -4391,6 +4393,7 @@ def __init__(self,
43914393
self.use_tvm_ffi = use_tvm_ffi
43924394

43934395
def tactic_search_cache_key(self):
4396+
"""Return the active NVMMH search policy discriminator for profiling caches."""
43944397
return _cutedsl_nvmmh_tactic_search_cache_key()
43954398

43964399
def get_valid_tactics(
@@ -4399,6 +4402,7 @@ def get_valid_tactics(
43994402
profile: OptimizationProfile,
44004403
**kwargs,
44014404
) -> List[int]:
4405+
"""Enumerate blockwise FP8 tactics and retain NVMMH-ranked structural families."""
44024406
if not is_sm_100f():
44034407
logger.debug(
44044408
f"CuteDSL: SM version {get_sm_version()} is not supported. "
@@ -9338,6 +9342,7 @@ class CuteDSLBf16BlackwellGemmRunner(CuteDSLTunableRunner):
93389342
target_sm = "blackwell"
93399343

93409344
def tactic_search_cache_key(self):
9345+
"""Return the active NVMMH search policy discriminator for profiling caches."""
93419346
return _cutedsl_nvmmh_tactic_search_cache_key()
93429347

93439348
tuning_config = TuningConfig(dynamic_tensor_specs=(DynamicTensorSpec(
@@ -9354,7 +9359,7 @@ def get_valid_tactics(
93549359
profile: OptimizationProfile,
93559360
**kwargs,
93569361
) -> List[int]:
9357-
9362+
"""Generate Blackwell BF16 tactics with optional tile and scheduler guidance."""
93589363
if not is_sm_100f():
93599364
logger.debug(
93609365
f"CuteDSL: SM version {get_sm_version()} is not supported. "
@@ -9631,6 +9636,7 @@ def _rank_prune_tactics(self, tactics, m, n, k, output_dtype):
96319636
# normal full-sweep candidates.
96329637

96339638
def _split_k(tactic):
9639+
"""Read the tactic split factor, defaulting unsplit variants to one."""
96349640
if (isinstance(tactic, tuple) and len(tactic) >= 6
96359641
and tactic[0] == "base"):
96369642
return max(1, int(tactic[5]))
@@ -9939,6 +9945,7 @@ def _sm107_bf16_gemm_kernel(kernel_variant: str,
99399945
raster_along: str = "m",
99409946
swizzle_size: int = 1,
99419947
use_tma_store: bool = True):
9948+
"""Compile and cache the Rubin BF16 kernel for its shape and scheduler options."""
99429949
kernel_class = _sm107_bf16_kernel_class(kernel_variant)
99439950
if kernel_variant == "preferred_cluster":
99449951
return kernel_class(
@@ -10009,6 +10016,7 @@ def forward(
1000910016
inputs: List[torch.Tensor],
1001010017
tactic,
1001110018
) -> None:
10019+
"""Launch a Rubin batched GEMM into the supplied output tensor."""
1001210020
(kernel_variant, use_2cta_instrs, mma_tiler_mn,
1001310021
preferred_cluster_shape_mn, cluster_shape_mn, max_num_ab_stage,
1001410022
_) = _parse_sm107_bf16_tactic(tactic)
@@ -10211,6 +10219,7 @@ class CuteDSLBf16RubinGemmRunner(CuteDSLBf16BlackwellGemmRunner):
1021110219
nvmmh_split_k_cta_k = 64
1021210220

1021310221
def tactic_search_cache_key(self):
10222+
"""Return the active NVMMH search policy discriminator for profiling caches."""
1021410223
return _cutedsl_nvmmh_tactic_search_cache_key()
1021510224

1021610225
# See CuteDSLBf16RubinBmmRunner.tuning_config.
@@ -10239,6 +10248,7 @@ def get_valid_tactics(
1023910248
profile: OptimizationProfile,
1024010249
**kwargs,
1024110250
) -> List[Tuple]:
10251+
"""Generate Rubin BF16 tactics and apply configured NVMMH search fields."""
1024210252
if not _is_sm107_cute_dsl_available():
1024310253
logger.debug(
1024410254
f"CuteDSL: SM version {get_sm_version()} is not supported. "
@@ -10274,6 +10284,7 @@ def forward(
1027410284
inputs: List[torch.Tensor],
1027510285
tactic,
1027610286
) -> None:
10287+
"""Launch the selected Rubin BF16 GEMM with its scheduler and split-K settings."""
1027710288
(kernel_variant, use_2cta_instrs, mma_tiler_mn,
1027810289
preferred_cluster_shape_mn, cluster_shape_mn, max_num_ab_stage,
1027910290
split_k_slices) = _parse_sm107_bf16_tactic(tactic)
@@ -12834,6 +12845,7 @@ def unique_id(self):
1283412845
self.use_tvm_ffi)
1283512846

1283612847
def tactic_search_cache_key(self):
12848+
"""Return the active NVMMH search policy discriminator for profiling caches."""
1283712849
if (self.nvmmh_precision is None or self.nvmmh_layout is None):
1283812850
return None
1283912851
return _cutedsl_nvmmh_tactic_search_cache_key()
@@ -12854,6 +12866,7 @@ def get_valid_tactics(
1285412866
**kwargs,
1285512867
) -> List[Tuple[int, int]]:
1285612868
# SM107 dense block-scaled GEMM only.
12869+
"""Enumerate valid Rubin block-scaled families before optional heuristic pruning."""
1285712870
if (sm_version := get_sm_version()) != 107:
1285812871
logger.debug(
1285912872
f"CuteDSL: SM version {sm_version} is not supported. "
@@ -13258,6 +13271,7 @@ def _rank_prune_tactics(self, tactics, m, n, real_k):
1325813271
if "split_k" in fields:
1325913272

1326013273
def _split_k(tactic):
13274+
"""Read the tactic split factor, defaulting unsplit variants to one."""
1326113275
if (isinstance(tactic, tuple) and tactic
1326213276
and tactic[0] == "base"
1326313277
and len(tactic) >= 9):
@@ -13311,6 +13325,7 @@ def _split_k(tactic):
1331113325
return fallback_tactics
1331213326

1331313327
def model_key(signature):
13328+
"""Drop locally swept cluster fields from a model-matching signature."""
1331413329
return self._nvmmh_cluster_family(
1331513330
signature,
1331613331
match_fields,
@@ -13363,7 +13378,7 @@ def model_key(signature):
1336313378
if annotated is not None:
1336413379
selected.append(annotated)
1336513380
selected.extend(
13366-
tactic for tactic in fallback_tactics
13381+
tactic for tactic in candidate_tactics
1336713382
if ((signature := self._nvmmh_tactic_signature(
1336813383
tactic, match_fields)) is not None
1336913384
and signature[-1] in unmatched_swaps))
@@ -14453,6 +14468,7 @@ def __init__(self,
1445314468
self.use_tvm_ffi = use_tvm_ffi
1445414469

1445514470
def tactic_search_cache_key(self):
14471+
"""Return the active NVMMH search policy discriminator for profiling caches."""
1445614472
return _cutedsl_nvmmh_tactic_search_cache_key()
1445714473

1445814474
def get_valid_tactics(
@@ -14461,7 +14477,7 @@ def get_valid_tactics(
1446114477
profile: OptimizationProfile,
1446214478
**kwargs,
1446314479
) -> List[int]:
14464-
14480+
"""Generate per-tensor Rubin FP8 tactics and apply optional NVMMH filtering."""
1446514481
if (sm_version := get_sm_version()) != 107:
1446614482
logger.debug(
1446714483
f"CuteDSL: SM version {sm_version} is not supported. "

‎tensorrt_llm/_torch/custom_ops/cutedsl_matmul_heuristics.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ def rank_configs(
326326
raise ValueError(f"Unsupported nvMatmulHeuristics split-K kind {split_k_kind!r}")
327327

328328
def _method(*names):
329+
"""Resolve a supported method name across NVMMH Python API versions."""
329330
for name in names:
330331
method = getattr(interface, name, None)
331332
if method is not None:
@@ -457,6 +458,7 @@ def filter_fp8_tactics(
457458
if sweep_cluster_n and fields & {"tile", "cluster"}:
458459

459460
def _cluster_n_family(signature):
461+
"""Ignore the locally swept cluster-N dimension in the matching key."""
460462
cta, cluster, *remaining = signature
461463
return (cta, int(cluster[0]), *remaining)
462464

0 commit comments

Comments
 (0)