Skip to content

Commit b1845b1

Browse files
ozturkosuassistant-librarian[bot]
authored andcommitted
[rocm-libraries] ROCm/rocm-libraries#12173 (commit 4606f83)
feat(ck-tile): make dispatcher LDS capacity budget architecture-aware (#12173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA ID : AICK-2249 ## Summary The dispatcher's codegen rejects any GEMM tile whose LDS staging footprint exceeds a cap. The cap was keyed on the **pipeline only** and carried no architecture term, so newer targets with substantially more LDS than gfx942 were all held to gfx942's budget. The effect is that the largest and deepest tiles — the ones most likely to win on large GEMM shapes — were never generated, never benchmarked and never selectable on those targets. On a gfx950 fp16 sweep, **160 of 288 offered configurations (56%) were rejected by this cap alone**. This PR makes the budget architecture-aware, adds gfx1250 to the spec, fixes both the Python and C++ validators together, and makes the check account for ping-pong LDS staging. **Scope:** the check is not GEMM-specific. `_validate_lds_capacity` sits in the common `validate_kernel` chain, ungated by operator, and `OperatorType` covers 11 operators (5 GEMM variants, 6 conv variants). In Python it is reached by `unified_gemm_codegen.py:1595` (universal GEMM) and `unified_grouped_conv_codegen.py:1941` (grouped convolution); in C++ by `Registry::filter_by_arch()` (`registry.cpp:160`), which is operator-agnostic and covers every registered kernel. The ctypes bridge paths (quant variants, batched contraction, multi-ABD, FMHA) validate via `validate_kernel_config` in `python/ctypes_utils.py`, which has no LDS check at all, so they are unaffected either way. Implements AICK-2249. Baseline evidence from AICK-2244. ## Motivation `arch_specs.json` had `pipeline_lds_limits` as a **top-level** key — a sibling of `architectures`, not nested inside it: ```json "pipeline_lds_limits": { "mem": <bytes>, "compv3": <bytes>, "compv4": <bytes>, ... "default": <bytes> } ``` Every value was a fixed byte count, derived from gfx942 and applied to every target. There was no architecture axis, and nowhere to put one. `_validate_lds_capacity` did `LDS_CAPACITY_LIMITS.get(config.pipeline, ...)` and nothing else. Meanwhile `arch.hpp` has declared a per-architecture LDS capacity all along, via `get_lds_size()`. On gfx942 the hardcoded cap and that capacity coincide; on the newer targets they do not, and the shortfall is large. This PR consumes the existing declaration rather than introducing any new hardware figure. This is the usual shape of this defect: a value that was correct when there was exactly one architecture, frozen into a schema with no slot for a second one. It survived because on gfx942 the correct answer and the hardcoded answer are the same number, so it only ever fails *silently, by generating less*. The evidence that it is binding rather than theoretical: on the gfx950 sweep the cutoff falls **exactly** on the hardcoded cap for compv3/mem, and exactly on the tighter cap for compv4, with zero exceptions in either direction. And the winning kernels on that target were `128x256x64` and `128x128x128` under compv3 — precisely tiles that compv4 was forbidden from using. When the measured optimum sits on the constraint boundary, the constraint is probably binding. ## Design note **I did not invent a new schema.** A per-architecture `lds_capacity_kb` field already existed in `arch_specs.json` (gfx950 already correctly said `160`), and `ADDING_NEW_GPU.md` already documented it as a required onboarding field. Nothing read it. Every new-GPU onboarding has been filling in a mandatory field that no code consumed — the contract was documented and unhonoured. So the fix is to honour it. Pipelines now declare a **basis** rather than a byte count: ```json "compv3": { "basis": "fraction", "value": 1.0 }, "compv4": { "basis": "fraction", "value": 0.5 } ``` resolved against each architecture's capacity at generation time. A new GPU needs **one** number, not ten. The alternative — nesting the byte table under each architecture — would have reintroduced the same failure mode one level down: 8 arches x 10 pipelines of hand-maintained constants free to drift from `arch.hpp` independently. Resolution happens in the generator, once, so the emitted Python and C++ get literal byte counts and cannot disagree about how to read the schema. ### On the tighter-capped pipelines — I read the sources rather than guessing Four pipelines sat at the tighter cap and the ticket flagged it as an open question: genuine double-buffering that should scale with capacity, or an independent absolute limit? On gfx942 the two readings are numerically identical, which is why it was never forced. **The answer is not uniform across the four:** | pipeline | doubles LDS? | evidence | |---|---|---| | `compv4` | **yes** | `GemmPipelineAgBgCrCompV4::GetSmemSize()` returns `2 * Policy::GetSmemSize<Problem>()` | | `preshufflev2` | **yes** | `WeightPreshufflePipelineAGmemBGmemCRegV2::GetSmemSize()` returns `DoubleSmemBuffer ? 2 * smem_size : smem_size` | | `compv6` | **no** | returns the policy size unmultiplied | | `preshufflev1` | n/a | no such pipeline exists; `preshuffle_pipelines.supported` lists only `preshufflev2` | This is corroborated by `DOUBLE_SMEM_PIPELINES = {"compv4", "preshufflev2", "comp_async"}`, which already exists in `unified_batched_contraction_codegen.py` and names exactly the same two. So for compv4 and preshufflev2, half-of-capacity is the **exact** model, not a safety margin: the validator checks `A+B` against `capacity/2`, which is precisely equivalent to checking the real `2*(A+B)` allocation against full capacity. For compv6 and preshufflev1 the halved cap is **unexplained by buffering**. I preserved their ratio rather than widening them, because no evidence supports a larger budget and a blind raise that regresses is worse than the status quo. Widening those two is a separate change that needs a measurement behind it. The reasoning is recorded in `arch_specs.json` next to each value. ### Ping-pong staging had to be threaded in The check modelled a *single* staging buffer. But `unified_grouped_conv_codegen.py:148` documents that `mem`, `compv3`, `compv5` and `compv6` make double buffering a **configuration choice** (`--double-smem-buffer`), not a property of the pipeline — and the conv path passed `pipeline=` into the validator without that flag, so the budget could not see it. That was harmless while the cap matched gfx942's capacity, because twice that still fits the larger parts. **Widening the budget removes the accidental headroom**, so I had to close it in the same PR: Writing `C` for a target's LDS capacity: | | compv3 budget | actually allocated | vs capacity | | |---|---|---|---|---| | develop | gfx942's cap | 2x that | below `C` | fits, by accident | | arch-aware alone | `C` | `2C` | **over `C`** | **overflows** | | with this fix | `C/2` | `C` | exactly `C` | fits exactly | So the flag is now threaded into both validators. Conv passes the value it already tracks; C++ reads `algorithm.double_buffer`, which registered kernels populate from `SelectedKernel::DoubleSmemBuffer` (`kernel_registration.hpp:60`). Pipelines that *always* double already carry the halving in their per-pipeline budget, so the two signals are combined with a `min()` — `compv4` and `preshufflev2` are never halved twice, which is what keeps gfx942 byte-identical. Independent confirmation that those are exactly the two: `unified_gemm_codegen.py:1531` sets `double_buffer = pipeline in ("compv4", "preshufflev2")`. One deliberate non-use: `KernelConfig::build_key()` (`kernel_config.hpp:280`) and `utils.hpp:676` hardcode `double_buffer = true` for *any* pipeline. Those build **query** keys, which never reach `validate_lds` — only registered-instance keys do — so the flag is trustworthy at the one site that reads it. Worth fixing separately. ### A landmine worth calling out `arch_specs_generated.py` **already contained gfx1250** — family, warp configs, warp tile combos — but `arch_specs.json`, the file it is generated from, did not. The generated C++ header did not either. Three-way drift, in a file stamped `AUTO-GENERATED - DO NOT EDIT DIRECTLY`. This means following the documented workflow (edit JSON, regenerate, commit) would have **silently deleted gfx1250's warp tables**. I transplanted them into the JSON verbatim rather than authoring anything, and the regeneration check below confirms every table came back byte-identical. ## Test plan - [x] New regression suite passes (15/15). Asserts budgets differ across gfx942/gfx950/gfx1250, that gfx942's budget is byte-identical to the historical table, that no budget exceeds the declared hardware capacity, that unknown targets get the *smallest* budget rather than the largest, and end-to-end that a large staging tile is rejected on gfx942 and accepted on gfx950 - [x] Double-buffer cases covered: configurable pipelines halve, `2 x budget <= capacity` on every arch/pipeline pair, always-double pipelines are not halved twice, single-buffered remains the default, and end-to-end a large staging tile is accepted on gfx950 single-buffered but rejected double-buffered - [x] Existing suites pass: `test_arch_filter_constraints`, `test_gemm_utils`, `test_codegen_common`, `test_dispatcher_common`, `test_tile_math`, `test_grouped_conv_codegen`, `test_grouped_conv_utils` - [x] Generator is idempotent; regeneration leaves all non-LDS tables **byte-identical** (`ARCH_FAMILY_MAP`, `WARP_SUPPORTED_COMBINATIONS`, `WARP_TILE_SUPPORTED_COMBINATIONS`, `PRESHUFFLE_WARP_TILE_SUPPORTED_COMBINATIONS`, `TRAIT_UNSUPPORTED_COMBINATIONS`, `ELEMENT_SIZE_MAP`, `DTYPE_COMBINATIONS`, `PRESHUFFLE_PIPELINES`) - [x] C++ compiles standalone and returns values identical to Python for every architecture and pipeline, single- and double-buffered, including the unknown-architecture fallback - [x] `clang-format-18 -style=file` clean on both headers - [ ] **GPU validation pending** — see below ### Equivalence proof This edits a shared table, so I enumerated the validator's survivor set per architecture, before and after. The axes now include **every pipeline the validators can see** — `comp_async` and `wavelet` as well as the nine that were in the old table — and both settings of the ping-pong staging flag. | arch | develop | this PR | gained | lost | |---|---:|---:|---:|---:| | gfx908 | 4,613 | 7,386 | +3,003 | **230** | | gfx90a | 2,701 | 4,138 | +1,595 | **158** | | gfx942 | 5,324 | 8,360 | +3,322 | **286** | | gfx950 | 7,116 | 19,016 | +11,900 | 0 | | gfx1100 | 1,155 | 1,870 | +770 | **55** | | gfx1200 | 442 | 668 | +253 | **27** | | gfx1201 | 442 | 668 | +253 | **27** | | gfx1250 | 2,012 | 10,926 | +8,914 | 0 | | **total** | | | **+30,010** | **783** | **There are losses, and an earlier version of this section claimed there were none.** That claim was measured over the old parameter space, which could not contain `comp_async` because the pipeline had no entry in the old table to enumerate. It was true of what it measured and wrong as a general statement. **All 783 losses are `comp_async`, on the six architectures whose budget is unchanged**, and they are intended. Attribution by pipeline across those six: ``` comp_async 783 (no other pipeline appears) ``` `comp_async` had no entry before, so it inherited the full-capacity default. It allocates two LDS buffers unconditionally — `GetSmemSize()` returns `num_lds_buffers * smem_size` with `num_lds_buffers = 2` — so it was budgeted for twice the staging it can actually use. A tile such as conv `128x64x128` at fp16 needs 48 KB of staging and was accepted, while the kernel would then have asked the hardware for twice that. Rejecting it is the fix, not a regression. The two architectures with a raised budget lose nothing, and no default configuration set in tree generates `comp_async`, so no shipped kernel disappears. ## Explicitly out of scope Two other filters in the same validation chain share this defect class (gfx942-derived constraints applied to every architecture). Neither rejected anything in the baseline campaign, so I left them alone: - `TRAIT_UNSUPPORTED_COMBINATIONS` — 10 tuples, no arch key - `_cshuffle_store_ok` — docstring says "GPU-verified on gfx942", applied to all architectures A wider audit of the validation chain found this same pattern in several more places, including a hand-written warp-tile lookup on the native side that disagrees with the generated Python table on a majority of supported architectures. That work is tracked separately in AICK-2264 and AICK-2266, under epic AICK-2265, and is deliberately not part of this PR: it is a different table on a different axis, and unlike this change it removes configurations, so it needs its own before/after enumeration and device validation. Also left alone: `get_smem_capacity()` in `arch.hpp` special-cases a single architecture and returns a fixed value otherwise, which disagrees with `get_lds_size()` for at least one target. It has three live consumers outside this ticket's scope (`cshuffle_epilogue.hpp`, `moe_sorting_kernel.hpp`, `grouped_convolution_forward_kernel.hpp`) and may legitimately encode a per-workgroup addressable limit rather than per-CU capacity. Flagging rather than changing it.
1 parent 6fdf2b0 commit b1845b1

13 files changed

Lines changed: 1358 additions & 83 deletions

dispatcher/codegen/arch_filter.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ class OperatorType(Enum):
159159
WARP_TILE_SUPPORTED_COMBINATIONS,
160160
PRESHUFFLE_WARP_TILE_SUPPORTED_COMBINATIONS,
161161
PRESHUFFLE_PIPELINES,
162-
LDS_CAPACITY_LIMITS,
162+
LDS_CAPACITY_LIMITS_BY_ARCH,
163+
get_lds_limit,
163164
TRAIT_UNSUPPORTED_COMBINATIONS,
164165
DTYPE_COMBINATIONS,
165166
)
@@ -241,7 +242,43 @@ class OperatorType(Enum):
241242

