Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8e23d63
Add tuned HIP GiMMiK preload variants
tomjen12 Jun 18, 2026
8f4d03e
Fix HIP GiMMiK block size metadata
tomjen12 Jun 18, 2026
96671a6
Address HIP GiMMiK review comments
tomjen12 Jun 22, 2026
739a82e
Handle ROCm feature suffixes for gfx942 tuning
tomjen12 Jun 22, 2026
0633539
Enable tuned HIP variants on gfx90a
tomjen12 Jun 22, 2026
7b59fb0
Parameterize HIP vector width and refine preload kernels
tomjen12 Jun 23, 2026
e9b921a
Use blockx launch bounds for HIP cstream preload
tomjen12 Jun 23, 2026
2aa2577
Always use non-temporal C accesses for HIP
tomjen12 Jun 24, 2026
be1c1db
feat(hip): add non-temporal B-load (NTB) variants for bstream-msplit
EricKing626 Jun 24, 2026
280e948
Use non-temporal B loads by default for HIP
tomjen12 Jun 25, 2026
c06216d
Make HIP preload-C a template option
tomjen12 Jun 25, 2026
e014e4d
Avoid HIP vector operator+= overloads
tomjen12 Jun 25, 2026
9dfd072
Add f64 MFMA dense kernel for CDNA3 (gfx94x)
EricKing626 Jun 25, 2026
6d237ef
Update mfma-dense.mako
EricKing626 Jun 25, 2026
f6bc308
Prune HIP tuned variants to 12
tomjen12 Jun 25, 2026
6689a9c
Update mfma-dense.mako
EricKing626 Jun 25, 2026
a3aee45
Remove HIP variant arch gate
tomjen12 Jun 25, 2026
b521427
Update hip.py
EricKing626 Jun 25, 2026
3390912
Add m-splitting and zero-tile skipping to MFMA dense kernel
EricKing626 Jun 25, 2026
99deb2e
Add software-pipelined (double-buffered B) MFMA dense variant
EricKing626 Jun 25, 2026
1e554de
Cut B traffic in MFMA m-split path with bix-compacted, vectorized LDS…
EricKing626 Jun 25, 2026
7988c70
Compact MFMA m-split LDS tile to active k-tiles only
EricKing626 Jun 25, 2026
1ee00bf
k-block the MFMA m-split path so it fits (and wins) large-k operators
EricKing626 Jun 25, 2026
2c7af9b
Restore MI355 HIP baseline variants
tomjen12 Jun 25, 2026
9e289a1
Merge branch 'hip-gimmik-mfma-dense' into hip-gimmik-mfma-dense-pr
tomjen12 Jul 1, 2026
ffc8aff
Clean up HIP MFMA dense integration
tomjen12 Jul 1, 2026
24968b3
Update HIP MFMA tile GEMM kernel
tomjen12 Jul 10, 2026
fb8544f
Add NT128 HIP MFMA tile candidates for MI325
tomjen12 Jul 10, 2026
1edef18
Simplify HIP bstream preload-C beta handling
tomjen12 Jul 13, 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
171 changes: 159 additions & 12 deletions gimmik/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,178 @@

from gimmik.base import MatMul

import numpy as np


class HIPMatMul(MatMul):
platform = 'hip'
basemeta = {'block': (128, 1, 1), 'width': 1, 'shared': 0}

def _kernel_generators(self, dtype, dsize, *, gcn_arch=None, warp_size=64):
# B loading, C streaming kernel
yield ('cstream', {}, {})
max_block_threads = 1024
max_shared = 64*1024

def emit(name, args, meta):
block = meta.get('block', self.basemeta['block'])
shared = meta.get('shared', self.basemeta['shared'])
threads = block[0]*block[1]*block[2]

if threads <= max_block_threads and shared <= max_shared:
yield (name, args, meta)

# B streaming, C accumulation kernel
yield ('bstream', {}, {})
def emit_preload(name, args, meta):
yield from emit(name, args | {'preload': True}, meta)

# Four-way m-split B streaming, C accumulation kernel
ms, bsz, blkx = 4, 24, 64
args = {'msplit': ms, 'bsz': bsz, 'blockx': blkx}
meta = {'block': (blkx, ms, 1), 'shared': 2*bsz*blkx*dsize}
yield ('bstream-msplit', args, meta)
meta = {
'block': (blkx, ms, 1), 'shared': 2*bsz*blkx*dsize,
'desc': f'bstream-msplit/m{ms}-b{bsz}-x{blkx}'
}
yield from emit('bstream-msplit', args, meta)

