Skip to content

Commit da7e99d

Browse files
j2kuncopybara-github
authored andcommitted
Improve waterline bootstrapping
This change replaces the single greedy waterline-bootstrapping pattern with an analysis pass that marks all the places bootstraps should be inserted before a secondary pass that actually mutates the IR. The original pattern was failing to compile a number of larger programs involving loops: if the op-result of a loop that exhausts all levels was later used as the input to a ct-ct mul, the pattern would not fire and it would try to modreduce after (or before) a level 0 ciphetext. The new analysis pass properly inserts a bootstrap in situations like this. After this change, the hotword convolutional model successfully compiles in ~70 seconds on my dev machine. update: this change also required a small change to the scale analysis code, so that it initializes plaintexts to the default scale in the forward pass for multiplications (which should always happen at the default scale), and updated backward exit states to respect function return annotations when present. This hardening step was needed because the change to how bootstraps are inserted necessitated adding additional adjust_scale ops (e.g., a bootstrap just before a modreduce) which don't have forward propagation in scale analysis. PiperOrigin-RevId: 953572669
1 parent 92acf3a commit da7e99d

23 files changed

Lines changed: 710 additions & 118 deletions

lib/Analysis/LevelAnalysis/BUILD

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ package(
77

88
cc_library(
99
name = "LevelAnalysis",
10-
srcs = ["LevelAnalysis.cpp"],
11-
hdrs = ["LevelAnalysis.h"],
10+
srcs = [
11+
"BootstrapWaterlineAnalysis.cpp",
12+
"LevelAnalysis.cpp",
13+
],
14+
hdrs = [
15+
"BootstrapWaterlineAnalysis.h",
16+
"LevelAnalysis.h",
17+
],
1218
deps = [
1319
"@heir//lib/Analysis:Utils",
1420
"@heir//lib/Analysis/SecretnessAnalysis",
@@ -21,6 +27,7 @@ cc_library(
2127
"@llvm-project//llvm:Support",
2228
"@llvm-project//mlir:Analysis",
2329
"@llvm-project//mlir:ArithDialect",
30+
"@llvm-project//mlir:ControlFlowInterfaces",
2431
"@llvm-project//mlir:FuncDialect",
2532
"@llvm-project//mlir:IR",
2633
"@llvm-project//mlir:Support",
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#include "lib/Analysis/LevelAnalysis/BootstrapWaterlineAnalysis.h"
2+
3+
#include <cassert>
4+
#include <functional>
5+
6+
#include "lib/Analysis/LevelAnalysis/LevelAnalysis.h"
7+
#include "lib/Analysis/Utils.h"
8+
#include "lib/Dialect/HEIRInterfaces.h"
9+
#include "lib/Dialect/Mgmt/IR/MgmtOps.h"
10+
#include "llvm/include/llvm/Support/Debug.h" // from @llvm-project
11+
#include "mlir/include/mlir/Analysis/DataFlowFramework.h" // from @llvm-project
12+
#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project
13+
#include "mlir/include/mlir/IR/Value.h" // from @llvm-project
14+
#include "mlir/include/mlir/Interfaces/CallInterfaces.h" // from @llvm-project
15+
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
16+
17+
#define DEBUG_TYPE "bootstrap-waterline-analysis"
18+
19+
namespace mlir {
20+
namespace heir {
21+
22+
LogicalResult BootstrapWaterlineAnalysis::visitOperation(
23+
Operation* op, ArrayRef<const BootstrapWaterlineLattice*> operands,
24+
ArrayRef<BootstrapWaterlineLattice*> results) {
25+
auto propagate = [&](Value value, const BootstrapWaterlineState& state) {
26+
auto* lattice = getLatticeElement(value);
27+
ChangeResult changed = lattice->join(state);
28+
propagateIfChanged(lattice, changed);
29+
};
30+
31+
// 1. Extract LevelState from operands
32+
SmallVector<LevelState> operandLevelStates;
33+
for (auto* operand : operands) {
34+
operandLevelStates.push_back(operand->getValue().getLevelState());
35+
}
36+
37+
// 2. Compute prospective level
38+
LevelState prospectiveLevel = deriveResultLevel(op, operandLevelStates);
39+
if (levelBudget > 0 && prospectiveLevel.isInt() &&
40+
prospectiveLevel.getInt() > levelBudget) {
41+
prospectiveLevel = LevelState(Invalid{});
42+
}
43+
44+
// 3. Determine if we need to reset/bootstrap
45+
LevelState resultLevel;
46+
bool resultNeedsBootstrap = false;
47+
48+
if (isa<ResetsLevelOpInterface>(op)) {
49+
resultLevel = prospectiveLevel;
50+
resultNeedsBootstrap = false;
51+
} else {
52+
// We wait as long as possible to bootstrap, meaning that the level will
53+
// remain at the waterline (i.e., level zero) until it hits a level-reducing
54+
// op, at which point we have to mark the _operand_ as needing a bootstrap.
55+
// But since that operand has already been processed by the analysis, we
56+
// mark the op result and then patch it up by the pass that uses this
57+
// analysis.
58+
bool exceedsWaterline =
59+
prospectiveLevel.isInvalid() ||
60+
(prospectiveLevel.isInt() && prospectiveLevel.getInt() > waterline);
61+
62+
resultNeedsBootstrap = exceedsWaterline;
63+
if (exceedsWaterline) {
64+
if (auto reduceOp = dyn_cast<ReducesLevelOpInterface>(op)) {
65+
resultLevel = LevelState(reduceOp.getLevelsToDrop());
66+
} else {
67+
resultLevel = LevelState(0);
68+
}
69+
} else {
70+
resultLevel = prospectiveLevel;
71+
}
72+
}
73+
74+
BootstrapWaterlineState resultState(resultLevel, resultNeedsBootstrap);
75+
76+
LLVM_DEBUG({
77+
llvm::dbgs() << "BWAnalysis: " << op->getName() << " prospective=";
78+
prospectiveLevel.print(llvm::dbgs());
79+
llvm::dbgs() << " -> result=";
80+
resultState.print(llvm::dbgs());
81+
llvm::dbgs() << "\n";
82+
});
83+
84+
for (auto result : op->getOpResults()) {
85+
if (isa<mgmt::InitOp>(op) || isSecretInternal(op, result)) {
86+
propagate(result, resultState);
87+
}
88+
}
89+
90+
return success();
91+
}
92+
93+
void BootstrapWaterlineAnalysis::visitExternalCall(
94+
CallOpInterface call,
95+
ArrayRef<const BootstrapWaterlineLattice*> argumentLattices,
96+
ArrayRef<BootstrapWaterlineLattice*> resultLattices) {
97+
auto callback =
98+
std::bind(&BootstrapWaterlineAnalysis::propagateIfChangedWrapper, this,
99+
std::placeholders::_1, std::placeholders::_2);
100+
::mlir::heir::visitExternalCall<BootstrapWaterlineState,
101+
BootstrapWaterlineLattice>(
102+
call, argumentLattices, resultLattices, callback);
103+
}
104+
105+
} // namespace heir
106+
} // namespace mlir
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#ifndef LIB_ANALYSIS_LEVELANALYSIS_BOOTSTRAPWATERLINEANALYSIS_H_
2+
#define LIB_ANALYSIS_LEVELANALYSIS_BOOTSTRAPWATERLINEANALYSIS_H_
3+
4+
#include "lib/Analysis/LevelAnalysis/LevelAnalysis.h"
5+
#include "lib/Analysis/SecretnessAnalysis/SecretnessAnalysis.h"
6+
#include "mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h" // from @llvm-project
7+
#include "mlir/include/mlir/Analysis/DataFlowFramework.h" // from @llvm-project
8+
#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project
9+
#include "mlir/include/mlir/IR/Value.h" // from @llvm-project
10+
#include "mlir/include/mlir/Interfaces/CallInterfaces.h" // from @llvm-project
11+
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
12+
13+
namespace mlir {
14+
namespace heir {
15+
16+
class BootstrapWaterlineState {
17+
public:
18+
BootstrapWaterlineState() : levelState(Uninit{}), needsBootstrap(false) {}
19+
BootstrapWaterlineState(LevelState levelState, bool needsBootstrap)
20+
: levelState(levelState), needsBootstrap(needsBootstrap) {}
21+
22+
LevelState getLevelState() const { return levelState; }
23+
bool getNeedsBootstrap() const { return needsBootstrap; }
24+
25+
bool operator==(const BootstrapWaterlineState& other) const {
26+
return levelState == other.levelState &&
27+
needsBootstrap == other.needsBootstrap;
28+
}
29+
30+
static BootstrapWaterlineState join(const BootstrapWaterlineState& lhs,
31+
const BootstrapWaterlineState& rhs) {
32+
return BootstrapWaterlineState(
33+
LevelState::join(lhs.levelState, rhs.levelState),
34+
lhs.needsBootstrap || rhs.needsBootstrap);
35+
}
36+
37+
void print(llvm::raw_ostream& os) const {
38+
os << "BWState(";
39+
levelState.print(os);
40+
os << ", needsBootstrap=" << (needsBootstrap ? "true" : "false") << ")";
41+
}
42+
43+
friend llvm::raw_ostream& operator<<(llvm::raw_ostream& os,
44+
const BootstrapWaterlineState& state) {
45+
state.print(os);
46+
return os;
47+
}
48+
49+
private:
50+
LevelState levelState;
51+
bool needsBootstrap;
52+
};
53+
54+
class BootstrapWaterlineLattice
55+
: public dataflow::Lattice<BootstrapWaterlineState> {
56+
public:
57+
using Lattice::Lattice;
58+
};
59+
60+
class BootstrapWaterlineAnalysis
61+
: public dataflow::SparseForwardDataFlowAnalysis<BootstrapWaterlineLattice>,
62+
public SecretnessAnalysisDependent<BootstrapWaterlineAnalysis> {
63+
public:
64+
BootstrapWaterlineAnalysis(DataFlowSolver& solver, int waterline = 20,
65+
int levelBudget = 20)
66+
: dataflow::SparseForwardDataFlowAnalysis<BootstrapWaterlineLattice>(
67+
solver),
68+
waterline(waterline),
69+
levelBudget(levelBudget) {}
70+
friend class SecretnessAnalysisDependent<BootstrapWaterlineAnalysis>;
71+
72+
void setToEntryState(BootstrapWaterlineLattice* lattice) override {
73+
propagateIfChanged(
74+
lattice, lattice->join(BootstrapWaterlineState(LevelState(0), false)));
75+
}
76+
77+
LogicalResult visitOperation(
78+
Operation* op, ArrayRef<const BootstrapWaterlineLattice*> operands,
79+
ArrayRef<BootstrapWaterlineLattice*> results) override;
80+
81+
void visitExternalCall(
82+
CallOpInterface call,
83+
ArrayRef<const BootstrapWaterlineLattice*> argumentLattices,
84+
ArrayRef<BootstrapWaterlineLattice*> resultLattices) override;
85+
86+
void propagateIfChangedWrapper(AnalysisState* state, ChangeResult changed) {
87+
propagateIfChanged(state, changed);
88+
}
89+
90+
private:
91+
int waterline;
92+
int levelBudget;
93+
};
94+
95+
} // namespace heir
96+
} // namespace mlir
97+
98+
#endif // LIB_ANALYSIS_LEVELANALYSIS_BOOTSTRAPWATERLINEANALYSIS_H_

lib/Analysis/LevelAnalysis/LevelAnalysis.cpp

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ namespace heir {
3737
// LevelAnalysis (Forward)
3838
//===----------------------------------------------------------------------===//
3939
[[maybe_unused]] static void debugLog(StringRef opName,
40-
ArrayRef<const LevelLattice*> operands,
40+
ArrayRef<LevelState> operands,
4141
const LevelState& result) {
4242
LLVM_DEBUG({
4343
llvm::dbgs() << "transferForward: " << opName << "(";
44-
for (auto* operand : operands) {
45-
operand->getValue().print(llvm::dbgs());
44+
for (const auto& operand : operands) {
45+
operand.print(llvm::dbgs());
4646
llvm::dbgs() << ", ";
4747
}
4848
llvm::dbgs() << ") = ";
@@ -52,7 +52,7 @@ namespace heir {
5252
};
5353

5454
LevelState transferForward(ReducesLevelOpInterface op,
55-
ArrayRef<const LevelLattice*> operands) {
55+
ArrayRef<LevelState> operands) {
5656
unsigned operandIdx = op.getOperandToReduce().getOperandNumber();
5757
LevelState result = std::visit(
5858
Overloaded{
@@ -63,13 +63,13 @@ LevelState transferForward(ReducesLevelOpInterface op,
6363
return LevelState(val + op.getLevelsToDrop());
6464
},
6565
},
66-
operands[operandIdx]->getValue().get());
66+
operands[operandIdx].get());
6767
LLVM_DEBUG(debugLog("ReduceLevelOpInterface", operands, result));
6868
return result;
6969
}
7070

7171
LevelState transferForward(ReducesAllLevelsOpInterface op,
72-
ArrayRef<const LevelLattice*> operands) {
72+
ArrayRef<LevelState> operands) {
7373
LevelState result = std::visit(
7474
Overloaded{
7575
// MaxLevel -> MaxLevel should result in a no-op, so technically
@@ -79,13 +79,13 @@ LevelState transferForward(ReducesAllLevelsOpInterface op,
7979
[](Invalid) -> LevelState { return LevelState(Invalid{}); },
8080
[](int val) -> LevelState { return LevelState(MaxLevel{}); },
8181
},
82-
operands[0]->getValue().get());
82+
operands[0].get());
8383
LLVM_DEBUG(debugLog("ReduceAllLevelsOpInterface", operands, result));
8484
return result;
8585
}
8686

8787
LevelState transferForward(ResetsLevelOpInterface op,
88-
ArrayRef<const LevelLattice*> operands) {
88+
ArrayRef<LevelState> operands) {
8989
unsigned operandIdx = op.getOperandToReset().getOperandNumber();
9090
LevelState result = std::visit(
9191
Overloaded{
@@ -94,13 +94,12 @@ LevelState transferForward(ResetsLevelOpInterface op,
9494
[](Invalid) -> LevelState { return LevelState(Invalid{}); },
9595
[](int val) -> LevelState { return LevelState(0); },
9696
},
97-
operands[operandIdx]->getValue().get());
97+
operands[operandIdx].get());
9898
LLVM_DEBUG(debugLog("ResetsLevelOpInterface", operands, result));
9999
return result;
100100
}
101101

102-
LevelState deriveResultLevel(Operation* op,
103-
ArrayRef<const LevelLattice*> operands) {
102+
LevelState deriveResultLevel(Operation* op, ArrayRef<LevelState> operands) {
104103
return llvm::TypeSwitch<Operation*, LevelState>(op)
105104
.Case<ResetsLevelOpInterface>(
106105
[&](auto op) -> LevelState { return transferForward(op, operands); })
@@ -110,8 +109,8 @@ LevelState deriveResultLevel(Operation* op,
110109
[&](auto op) -> LevelState { return transferForward(op, operands); })
111110
.Default([&](auto* op) -> LevelState {
112111
LevelState result;
113-
for (auto* operandState : operands) {
114-
result = LevelState::join(result, operandState->getValue());
112+
for (const auto& operand : operands) {
113+
result = LevelState::join(result, operand);
115114
}
116115
LLVM_DEBUG(debugLog(op->getName().getStringRef(), operands, result));
117116
return result;
@@ -127,7 +126,11 @@ LogicalResult LevelAnalysis::visitOperation(
127126
propagateIfChanged(lattice, changed);
128127
};
129128

130-
LevelState resultLevel = deriveResultLevel(op, operands);
129+
SmallVector<LevelState> operandStates;
130+
for (auto* operand : operands) {
131+
operandStates.push_back(operand->getValue());
132+
}
133+
LevelState resultLevel = deriveResultLevel(op, operandStates);
131134
if (resultLevel.isInt() && resultLevel.getInt() > levelBudget) {
132135
resultLevel = LevelState(Invalid{});
133136
}

lib/Analysis/LevelAnalysis/LevelAnalysis.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,7 @@ class LevelAnalysis
218218
int levelBudget;
219219
};
220220

221-
LevelState deriveResultLevel(Operation* op,
222-
ArrayRef<const LevelLattice*> operands);
221+
LevelState deriveResultLevel(Operation* op, ArrayRef<LevelState> operands);
223222

224223
/// Backward Analyze the level of plaintext operands of ct-pt ops.
225224
///
@@ -265,14 +264,12 @@ std::optional<int> getMaxLevel(Operation* root);
265264
// Get the maximum level of SSA values in the op, from the data flow solver.
266265
int getMaxLevel(Operation* top, DataFlowSolver* solver);
267266

268-
LevelState transferForward(mgmt::ModReduceOp op,
269-
ArrayRef<const LevelLattice*> operands);
267+
LevelState transferForward(mgmt::ModReduceOp op, ArrayRef<LevelState> operands);
270268
LevelState transferForward(mgmt::LevelReduceOp op,
271-
ArrayRef<const LevelLattice*> operands);
269+
ArrayRef<LevelState> operands);
272270
LevelState transferForward(mgmt::LevelReduceMinOp op,
273-
ArrayRef<const LevelLattice*> operands);
274-
LevelState transferForward(mgmt::BootstrapOp op,
275-
ArrayRef<const LevelLattice*> operands);
271+
ArrayRef<LevelState> operands);
272+
LevelState transferForward(mgmt::BootstrapOp op, ArrayRef<LevelState> operands);
276273

277274
} // namespace heir
278275
} // namespace mlir

0 commit comments

Comments
 (0)