Skip to content

Commit 4b175dd

Browse files
A-nnonymousclaude
andcommitted
feat(gemm): unified gemm_num_sms SM-count cap for all frontier GEMMs
Add a single knob `SonicMoEConfig.gemm_num_sms` (env `SONIC_MOE_GEMM_NUM_SMS`) that caps the number of SMs every frontier CUTLASS-DSL persistent GEMM may occupy, mirroring DeepGEMM's `set_num_sms`. Intended for compute/communication multi-stream overlap: capping the GEMM leaves SMs free for a comm kernel on another stream. Mechanism (new helper `quack_utils/sm_limit.py`), applied identically at every frontier call site: 1. `capped_max_active_clusters` narrows the runtime-dynamic max_active_clusters (SM budget -> cluster budget, floor div, min 1). 2. `clc_persistence_default` forces use_clc_persistence=False (STATIC scheduler) whenever the cap is set. On Blackwell (SM100+) the default CLC scheduler ignores max_active_clusters entirely (grid is always the full problem grid), so the cap only bites under STATIC. This is a compile-time constant, so `sm_cap_enabled()` is folded into every compile key (only the STATIC-vs-CLC bool, not the SM count -- the count only drives the runtime-dynamic grid, no recompile). When gemm_num_sms is None, behavior is unchanged bit-for-bit (CLC path, full grid). Covers: gemm_gated, gemm_dgated, the 3 gather_A+blockscaled zeromat variants, the 7 blockscaled_fp8_gemm call sites, and bf16_wgrad_gemm. SM90/Hopper is out of scope (no CLC; STATIC already honors max_active_clusters there). Verified on B300 (SM103, 148 SM, CUDA 13.2): - All GEMM unit tests pass with cap off AND cap forced to 64 (grid-only change, no reduction-order change -> numerics bit-identical). - ncu (real knob): grid.z tracks the cap (none=CLC full / 112->56 / 64->32 / 32->16 clusters), SM_active% ~= residentCTA/148, per-SM efficiency stays ~1.0 (near-linear -- this is a compute-bound GEMM, so capping trades throughput ~linearly; the knob is for freeing SMs, not a free lunch). - Compile cache grows by exactly one STATIC entry when toggling cap on; different non-None caps reuse the same STATIC compilation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent e7bbf3f commit 4b175dd

7 files changed

Lines changed: 173 additions & 14 deletions

File tree