# Two-way k-split B loading, C streaming kernel
ks, csz, blkx = 2, 24, 64
args = {'ksplit': ks, 'csz': csz, 'blockx': blkx}
meta = {'block': (blkx, ks, 1), 'shared': (ks - 1)*csz*blkx*dsize}
yield ('cstream-ksplit', args, meta)
meta = {
'block': (blkx, ks, 1), 'shared': (ks - 1)*csz*blkx*dsize,
'desc': f'cstream-ksplit/k{ks}-c{csz}-x{blkx}'
}
yield from emit('cstream-ksplit', args, meta)

# Tuned HIP variants
msplits, ksplits = [8, 4], [4, 2]
bsz, csz, blkx = 8, 8, 64
widths = [1]
if self.aligne is not None and self.aligne % 2 == 0:
widths.insert(0, 2)

for width in widths:
wargs = ({'dtype': f'{dtype}{width}', 'width': width}
if width > 1 else {})
wmeta = {'width': width} if width > 1 else {}
wpfx = f'w{width}-' if width > 1 else ''

for ms in msplits:
# m-split B streaming, C accumulation kernel
args = {'msplit': ms, 'bsz': bsz, 'blockx': blkx} | wargs
shared = 2*bsz*blkx*dsize*width
meta = {
'block': (blkx, ms, 1), 'shared': shared,
'desc': f'bstream-msplit/{wpfx}m{ms}-b{bsz}-x{blkx}'
} | wmeta
yield from emit('bstream-msplit', args, meta)

for ms in msplits:
# m-split B streaming, C preloading, C accumulation kernel
args = {'msplit': ms, 'bsz': bsz, 'blockx': blkx} | wargs
shared = 2*bsz*blkx*dsize*width
meta = {
'block': (blkx, ms, 1), 'shared': shared,
'desc': (
f'bstream-msplit-preload-c/'
f'{wpfx}m{ms}-b{bsz}-x{blkx}'
)
} | wmeta
yield from emit_preload('bstream-msplit', args, meta)

for ks in ksplits:
# k-split B loading, C preloading, C streaming kernel
args = {'ksplit': ks, 'csz': csz, 'blockx': blkx} | wargs
shared = (ks - 1)*csz*blkx*dsize*width
meta = {
'block': (blkx, ks, 1), 'shared': shared,
'desc': (
f'cstream-ksplit-preload-c/'
f'{wpfx}k{ks}-c{csz}-x{blkx}'
)
} | wmeta
yield from emit_preload('cstream-ksplit', args, meta)

if dsize == 8:
# ── mfma-tile-gemm ────────────────────────────────────────────
# Packed Direct-A MFMA path with B-reuse workgroup mapping and
# cached B loads. NT is the scalar output-column tile; width
# converts it to vector columns before rendering the kernel.
packed_mfma_tiles = [
(64, 64, 8, 64, 4),
(128, 64, 8, 64, 4),
(64, 128, 8, 64, 4),
(128, 128, 8, 64, 4),
]

widths = [2] if self.aligne is not None and self.aligne % 2 == 0 else [1]

for width in widths:
wargs = ({'dtype': f'{dtype}{width}', 'width': width,
'sdtype': dtype} if width > 1 else {})
wpfx = f'w{width}-' if width > 1 else ''

for MT, NT, KT, blockx, blocky in packed_mfma_tiles:
if NT % width:
raise ValueError('mfma-tile-gemm width expects NT divisible by width')

vNT = NT // width
block = (blockx, blocky, 1)
a_packed_hex, m_pad, k_pad = self._dense_mfma_lane_bake(MT, KT)
direct_a_shared = 2*(KT*NT)*dsize
bpfx = f'b{blocky}-' if blocky != 4 else ''
args = {
'MT': MT, 'NT': vNT, 'KT': KT,
'blockx': block[0], 'blocky': block[1],
'a_hex': a_packed_hex, 'm_pad': m_pad,
'k_pad': k_pad,
} | wargs
width_meta = {'width': width} if width > 1 else {}
yield from emit(
'mfma-tile-gemm',
args,
{
'block': block, 'shared': direct_a_shared,
'bm': MT, 'ncols': vNT,
'desc': (
f'mfma-tile-gemm/'
f'{wpfx}{bpfx}mt{MT}-nt{NT}-kt{KT}'
),
} | width_meta
)

