Skip to content

[memref] Support dense resources for memref-to-llvm #79380

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

Closed
wants to merge 4 commits into from

Conversation

rsuderman
Copy link
Contributor

Memref to llvm did not handle the DenseResourceElementsAttr. Reworked
the lowering so that it converts to a valid LLVM type.

Memref to llvm did not handle the DenseResourceElementsAttr. Reworked
the lowering so that it converts to a valid LLVM type.
@llvmbot
Copy link
Member

llvmbot commented Jan 24, 2024

@llvm/pr-subscribers-mlir

Author: Rob Suderman (rsuderman)

Changes

Memref to llvm did not handle the DenseResourceElementsAttr. Reworked
the lowering so that it converts to a valid LLVM type.


Full diff: https://github.com/llvm/llvm-project/pull/79380.diff

2 Files Affected:

  • (modified) mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp (+16-7)
  • (modified) mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir (+15)
diff --git a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
index 2bfca303b5fd48..8fd1f37acdad6c 100644
--- a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
+++ b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
@@ -23,6 +23,7 @@
 #include "mlir/Dialect/MemRef/Utils/MemRefUtils.h"
 #include "mlir/IR/AffineMap.h"
 #include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/DialectResourceBlobManager.h"
 #include "mlir/IR/IRMapping.h"
 #include "mlir/Pass/Pass.h"
 #include "mlir/Support/MathExtras.h"
@@ -501,13 +502,21 @@ struct GlobalMemrefOpLowering
 
     Attribute initialValue = nullptr;
     if (!global.isExternal() && !global.isUninitialized()) {
-      auto elementsAttr = llvm::cast<ElementsAttr>(*global.getInitialValue());
-      initialValue = elementsAttr;
-
-      // For scalar memrefs, the global variable created is of the element type,
-      // so unpack the elements attribute to extract the value.
-      if (type.getRank() == 0)
-        initialValue = elementsAttr.getSplatValue<Attribute>();
+      Attribute initialAttr = *global.getInitialValue();
+      if (auto resourceAttr = llvm::dyn_cast<DenseResourceElementsAttr>(initialAttr)) {
+        auto blob = resourceAttr.getRawHandle().getBlob();
+        initialAttr = DenseElementsAttr::getFromRawBuffer(
+          resourceAttr.getType(), blob->getData());
+      } 
+
+      if (auto elementsAttr = llvm::cast<ElementsAttr>(initialAttr)) {
+        initialValue = elementsAttr;
+
+        // For scalar memrefs, the global variable created is of the element type,
+        // so unpack the elements attribute to extract the value.
+        if (type.getRank() == 0)
+          initialValue = elementsAttr.getSplatValue<Attribute>();
+      }
     }
 
     uint64_t alignment = global.getAlignment().value_or(0);
diff --git a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
index 37999d6fc14ad1..9e00fd1bb5c73c 100644
--- a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
+++ b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
@@ -330,6 +330,21 @@ memref.global "private" @gv4 : memref<f32> = dense<1.0> {alignment = 64}
 
 // -----
 
