-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[mlir] Add use-vector-alignment flag to ConvertVectorToLLVMPass #137389
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
Conversation
@llvm/pr-subscribers-mlir-vector @llvm/pr-subscribers-mlir Author: Lily Orth-Smith (electriclilies) ChangesPreviously, the alignment of a MemRefType was set to the alignment of its element type. This caused us to have to set the preferred alignment of scalar types to the desired preferred alignment for vector types. For example, we had to set the preferred alignment of i1s to 512 in our DataLayoutDescription, which is not ideal. This change constructs a vector type which has the same datatype and size as the MemRefType, and then extracts the preferred alignment from that vector type. I did try to use the MemRefType directly, but the output alignments were not correct. Full diff: https://github.com/llvm/llvm-project/pull/137389.diff 1 Files Affected:
diff --git a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp
index 076e5512f375b..480819bd07680 100644
--- a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp
+++ b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp
@@ -70,8 +70,11 @@ static Value extractOne(ConversionPatternRewriter &rewriter,
// Helper that returns data layout alignment of a memref.
LogicalResult getMemRefAlignment(const LLVMTypeConverter &typeConverter,
MemRefType memrefType, unsigned &align) {
- Type elementTy = typeConverter.convertType(memrefType.getElementType());
- if (!elementTy)
+ // Align MemRefTypes to the alignment of a VectorType with the same
+ // size and dtype.
+ Type convertedVecType = typeConverter.convertType(
+ VectorType::get(memrefType.getShape(), memrefType.getElementType()));
+ if (!convertedVecType)
return failure();
// TODO: this should use the MLIR data layout when it becomes available and
|
18dfae0
to
c9997ff
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
c9997ff
to
de73656
Compare
I can see how this can solve your problem for Why not align vectors of sub-bytes on byte boundary? Btw, we need a way to test this change. MLIR examples would also be helpful (e.g. in the summary). |
At the point in the stack we use these patterns, we've gotten rid of all non-statically shaped MemRefs. But I can see how this would be a problem for the generic use case. I can't reveal too many details about our hardware, but most SIMD vectors in our hardware backend have a specific preferred alignment that we need to specify. For example, we had to set the preferred alignment of f16, f32, and n32 to the preferred alignment for their corresponding vector dtypes. So a portion of our data layout description looks like this, because the preferred alignments for vector types aren't being used
This isn't ideal because these alignments are also being used for scalar alignment. What we'd like is for our data layout description to look more like this:
|
I agree that we need a way to test this upstream. That would probably require adding a test that has a different or fake data layout description, and I'm not sure how to do that. |
Hey @electriclilies — this isn’t my area of expertise, but since no one else has chimed in yet, I’ll try to help (at least by pointing you toward the right people). Right now, this feels a bit ad-hoc and assumes that everyone wants their MemRef(s) aligned to a vector boundary rather than a scalar one. That’s not ideal and should generally be avoided — but having a flag to control this seems fine. To me, your requirements don’t seem that exotic and should be relatively straightforward to support. Would adding a flag be sufficient for your use case?
IIUC, this format is supported by LLVM’s data layout: Is there a specific issue you’re running into with this?
A couple of questions:
As per specifying the data layout in a test, I will need to ask around myself 😅 |
@banach_space Thanks for the response! I was thinking yesterday about the code I wrote here and I agree it is a bit ad-hoc and actually incorrect in some cases. If we consider this example from vector_to_llvm_interface.mlir, my old implementation would try to calculate the alignment based on the total size of
I think adding a flag is a solution that would work well for us. In our case, we always have the vector type specified, so I would prefer to use the vector type directly instead of extracting the data type and size from the memref type, converting that into a vector type, and then getting the alignment.
Yes, it is supported, but the vector alignments aren't actually being used by MLIR during vector lowering. So specifying the vector alignments doesn't actually cause the generated vectors to be aligned. For example, when we set the alignment of f32 scalars to 64 bits, the alignment in the code generated below is 8, even if we've set the alignment of 4096 vectors (128 * 32) in the data layout string.
I've updated the code to use a flag toggle between using vector alignment and memref alignment-- let me know what you think! I've also added a test for all the ops affected by this change. |
0baf2d2
to
0512c00
Compare
Also, it looks like I can pass a data layout description into mlir-opt as an option! So, problem solved there. |
d2f5741
to
c9cc43c
Compare
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.
This approach makes sense to me. I've left some comments inline.
Thanks for working on this!
077057f
to
29598e8
Compare
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.
Just some final (minor) pointers/requests :) Thanks for all the updates so far!
@@ -82,6 +97,22 @@ LogicalResult getMemRefAlignment(const LLVMTypeConverter &typeConverter, | |||
return success(); | |||
} | |||
|
|||
LogicalResult getVectorToLLVMAlignment(const LLVMTypeConverter &typeConverter, |
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.
Could you document this as well? Some context useVectorAlignment
would be helpful (similar to what you did elsewhere).
// RUN: mlir-opt %s --convert-vector-to-llvm='use-vector-alignment=0' --split-input-file | FileCheck %s --check-prefix=MEMREF-ALIGN | ||
// RUN: mlir-opt %s --convert-vector-to-llvm='use-vector-alignment=1' --split-input-file | FileCheck %s --check-prefix=VEC-ALIGN |
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.
Previously this would also specify the actual alignment - what happened here?
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.
I think the option I was passing in was not actually doing anything-- it was setting the data layout as a MLIR attribute at the module level but I guess that wasn't being propagated into the LLVM context? The only test that uses the option just checks that the data layout description attr is present at the module level, and doesn't do anything else with it.
There's a note in the original getMemRefAlignment method saying that we should be getting the data layout description from MLIR but instead it's gotten from the LLVMContext. So I think it's now just using whatever the default is? I'll try and look into it a bit more
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.
Thanks for the explanation and for being so diligent about this. No need to dig deeper, this is sufficient for me.
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.
Yeah - in some future point we should have a MLIR data layout <=> LLVM data layout translation that'll make everything work nicely.
I think you'll get what you want by changing the options(context)
to
LowerToLLVMOptions options(
ctx, DataLayout(cast<DataLayoutOpInterface>(m.getOperation())));
That'll let you pick things up from an MLIR DLTI, though I don't think that supports vector alignments yet
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.
You can also check for llvm.data_layout
attributes (aka LLVM::LLVMDialect::getDataLayoutAttrName()
) on the op you're processing's enclosing module and do options.datalayout =
// VEC-ALIGN-LABEL: func @load | ||
// VEC-ALIGN: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64 | ||
// VEC-ALIGN: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64 | ||
// VEC-ALIGN: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64 | ||
// VEC-ALIGN: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32 | ||
// VEC-ALIGN: llvm.load %[[GEP]] {alignment = 32 : i64} : !llvm.ptr -> vector<8xf32> | ||
|
||
// MEMREF-ALIGN-LABEL: func @load | ||
// MEMREF-ALIGN: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64 | ||
// MEMREF-ALIGN: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64 | ||
// MEMREF-ALIGN: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64 | ||
// MEMREF-ALIGN: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32 | ||
// MEMREF-ALIGN: llvm.load %[[GEP]] {alignment = 4 : i64} : !llvm.ptr -> vector<8xf32> |
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.
Since we only care about the alignment here (and the rest of the conversion logic is already well tested elsewhere), I think it would be fine to reduce the CHECK lines to something like:
// ALL-LABEL: func @load
// VEC-ALIGN: llvm.load %[[GEP]] {alignment = 32 : i64} : !llvm.ptr -> vector<8xf32>
// MEMREF-ALIGN: llvm.load %[[GEP]] {alignment = 4 : i64} : !llvm.ptr -> vector<8xf32>
This is mostly in the spirit of "less is more", but it's also consistent with our FileCheck best practices:
- "Tests should be minimal, and only check what is absolutely necessary."
Also, note that you can use a dedicated prefix for lines shared between configurations. For example:
// ALL-LABEL: test_linearize |
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.
Thanks for the tips! The file is much smaller now :)
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.
LGTM!
This is very nicely polished - well done, and thank you for addressing all my comments 🙏🏻
Just to confirm: do you have commit access? If not, I’m happy to land this for you. Normally, I’d wait a few more days to give other reviewers a chance to chime in, but I’ll be away next week and don’t want to delay you - so I’m happy to merge it tomorrow afternoon (Europe time) if that works.
Thank you! I do not have commit access-- I did at one point but was inactive for a while and now I don't anymore. |
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.
A few notes about how to sneak data layout in
(See LowerGPUOpsToROCDL
for where I was getting some examples from)
"bool", /*default=*/"false", | ||
"Use the preferred alignment of a vector type in load/store " | ||
"operations instead of the alignment of the element type of the " | ||
"memref. This flag is intended for use with hardware which requires" |
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.
"or in application contexts where it is known all vector accesses are naturally aligned"?
// RUN: mlir-opt %s --convert-vector-to-llvm='use-vector-alignment=0' --split-input-file | FileCheck %s --check-prefix=MEMREF-ALIGN | ||
// RUN: mlir-opt %s --convert-vector-to-llvm='use-vector-alignment=1' --split-input-file | FileCheck %s --check-prefix=VEC-ALIGN |
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.
Yeah - in some future point we should have a MLIR data layout <=> LLVM data layout translation that'll make everything work nicely.
I think you'll get what you want by changing the options(context)
to
LowerToLLVMOptions options(
ctx, DataLayout(cast<DataLayoutOpInterface>(m.getOperation())));
That'll let you pick things up from an MLIR DLTI, though I don't think that supports vector alignments yet
// RUN: mlir-opt %s --convert-vector-to-llvm='use-vector-alignment=0' --split-input-file | FileCheck %s --check-prefix=MEMREF-ALIGN | ||
// RUN: mlir-opt %s --convert-vector-to-llvm='use-vector-alignment=1' --split-input-file | FileCheck %s --check-prefix=VEC-ALIGN |
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.
You can also check for llvm.data_layout
attributes (aka LLVM::LLVMDialect::getDataLayoutAttrName()
) on the op you're processing's enclosing module and do options.datalayout =
Update, on further reading, the MLIR data layout is really not plumbed through - it's only used for index bitwidth. So |
Thanks for all the pointers, these are very helpful! 🙏🏻
Is it something that you'd like included in this PR? To me, this change is already self-contained, so I am fine with it as is. |
@krzysz00 Thanks for the pointers! I'd prefer to keep this change self contained if that's ok with you. I think your solution would work for the purposes of the test, but I'd like to be able to use the data layout description that's set the LLVMContext, since it's used in the rest of the compiler. To clarify, I've tested with our backend and the vector alignments from the data layout in the LLVMContext are definitely getting correctly propagated. I also don't like the idea of introducing a new source of truth for the data layout description, especially if we only use it here and not anywhere else in the codebase. |
Yesh, let's not mess with how the data layout options get set in this PR just for the test. No need to make the change I suggested, we're good to land |
9ad8538
to
47a9f63
Compare
Great, thanks! I just updated the documentation like you suggested. I don't have merge access so someone else will need to hit the button once it goes green again :) |
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.
Great contribution, thanks! I think we should also start thinking about to bring the alignment information to the corresponding vector operations so that eventually we don't have to use this flag.
…#137389) In ConvertVectorToLLVM, the only option for setting alignment of `vector.gather`, `vector.scatter`, and the `vector.load/store` ops was to extract it from the datatype of the memref type. However, this is insufficient for hardware backends requiring alignment of vector types. This PR introduces the `use-vector-alignment` option to the `ConvertVectorToLLVMPass`, which makes the pass use the alignment of the vector type of these operations instead of the alignment of the memref type. --------- Co-authored-by: Lily Orth-Smith <[email protected]>
…#137389) In ConvertVectorToLLVM, the only option for setting alignment of `vector.gather`, `vector.scatter`, and the `vector.load/store` ops was to extract it from the datatype of the memref type. However, this is insufficient for hardware backends requiring alignment of vector types. This PR introduces the `use-vector-alignment` option to the `ConvertVectorToLLVMPass`, which makes the pass use the alignment of the vector type of these operations instead of the alignment of the memref type. --------- Co-authored-by: Lily Orth-Smith <[email protected]>
…#137389) In ConvertVectorToLLVM, the only option for setting alignment of `vector.gather`, `vector.scatter`, and the `vector.load/store` ops was to extract it from the datatype of the memref type. However, this is insufficient for hardware backends requiring alignment of vector types. This PR introduces the `use-vector-alignment` option to the `ConvertVectorToLLVMPass`, which makes the pass use the alignment of the vector type of these operations instead of the alignment of the memref type. --------- Co-authored-by: Lily Orth-Smith <[email protected]>
…#137389) In ConvertVectorToLLVM, the only option for setting alignment of `vector.gather`, `vector.scatter`, and the `vector.load/store` ops was to extract it from the datatype of the memref type. However, this is insufficient for hardware backends requiring alignment of vector types. This PR introduces the `use-vector-alignment` option to the `ConvertVectorToLLVMPass`, which makes the pass use the alignment of the vector type of these operations instead of the alignment of the memref type. --------- Co-authored-by: Lily Orth-Smith <[email protected]>
In ConvertVectorToLLVM, the only option for setting alignment of
vector.gather
,vector.scatter
, and thevector.load/store
ops was to extract it from the datatype of the memref type. However, this is insufficient for hardware backends requiring alignment of vector types. This PR introduces theuse-vector-alignment
option to theConvertVectorToLLVMPass
, which makes the pass use the alignment of the vector type of these operations instead of the alignment of the memref type.