Skip to content

Commit 9599fbc

Browse files
j2kuncopybara-github
authored andcommitted
Support non-constant splats in RotationAnalysis
PiperOrigin-RevId: 889451284
1 parent 23e2143 commit 9599fbc

14 files changed

Lines changed: 387 additions & 82 deletions

File tree

lib/Analysis/RotationAnalysis/DagBuilder.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,13 @@ FailureOr<NodePtr> DagBuilder::visit(tensor::SplatOp op) {
293293
IntegerAttr attr;
294294
if (matchPattern(op.getInput(), m_Constant(&attr))) {
295295
LDBG() << "Matched splatted value to constant scalar " << attr;
296+
auto dagNode =
297+
Node::splat(attr.getInt(), mlirTypeToDagType(op.getResult().getType()));
298+
valueToNode[op.getResult()] = dagNode;
299+
return dagNode;
296300
}
297301

298-
auto dagNode =
299-
Node::splat(attr.getInt(), mlirTypeToDagType(op.getResult().getType()));
300-
valueToNode[op.getResult()] = dagNode;
301-
return dagNode;
302+
return findNodeOrMakeNewVariable(op.getResult());
302303
}
303304

304305
FailureOr<NodePtr> DagBuilder::visit(arith::DivSIOp op) {

lib/Dialect/HEIRInterfaces.cpp

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -75,67 +75,68 @@ LogicalResult verifyElementwiseByOperandImpl(
7575
ElementwiseByOperandOpInterface opInterface) {
7676
Operation* op = opInterface.getOperation();
7777

78-
auto typeToShapeStr = [](Type type) {
79-
if (auto rankedTensorType = dyn_cast<RankedTensorType>(type)) {
80-
std::string shapeStr = "(";
81-
for (auto dim : rankedTensorType.getShape()) {
82-
shapeStr += std::to_string(dim) + ",";
83-
}
84-
shapeStr += ")";
85-
return shapeStr;
86-
}
87-
return std::string("(not statically ranked)");
78+
auto typeToShapeStr = [](ArrayRef<int64_t> shape) {
79+
return "(" +
80+
llvm::join(
81+
llvm::map_range(shape,
82+
[](int64_t dim) { return std::to_string(dim); }),
83+
", ") +
84+
")";
8885
};
8986

90-
std::optional<TensorType> tensorType;
91-
int64_t operandIndex = 0;
87+
std::optional<SmallVector<int64_t>> mappedShape;
88+
int64_t firstMappableOperandIndex = -1;
89+
9290
for (auto [i, operand] : llvm::enumerate(op->getOperands())) {
9391
auto thisTensorType = dyn_cast<TensorType>(operand.getType());
94-
if (!thisTensorType)
95-
// Non-tensor types are acceptable, and need not be specified as mappable
96-
// or not mappable by the interface.
97-
continue;
92+
if (!thisTensorType) continue;
9893

9994
if (opInterface.operandIsMappable(i)) {
100-
if (!tensorType) {
101-
tensorType = thisTensorType;
102-
operandIndex = i;
95+
SmallVector<int64_t> thisMappedShape;
96+
for (int dim : opInterface.mappedDimensionsForOperand(i)) {
97+
thisMappedShape.push_back(thisTensorType.getDimSize(dim));
98+
}
99+
100+
if (!mappedShape) {
101+
mappedShape = thisMappedShape;
102+
firstMappableOperandIndex = i;
103103
continue;
104104
}
105105

106-
if (thisTensorType.getShape() != tensorType->getShape()) {
106+
if (thisMappedShape != *mappedShape) {
107107
return op->emitOpError()
108-
<< "expected all mappable operands to have the same shape, "
109-
<< "but found shape " << typeToShapeStr(*tensorType)
110-
<< " at operand " << operandIndex << " and "
111-
<< typeToShapeStr(thisTensorType) << " at operand " << i;
108+
<< "expected all mappable operands to have the same mapped "
109+
"shape, but found mapped shape "
110+
<< typeToShapeStr(*mappedShape) << " at operand "
111+
<< firstMappableOperandIndex << " and "
112+
<< typeToShapeStr(thisMappedShape) << " at operand " << i;
112113
}
113114
}
114115
}
115116

116117
for (auto [i, result] : llvm::enumerate(op->getResults())) {
117118
auto thisTensorType = dyn_cast<TensorType>(result.getType());
118-
if (tensorType && !thisTensorType)
119+
if (mappedShape && !mappedShape->empty() && !thisTensorType)
119120
return op->emitOpError()
120-
<< "expected all results operands to have the same tensor shape, "
121-
<< "as the mappable input operands, but found shape "
122-
<< typeToShapeStr(*tensorType) << " at operand " << operandIndex
123-
<< " and result " << i << " of non-tensor type "
124-
<< result.getType();
121+
<< "expected all results to be tensors with shape "
122+
<< typeToShapeStr(*mappedShape)
123+
<< " due to mappable operands, but result " << i
124+
<< " is of non-tensor type " << result.getType();
125125

126-
if (!tensorType && thisTensorType)
126+
if (!mappedShape && thisTensorType)
127127
return op->emitOpError()
128-
<< "No operands were tensor typed, but result at index " << i
129-
<< " is a tensor of shape " << typeToShapeStr(thisTensorType);
128+
<< "No operands were mappable, but result at index " << i
129+
<< " is a tensor of shape "
130+
<< typeToShapeStr(thisTensorType.getShape());
130131

131-
if (tensorType && thisTensorType &&
132-
thisTensorType.getShape() != tensorType->getShape()) {
132+
if (mappedShape && thisTensorType &&
133+
thisTensorType.getShape() != ArrayRef<int64_t>(*mappedShape)) {
133134
return op->emitOpError()
134135
<< "expected all tensor results to have the same shape as "
135136
"mappable operands, but found shape "
136-
<< typeToShapeStr(*tensorType) << " at operand " << operandIndex
137-
<< " and shape " << typeToShapeStr(thisTensorType) << " at result "
138-
<< i;
137+
<< typeToShapeStr(*mappedShape) << " at operand "
138+
<< firstMappableOperandIndex << " and shape "
139+
<< typeToShapeStr(thisTensorType.getShape()) << " at result " << i;
139140
}
140141
}
141142

lib/Dialect/HEIRInterfaces.td

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ def ElementwiseByOperandOpInterface : OpInterface<"ElementwiseByOperandOpInterfa
119119
be replicated and not mapped.
120120

121121
This trait is designed to work with the `convert-elementwise-to-affine`
122-
pass.
122+
pass. This pass assumes that the mapped dimensions for each operand
123+
constitute a contiguous prefix of that operand's dimensions.
123124
}];
124125

125126
let methods = [
@@ -130,6 +131,24 @@ def ElementwiseByOperandOpInterface : OpInterface<"ElementwiseByOperandOpInterfa
130131
/*args=*/(ins "unsigned":$operandIndex)
131132
>,
132133

134+
InterfaceMethod<
135+
/*desc=*/"Return the subset of indices along which this operand is mapped elementise",
136+
/*retTy=*/"llvm::SmallVector<int>",
137+
/*methodName=*/"mappedDimensionsForOperand",
138+
/*args=*/(ins "unsigned":$operandIndex),
139+
/*methodBody=*/[{}],
140+
/*defaultImplementation=*/[{
141+
// By default, map over all dimensions.
142+
auto operandType = $_op->getOperand(operandIndex).getType();
143+
if (auto tensorTy = llvm::dyn_cast<mlir::ShapedType>(operandType)) {
144+
auto r = llvm::seq<int>(0, tensorTy.getRank());
145+
llvm::SmallVector<int, 4> v(r.begin(), r.end());
146+
return v;
147+
}
148+
return {};
149+
}]
150+
>,
151+
133152
// It may make sense some day to allow only a subset of op results to be
134153
// mapped, but for now we assume all results are tensors of the same shape
135154
// as the mapped operands.

lib/Dialect/LWE/IR/LWEOps.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "lib/Dialect/Polynomial/IR/PolynomialAttributes.h"
1111
#include "lib/Dialect/RNS/IR/RNSOps.h"
1212
#include "lib/Dialect/RNS/IR/RNSTypes.h"
13+
#include "llvm/include/llvm/ADT/Sequence.h" // from @llvm-project
1314
#include "llvm/include/llvm/ADT/TypeSwitch.h" // from @llvm-project
1415
#include "llvm/include/llvm/Support/ErrorHandling.h" // from @llvm-project
1516
#include "mlir/include/mlir/IR/BuiltinAttributes.h" // from @llvm-project
@@ -325,6 +326,23 @@ void RMulPlainOp::getCanonicalizationPatterns(RewritePatternSet& results,
325326
results.add<lwe::PutCiphertextInFirstOperand<RMulPlainOp>>(context);
326327
}
327328

329+
bool RLWEEncodeOp::operandIsMappable(unsigned operandIndex) {
330+
// only `input`
331+
return operandIndex == 0;
332+
}
333+
334+
SmallVector<int> RLWEEncodeOp::mappedDimensionsForOperand(
335+
unsigned operandIndex) {
336+
auto operandType = getOperation()->getOperand(operandIndex).getType();
337+
if (auto tensorTy = dyn_cast<mlir::ShapedType>(operandType)) {
338+
// rank - 1 because the trailing dimension is the slot dimension.
339+
auto r = llvm::seq<int>(0, tensorTy.getRank() - 1);
340+
llvm::SmallVector<int, 4> v(r.begin(), r.end());
341+
return v;
342+
}
343+
return {};
344+
}
345+
328346
} // namespace lwe
329347
} // namespace heir
330348
} // namespace mlir

lib/Dialect/LWE/IR/LWEOps.td

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class LWE_BinOp<string mnemonic, list<Trait> traits = []> :
7373
}
7474

7575
class LWE_CiphertextPlaintextOp<string mnemonic, list<Trait> traits = []> :
76-
LWE_Op<mnemonic, traits # [IsCiphertextPlaintextOp, InferTypeOpAdaptor]> {
76+
LWE_Op<mnemonic, traits # [IsCiphertextPlaintextOp, InferTypeOpAdaptor, ElementwiseMappable]> {
7777
let arguments = (ins LWEPlaintextOrCiphertextLike:$lhs, LWEPlaintextOrCiphertextLike:$rhs);
7878
let results = (outs LWECiphertextLike:$output);
7979
}
@@ -183,12 +183,21 @@ def LWE_MulScalarOp : LWE_Op<"mul_scalar", [ElementwiseMappable,
183183
let hasFolder = 1;
184184
}
185185

