Skip to content

Commit 4424d31

Browse files
j2kuncopybara-github
authored andcommitted
Make rotation analysis more robust for loops with if statements
Here the problem was that, for more complicated looped kernels, the rotation analysis was not evaluating the loop correctly. The previous implementation was trying to skip various intermediate operations on tensors/ciphertexts, but this causes the DAG to be disconnected and so the loop's yield may not end up being visited, so that it doesn't proceed to the next iteration and truncates the rotations at the first one found (without these changes, the if_else.mlir test will only annotate with the index 512). PiperOrigin-RevId: 885851705
1 parent 6d9153e commit 4424d31

9 files changed

Lines changed: 266 additions & 60 deletions

File tree

lib/Analysis/RotationAnalysis/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ cc_library(
3030
srcs = ["DagBuilder.cpp"],
3131
hdrs = ["DagBuilder.h"],
3232
deps = [
33+
"@heir//lib/Dialect:HEIRInterfaces",
3334
"@heir//lib/Dialect/TensorExt/IR:Dialect",
3435
"@heir//lib/Kernel:AbstractValue",
3536
"@heir//lib/Kernel:ArithmeticDag",

lib/Analysis/RotationAnalysis/DagBuilder.cpp

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <memory>
55
#include <vector>
66

7+
#include "lib/Dialect/HEIRInterfaces.h"
78
#include "lib/Dialect/TensorExt/IR/TensorExtOps.h"
89
#include "lib/Kernel/AbstractValue.h"
910
#include "lib/Kernel/ArithmeticDag.h"
@@ -135,6 +136,29 @@ FailureOr<NodePtr> DagBuilder::visit(scf::ForOp op) {
135136
return dagNode;
136137
}
137138

139+
FailureOr<NodePtr> DagBuilder::visit(scf::IfOp op) {
140+
if (op.getElseRegion().empty()) {
141+
op.emitOpError()
142+
<< "skipped: If/Else without an else branch is not supported\n";
143+
return failure();
144+
}
145+
146+
auto condition = findNodeOrMakeNewVariable(op.getCondition());
147+
auto thenRes = visitBlockWithSingleTerminator(&op.getThenRegion().front());
148+
if (failed(thenRes)) return failure();
149+
NodePtr thenBody = thenRes.value();
150+
151+
auto elseRes = visitBlockWithSingleTerminator(&op.getElseRegion().front());
152+
if (failed(elseRes)) return failure();
153+
NodePtr elseBody = elseRes.value();
154+
155+
auto dagNode = Node::ifElse(condition, thenBody, elseBody);
156+
for (OpResult opResult : op->getOpResults()) {
157+
valueToNode[opResult] = Node::resultAt(dagNode, opResult.getResultNumber());
158+
}
159+
return dagNode;
160+
}
161+
138162
FailureOr<NodePtr> DagBuilder::visit(scf::YieldOp op) {
139163
std::vector<NodePtr> operands;
140164
operands.reserve(op->getNumOperands());
@@ -144,11 +168,32 @@ FailureOr<NodePtr> DagBuilder::visit(scf::YieldOp op) {
144168
return Node::yield(operands);
145169
}
146170

147-
FailureOr<NodePtr> DagBuilder::visit(tensor_ext::RotateOp op) {
148-
auto tensor = findNodeOrMakeNewVariable(op.getTensor());
149-
auto shift = findNodeOrMakeNewVariable(op.getShift());
150-
auto dagNode = Node::leftRotate(tensor, shift);
151-
valueToNode[op.getResult()] = dagNode;
171+
FailureOr<NodePtr> DagBuilder::visit(RotationOpInterface op) {
172+
LDBG() << "Processing RotationOpInterface " << op;
173+
OpFoldResult ofr = op.getRotationIndex();
174+
NodePtr shift;
175+
if (auto attr = dyn_cast<Attribute>(ofr)) {
176+
auto intAttr = cast<IntegerAttr>(attr);
177+
shift = Node::constantScalar(intAttr.getInt(),
178+
mlirTypeToDagType(intAttr.getType()));
179+
} else {
180+
shift = findNodeOrMakeNewVariable(cast<Value>(ofr));
181+
}
182+
183+
// Find the rotatable operand.
184+
// We assume it's the operand that has the same type as the result.
185+
OpOperand* rotatedOperand = op.getRotatedOperand();
186+
187+
if (!rotatedOperand) {
188+
LDBG() << "Could not find a rotated operand for op " << op
189+
<< ". It may be that the default implementation of "
190+
"getRotatedOperand is incorrect for this RotationOpInterface.";
191+
return failure();
192+
}
193+
194+
auto tensorNode = findNodeOrMakeNewVariable(rotatedOperand->get());
195+
auto dagNode = Node::leftRotate(tensorNode, shift);
196+
valueToNode[op->getResult(0)] = dagNode;
152197
return dagNode;
153198
}
154199

@@ -176,6 +221,41 @@ FailureOr<NodePtr> DagBuilder::visit(arith::SubIOp op) {
176221
return dagNode;
177222
}
178223

224+
FailureOr<NodePtr> DagBuilder::visit(arith::CmpIOp op) {
225+
auto lhs = findNodeOrMakeNewVariable(op.getLhs());
226+
auto rhs = findNodeOrMakeNewVariable(op.getRhs());
227+
228+
kernel::ComparisonPredicate pred;
229+
switch (op.getPredicate()) {
230+
case arith::CmpIPredicate::slt:
231+
case arith::CmpIPredicate::ult:
232+
pred = kernel::ComparisonPredicate::LT;
233+
break;
234+
case arith::CmpIPredicate::sle:
235+
case arith::CmpIPredicate::ule:
236+
pred = kernel::ComparisonPredicate::LE;
237+
break;
238+
case arith::CmpIPredicate::sgt:
239+
case arith::CmpIPredicate::ugt:
240+
pred = kernel::ComparisonPredicate::GT;
241+
break;
242+
case arith::CmpIPredicate::sge:
243+
case arith::CmpIPredicate::uge:
244+
pred = kernel::ComparisonPredicate::GE;
245+
break;
246+
case arith::CmpIPredicate::eq:
247+
pred = kernel::ComparisonPredicate::EQ;
248+
break;
249+
case arith::CmpIPredicate::ne:
250+
pred = kernel::ComparisonPredicate::NE;
251+
break;
252+
}
253+
254+
auto dagNode = Node::comparison(lhs, rhs, pred);
255+
valueToNode[op.getResult()] = dagNode;
256+
return dagNode;
257+
}
258+
179259
FailureOr<NodePtr> DagBuilder::visit(arith::ConstantOp op) {
180260
NodePtr dagNode =
181261
TypeSwitch<Attribute, NodePtr>(op.getValue())
@@ -234,16 +314,61 @@ FailureOr<NodePtr> DagBuilder::visit(arith::DivSIOp op) {
234314
return dagNode;
235315
}
236316

317+
FailureOr<NodePtr> DagBuilder::visit(arith::AddFOp op) {
318+
auto lhs = findNodeOrMakeNewVariable(op.getLhs());
319+
auto rhs = findNodeOrMakeNewVariable(op.getRhs());
320+
auto dagNode = Node::add(lhs, rhs);
321+
valueToNode[op.getResult()] = dagNode;
322+
return dagNode;
323+
}
324+
325+
FailureOr<NodePtr> DagBuilder::visit(arith::MulFOp op) {
326+
auto lhs = findNodeOrMakeNewVariable(op.getLhs());
327+
auto rhs = findNodeOrMakeNewVariable(op.getRhs());
328+
auto dagNode = Node::mul(lhs, rhs);
329+
valueToNode[op.getResult()] = dagNode;
330+
return dagNode;
331+
}
332+
333+
FailureOr<NodePtr> DagBuilder::visit(arith::SubFOp op) {
334+
auto lhs = findNodeOrMakeNewVariable(op.getLhs());
335+
auto rhs = findNodeOrMakeNewVariable(op.getRhs());
336+
auto dagNode = Node::sub(lhs, rhs);
337+
valueToNode[op.getResult()] = dagNode;
338+
return dagNode;
339+
}
340+
341+
FailureOr<NodePtr> DagBuilder::visit(arith::NegFOp op) {
342+
auto lhs = findNodeOrMakeNewVariable(op.getOperand());
343+
// ArithmeticDag doesn't have NegNode, so we can use (0 - lhs)
344+
auto zero = Node::constantScalar(0.0, mlirTypeToDagType(op.getType()));
345+
auto dagNode = Node::sub(zero, lhs);
346+
valueToNode[op.getResult()] = dagNode;
347+
return dagNode;
348+
}
349+
350+
FailureOr<NodePtr> DagBuilder::visit(tensor::ExtractSliceOp op) {
351+
// For rotation analysis, we can approximate extract_slice as just returning
352+
// the source tensor if we don't care about the specific values, or we can
353+
// try to be more precise. Since rotation analysis usually only cares about
354+
// the fact that *some* tensor is being rotated, returning the source is
355+
// enough to keep the DAG connected.
356+
auto source = findNodeOrMakeNewVariable(op.getSource());
357+
valueToNode[op.getResult()] = source;
358+
return source;
359+
}
360+
237361
FailureOr<NodePtr> DagBuilder::build(Operation* op) {
238362
LDBG() << "Visiting op " << *op;
239363
return llvm::TypeSwitch<Operation*, FailureOr<NodePtr>>(op)
240-
.Case<arith::AddIOp, arith::ConstantOp, arith::DivSIOp, arith::MulIOp,
241-
arith::SubIOp, scf::ForOp, scf::YieldOp, tensor::ExtractOp,
242-
tensor::SplatOp, tensor_ext::RotateOp>(
243-
[&](auto op) { return visit(op); })
244-
.Default([&](auto op) {
245-
LDBG() << "Unsupported op type " << op->getName();
246-
return failure();
364+
.Case<arith::AddFOp, arith::AddIOp, arith::CmpIOp, arith::ConstantOp,
365+
arith::DivSIOp, arith::MulFOp, arith::MulIOp, arith::SubFOp,
366+
arith::SubIOp, arith::NegFOp, scf::ForOp, scf::IfOp, scf::YieldOp,
367+
tensor::ExtractOp, tensor::ExtractSliceOp, tensor::SplatOp,
368+
RotationOpInterface>([&](auto op) { return visit(op); })
369+
.Default([&](Operation* op) -> FailureOr<NodePtr> {
370+
LDBG() << "Unsupported op type " << op->getName() << ", skipping";
371+
return NodePtr(nullptr);
247372
});
248373
}
249374

lib/Analysis/RotationAnalysis/DagBuilder.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <memory>
55

6+
#include "lib/Dialect/HEIRInterfaces.h"
67
#include "lib/Dialect/TensorExt/IR/TensorExtOps.h"
78
#include "lib/Kernel/AbstractValue.h"
89
#include "lib/Kernel/ArithmeticDag.h"
@@ -35,16 +36,23 @@ class DagBuilder {
3536
// Visit a block and return the NodePtr corresponding to its terminator
3637
FailureOr<NodePtr> visitBlockWithSingleTerminator(Block* block);
3738

39+
FailureOr<NodePtr> visit(arith::AddFOp op);
3840
FailureOr<NodePtr> visit(arith::AddIOp op);
41+
FailureOr<NodePtr> visit(arith::CmpIOp op);
3942
FailureOr<NodePtr> visit(arith::ConstantOp op);
4043
FailureOr<NodePtr> visit(arith::DivSIOp op);
44+
FailureOr<NodePtr> visit(arith::MulFOp op);
4145
FailureOr<NodePtr> visit(arith::MulIOp op);
46+
FailureOr<NodePtr> visit(arith::SubFOp op);
4247
FailureOr<NodePtr> visit(arith::SubIOp op);
48+
FailureOr<NodePtr> visit(arith::NegFOp op);
4349
FailureOr<NodePtr> visit(scf::ForOp op);
50+
FailureOr<NodePtr> visit(scf::IfOp op);
4451
FailureOr<NodePtr> visit(scf::YieldOp op);
4552
FailureOr<NodePtr> visit(tensor::ExtractOp op);
53+
FailureOr<NodePtr> visit(tensor::ExtractSliceOp op);
4654
FailureOr<NodePtr> visit(tensor::SplatOp op);
47-
FailureOr<NodePtr> visit(tensor_ext::RotateOp op);
55+
FailureOr<NodePtr> visit(RotationOpInterface op);
4856

4957
// A mapping of previously visited Values
5058
DenseMap<Value, NodePtr> valueToNode;

lib/Analysis/RotationAnalysis/RotationAnalysis.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ LogicalResult RotationAnalysis::handleScfFor(scf::ForOp forOp) {
4747

4848
NodePtr dag = res.value();
4949
auto shifts = evalRotations(dag);
50-
rotationIndices = DenseSet<int64_t>(shifts.begin(), shifts.end());
50+
rotationIndices.insert(shifts.begin(), shifts.end());
5151

5252
// All the rotation ops within the outermost for loop are analyzed.
5353
outermostFor->walk([&](RotationOpInterface rotOp) { markVisited(rotOp); });

lib/Analysis/RotationAnalysis/RotationEvalVisitor.cpp

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,24 @@ using kernel::LiteralValue;
2727
using kernel::VariableNode;
2828

2929
// This is a copy of EvalVisitor::operator() for LeftRotateNode, but recording
30-
// the materialized rotation amount.
30+
// the materialized rotation amount and skipping the actual computation.
3131
EvalResults RotationEvalVisitor::operator()(
3232
const LeftRotateNode<LiteralValue>& node) {
3333
auto operand = this->process(node.operand)[0];
3434
auto shape = operand.getShape();
3535
assert(!shape.empty() && "rotate operand must be a tensor");
36-
auto dim = shape[0];
36+
auto dim = shape.back(); // Use the slot dimension
3737

3838
auto evaluatedShift = this->process(node.shift)[0];
3939
int amount = std::get<int>(evaluatedShift.get());
40+
4041
// Normalize amount to be in [0, dim)
4142
amount = ((amount % dim) + dim) % dim;
42-
43-
// Save the evaluated shift
4443
evaluatedShifts.insert(amount);
4544

46-
const auto& oVal = operand.get();
47-
const auto* oVec = std::get_if<std::vector<int>>(&oVal);
48-
assert(oVec && "unsupported rotate operand type");
49-
50-
std::vector<int> result(dim);
51-
for (size_t i = 0; i < dim; ++i) {
52-
result[i] = (*oVec)[(i + amount) % oVec->size()];
53-
}
54-
return {result};
45+
// We don't need to rotate the values for rotation analysis. We just return
46+
// the operand as-is to keep the IR connected.
47+
return {operand};
5548
}
5649

5750
EvalResults RotationEvalVisitor::operator()(

lib/Dialect/HEIRInterfaces.td

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,23 @@ def RotationOpInterface : OpInterface<"RotationOpInterface"> {
451451
"Returns the rotation index as an OpFoldResult (Value or Attribute).",
452452
"::mlir::OpFoldResult", "getRotationIndex"
453453
>,
454+
455+
InterfaceMethod<
456+
/*desc=*/"Returns the rotated operand.",
457+
/*retTy=*/"::mlir::OpOperand *",
458+
/*methodName=*/"getRotatedOperand",
459+
/*args=*/(ins ),
460+
/*defaultImplementation=*/[{
461+
// By default, find the first operand that matches the first result
462+
// type.
463+
for (::mlir::OpOperand &operand : $_op->getOpOperands()) {
464+
if (operand.get().getType() == $_op->getResult(0).getType()) {
465+
return &operand;
466+
}
467+
}
468+
return nullptr;
469+
}]
470+
>,
454471
];
455472
}
456473
#endif // LIB_DIALECT_HEIR_IR_HEIRINTERFACES_TD_

lib/Kernel/EvalVisitor.cpp

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,17 @@ EvalResults EvalVisitor::operator()(const AddNode<LiteralValue>& node) {
5757
auto dim = left.getShape()[0];
5858
const auto* lVec = std::get_if<std::vector<int>>(&lVal);
5959
const auto* rVec = std::get_if<std::vector<int>>(&rVal);
60-
assert(lVec && rVec && "unsupported add operands");
61-
assert(left.getShape() == right.getShape() && "disagreeing shapes");
62-
std::vector<int> result(dim);
63-
for (size_t i = 0; i < dim; ++i) {
64-
result[i] = (*lVec)[i] + (*rVec)[i];
60+
if (lVec && rVec) {
61+
assert(left.getShape() == right.getShape() && "disagreeing shapes");
62+
std::vector<int> result(dim);
63+
for (size_t i = 0; i < dim; ++i) {
64+
result[i] = (*lVec)[i] + (*rVec)[i];
65+
}
66+
return {result};
6567
}
66-
return {result};
68+
69+
// If types are not supported, just return left as dummy
70+
return {left};
6771
}
6872

6973
EvalResults EvalVisitor::operator()(const SubtractNode<LiteralValue>& node) {
@@ -84,14 +88,16 @@ EvalResults EvalVisitor::operator()(const SubtractNode<LiteralValue>& node) {
8488
auto dim = left.getShape()[0];
8589
const auto* lVec = std::get_if<std::vector<int>>(&lVal);
8690
const auto* rVec = std::get_if<std::vector<int>>(&rVal);
87-
assert(lVec && rVec && "unsupported sub operands");
88-
assert(left.getShape() == right.getShape() && "disagreeing shapes");
89-
std::vector<int> result(dim);
90-
for (size_t i = 0; i < dim; ++i) {
91-
result[i] = (*lVec)[i] - (*rVec)[i];
91+
if (lVec && rVec) {
92+
assert(left.getShape() == right.getShape() && "disagreeing shapes");
93+
std::vector<int> result(dim);
94+
for (size_t i = 0; i < dim; ++i) {
95+
result[i] = (*lVec)[i] - (*rVec)[i];
96+
}
97+
return {result};
9298
}
9399

94-
return {result};
100+
return {left};
95101
}
96102

97103
EvalResults EvalVisitor::operator()(const MultiplyNode<LiteralValue>& node) {
@@ -112,13 +118,15 @@ EvalResults EvalVisitor::operator()(const MultiplyNode<LiteralValue>& node) {
112118
auto dim = left.getShape()[0];
113119
const auto* lVec = std::get_if<std::vector<int>>(&lVal);
114120
const auto* rVec = std::get_if<std::vector<int>>(&rVal);
115-
assert(lVec && rVec && "unsupported mul operands");
116-
assert(left.getShape() == right.getShape() && "disagreeing shapes");
117-
std::vector<int> result(dim);
118-
for (size_t i = 0; i < dim; ++i) {
119-
result[i] = (*lVec)[i] * (*rVec)[i];
121+
if (lVec && rVec) {
122+
assert(left.getShape() == right.getShape() && "disagreeing shapes");
123+
std::vector<int> result(dim);
124+
for (size_t i = 0; i < dim; ++i) {
125+
result[i] = (*lVec)[i] * (*rVec)[i];
126+
}
127+
return {result};
120128
}
121-
return {result};
129+
return {left};
122130
}
123131

124132
EvalResults EvalVisitor::operator()(const FloorDivNode<LiteralValue>& node) {
@@ -147,20 +155,23 @@ EvalResults EvalVisitor::operator()(const FloorDivNode<LiteralValue>& node) {
147155
EvalResults EvalVisitor::operator()(const LeftRotateNode<LiteralValue>& node) {
148156
LDBG() << "Visiting LeftRotateNode";
149157
auto operand = this->process(node.operand)[0];
150-
auto dim = operand.getShape()[0];
158+
auto dim = operand.getShape().back();
151159
auto evaluatedShift = this->process(node.shift)[0];
152160
int amount = std::get<int>(evaluatedShift.get());
153161
// Normalize amount to be in [0, dim)
154162
amount = ((amount % dim) + dim) % dim;
155163

156164
const auto& oVal = operand.get();
157-
const auto* oVec = std::get_if<std::vector<int>>(&oVal);
158-
assert(oVec && "unsupported rotate operand");
159-
std::vector<int> result(dim);
160-
for (size_t i = 0; i < dim; ++i) {
161-
result[i] = (*oVec)[(i + amount) % oVec->size()];
165+
if (const auto* oVec = std::get_if<std::vector<int>>(&oVal)) {
166+
std::vector<int> result(dim);
167+
for (size_t i = 0; i < dim; ++i) {
168+
result[i] = (*oVec)[(i + amount) % oVec->size()];
169+
}
170+
return {result};
162171
}
163-
return {result};
172+
173+
// If the operand is not a 1D vector (e.g., a 2D float tensor), return as-is.
174+
return {operand};
164175
}
165176

166177
EvalResults EvalVisitor::operator()(const ExtractNode<LiteralValue>& node) {

0 commit comments

Comments
 (0)