-
Notifications
You must be signed in to change notification settings - Fork 16
Add tuned HIP GiMMiK preload-C and width variants with non-temporal loads and stores #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8e23d63
8f4d03e
96671a6
739a82e
0633539
7b59fb0
e9b921a
2aa2577
be1c1db
280e948
c06216d
e014e4d
f6bc308
a3aee45
2c7af9b
f6075ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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()} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
@@ -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(); | ||
|
|
@@ -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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This if block is a tad strange since two of the cases reduce to Suggests we should reorder. |
||
| 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,15 @@ | ||
| <%inherit file='base'/> | ||
|
|
||
| __global__ __launch_bounds__(128) void | ||
| <% preload = context.get('preload', False) %> | ||
|
|
||
| __global__ __launch_bounds__(${blockx}) void | ||
| % if n is None: | ||
| ${kname}(int n, | ||
| const ${dtype}* __restrict__ b, int ldb, | ||
| ${dtype}* __restrict__ c, int ldc) | ||
| { | ||
| % if width > 1: | ||
| n = ((n + ${width} - 1) / ${width}) * ${width}; | ||
| n = (n + ${width} - 1) / ${width}; | ||
| ldb /= ${width}; | ||
| ldc /= ${width}; | ||
| % endif | ||
|
|
@@ -24,32 +26,45 @@ ${kname}(const ${dtype}* __restrict__ b, ${dtype}* __restrict__ c) | |
| { | ||
| ${dtype} bv, csub[${m}]; | ||
|
|
||
| ## Iterare through the used rows of B | ||
| % if preload and beta != 0: | ||
| ## Preload C values for rows which will receive a non-zero dot product | ||
| % for j, jx in enumerate(afix): | ||
| % if jx != -1: | ||
| csub[${j}] = ${'' if beta == 1 else f'{beta}*'}load_c(&c[i + ${j}*ldc]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the mul here make sense compared with say doing it inline (so we go from a separate mul and add to an fma)? |
||
| % endif | ||
| % endfor | ||
| % endif | ||
|
|
||
| ## Iterate through the used rows of B | ||
| % for kx in bix: | ||
| bv = b[i + ${kx}*ldb]; | ||
| bv = load_b(&b[i + ${kx}*ldb]); | ||
| % for j, jx in enumerate(A[:, kx]): | ||
| % if jx != 0 and kx == afix[j]: | ||
| % if preload and beta != 0 and jx != 0: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, two cases resulting in the same code. |
||
| csub[${j}] += ${jx}*bv; | ||
| % elif jx != 0 and kx == afix[j]: | ||
| csub[${j}] = ${jx}*bv; | ||
| % elif jx != 0: | ||
| csub[${j}] += ${jx}*bv; | ||
| % endif | ||
| ## | ||
| % if kx == alix[j] and beta == 0: | ||
| c[i + ${j}*ldc] = csub[${j}]; | ||
| % if preload and kx == alix[j]: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also two branches here resulting in the same code being emitted suggests we can combine the tests with an or. |
||
| store_c(&c[i + ${j}*ldc], csub[${j}]); | ||
| % elif kx == alix[j] and beta == 0: | ||
| store_c(&c[i + ${j}*ldc], csub[${j}]); | ||
| % elif kx == alix[j] and beta == 1: | ||
| c[i + ${j}*ldc] += csub[${j}]; | ||
| store_c(&c[i + ${j}*ldc], load_c(&c[i + ${j}*ldc]) + csub[${j}]); | ||
| % elif kx == alix[j]: | ||
| c[i + ${j}*ldc] = csub[${j}] + ${beta}*c[i + ${j}*ldc]; | ||
| store_c(&c[i + ${j}*ldc], csub[${j}] + ${beta}*load_c(&c[i + ${j}*ldc])); | ||
| % endif | ||
| % endfor | ||
| % endfor | ||
|
|
||
| ## Handle rows of A which are all zero | ||
| % for j, jx in enumerate(afix): | ||
| % if jx == -1 and beta == 0: | ||
| c[i + ${j}*ldc] = make_zero(); | ||
| store_c(&c[i + ${j}*ldc], make_zero()); | ||
| % elif jx == -1 and beta != 1: | ||
| c[i + ${j}*ldc] *= ${beta}; | ||
| store_c(&c[i + ${j}*ldc], ${beta}*load_c(&c[i + ${j}*ldc])); | ||
| % endif | ||
| % endfor | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need/benefit from these wrappers? Just calling nt_load/store might be easier.