-
Notifications
You must be signed in to change notification settings - Fork 17
order - fuse - order kernel logic #74
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -54,7 +54,7 @@ function Base.map!( | |
|
|
||
| any(isequal(0), dims) && return b # don't do anything | ||
|
|
||
| _mapreduce_fuse!(f, nothing, nothing, dims, (b, a1, A...)) | ||
| _mapreduce_order!(f, nothing, nothing, dims, (b, a1, A...)) | ||
|
|
||
| return b | ||
| end | ||
|
|
@@ -101,18 +101,35 @@ function _mapreducedim!( | |
| map!(initop, arrays[1], arrays[1]) | ||
| end | ||
| else | ||
| _mapreduce_fuse!(f, op, initop, dims, promoteshape(dims, arrays...)) | ||
| _mapreduce_order!(f, op, initop, dims, promoteshape(dims, arrays...)) | ||
| end | ||
| return arrays[1] | ||
| end | ||
|
|
||
| function _mapreduce_fuse!( | ||
| @nospecialize(f), @nospecialize(op), @nospecialize(initop), | ||
| dims::Dims, arrays::Tuple{Vararg{StridedView}} | ||
| ) | ||
| # Fuse dimensions if possible: assume that at least one array, e.g. the output array in | ||
| # arrays[1], has its strides sorted | ||
| allstrides = map(strides, arrays) | ||
| # Cache-friendly reordering of the loop dimensions: put the most important | ||
| # (smallest-stride, most contiguous) dimension first and size-1 dimensions last. | ||
| # The importance of each dimension is modelled from the `indexorder` of every | ||
| # array's strides, with the output array (`strides[1]`) weighted by a factor 2. | ||
| # `strides` is the per-array tuple-of-tuples; returns the reordered `(dims, strides)`. | ||
| function _sortdims(dims, strides) | ||
| M = length(strides) | ||
| N = length(dims) | ||
| # ceil(Int, log2(M+2)) # to account for the fact that there are M arrays, where the first one is counted with a factor 2 | ||
| g = 8 * sizeof(Int) - leading_zeros(M + 1) | ||
| importance = 2 .* (1 .<< (g .* (N .- indexorder(strides[1])))) | ||
| for k in 2:M | ||
| importance = importance .+ (1 .<< (g .* (N .- indexorder(strides[k])))) | ||
| end | ||
|
Comment on lines
+118
to
+122
Member
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. I have to admit that I forgot exactly how this cost model works 😄
Member
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. Something like, the importance of a dimension is sum over arrays of 2^(log2(M+2) * (N - indexorder of dimension in array)) So if an index is the first index in each of the arrays, its importance is (M+1) * (M+2)^((N-1))
Member
Author
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. I also didn't really look into this in detail. I think realistically, any stride that goes beyond 2 cacheline widths probably doesn't actually matter all that much, so the main part is that we want as many contiguous writes and then contiguous reads? To be totally honest, I was looking at the permutation part of this last week and since I have a machine with 512b SIMD registers, it's actually possible to speed up this kernel by quite a lot by doing an in-register transpose, for example reading in 8 lanes of 8 contiguous The caveat is of course that you need 2 stride-1 dimensions that are large enough, the exact kernel sizes have to be tuned for the machines, and that this works mainly for larger SIMD widths, as the benefit decreases for larger Anyways, might at some point try and include such a kernel, but probably don't want to do this in Julia, that seems a compile-time nightmare :)
Member
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. Oh yes, the strided kernels are far from optimal, especially for very specific tasks like transpose. At some point I wanted to invest time to use LoopVectorization for writing a dedicated |
||
|
|
||
| importance = importance .* (dims .> 1) # put dims 1 at the back | ||
| p = TupleTools.sortperm(importance; rev = true) | ||
| return TupleTools.getindices(dims, p), broadcast(TupleTools.getindices, strides, (p,)) | ||
| end | ||
|
|
||
| # Fuse dimensions if possible: merge dimension `i` into `i-1` when the two are | ||
| # contiguous in *every* array (`s[i] == dims[i-1] * s[i-1]`). | ||
| # Merged dimensions fold their extent into `i-1` and become size 1. | ||
| function _fusedims(dims, allstrides) | ||
| @inbounds for i in length(dims):-1:2 | ||
| merge = true | ||
| for s in allstrides | ||
|
|
@@ -126,31 +143,29 @@ function _mapreduce_fuse!( | |
| dims = TupleTools.setindex(dims, 1, i) | ||
| end | ||
| end | ||
| return _mapreduce_order!(f, op, initop, dims, allstrides, arrays) | ||
| return dims | ||
| end | ||
|
|
||
| function order_and_fuse_dims(dims, allstrides) | ||
| dims, allstrides = _sortdims(dims, allstrides) | ||
| dims = _fusedims(dims, allstrides) | ||
| return _sortdims(dims, allstrides) | ||
| end | ||
|
|
||
| # Per-dimension cost used by the blocking and thread-splitting heuristics, | ||
| # derived from the minimum (doubled) stride across arrays. | ||
| _computecosts(strides) = map(a -> ifelse(iszero(a), 1, a << 1), map(min, strides...)) | ||
|
|
||
| # Pipeline entry point: order → fuse → order → block → kernel. | ||
| # Fusing needs ordered entries, and final order pass brings remaining dim 1 to end | ||
| function _mapreduce_order!( | ||
| @nospecialize(f), @nospecialize(op), @nospecialize(initop), | ||
| dims, strides, arrays | ||
| dims::Dims, arrays::Tuple{Vararg{StridedView}} | ||
| ) | ||
| M = length(arrays) | ||
| N = length(dims) | ||
| # sort order of loops/dimensions by modelling the importance of each dimension | ||
| g = 8 * sizeof(Int) - leading_zeros(M + 1) # ceil(Int, log2(M+2)) # to account for the fact | ||
| # that there are M arrays, where the first one is counted with a factor 2 | ||
| importance = 2 .* (1 .<< (g .* (N .- indexorder(strides[1])))) # first array is output | ||
| # and is more important by a factor 2 | ||
| for k in 2:M | ||
| importance = importance .+ (1 .<< (g .* (N .- indexorder(strides[k])))) | ||
| end | ||
|
|
||
| importance = importance .* (dims .> 1) # put dims 1 at the back | ||
| p = TupleTools.sortperm(importance; rev = true) | ||
| dims = TupleTools.getindices(dims, p) | ||
| strides = broadcast(TupleTools.getindices, strides, (p,)) | ||
| dims, allstrides = order_and_fuse_dims(dims, map(strides, arrays)) | ||
| offsets = map(offset, arrays) | ||
| costs = map(a -> ifelse(iszero(a), 1, a << 1), map(min, strides...)) | ||
| return _mapreduce_block!(f, op, initop, dims, strides, offsets, costs, arrays) | ||
| costs = _computecosts(allstrides) | ||
| return _mapreduce_block!(f, op, initop, dims, allstrides, offsets, costs, arrays) | ||
| end | ||
|
|
||
| const MINTHREADLENGTH = 1 << 15 # minimal length before any kind of threading is applied | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.