186-
def LWE_RLWEEncodeOp : LWE_Op<"rlwe_encode", [HasEncoding<"output", "encoding", "LWEPlaintextType">]> {
186+
def LWE_RLWEEncodeOp : LWE_Op<"rlwe_encode", [
187+
HasEncoding<"output", "encoding", "LWEPlaintextType">,
188+
DeclareOpInterfaceMethods<ElementwiseByOperandOpInterface, [
189+
"operandIsMappable",
190+
// When encoding a tensor<3x1024xi16>, for example, the leading dimension
191+
// is mapped over and the trailing dimension is encoded into slots, so the
192+
// result type is tensor<3x!pt>.
193+
"mappedDimensionsForOperand",
194+
]>,
195+
]> {
187196
let summary = "Encode an integer to yield an RLWE plaintext";
188197
let description = [{
189-
Encode an integer to yield an RLWE plaintext.
198+
Encode a packed cleartext tensor to yield an RLWE plaintext.
190199

191-
This op uses a an encoding attribute to encode the bits of the integer into
200+
This op uses a an encoding attribute to encode the bits of the input into
192201
an RLWE plaintext value that can then be encrypted. CKKS cleartext inputs may
193202
be floating points, and a scaling factor described by the encoding will be
194203
applied.

lib/Dialect/LWE/Transforms/ImplementTrivialEncryptionAsAddition.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,20 +146,19 @@ func::FuncOp getOrCreateEncryptionOfZerosFunc(func::FuncOp parentFunc,
146146

147147
Type coeffType =
148148
plaintextType.getPlaintextSpace().getRing().getCoefficientType();
149-
Type cleartextElementType = builder.getIntegerType(
150-
llvm::TypeSwitch<Type, int>(coeffType)
151-
.Case<IntegerType, FloatType>(
152-
[&](auto ty) { return ty.getIntOrFloatBitWidth(); })
153-
.Case<mod_arith::ModArithType>([&](auto ty) {
154-
return ty.getModulus().getType().getIntOrFloatBitWidth();
155-
})
149+
Type cleartextElementType =
150+
llvm::TypeSwitch<Type, Type>(coeffType)
151+
.Case<IntegerType, FloatType>([&](auto ty) { return ty; })
152+
.Case<mod_arith::ModArithType>(
153+
[&](auto ty) { return ty.getModulus().getType(); })
156154
.Default([&](auto ty) {
157155
originalOp->emitOpError()
158156
<< "has unsupported plaintext coefficient type; can't "
159157
"determine what constant type to use for encryption of "
160158
"zero.";
161-
return 0;
162-
}));
159+
return Type();
160+
});
161+
if (!cleartextElementType) return nullptr;
163162

164163
RankedTensorType zeroType =
165164
RankedTensorType::get({numSlots}, cleartextElementType);

0 commit comments

Comments
 (0)