Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4abc828
feat(rocke): gfx1250 direct wgrad kernels, two-stage reduction and AO…
jakpiase Nov 1, 2026
bbb0385
feat(rocke): cache the gfx1250 small-C wgrad stems for 3 and 4 input …
jakpiase Nov 1, 2026
e9bbedf
fix(rocke): skip a direct-conv plan whose kernargs do not fit
jakpiase Nov 1, 2026
fa89a82
feat(rocke): run >2 GiB wgrad as batch slices that accumulate into dW
jakpiase Nov 1, 2026
8bd7ff3
fix(rocke): bump the binary ABI to 7 for the grown wgrad spec
jakpiase Nov 1, 2026
5171545
test(rocke): fail the depthwise wgrad check on a NaN error
jakpiase Nov 1, 2026
d96f440
docs(rocke): buffer atomics need guarded offsets, not OOB masking
jakpiase Nov 1, 2026
5f32bed
test(rocke): run the buffer atomic checks in rocke_buffer_rsrc_hip
jakpiase Nov 1, 2026
1aa95f0
fix(rocke): require an integer cachepolicy in 0..31 when lowering buf…
jakpiase Nov 1, 2026
40e2300
fix(rocke): measure the buffer atomic fadd decl on the llvm20, llvm22…
jakpiase Nov 1, 2026
a7341a2
fix(rocke): do not cache the spilling 16-wave register-staged wgrad i…
jakpiase Nov 1, 2026
647ca7f
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 2, 2026
92eef6b
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 3, 2026
948ed9b
test(rocke): hold the gfx1250 wgrad kernel-cache entries in memory
jakpiase Nov 3, 2026
f4d50c5
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 3, 2026
5260058
refactor(rocke): share the wave32 transpose-read lane map in both eng…
jakpiase Nov 3, 2026
1590111
test(rocke): pin every gfx1250 direct wgrad refusal by reason
jakpiase Nov 3, 2026
ca9e597
docs(rocke): state the gfx1250 direct wgrad kernels' real scope
jakpiase Nov 3, 2026
7be496f
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 3, 2026
a0307d2
docs(rocke): name the dgrad WMMA builders that still inline the tr la…
jakpiase Nov 3, 2026
9663260
fix(rocke): name the offending value in every direct wgrad refusal
jakpiase Nov 3, 2026
cf0effd
test(rocke): run the depthwise wgrad numerics wherever the cache ship…
jakpiase Nov 3, 2026
487aa1c
docs(rocke): point to the image wgrad register estimate instead of re…
jakpiase Nov 3, 2026
4687d9f
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 4, 2026
0e5ec03
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 4, 2026
6ec8661
refactor(rocke): set the added direct wgrad "ok" reasons with rocke_s…
jakpiase Nov 4, 2026
ecfc11b
test(rocke): pin the wgrad spec layout in the wgrad refusals host test
jakpiase Nov 4, 2026
41a887c
test(rocke): require the "(got ...)" tail in the wgrad refusal value …
jakpiase Nov 4, 2026
10e926d
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 4, 2026
077f3e4
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 4, 2026
d550248
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 5, 2026
a08c89e
fix(rocke): address review of the gfx1250 direct wgrad
jakpiase Nov 5, 2026
f19b203
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 6, 2026
3029760
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 6, 2026
e9cb4c1
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 6, 2026
03ddb35
fix(rocke): address the 11-06 review of the gfx1250 direct wgrad
jakpiase Nov 6, 2026
6a9aab8
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 6, 2026
b45b342
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 6, 2026
4921f11
test(rocke): guard the wgrad refusals host test on the library tree
jakpiase Nov 6, 2026
a87db9b
Merge remote-tracking branch 'origin/develop' into users/jakpiase/roc…
jakpiase Nov 8, 2026
93f675f
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 9, 2026
058b27b
Merge branch 'develop' into users/jakpiase/rocke/gfx1250-wgrad-direct
jakpiase Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ test_categories:
- "rocke_fp8_scale_conversion_gate"
- "rocke_mx_scale_decode"
- "rocke_buffer_rsrc_hip"
- "rocke_buffer_atomic_fadd"
- "rocke_tiled_attention_2d_reentrancy"
- "rocke_unified_attention_scalar_lower_to_llvm"
- "rocke_python_import_smoke"
Expand Down Expand Up @@ -106,6 +107,7 @@ test_categories:
- "rocke_fp8_scale_conversion_gate"
- "rocke_mx_scale_decode"
- "rocke_buffer_rsrc_hip"
- "rocke_buffer_atomic_fadd"
- "rocke_tiled_attention_2d_reentrancy"
- "rocke_unified_attention_scalar_lower_to_llvm"
- "rocke_python_import_smoke"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import dataclasses
import itertools
import os
import struct
import sys
from dataclasses import dataclass
from typing import List
Expand Down Expand Up @@ -1305,6 +1306,7 @@ def _run_wgrad_sweep(
"""Benchmark the direct wgrad kernel."""
import torch

from rocke.core.arch import ArchTarget
from kernels.common.conv_abi import conv_direct_args_signature
from kernels.common.conv_args import ConvArgs
from kernels.common.conv_direct_grouped import (
Expand Down Expand Up @@ -1356,12 +1358,15 @@ def _run_wgrad_sweep(
_WAVES = sorted({w for w in _WAVES if w[1] <= n_c_tiles_1})
_HPB = [30, 60, 120]
_MK = [32]
# wave64 MFMA on gfx950, wave32 WMMA on gfx1250: the block-size cap and the
# spec both follow the arch.
_wave = ArchTarget.from_gfx(arch).wave_size
combos = [
(wk, wc, wq, hpb, mk)
for wk, wc, wq in _WAVES
for hpb in _HPB
for mk in _MK
if wk * wc <= 16 and wk * wc * wq * 64 <= 1024
if wk * wc <= 16 and wk * wc * wq * _wave <= 1024
]

print(
Expand All @@ -1380,6 +1385,7 @@ def _run_wgrad_sweep(
waves_q=waves_q,
ho_per_block=hpb,
mfma_k=mk,
wave_size=_wave,
)
ok, _ = is_valid_wgrad_spec(spec, arch=arch)
if not ok:
Expand Down Expand Up @@ -2253,10 +2259,15 @@ def bind(plan):
return None

# Each step packed once: a timed loop that packs per launch times the
# host, not a short kernel.
bound = [
launcher.bind(values, config=cfg) for launcher, values, cfg in launches
]
# host, not a short kernel. A kernarg past its packed width (a byte
# extent over 2^31 on an i32 field) skips the plan, not the sweep.
try:
bound = [
launcher.bind(values, config=cfg) for launcher, values, cfg in launches
]
except struct.error as e:
print(f" [skip] {plan.label}: kernarg does not fit ({e})", flush=True)
return None

def run(bound=bound):
for launch in bound:
Expand All @@ -2269,6 +2280,10 @@ def verify(plan, run) -> bool:
nonlocal rc

rt.memset(ptrs[out_name], 0, Out_t.nbytes)
# Two-stage wgrad scratch accumulates too: zeroed again for the
# verified launch, as the target is.
for name in plan.zeroed:
rt.memset(ptrs[name], 0, sizes[name])
run()
synchronize_and_release(0)
out_cpu = torch.empty_like(Out_t)
Expand Down Expand Up @@ -2466,6 +2481,7 @@ def _cache_dispatch(args, arch: str, cases: list) -> int:
"direct_dgrad_helper",
"direct_fwd_helper",
"direct_wgrad",
"direct_wgrad_helper",
),
)
if rc:
Expand All @@ -2478,13 +2494,14 @@ def _cache_dispatch(args, arch: str, cases: list) -> int:

def _batch_chunks(p, direction: str) -> List[int]:
"""Images per launch: the fewest balanced batch chunks that keep every
activation under 2 GiB (``[N]`` when one launch fits, and for wgrad).
activation under 2 GiB (``[N]`` when one launch fits).

The chunks need not divide N; they differ by at most one image (N=31 that
fits 15 images per launch runs as 11 + 10 + 10).
fits 15 images per launch runs as 11 + 10 + 10). wgrad reduces over N, so
its chunks are not independent launches, but they can accumulate: every
chunk adds into the one dW, zeroed once, which only a kernel that adds
into dW can do (:func:`_accumulates_dw`).
"""
if direction == "wgrad":
return [p.N]
elem = 2
per_image = max(p.H * p.W * p.total_c, p.Ho * p.Wo * p.total_k) * elem
per_launch = (_TENSOR_LIMIT - 1) // per_image
Expand All @@ -2502,7 +2519,9 @@ def _chunk_buffers(ptrs, sizes, inputs, out_name, chunks, i):
return ptrs, sizes
ptrs, sizes = dict(ptrs), dict(sizes)
n_total, n0 = sum(chunks), sum(chunks[:i])
for name in [n for n in inputs if n != "w"] + [out_name]:
# wgrad's dW has no batch: every chunk adds into the whole of it.
batched = [out_name] if out_name != "dw" else []
for name in [n for n in inputs if n != "w"] + batched:
per_image = sizes[name] // n_total
ptrs[name] = ptrs[name] + n0 * per_image
sizes[name] = chunks[i] * per_image
Expand Down Expand Up @@ -2537,6 +2556,31 @@ def _chunk_plans(plan_fn, p, chunks):
return plans, rejected, per_chunk


def _accumulates_dw(plan) -> bool:
"""A one-step wgrad plan with no scratch: its kernel adds its tiles into
the zeroed fp32 dW (atomics), so its launches over batch chunks sum. A fold
or reduce step, or a scratch slab, writes dW instead."""
return len(plan.steps) == 1 and not plan.workspaces and not plan.zeroed


def _wgrad_chunk_plans(plans, rejected, per_chunk, chunks):
"""``(plans, rejected)`` for a wgrad problem run as ``chunks``: past one
chunk, only the plans whose every chunk accumulates into dW."""
if len(chunks) == 1:
return plans, rejected
keep = [pl for pl in plans if all(map(_accumulates_dw, per_chunk[pl.label]))]
rejected = list(rejected) + [
(
pl.identity,
f"a wgrad batch run as {len(chunks)} chunks needs a kernel that "
f"accumulates into dW",
)
for pl in plans
if pl not in keep
]
return keep, rejected


def _run_from_cache(args, arch: str, cases: list, cache, directions) -> int:
"""Benchmark every cached kernel that can run each case. Nothing is compiled.

Expand Down Expand Up @@ -2599,6 +2643,8 @@ def _u8(t):
p,
chunks,
)
if direction == "wgrad":
plans, rejected = _wgrad_chunk_plans(plans, rejected, per_chunk, chunks)
if not plans:
why = _no_plan_reason(rejected, dtype, variants)
print(
Expand Down Expand Up @@ -2665,6 +2711,10 @@ def _u8(t):
sizes[name] = max(sizes.get(name, 0), nbytes)
for name in set(sizes) - set(ptrs):
ptrs[name] = rt.alloc(sizes[name])
# Two-stage wgrad scratch: zeroed like dW (once for timing, again
# before each verified run).
for name in {n for plan in plans for n in plan.zeroed}:
rt.memset(ptrs[name], 0, sizes[name])

flop = float(p.flops)
bytes_xfer = float(sum(t.nbytes for t in inputs.values()) + Out_t.nbytes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4783,6 +4783,20 @@ def _u8(t):
flush=True,
)
continue
if direction == "wgrad" and len(batch_chunks) > 1:
# A batch too large for the kernels' i32 addressing runs as
# slices that accumulate into dW: only the kernels that add
# into dW can serve it.
candidates = [c for c in candidates if _wgrad_accumulates(c[0])]
if not candidates:
print(
f"Case {case_idx} {problem.short()} {case_dtype} wgrad: "
f"no compatible kernel (the batch runs as "
f"{len(batch_chunks)} slices, which needs a split-K "
f"kernel that accumulates into dW)",
flush=True,
)
continue
if direction == "wgrad" and args.two_stage != "always":
# Same rule as the JIT sweep's --two-stage: by default the
# two-stage (scratch + reduce) kernels only run when C/groups
Expand Down Expand Up @@ -6090,9 +6104,14 @@ def _batch_chunks(direction, problem, dtype) -> list:
sentinel is the i32 maximum, so no operand it is launched on may exceed
2^31 - 1 bytes. fwd and dgrad compute every image independently, and N is
a kernarg: a batch whose activations are larger runs as consecutive
launches of the same binary over equal slices of N, at offset pointers.
wgrad reduces over N, so it cannot be split that way; neither can a weight
tensor, which has no batch.
launches of the same binary over equal slices of N, at offset pointers. A
weight tensor, which has no batch, cannot be split.

wgrad reduces over N, so its slices are not independent launches -- but
they can accumulate: a kernel that adds into dW (the packed-atomic split-K
family), run over consecutive slices with dW zeroed once before the first,
computes the whole reduction. The caller offers only such kernels when
this returns more than one slice (see :func:`_wgrad_accumulates`).

Raises ``ValueError`` for a problem no split can bring into range.
"""
Expand All @@ -6108,12 +6127,6 @@ def _batch_chunks(direction, problem, dtype) -> list:
)
if per_image * problem.N <= _INT32_MAX:
return [(0, problem.N)]
if direction == "wgrad":
raise ValueError(
f"the activations ({per_image * problem.N} bytes) exceed the "
f"kernels' 2^31-byte operand addressing, and wgrad reduces over the "
f"batch, so it cannot run as batch slices"
)
if per_image > _INT32_MAX:
raise ValueError(
f"one image's activations ({per_image} bytes) exceed the kernels' "
Expand All @@ -6124,6 +6137,17 @@ def _batch_chunks(direction, problem, dtype) -> list:
return [(n0, min(step, problem.N - n0)) for n0 in range(0, problem.N, step)]


def _wgrad_accumulates(ident) -> bool:
"""Whether a cached wgrad binary adds into dW rather than storing it.

Only those can run a wgrad problem as batch slices (:func:`_batch_chunks`):
the packed-atomic split-K family accumulates into a dW zeroed once. The
non-split kernel stores dW, and two-stage and stream-K finish with a
reduce that overwrites it, so each slice would erase the previous ones.
"""
return ident.split_k > 1 and not ident.two_stage and ident.streamk == "off"


def _cached_bind_batch(batch_chunks, direction, problem, ident, *rest):
""":func:`_cached_bind` over the batch slices of :func:`_batch_chunks`.

Expand All @@ -6135,9 +6159,28 @@ def _cached_bind_batch(batch_chunks, direction, problem, ident, *rest):
if len(batch_chunks) == 1:
return _cached_bind(direction, problem, ident, *rest)
(meta, rt, launcher, case_dtype, split_k, ptrs, sizes, *tail) = rest
a_image, d_image = sizes[0] // problem.N, sizes[2] // problem.N
runs = []
if direction == "wgrad":
# A (dY) and B (X) are the activations, D (dW) the reduction target:
# every slice adds into the whole dW, which only the first one zeroes.
if not _wgrad_accumulates(ident):
print(
f" [skip] {ident.label()}: a wgrad problem split into "
f"{len(batch_chunks)} batch slices needs a kernel that "
f"accumulates into dW",
flush=True,
)
return None
a_image, b_image = sizes[0] // problem.N, sizes[1] // problem.N
else:
a_image, d_image = sizes[0] // problem.N, sizes[2] // problem.N
for n0, n in batch_chunks:
if direction == "wgrad":
sl_ptrs = (ptrs[0] + n0 * a_image, ptrs[1] + n0 * b_image, ptrs[2])
sl_sizes = (n * a_image, n * b_image, sizes[2])
else:
sl_ptrs = (ptrs[0] + n0 * a_image, ptrs[1], ptrs[2] + n0 * d_image)
sl_sizes = (n * a_image, sizes[1], n * d_image)
bound = _cached_bind(
direction,
dc_replace(problem, N=n),
Expand All @@ -6147,9 +6190,10 @@ def _cached_bind_batch(batch_chunks, direction, problem, ident, *rest):
launcher,
case_dtype,
split_k,
(ptrs[0] + n0 * a_image, ptrs[1], ptrs[2] + n0 * d_image),
(n * a_image, sizes[1], n * d_image),
sl_ptrs,
sl_sizes,
*tail,
zero_dw=n0 == 0,
)
if bound is None:
return None
Expand Down Expand Up @@ -6178,9 +6222,14 @@ def _cached_bind(
KernelLauncher,
LaunchConfig,
live_extras: list,
zero_dw: bool = True,
):
"""``(run, label)`` of a cached (non-stream-K) binary at ``split_k``.

``zero_dw=False`` leaves out the dW clear of the packed-atomic split-K
wgrad launch: the later batch slices of a sliced wgrad problem add into
the dW the first slice zeroed (:func:`_cached_bind_batch`).

``ptrs`` / ``sizes`` are the A, B, D buffers. The device scratch the
launch config allocates is appended to ``live_extras`` (freed by
:func:`_free_extras`). Returns None, after saying why, when the binary
Expand Down Expand Up @@ -6228,7 +6277,8 @@ def _cached_bind(
# queues ahead of this kernel's launches rather than draining for them.
stream = resolve_stream(cfg.stream)
if (
direction == "wgrad"
zero_dw
and direction == "wgrad"
and ident.split_k > 1
and not ident.two_stage
and ident.streamk == "off"
Expand Down
Loading
Loading