def _dense_mfma_lane_bake(self, BM, BK):
# Pack A so each lane can vector-load the two FP64 operands it consumes
# across a pair of consecutive 16x16x4 MFMA K groups:
# Apg[row16_tile][kg_pair][lane][which]
# where lane = g*16 + p and which selects kg_pair*2 + {0, 1}.
if BK % 8:
raise ValueError('mfma lane-packed A expects BK to be a multiple of 8')

m, k = self.A.shape
m_pad = -(-m // BM) * BM
k_pad = -(-k // BK) * BK
a_pad = np.zeros((m_pad, k_pad), dtype=np.float64)
a_pad[:m, :k] = self.A

packed = []
kg_pairs = BK // 8
for row16 in range(m_pad // 16):
row_base = row16*16
for ktile in range(k_pad // BK):
k_base = ktile*BK
for kgp in range(kg_pairs):
for lane in range(64):
g = lane // 16
p = lane % 16
row = row_base + p
for which in range(2):
kg = 2*kgp + which
packed.append(a_pad[row, k_base + kg*4 + g])

return [float(x).hex() for x in packed], m_pad, k_pad

def _process_meta(self, meta):
bm = meta.get('bm')
if bm is not None:
meta['grid_y'] = -(-self.A.shape[0] // bm)

if self.n is not None:
div = meta['block'][0]*meta['width']
meta['grid'] = (-(-self.n // div), 1, 1)
div = meta.get('ncols', meta['block'][0])*meta['width']
gy = meta.get('grid_y', 1)
meta['grid'] = (-(-self.n // div), gy, 1)
68 changes: 65 additions & 3 deletions gimmik/kernels/hip/base.mako
Original file line number Diff line number Diff line change
@@ -1,12 +1,74 @@
% if dtype.endswith('4'):
static inline __device__ ${dtype} make_zero()
inline __device__ ${dtype} operator+(${dtype} a, ${dtype} b)
{ return make_${dtype}(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }

inline __device__ ${dtype} operator*(${dtype[:-1]} a, ${dtype} b)
{ return make_${dtype}(a*b.x, a*b.y, a*b.z, a*b.w); }

inline __device__ ${dtype} make_zero()
{ return make_${dtype}(0, 0, 0, 0); }
% elif dtype.endswith('2'):
static inline __device__ ${dtype} make_zero()
inline __device__ ${dtype} operator+(${dtype} a, ${dtype} b)
{ return make_${dtype}(a.x + b.x, a.y + b.y); }

inline __device__ ${dtype} operator*(${dtype[:-1]} a, ${dtype} b)
{ return make_${dtype}(a*b.x, a*b.y); }

inline __device__ ${dtype} make_zero()
{ return make_${dtype}(0, 0); }
% else:
static inline __device__ ${dtype} make_zero()
inline __device__ ${dtype} make_zero()
{ return 0; }
% endif

static inline __device__ void
nt_store(${dtype}* p, ${dtype} v)
{
% if dtype.endswith('4'):
__builtin_nontemporal_store(v.x, &p->x);
__builtin_nontemporal_store(v.y, &p->y);
__builtin_nontemporal_store(v.z, &p->z);
__builtin_nontemporal_store(v.w, &p->w);
% elif dtype.endswith('2'):
__builtin_nontemporal_store(v.x, &p->x);
__builtin_nontemporal_store(v.y, &p->y);
% else:
__builtin_nontemporal_store(v, p);
% endif
}

static inline __device__ ${dtype}
nt_load(const ${dtype}* p)
{
% if dtype.endswith('4'):
return make_${dtype}(__builtin_nontemporal_load(&p->x),
__builtin_nontemporal_load(&p->y),
__builtin_nontemporal_load(&p->z),
__builtin_nontemporal_load(&p->w));
% elif dtype.endswith('2'):
return make_${dtype}(__builtin_nontemporal_load(&p->x),
__builtin_nontemporal_load(&p->y));
% else:
return __builtin_nontemporal_load(p);
% endif
}

static inline __device__ void
store_c(${dtype}* p, ${dtype} v)
{
nt_store(p, v);
}

static inline __device__ ${dtype}
load_c(const ${dtype}* p)
{
return nt_load(p);
}

static inline __device__ ${dtype}
load_b(const ${dtype}* p)
{
return nt_load(p);
}

${next.body()}
34 changes: 24 additions & 10 deletions gimmik/kernels/hip/bstream-msplit.mako
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<%
mx = partition(A, into=msplit, by='rows')
bchunks = chunk(bix, bsz)
preload = context.get('preload', False)
%>

__global__ __launch_bounds__(${blockx*msplit}) void
Expand All @@ -12,7 +13,7 @@ ${kname}(int n,
${dtype}* __restrict__ c, int ldc)
{
% if width > 1:
n = ((n + ${width} - 1) / ${width}) * ${width};
n = (n + ${width} - 1) / ${width};
ldb /= ${width};
ldc /= ${width};
% endif
Expand All @@ -34,9 +35,18 @@ ${kname}(const ${dtype}* __restrict__ b, ${dtype}* __restrict__ c)
{
% for kx in bchunks[0]:
% if loop.index % msplit == cid:
bsub[0][${loop.index}][threadIdx.x] = b[i + ${kx}*ldb];
bsub[0][${loop.index}][threadIdx.x] = load_b(&b[i + ${kx}*ldb]);
% endif
% endfor

% if preload and beta != 0:
## Preload C values for active rows owned by this m-split lane
% for j, jx in enumerate(mx[cid]):
% if afix[jx] != -1:
csub[${j}] = ${'' if beta == 1 else f'{beta}*'}load_c(&c[i + ${jx}*ldc]);
% endif
% endfor
% endif
}
% endfor
__syncthreads();
Expand All @@ -51,36 +61,40 @@ ${kname}(const ${dtype}* __restrict__ b, ${dtype}* __restrict__ c)
% if not loop.parent.last:
% for kx in bchunks[bb + 1]:
% if loop.index % msplit == cid:
bsub[${(bb + 1) % 2}][${loop.index}][threadIdx.x] = b[i + ${kx}*ldb];
bsub[${(bb + 1) % 2}][${loop.index}][threadIdx.x] = load_b(&b[i + ${kx}*ldb]);
% endif
% endfor
% endif
## Accumulate our dot products
% for kx in bchunks[bb]:
bv = bsub[${bb % 2}][${loop.index}][threadIdx.x];
% for j, jx in enumerate(A[mcx, kx]):
% if jx != 0 and kx == afix[mcx[j]]:
% if preload and beta != 0 and jx != 0:
csub[${j}] += ${jx}*bv;
% elif jx != 0 and kx == afix[mcx[j]]:
csub[${j}] = ${jx}*bv;
% elif jx != 0:
csub[${j}] += ${jx}*bv;
% endif
## If we're done with this dot product then store to global
% if kx == alix[mcx[j]] and beta == 0:
c[i + ${mcx[j]}*ldc] = csub[${j}];
% if preload and kx == alix[mcx[j]]:
store_c(&c[i + ${mcx[j]}*ldc], csub[${j}]);
% elif kx == alix[mcx[j]] and beta == 0:
store_c(&c[i + ${mcx[j]}*ldc], csub[${j}]);
% elif kx == alix[mcx[j]] and beta == 1:
c[i + ${mcx[j]}*ldc] += csub[${j}];
store_c(&c[i + ${mcx[j]}*ldc], load_c(&c[i + ${mcx[j]}*ldc]) + csub[${j}]);
% elif kx == alix[mcx[j]]:
c[i + ${mcx[j]}*ldc] = csub[${j}] + ${beta}*c[i + ${mcx[j]}*ldc];
store_c(&c[i + ${mcx[j]}*ldc], csub[${j}] + ${beta}*load_c(&c[i + ${mcx[j]}*ldc]));
% endif
% endfor
% endfor
## Handle rows of A which are all zero
% if loop.parent.last:
% for j, jx in enumerate(afix):
% if jx == -1 and j % msplit == cid and beta == 0:
c[i + ${j}*ldc] = make_zero();
store_c(&c[i + ${j}*ldc], make_zero());
% elif jx == -1 and j % msplit == cid and beta != 1:
c[i + ${j}*ldc] *= ${beta};
store_c(&c[i + ${j}*ldc], ${beta}*load_c(&c[i + ${j}*ldc]));
% endif
% endfor
% endif
Expand Down
Loading