242243
PRESHUFFLE_PIPELINES = ["preshufflev2"]
243244

244-
LDS_CAPACITY_LIMITS = {"compv4": 32768, "preshufflev2": 32768, "default": 65536}
245+
# Conservative fallback: the historical 64 KB / 32 KB budget, applied to
246+
# every architecture. It deliberately understates gfx950 and gfx1250 rather
247+
# than overstating anything, because a budget larger than the silicon
248+
# produces kernels that cannot launch. The generated module carries the
249+
# real per-architecture numbers; regenerate it rather than relying on this.
250+
_FALLBACK_LDS_BUDGET = {
251+
"mem": 65536,
252+
"compv1": 65536,
253+
"compv2": 65536,
254+
"compv3": 65536,
255+
"compv4": 32768,
256+
"compv5": 65536,
257+
"compv6": 32768,
258+
"preshufflev1": 32768,
259+
"preshufflev2": 32768,
260+
# Mandatory double buffering (num_lds_buffers = 2), so half the budget.
261+
"comp_async": 32768,
262+
"wavelet": 65536,
263+
"default": 65536,
264+
}
265+
266+
LDS_CAPACITY_LIMITS_BY_ARCH = {
267+
arch: dict(_FALLBACK_LDS_BUDGET) for arch in ARCH_FAMILY_MAP
268+
}
269+
270+
def get_lds_limit(
271+
gpu_arch: str, pipeline: str, double_smem_buffer: bool = False
272+
) -> int:
273+
"""Get the LDS staging budget in bytes for an architecture and pipeline."""
274+
per_pipeline = LDS_CAPACITY_LIMITS_BY_ARCH.get(
275+
gpu_arch.lower(), _FALLBACK_LDS_BUDGET
276+
)
277+
budget = per_pipeline.get(pipeline.lower(), per_pipeline["default"])
278+
if double_smem_buffer:
279+
# Conservative: the fallback assumes the smallest capacity we ship.
280+
budget = min(budget, _FALLBACK_LDS_BUDGET["default"] // 2)
281+
return budget
245282

246283
TRAIT_UNSUPPORTED_COMBINATIONS = {
247284
("compv3", "cshuffle", "interwave"),
@@ -353,6 +390,11 @@ class KernelConfig:
353390
epilogue: str = "cshuffle"
354391
scheduler: str = "intrawave"
355392

393+
# Ping-pong LDS staging. Only meaningful for the pipelines that make it a
394+
# choice (mem, compv3, compv5, compv6); the ones that always double already
395+
# carry it in their per-pipeline budget.
396+
double_smem_buffer: bool = False
397+
356398
# Layout (for whole-workgroup cover validation)
357399
layout: str = "rcr"
358400

@@ -533,6 +575,7 @@ def is_kernel_valid(
533575
scheduler: str = "intrawave",
534576
layout: str = "rcr",
535577
operator: Optional[OperatorType] = None,
578+
double_smem_buffer: bool = False,
536579
) -> bool:
537580
"""
538581
Quick validation check for a kernel configuration.
@@ -544,6 +587,8 @@ def is_kernel_valid(
544587
warp_tile_m, warp_tile_n, warp_tile_k: Warp tile dimensions
545588
pipeline, epilogue, scheduler: Kernel traits
546589
layout: Matrix layout (e.g., "rcr")
590+
double_smem_buffer: Ping-pong LDS staging. Halves the staging
591+
budget for the pipelines that make it a choice.
547592
operator: Operator type (GEMM, CONV_FWD, CONV_BWD_DATA, etc.)
548593
Affects validation rules for tile constraints.
549594
Defaults to GEMM if not specified.
@@ -568,6 +613,7 @@ def is_kernel_valid(
568613
epilogue=epilogue.lower(),
569614
scheduler=scheduler.lower(),
570615
layout=layout.lower(),
616+
double_smem_buffer=double_smem_buffer,
571617
operator=operator if operator is not None else OperatorType.GEMM,
572618
)
573619
return self.validate_kernel(config).valid
@@ -709,17 +755,29 @@ def _validate_lds_capacity(self, config: KernelConfig, result: ValidationResult)
709755
elem_size_a = ELEMENT_SIZE_MAP.get(config.datatype_a, 2)
710756
elem_size_b = ELEMENT_SIZE_MAP.get(config.datatype_b, 2)
711757

758+
# When the B cast policy runs before the LDS write, B is staged as
759+
# ADataType rather than BDataType (GetSmemSizeB in
760+
# gemm_universal_pipeline_ag_bg_cr_policy.hpp). Charging B at the wider
761+
# of the two keeps a mixed-precision pair from being under-counted; for
762+
# equal dtypes it is the same number.
763+
elem_size_b_staged = max(elem_size_a, elem_size_b)
764+
712765
matrix_a_size = config.tile_m * config.tile_k * elem_size_a
713-
matrix_b_size = config.tile_n * config.tile_k * elem_size_b
766+
matrix_b_size = config.tile_n * config.tile_k * elem_size_b_staged
714767
total_lds = matrix_a_size + matrix_b_size
715768

716-
max_lds = LDS_CAPACITY_LIMITS.get(
717-
config.pipeline, LDS_CAPACITY_LIMITS["default"]
769+
# The budget depends on the target, not just the pipeline: a tile that
770+
# overflows one architecture's LDS may fit comfortably in another's.
771+
max_lds = get_lds_limit(
772+
self.gpu_arch, config.pipeline, config.double_smem_buffer
718773
)
719774

720775
if total_lds > max_lds:
776+
staging = " double-buffered" if config.double_smem_buffer else ""
721777
result.add_error(
722-
f"LDS capacity exceeded: {total_lds} bytes > {max_lds} bytes limit. "
778+
f"LDS capacity exceeded on {self.gpu_arch} "
779+
f"(pipeline={config.pipeline}{staging}): "
780+
f"{total_lds} bytes > {max_lds} bytes limit. "
723781
f"Matrix A: {config.tile_m}x{config.tile_k}x{elem_size_a}={matrix_a_size}B, "
724782
f"Matrix B: {config.tile_n}x{config.tile_k}x{elem_size_b}={matrix_b_size}B"
725783
)

dispatcher/codegen/arch_specs.json

Lines changed: 136 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,57 @@
172172
"bf8_fp8_fp32": [[16, 16, 16]],
173173
"int8_int8_int32": [[16, 16, 16]]
174174
}
175+
},
176+
177+
"gfx1250": {
178+
"family": "cdna5",
179+
"target_family": "gfx125",
180+
"architecture": "rdna",
181+
"_architecture_note": [
182+
"architecture is the ISA lineage, not the product generation. arch.hpp",
183+
"states that gfx1250 shares the RDNA architecture with the GFX12 family",
184+
"while being its own standalone target family, because its MMA builtins",
185+
"and data-type ABI differ. family records the product generation."
186+
],
187+
"description": "AMD gfx1250 (wave32, WMMA rather than MFMA)",
188+
"warp_size": 32,
189+
"_lds_capacity_kb_source": "get_lds_size(gfx125_t) in include/ck_tile/core/arch/arch.hpp",
190+
"lds_capacity_kb": 320,
191+
"_warp_tables_provenance": [
192+
"warp_configs and warp_tile_combos below are transplanted verbatim from",
193+
"arch_specs_generated.py, where gfx1250 was already present and in use.",
194+
"They are NOT newly authored here. The generated module had drifted ahead",
195+
"of this file; without this entry, regenerating would delete them.",
196+
"Any change to these lists belongs to gfx1250 enablement, not here."
197+
],
198+
"warp_configs": [
199+
[2, 4, 1],
200+
[1, 8, 1],
201+
[8, 1, 1],
202+
[4, 2, 1],
203+
[2, 1, 1],
204+
[1, 2, 2],
205+
[4, 1, 1],
206+
[1, 4, 1],
207+
[2, 2, 1]
208+
],
209+
"_warp_tile_8bit_source": [
210+
"The 8-bit shapes are the dense WmmaTraits<gfx125_t, ...> specialisations in",
211+
"include/ck_tile/ops/gemm/warp/warp_gemm_attribute_wmma_impl_8bit_traits.hpp:",
212+
"fp8/fp8 and bf8/bf8 each have a 16x16x64 and a 16x16x128 form.",
213+
"The mixed fp8/bf8 and bf8/fp8 pairs exist there too but are not listed",
214+
"here; enabling them would newly generate kernels for combinations this",
215+
"bridge has never exercised, which needs its own device validation."
216+
],
217+
"warp_tile_combos": {
218+
"fp16_fp16_fp32": [[16, 16, 32]],
219+
"bf16_bf16_fp32": [[16, 16, 32]],
220+
"fp8_fp8_fp32": [[16, 16, 64], [16, 16, 128]],
221+
"bf8_bf8_fp32": [[16, 16, 64], [16, 16, 128]]
222+
}
175223
}
176224
},
177-
225+
178226
"element_sizes": {
179227
"fp16": 2,
180228
"bf16": 2,
@@ -221,18 +269,93 @@
221269
"c": "ck_tile::tensor_layout::gemm::ColumnMajor"
222270
},
223271

224-
"pipeline_lds_limits": {
225-
"_comment": "LDS capacity limits in bytes for different pipeline types",
226-
"mem": 65536,
227-
"compv1": 65536,
228-
"compv2": 65536,
229-
"compv3": 65536,
230-
"compv4": 32768,
231-
"compv5": 65536,
232-
"compv6": 32768,
233-
"preshufflev1": 32768,
234-
"preshufflev2": 32768,
235-
"default": 65536
272+
"pipeline_lds_budget": {
273+
"_comment": [
274+
"Per-pipeline budget for the A+B staging tiles, resolved at generation",
275+
"time against each architecture's lds_capacity_kb.",
276+
"",
277+
" basis 'fraction' -> bytes = floor(lds_capacity_kb * 1024 * value)",
278+
" basis 'absolute_kb' -> bytes = value * 1024, identical on every arch",
279+
"",
280+
"Use 'absolute_kb' only for a limit that genuinely does not scale with",
281+
"how much LDS the silicon has. Anything proportional to capacity must be",
282+
"a 'fraction', otherwise a new architecture silently inherits the budget",
283+
"of whichever one the constant was written for.",
284+
"",
285+
"The check models the A+B staging footprint only, matching the kernel's",
286+
"own accounting; the epilogue reuses that space rather than adding to it."
287+
],
288+
289+
"mem": { "basis": "fraction", "value": 1.0 },
290+
"compv1": { "basis": "fraction", "value": 1.0 },
291+
"compv2": { "basis": "fraction", "value": 1.0 },
292+
"compv3": { "basis": "fraction", "value": 1.0 },
293+
"compv5": { "basis": "fraction", "value": 1.0 },
294+
295+
"compv4": {
296+
"basis": "fraction",
297+
"value": 0.5,
298+
"_reason": [
299+
"Double-buffered. GemmPipelineAgBgCrCompV4::GetSmemSize() returns",
300+
"2 * Policy::GetSmemSize<Problem>(), so the kernel allocates two",
301+
"staging buffers and only half the capacity is available to A+B.",
302+
"Half-of-capacity is therefore the exact model, not a safety margin."
303+
]
304+
},
305+
"preshufflev2": {
306+
"basis": "fraction",
307+
"value": 0.5,
308+
"_reason": [
309+
"Double-buffered when DoubleSmemBuffer is set:",
310+
"WeightPreshufflePipelineAGmemBGmemCRegV2::GetSmemSize() returns",
311+
"DoubleSmemBuffer ? 2 * smem_size : smem_size. Budgeted for the",
312+
"doubling case, which is the one that can overflow."
313+
]
314+
},
315+
"comp_async": {
316+
"basis": "fraction",
317+
"value": 0.5,
318+
"_reason": [
319+
"Mandatory double buffering, no configuration choice:",
320+
"GemmPipelineAgBgCrCompAsync::GetSmemSize() returns",
321+
"num_lds_buffers * smem_size with num_lds_buffers = 2.",
322+
"Has no Pipeline enumerator on the C++ side, so this entry only",
323+
"affects the Python validator; it is emitted to C++ only for",
324+
"pipelines that exist in that enum."
325+
]
326+
},
327+
"wavelet": {
328+
"basis": "fraction",
329+
"value": 1.0,
330+
"_reason": [
331+
"Single-buffered. Listed explicitly so that every pipeline the",
332+
"validators can see has a deliberate entry rather than silently",
333+
"inheriting 'default'."
334+
]
335+
},
336+
337+
"compv6": {
338+
"basis": "fraction",
339+
"value": 0.5,
340+
"_reason": [
341+
"NOT double-buffered: GemmPipelineAgBgCrCompV6::GetSmemSize() returns",
342+
"the policy size unmultiplied. The historical halved cap is therefore",
343+
"unexplained by buffering. Its ratio is preserved rather than widened,",
344+
"because no evidence supports a larger budget; raising it needs a",
345+
"measurement, not an assumption."
346+
]
347+
},
348+
"preshufflev1": {
349+
"basis": "fraction",
350+
"value": 0.5,
351+
"_reason": [
352+
"Vestigial. No WeightPreshufflePipelineAGmemBGmemCRegV1 exists and",
353+
"preshuffle_pipelines.supported lists only preshufflev2, so nothing",
354+
"generates this pipeline. Ratio preserved so the entry stays inert."
355+
]
356+
},
357+
358+
"default": { "basis": "fraction", "value": 1.0 }
236359
},
237360

238361
"unsupported_trait_combos": {

0 commit comments

Comments
 (0)