sonicmoe/config.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ class SonicMoEConfig:
7979
Env: ``SONIC_MOE_STAGEWISE_MEMORY``. Default: False.
8080
swiglu_clamp_value: Optional user-controlled SwiGLU clamp value.
8181
Default: 0.0 (disabled).
82+
gemm_num_sms: Absolute SM-count cap for all frontier CUTLASS GEMMs
83+
(single knob, à la DeepGEMM ``set_num_sms``). None = use all SMs.
84+
Non-None auto-switches the scheduler off Blackwell CLC onto STATIC
85+
so the cap takes effect. Env: ``SONIC_MOE_GEMM_NUM_SMS``.
8286
"""
8387

8488
use_fp8: Optional[bool] = None
@@ -96,6 +100,7 @@ class SonicMoEConfig:
96100
stagewise_memory: Optional[bool] = None
97101
iso32_weight: Optional[bool] = None
98102
swiglu_clamp_value: Optional[float] = None
103+
gemm_num_sms: Optional[int] = None
99104

100105
def __post_init__(self) -> None:
101106
# Auto-enable quack_gemm when fp8 is explicitly enabled.
@@ -194,6 +199,30 @@ def resolve_swiglu_clamp_value(self) -> float:
194199
return max(float(self.swiglu_clamp_value), 0.0)
195200
return 0.0
196201

202+
def resolve_gemm_num_sms(self) -> Optional[int]:
203+
"""Global SM-count cap for all frontier CUTLASS GEMMs.
204+
205+
Returns an absolute upper bound on the number of SMs a persistent
206+
GEMM may occupy, or ``None`` for "use all SMs" (unchanged behavior).
207+
Intended for compute/communication multi-stream overlap: cap the
208+
GEMM so a DeepEP/HybridEP comm kernel on another stream can claim
209+
the freed SMs. Mirrors DeepGEMM's ``set_num_sms`` single-knob API.
210+
211+
When non-None, the GEMM wrappers additionally switch the persistent
212+
tile scheduler off the Blackwell CLC path (which ignores the SM cap)
213+
onto STATIC scheduling, where the cap actually shrinks the grid.
214+
Env fallback: ``SONIC_MOE_GEMM_NUM_SMS`` (int).
215+
"""
216+
if self.gemm_num_sms is not None:
217+
return int(self.gemm_num_sms)
218+
raw = os.getenv("SONIC_MOE_GEMM_NUM_SMS", "").strip()
219+
if raw:
220+
try:
221+
return int(raw)
222+
except ValueError:
223+
return None
224+
return None
225+
197226
# --- Context manager for temporary activation ----------------------------
198227

199228
@contextmanager

sonicmoe/quack_utils/bf16_wgrad_gemm.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from quack.gemm_wrapper_utils import GemmTensorInfo, GemmWrapperBase
1515

1616
from ..cache_manager import InstrumentedCompileCache as _ICC
17+
from .sm_limit import capped_max_active_clusters, clc_persistence_default, sm_cap_enabled
1718

1819

1920
_MAX_FAST_PATH_ENTRIES = 64
@@ -168,7 +169,7 @@ def _run_bf16_wgrad_varlen_k(
168169
):
169170
raise TypeError("Unsupported BF16 wgrad type/major combination for varlen_k")
170171

171-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
172+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
172173
scheduler_args = GemmWrapperBase.create_scheduler_args(
173174
max_active_clusters,
174175
tile_count_semaphore=None,
@@ -198,6 +199,7 @@ def _run_bf16_wgrad_varlen_k(
198199
True,
199200
config.is_dynamic_persistent,
200201
config.device_capacity,
202+
sm_cap_enabled(),
201203
)
202204
compiled = compile_cache.get(compile_key)
203205
if compiled is None:
@@ -207,7 +209,7 @@ def _run_bf16_wgrad_varlen_k(
207209
tile_shape_mn,
208210
cluster_shape_mnk,
209211
gather_A=True,
210-
use_clc_persistence=config.is_dynamic_persistent,
212+
use_clc_persistence=clc_persistence_default(config.is_dynamic_persistent),
211213
)
212214
compiled = cute.compile(
213215
gemm_obj,

sonicmoe/quack_utils/blockscaled_fp8_gemm.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def _tile_atom_to_shape_SF_rank_aware(
112112
tuple[torch.Tensor, torch.Tensor],
113113
] = {}
114114
from ..cache_manager import InstrumentedCompileCache as _ICC
115+
from .sm_limit import capped_max_active_clusters, clc_persistence_default, sm_cap_enabled
115116
_COMPILE_CACHE = _ICC("blockscaled_grouped")
116117
_PAD_PLAN_CACHE: dict = {} # content-key -> plan
117118
# Fast-path cache: skip validation/tensor-info/compile-key on steady-state calls.
@@ -2265,7 +2266,7 @@ def _run_cutlass_blockscaled_gemm_varlen_k(
22652266
):
22662267
raise TypeError("Unsupported FP8 blockscaled type/major combination for varlen_k")
22672268

2268-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
2269+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
22692270
scheduler_args = GemmWrapperBase.create_scheduler_args(
22702271
max_active_clusters,
22712272
tile_count_semaphore=None, batch_idx_permute=None,
@@ -2292,13 +2293,15 @@ def _run_cutlass_blockscaled_gemm_varlen_k(
22922293
tensor_infos["A"].major, tensor_infos["B"].major,
22932294
tensor_infos["D"].major,
22942295
config.pingpong, _SF_VEC_SIZE,
2296+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
22952297
)
22962298
compiled = _COMPILE_CACHE_VK.get(compile_key)
22972299
if compiled is None:
22982300
gemm_obj = GemmDefaultSm100(
22992301
Float32, tensor_infos["A"].dtype,
23002302
tile_shape_mn, cluster_shape_mnk,
23012303
sf_vec_size=_SF_VEC_SIZE, gather_A=False,
2304+
use_clc_persistence=clc_persistence_default(True),
23022305
)
23032306
compiled = cute.compile(
23042307
gemm_obj,
@@ -2433,7 +2436,7 @@ def _run_cutlass_blockscaled_gemm_varlen_k_accumulate(
24332436
):
24342437
raise TypeError("Unsupported FP8 blockscaled type/major combination for varlen_k accumulate")
24352438

2436-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
2439+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
24372440
scheduler_args = GemmWrapperBase.create_scheduler_args(
24382441
max_active_clusters,
24392442
tile_count_semaphore=None, batch_idx_permute=None,
@@ -2458,13 +2461,15 @@ def _run_cutlass_blockscaled_gemm_varlen_k_accumulate(
24582461
tensor_infos["A"].major, tensor_infos["B"].major,
24592462
tensor_infos["D"].major,
24602463
config.pingpong, _SF_VEC_SIZE,
2464+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
24612465
)
24622466
compiled = _COMPILE_CACHE_VK_ACCUM.get(compile_key)
24632467
if compiled is None:
24642468
gemm_obj = GemmDefaultSm100(
24652469
Float32, tensor_infos["A"].dtype,
24662470
tile_shape_mn, cluster_shape_mnk,
24672471
sf_vec_size=_SF_VEC_SIZE, gather_A=False,
2472+
use_clc_persistence=clc_persistence_default(True),
24682473
)
24692474
compiled = cute.compile(
24702475
gemm_obj,
@@ -2596,7 +2601,7 @@ def _run_cutlass_blockscaled_gemm_varlen_k_tma_add(
25962601
):
25972602
raise TypeError("Unsupported FP8 blockscaled type/major combination for varlen_k tma_add")
25982603

2599-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
2604+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
26002605
scheduler_args = GemmWrapperBase.create_scheduler_args(
26012606
max_active_clusters,
26022607
tile_count_semaphore=None, batch_idx_permute=None,
@@ -2621,13 +2626,15 @@ def _run_cutlass_blockscaled_gemm_varlen_k_tma_add(
26212626
tensor_infos["A"].major, tensor_infos["B"].major,
26222627
tensor_infos["D"].major,
26232628
config.pingpong, _SF_VEC_SIZE,
2629+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
26242630
)
26252631
compiled = _COMPILE_CACHE_VK_TMA_ADD.get(compile_key)
26262632
if compiled is None:
26272633
gemm_obj = GemmDefaultSm100(
26282634
Float32, tensor_infos["A"].dtype,
26292635
tile_shape_mn, cluster_shape_mnk,
26302636
sf_vec_size=_SF_VEC_SIZE, gather_A=False,
2637+
use_clc_persistence=clc_persistence_default(True),
26312638
)
26322639
compiled = cute.compile(
26332640
gemm_obj,
@@ -2875,7 +2882,7 @@ def blockscaled_fp8_gemm_grouped(
28752882
):
28762883
raise TypeError("Skipping due to unsupported FP8 blockscaled type/major combination")
28772884

2878-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
2885+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
28792886
scheduler_args = GemmWrapperBase.create_scheduler_args(
28802887
max_active_clusters,
28812888
tile_count_semaphore=None,
@@ -2911,6 +2918,7 @@ def blockscaled_fp8_gemm_grouped(
29112918
config.pingpong,
29122919
True,
29132920
_SF_VEC_SIZE,
2921+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
29142922
)
29152923
compiled = _COMPILE_CACHE.get(compile_key)
29162924
if compiled is None:
@@ -2921,6 +2929,7 @@ def blockscaled_fp8_gemm_grouped(
29212929
cluster_shape_mnk,
29222930
sf_vec_size=_SF_VEC_SIZE,
29232931
gather_A=False,
2932+
use_clc_persistence=clc_persistence_default(True),
29242933
)
29252934
compiled = cute.compile(
29262935
gemm_obj,
@@ -4591,7 +4600,7 @@ def _run_cutlass_blockscaled_gemm(
45914600
):
45924601
raise TypeError("Unsupported FP8 blockscaled type/major combination")
45934602

4594-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
4603+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
45954604

45964605
scheduler_args = GemmWrapperBase.create_scheduler_args(
45974606
max_active_clusters,
@@ -4631,6 +4640,7 @@ def _run_cutlass_blockscaled_gemm(
46314640
config.pingpong,
46324641
True,
46334642
_SF_VEC_SIZE,
4643+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
46344644
)
46354645
compiled = _COMPILE_CACHE.get(compile_key)
46364646
if compiled is None:
@@ -4641,6 +4651,7 @@ def _run_cutlass_blockscaled_gemm(
46414651
cluster_shape_mnk,
46424652
sf_vec_size=_SF_VEC_SIZE,
46434653
gather_A=False,
4654+
use_clc_persistence=clc_persistence_default(True),
46444655
)
46454656
compiled = cute.compile(
46464657
gemm_obj,
@@ -4842,7 +4853,7 @@ def blockscaled_fp8_weight_grad_gemm(
48424853
):
48434854
raise TypeError("Unsupported FP8 blockscaled type/major combination for weight-grad GEMM")
48444855

4845-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
4856+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
48464857
scheduler_args = GemmWrapperBase.create_scheduler_args(
48474858
max_active_clusters,
48484859
tile_count_semaphore=None,
@@ -4873,6 +4884,7 @@ def blockscaled_fp8_weight_grad_gemm(
48734884
config.pingpong,
48744885
True,
48754886
_SF_VEC_SIZE,
4887+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
48764888
)
48774889
compiled = _COMPILE_CACHE.get(compile_key)
48784890
if compiled is None:
@@ -4883,6 +4895,7 @@ def blockscaled_fp8_weight_grad_gemm(
48834895
cluster_shape_mnk,
48844896
sf_vec_size=_SF_VEC_SIZE,
48854897
gather_A=False,
4898+
use_clc_persistence=clc_persistence_default(True),
48864899
)
48874900
compiled = cute.compile(
48884901
gemm_obj,
@@ -5038,7 +5051,7 @@ def blockscaled_fp8_weight_grad_gemm_fast(
50385051
):
50395052
raise TypeError("Unsupported FP8 blockscaled type/major combination for weight-grad GEMM")
50405053

5041-
max_active_clusters = get_max_active_clusters(config.cluster_m * config.cluster_n)
5054+
max_active_clusters = capped_max_active_clusters(config.cluster_m * config.cluster_n)
50425055
scheduler_args = GemmWrapperBase.create_scheduler_args(
50435056
max_active_clusters,
50445057
tile_count_semaphore=None,
@@ -5067,6 +5080,7 @@ def blockscaled_fp8_weight_grad_gemm_fast(
50675080
config.pingpong,
50685081
True,
50695082
_SF_VEC_SIZE,
5083+
sm_cap_enabled(), # STATIC vs CLC scheduler (compile-time)
50705084
)
50715085
compiled = _COMPILE_CACHE.get(compile_key)
50725086
if compiled is None:
@@ -5077,6 +5091,7 @@ def blockscaled_fp8_weight_grad_gemm_fast(
50775091
cluster_shape_mnk,
50785092
sf_vec_size=_SF_VEC_SIZE,
50795093
gather_A=False,
5094+
use_clc_persistence=clc_persistence_default(True),
50805095
)
50815096
compiled = cute.compile(
50825097
gemm_obj,

sonicmoe/quack_utils/gemm_dgated.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
GemmDGatedSm100ZeroMat,
4949
GemmDGatedFP8CLoadSm100ZeroMat,
5050
)
51+
from .sm_limit import capped_max_active_clusters, clc_persistence_default, sm_cap_enabled
5152

5253
_E8M0_DTYPE = getattr(torch, "float8_e8m0fnu", torch.uint8)
5354

@@ -194,7 +195,7 @@ def gemm_dgated(
194195
):
195196
raise TypeError("Skipping due to unsupported combination of types and majors")
196197

197-
max_active_clusters = get_max_active_clusters(cluster_M * cluster_N) if persistent else 0
198+
max_active_clusters = capped_max_active_clusters(cluster_M * cluster_N, persistent=persistent)
198199
for name, info in tensor_infos.items():
199200
if info.tensor is not None and name in major_configs:
200201
info.cute_tensor = _make_cute_tensor_dynamic(
@@ -265,19 +266,24 @@ def gemm_dgated(
265266
blockscaled,
266267
fp8_preact_mode,
267268
float(swiglu_clamp_value),
269+
sm_cap_enabled(),
268270
key_tensor_names=("A", "B", "D", "PostAct", "C"),
269271
)
270272
cache = gemm_dgated.compile_cache
271273
if compile_key not in cache:
272274
if device_capacity[0] == 9:
273275
GemmCls = partial(GemmCls, pingpong=pingpong, is_persistent=persistent)
276+
extra_kwargs = {}
277+
else:
278+
extra_kwargs = {"use_clc_persistence": clc_persistence_default(True)}
274279
gemm_obj = GemmCls(
275280
acc_dtype,
276281
tensor_infos["A"].dtype,
277282
tile_shape_mn,
278283
cluster_shape_mnk,
279284
gather_A=gather_A,
280285
sf_vec_size=sf_vec_size,
286+
**extra_kwargs,
281287
)
282288
cache[compile_key] = cute.compile(
283289
gemm_obj,

sonicmoe/quack_utils/gemm_gated.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
GemmSm100ZeroMatBlockscaledQuant,
4949
)
5050

51+
from .sm_limit import capped_max_active_clusters, clc_persistence_default, sm_cap_enabled
52+
5153
_E8M0_DTYPE = getattr(torch, "float8_e8m0fnu", torch.uint8)
5254

5355

@@ -198,7 +200,7 @@ def gemm_gated(
198200
):
199201
raise TypeError("Skipping due to unsupported combination of types and majors")
200202

201-
max_active_clusters = get_max_active_clusters(cluster_M * cluster_N) if persistent else 0
203+
max_active_clusters = capped_max_active_clusters(cluster_M * cluster_N, persistent=persistent)
202204
for name, info in tensor_infos.items():
203205
if info.tensor is not None and name in major_configs:
204206
leading_dim = 1 if info.major == major_configs[name][1] else 0
@@ -274,19 +276,27 @@ def gemm_gated(
274276
postact_quant,
275277
float(swiglu_clamp_value),
276278
bool(postact_bf16_trunc),
279+
sm_cap_enabled(),
277280
key_tensor_names=("A", "B", "D", "PostAct", "C"),
278281
)
279282
cache = gemm_gated.compile_cache
280283
if compile_key not in cache:
281284
if device_capacity[0] == 9:
282285
GemmCls = partial(GemmCls, pingpong=pingpong, is_persistent=persistent)
286+
extra_kwargs = {}
287+
else:
288+
# SM100+: force STATIC scheduling (use_clc_persistence=False) when
289+
# the SM cap is active, so the narrowed max_active_clusters shrinks
290+
# the persistent grid (the CLC path ignores it).
291+
extra_kwargs = {"use_clc_persistence": clc_persistence_default(True)}
283292
gemm_obj = GemmCls(
284293
acc_dtype,
285294
tensor_infos["A"].dtype,
286295
tile_shape_mn,
287296
cluster_shape_mnk,
288297
gather_A=gather_A,
289298
sf_vec_size=sf_vec_size,
299+
**extra_kwargs,
290300
)
291301
cache[compile_key] = cute.compile(
292302
gemm_obj,

0 commit comments

Comments
 (0)