+module {
+  // CHECK: llvm.mlir.global private constant @__constant_xf32(1.41421354 : f32) {addr_space = 0 : i32} : f32
+  memref.global "private" constant @__constant_xf32 : memref<f32> = dense_resource<NAME>
+}
+
+{-#
+  dialect_resources: {
+    builtin: {
+      NAME: "0x08000000F304B53F"
+    }
+  }
+#-}
+
+// -----
+
 // Expand shapes need to be expanded outside of the memref-to-llvm pass.
 // CHECK-LABEL: func @expand_shape_static(
 // CHECK-SAME:         %[[ARG:.*]]: memref<{{.*}}>)

Copy link

github-actions bot commented Jan 24, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Collaborator

@joker-eph joker-eph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work on big endian?

(see recent failure with dense resources support: #78958 (comment) )

@rsuderman
Copy link
Contributor Author

Will this work on big endian?

(see recent failure with dense resources support: #78958 (comment) )

So that is a very good question that I am not 100% sure on. I would assume the data storage order should not change between the DenseResourcesAttr as it is just a bag of bytes I would assume it maps to the same way for DenseElementsAttr's byte ordering expectations. Is there a good way to validate?

@joker-eph
Copy link
Collaborator

@uweigand or @Groverkss may be able to help answering here?

@Groverkss
Copy link
Member

Groverkss commented Jan 24, 2024

This will probably have the same failures because you would interpret the bytes differently on big endian systems and the test will not match. So even if it is correct, your test will fail, because the answer is dependent on endianness.

This also needs some tests for non-splat values.

@rsuderman
Copy link
Contributor Author

This will probably have the same failures because you would interpret the bytes differently on big endian systems and the test will not match. So even if it is correct, your test will fail, because the answer is dependent on endianness.

This also needs some tests for non-splat values.

Non-splat case added.

I am a little confused by your comments: are you saying that if endianness matters it will fail no matter what, or even if this test passes it may not be correct.

Thanks!

@Groverkss
Copy link
Member

This will probably have the same failures because you would interpret the bytes differently on big endian systems and the test will not match. So even if it is correct, your test will fail, because the answer is dependent on endianness.
This also needs some tests for non-splat values.

Non-splat case added.

I am a little confused by your comments: are you saying that if endianness matters it will fail no matter what, or even if this test passes it may not be correct.

Thanks!

Thank you for adding the test.

I'll try to be more clear. Let's take this test:

  // CHECK: llvm.mlir.global private constant @__constant_xf32_1(dense<[5.000000e-01, 2.500000e-01]> : tensor<2xf32>) {addr_space = 0 : i32} : !llvm.array<2 x f32>
  memref.global "private" constant @__constant_xf32_1 : memref<2xf32> = dense_resource<NAME1>

Here, dense<[5.000000e-01, 2.500000e-01]> is how little endian systems interpret the byte array in NAME1:

NAME1: "0x080000000000003F0000803E"

For a big-endian system, this may be differently interpreted and there would be a different dense<> attribute. So the test output actually depends on the endianness of the system.

I think we need to disable these tests for big endian systems.

@joker-eph
Copy link
Collaborator

For a big-endian system, this may be differently interpreted and there would be a different dense<> attribute. So the test output actually depends on the endianness of the system.

To be clear: that's a bug though right?

@joker-eph
Copy link
Collaborator

Following #63469 ; I'm wondering if endianness decoding shouldn't be just handled here? Is getFromRawBuffer with blob->getData() correct as-is? That may be the place where endianness should be handled?

@Groverkss
Copy link
Member

For a big-endian system, this may be differently interpreted and there would be a different dense<> attribute. So the test output actually depends on the endianness of the system.

To be clear: that's a bug though right?

No, it's not a bug. dense_resource does not specify what endianness to use. So the best we can do is assume the endianness of the system (which is what we do). So this test simply has different outputs on a big endian machine.

The expectation for dense_resource, AFAIU, is that whoever created it should read it.

@stellaraccident
Copy link
Contributor

I think this is a test bug: dense_resource_elements takes a blob of memory and bitcasts it for access. It is the same as if someone put a char[] literal in their c program and cast it to an array of a multi byte type. It is actually a pretty important part of the contract that intervening code not go mucking with trying to manage/copy it, etc.

We use this for capturing raw memory from the framework for jit and related cases, and it is implied that if portability to another endianness is important, that is up to the user to make them agree or convert.

This is breaking because the test encodes a little endian blob. To test on big endian, it would need a big endian blob.

@joker-eph
Copy link
Collaborator

I'm not sure I agree just now that this is totally reasonable to serialize non-portable bytecode, without providing any tools to the user to not shoot themselves in the foot like that.
Is this behavior documented?

@stellaraccident
Copy link
Contributor

I'm not sure I agree just now that this is totally reasonable to serialize non-portable bytecode, without providing any tools to the user to not shoot themselves in the foot like that. Is this behavior documented?

I think it should be documented.

I'm not saying the use case isn't valid for someone and that there shouldn't be an attribute that does more, but that we need one that doesn't have such features -- which put constraints on use and break the memory model. If we don't get that upstream, we'll need to fork our own, most likely.

Does llvm bitcode take effort to be portable across machines for its low level constants (as in -- can you load bitcode compiled on one architecture, lto it in another and expect that something has intervened to swap endianness of the data structures at rest)? I don't actually know the answer to that 100% but that is the use case, imo.

@joker-eph
Copy link
Collaborator

Does llvm bitcode take effort to be portable across machines for its low level constants

I don't find code that would handle that, so I'd say no (but not with a high confidence)

@rsuderman
Copy link
Contributor Author

Probably better to fix closer to the onnx import.

@rsuderman rsuderman closed this Jan 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants