Skip to content

Commit 6d6cb08

Browse files
wtd2copybara-github
authored andcommitted
fix: implement physical slot replication for bicyclic and tricyclic layout after
matrix multiplication. In the bicyclic layout, we require for each 0 <= slot < numSlot, (ct, slot) is mapped to (slot % n, slot % m). However, after computing through BSGS we can only guarantee for all 0 <= slot < n*m this property preserves (indeed we can guarantee more, but for the tail part it is not correct if n*m does not divide numSlot). In this commit, we add a new relation called `periodic replication relation` that replicate the first copy to all slots periodically. After each matrix multiplication with CRT layouts (bicyclic, tricyclic), we compose this relation to derive the result. The cost of the layout switching will be up to logN rotations. PiperOrigin-RevId: 953525822
1 parent 0f1bf99 commit 6d6cb08

18 files changed

Lines changed: 316 additions & 16 deletions

File tree

lib/Kernel/BUILD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,10 @@ cc_test(
211211
":KernelImplementation",
212212
"@fuzztest//fuzztest",
213213
"@googletest//:gtest_main",
214+
"@heir//lib/Utils:MathUtils",
214215
"@heir//lib/Utils/Layout:Evaluate",
215216
"@heir//lib/Utils/Layout:Utils",
217+
"@llvm-project//mlir:Analysis",
216218
"@llvm-project//mlir:IR",
217219
"@llvm-project//mlir:Support",
218220
],
@@ -228,8 +230,10 @@ cc_test(
228230
":KernelImplementation",
229231
"@fuzztest//fuzztest",
230232
"@googletest//:gtest_main",
233+
"@heir//lib/Utils:MathUtils",
231234
"@heir//lib/Utils/Layout:Evaluate",
232235
"@heir//lib/Utils/Layout:Utils",
236+
"@llvm-project//mlir:Analysis",
233237
"@llvm-project//mlir:IR",
234238
"@llvm-project//mlir:Support",
235239
],
@@ -245,8 +249,10 @@ cc_test(
245249
":KernelImplementation",
246250
"@fuzztest//fuzztest",
247251
"@googletest//:gtest_main",
252+
"@heir//lib/Utils:MathUtils",
248253
"@heir//lib/Utils/Layout:Evaluate",
249254
"@heir//lib/Utils/Layout:Utils",
255+
"@llvm-project//mlir:Analysis",
250256
"@llvm-project//mlir:IR",
251257
"@llvm-project//mlir:Support",
252258
],

lib/Kernel/BicyclicDiagonalMatmulFuzzTest.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "lib/Kernel/KernelImplementation.h"
1313
#include "lib/Utils/Layout/Evaluate.h"
1414
#include "lib/Utils/Layout/Utils.h"
15+
#include "lib/Utils/MathUtils.h"
16+
#include "mlir/include/mlir/Analysis/Presburger/PresburgerSpace.h" // from @llvm-project
1517
#include "mlir/include/mlir/IR/BuiltinTypes.h" // from @llvm-project
1618
#include "mlir/include/mlir/IR/MLIRContext.h" // from @llvm-project
1719

@@ -29,7 +31,8 @@ std::vector<std::vector<int>> runDiagonalMatmul(bool isCtPt,
2931
int64_t m, int64_t n, int64_t p,
3032
bool unroll = true) {
3133
MLIRContext context;
32-
int64_t numSlots = 2 * m * n * p;
34+
int64_t minSlots = (isCtPt ? m * n : n * p) + m * p;
35+
int64_t numSlots = nextPowerOfTwo(minSlots);
3336

3437
int64_t rowsCt = isCtPt ? m : n;
3538
int64_t colsCt = isCtPt ? n : p;
@@ -71,7 +74,10 @@ std::vector<std::vector<int>> runDiagonalMatmul(bool isCtPt,
7174

7275
auto resultLayout = getBicyclicLayoutRelation(
7376
RankedTensorType::get({m, p}, mlir::IndexType::get(&context)), numSlots);
74-
77+
// Restrict the unpacking to the first output period.
78+
addBounds(resultLayout,
79+
resultLayout.getVarKindOffset(presburger::VarKind::Range) + 1, 0,
80+
m * p - 1);
7581
return unpackLayoutToMatrix<int>(resultLayout, {resultVec}, {m, p});
7682
}
7783

lib/Kernel/BicyclicMatmulFuzzTest.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "lib/Kernel/KernelImplementation.h"
1212
#include "lib/Utils/Layout/Evaluate.h"
1313
#include "lib/Utils/Layout/Utils.h"
14+
#include "lib/Utils/MathUtils.h"
15+
#include "mlir/include/mlir/Analysis/Presburger/PresburgerSpace.h" // from @llvm-project
1416
#include "mlir/include/mlir/IR/BuiltinTypes.h" // from @llvm-project
1517
#include "mlir/include/mlir/IR/MLIRContext.h" // from @llvm-project
1618
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
@@ -28,7 +30,8 @@ std::vector<std::vector<int>> runBicyclicMatmul(const std::vector<int>& vecA,
2830
int64_t m, int64_t n,
2931
int64_t p) {
3032
MLIRContext context;
31-
int64_t numSlots = m * n * p;
33+
int64_t minSlots = m * n + n * p + m * p;
34+
int64_t numSlots = nextPowerOfTwo(minSlots);
3235

3336
auto layoutA = getBicyclicLayoutRelation(
3437
RankedTensorType::get({m, n}, mlir::IndexType::get(&context)), numSlots);
@@ -52,6 +55,10 @@ std::vector<std::vector<int>> runBicyclicMatmul(const std::vector<int>& vecA,
5255

5356
auto resultLayout = getBicyclicLayoutRelation(
5457
RankedTensorType::get({m, p}, mlir::IndexType::get(&context)), numSlots);
58+
// Restrict the unpacking to the first output period.
59+
addBounds(resultLayout,
60+
resultLayout.getVarKindOffset(presburger::VarKind::Range) + 1, 0,
61+
m * p - 1);
5562
return unpackLayoutToMatrix<int>(resultLayout, {resultVec}, {m, p});
5663
}
5764

lib/Kernel/TricyclicBatchMatmulFuzzTest.cpp

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "lib/Kernel/KernelImplementation.h"
1212
#include "lib/Utils/Layout/Evaluate.h"
1313
#include "lib/Utils/Layout/Utils.h"
14+
#include "lib/Utils/MathUtils.h"
15+
#include "mlir/include/mlir/Analysis/Presburger/PresburgerSpace.h" // from @llvm-project
1416
#include "mlir/include/mlir/IR/BuiltinTypes.h" // from @llvm-project
1517
#include "mlir/include/mlir/IR/MLIRContext.h" // from @llvm-project
1618
#include "mlir/include/mlir/Support/LLVM.h" // from @llvm-project
@@ -34,7 +36,8 @@ void tricyclicBatchMatmulMatchesNaive(
3436
}
3537

3638
MLIRContext context;
37-
int64_t numSlots = h * m * n * p;
39+
int64_t minSlots = h * (m * n + n * p + m * p);
40+
int64_t numSlots = nextPowerOfTwo(minSlots);
3841

3942
RankedTensorType typeA =
4043
RankedTensorType::get({h, m, n}, mlir::IndexType::get(&context));
@@ -65,18 +68,27 @@ void tricyclicBatchMatmulMatchesNaive(
6568
RankedTensorType resultType =
6669
RankedTensorType::get({h, m, p}, mlir::IndexType::get(&context));
6770
auto resultLayout = getTricyclicLayoutRelation(resultType, numSlots);
68-
auto expectedPacked =
69-
evaluateLayout<int>(resultLayout, [&](const std::vector<int64_t>& pt) {
70-
int64_t ih = pt[0], im = pt[1], ip = pt[2];
71-
int sum = 0;
71+
// Restrict the unpacking to the first output period.
72+
addBounds(resultLayout,
73+
resultLayout.getVarKindOffset(presburger::VarKind::Range) + 1, 0,
74+
h * m * p - 1);
75+
auto actual =
76+
unpackLayoutTo3DTensor<int>(resultLayout, {actualVec}, {h, m, p});
77+
78+
std::vector<std::vector<std::vector<int>>> expected(
79+
h, std::vector<std::vector<int>>(m, std::vector<int>(p, 0)));
80+
for (int64_t ih = 0; ih < h; ++ih) {
81+
for (int64_t im = 0; im < m; ++im) {
82+
for (int64_t ip = 0; ip < p; ++ip) {
7283
for (int64_t in = 0; in < n; ++in) {
73-
sum +=
84+
expected[ih][im][ip] +=
7485
vecA[ih * m * n + im * n + in] * vecB[ih * n * p + in * p + ip];
7586
}
76-
return sum;
77-
});
87+
}
88+
}
89+
}
7890

79-
EXPECT_EQ(expectedPacked[0], actualVec);
91+
EXPECT_EQ(expected, actual);
8092
}
8193

8294
auto tricyclicShapeAndTensors() {

lib/Transforms/ConvertToCiphertextSemantics/ConvertToCiphertextSemantics.cpp

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,23 @@ Operation* remapAndExtractResult(ImplicitLocOpBuilder& builder, Value input,
155155
return extractRemap;
156156
}
157157

158+
// Rebuilds the full periodic layout of a kernel's output from its first valid
159+
// period. Assumes the first period is uncorrupted by wrap-around bounds.
160+
Operation* replicateFirstPeriodOfResult(ImplicitLocOpBuilder& b, Value input,
161+
LayoutAttr resultLayout,
162+
int64_t period) {
163+
auto ctSemanticType = cast<RankedTensorType>(input.getType());
164+
int64_t numCiphertexts = ctSemanticType.getDimSize(0);
165+
int64_t numSlots = ctSemanticType.getDimSize(1);
166+
IntegerRelation replication =
167+
getPeriodicReplicationRelation(numCiphertexts, numSlots, period);
168+
LayoutAttr replicationMapping =
169+
LayoutAttr::getFromIntegerRelation(b.getContext(), replication);
170+
auto remapOp = tensor_ext::RemapOp::create(b, input, replicationMapping);
171+
remapOp->setAttr(kLayoutAttrName, resultLayout);
172+
return remapOp;
173+
}
174+
158175
} // namespace
159176

160177
// An unset value of a permutation as it's being built up.
@@ -2807,7 +2824,15 @@ struct ConvertLinalgMatmul
28072824
makeAppropriatelyTypedAddOp(b, op->getLoc(), finalOutput, result);
28082825
addBias->setAttr(kLayoutAttrName, layoutAttr);
28092826
setMaterializedAttr(addBias);
2810-
rewriter.replaceOp(op, addBias);
2827+
2828+
// Rebuild the full periodic output layout from the first period.
2829+
auto dataSemanticResultType =
2830+
cast<RankedTensorType>(op->getResult(0).getType());
2831+
Operation* replicated =
2832+
replicateFirstPeriodOfResult(b, addBias->getResult(0), layoutAttr,
2833+
dataSemanticResultType.getNumElements());
2834+
setMaterializedAttr(replicated);
2835+
rewriter.replaceOp(op, replicated);
28112836
}
28122837

28132838
bool supportsBicyclic(linalg::MatmulOp op, OpAdaptor adaptor) const {
@@ -2856,7 +2881,15 @@ struct ConvertLinalgMatmul
28562881
makeAppropriatelyTypedAddOp(b, op->getLoc(), finalOutput, result);
28572882
addBias->setAttr(kLayoutAttrName, layoutAttr);
28582883
setMaterializedAttr(addBias);
2859-
rewriter.replaceOp(op, addBias);
2884+
2885+
// Rebuild the full periodic output layout from the first period.
2886+
auto dataSemanticResultType =
2887+
cast<RankedTensorType>(op->getResult(0).getType());
2888+
Operation* replicated =
2889+
replicateFirstPeriodOfResult(b, addBias->getResult(0), layoutAttr,
2890+
dataSemanticResultType.getNumElements());
2891+
setMaterializedAttr(replicated);
2892+
rewriter.replaceOp(op, replicated);
28602893
}
28612894

28622895
LogicalResult matchAndRewrite(
@@ -2934,7 +2967,15 @@ struct ConvertLinalgBatchMatmul
29342967
makeAppropriatelyTypedAddOp(b, op->getLoc(), finalOutput, result);
29352968
addBias->setAttr(kLayoutAttrName, layoutAttr);
29362969
setMaterializedAttr(addBias);
2937-
rewriter.replaceOp(op, addBias);
2970+
2971+
// Rebuild the full periodic output layout from the first period.
2972+
auto dataSemanticResultType =
2973+
cast<RankedTensorType>(op->getResult(0).getType());
2974+
Operation* replicated =
2975+
replicateFirstPeriodOfResult(b, addBias->getResult(0), layoutAttr,
2976+
dataSemanticResultType.getNumElements());
2977+
setMaterializedAttr(replicated);
2978+
rewriter.replaceOp(op, replicated);
29382979
}
29392980

29402981
LogicalResult matchAndRewrite(

lib/Utils/Layout/Utils.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,42 @@ presburger::IntegerRelation getTricyclicLayoutRelation(
469469
return result;
470470
}
471471

472+
presburger::IntegerRelation getPeriodicReplicationRelation(
473+
int64_t numCiphertexts, int64_t numSlots, int64_t period) {
474+
assert(numCiphertexts == 1 && "only support single ciphertext layout");
475+
assert(period > 0 && period <= numSlots &&
476+
"period must be positive and at most numSlots");
477+
478+
IntegerRelation result(PresburgerSpace::getRelationSpace(
479+
/*numDomain=*/2, /*numRange=*/2, /*numSymbol=*/0,
480+
/*numLocals=*/0));
481+
482+
int domainOffset = result.getVarKindOffset(VarKind::Domain);
483+
int rangeOffset = result.getVarKindOffset(VarKind::Range);
484+
int sourceCtIndex = domainOffset;
485+
int sourceSlotIndex = domainOffset + 1;
486+
int targetCtIndex = rangeOffset;
487+
int targetSlotIndex = rangeOffset + 1;
488+
489+
addBounds(result, sourceCtIndex, 0, numCiphertexts - 1);
490+
addBounds(result, sourceSlotIndex, 0, period - 1);
491+
addBounds(result, targetSlotIndex, 0, numSlots - 1);
492+
493+
addConstraint(result, {{sourceCtIndex, 1}, {targetCtIndex, -1}},
494+
/*equality=*/true);
495+
496+
// source_slot = target_slot % period
497+
SmallVector<int64_t> targetSlotCoeffs(result.getNumCols(), 0);
498+
targetSlotCoeffs[targetSlotIndex] = 1;
499+
auto targetSlotMod = addModConstraint(result, targetSlotCoeffs, period);
500+
SmallVector<int64_t> sourceEquality(result.getNumCols(), 0);
501+
sourceEquality[sourceSlotIndex] = 1;
502+
sourceEquality[targetSlotMod] = -1;
503+
result.addEquality(sourceEquality);
504+
505+
return result;
506+
}
507+
472508
presburger::IntegerRelation getPerRowLayoutRelation(RankedTensorType matrixType,
473509
int64_t minSlotCount) {
474510
auto domainSize = matrixType.getRank();

lib/Utils/Layout/Utils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ presburger::IntegerRelation getBicyclicDiagonalRelation(
9696
RankedTensorType matrixType, int64_t contractionDim, int64_t stride,
9797
int64_t numSlots);
9898

99+
// Returns an IntegerRelation with domain and range space both (ct, slot) that
100+
// maps each slot s in [0, period) of a ciphertext to every slot s' in [0,
101+
// numSlots) with s' equiv s (mod period). Excepts numCiphertexts == 1.
102+
presburger::IntegerRelation getPeriodicReplicationRelation(
103+
int64_t numCiphertexts, int64_t numSlots, int64_t period);
104+
99105
// Returns an IntegerRelation that represents a per-row layout for a matrix
100106
// such that each row of the matrix is in a separate ciphertext.
101107
presburger::IntegerRelation getPerRowLayoutRelation(RankedTensorType matrixType,

lib/Utils/Layout/UtilsTest.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,24 @@ TEST(UtilsTest, BicyclicLayout3x5Repeated) {
240240
EXPECT_EQ(packedMatrix, expected);
241241
}
242242

243+
TEST(UtilsTest, PeriodicReplicationRelation) {
244+
int64_t numSlots = 10;
245+
int64_t period = 3;
246+
IntegerRelation replication =
247+
getPeriodicReplicationRelation(/*numCiphertexts=*/1, numSlots, period);
248+
249+
// Every target slot t is reached exactly from source slot t % period.
250+
for (int64_t t = 0; t < numSlots; ++t) {
251+
for (int64_t s = 0; s < period; ++s) {
252+
EXPECT_EQ(replication.containsPointNoLocal({0, s, 0, t}).has_value(),
253+
s == t % period);
254+
}
255+
}
256+
257+
// Source slots outside the first period are not in the domain.
258+
EXPECT_FALSE(replication.containsPointNoLocal({0, period, 0, period}));
259+
}
260+
243261
TEST(UtilsTest, BicyclicCtPtDiagonal3x5x7) {
244262
MLIRContext context;
245263
int64_t numSlots = 105;

tests/Dialect/TensorExt/Transforms/implement_shift_network.mlir

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,15 @@ func.func @multi_ciphertext_complex(%0: tensor<4x64xi32>) -> tensor<4x64xi32> {
8686
%1 = tensor_ext.remap %0 {permutation = #map5} : tensor<4x64xi32>
8787
return %1 : tensor<4x64xi32>
8888
}
89+
90+
// CHECK: func.func @periodic_replication
91+
// CHECK-NOT: tensor_ext.remap
92+
// CHECK: tensor.extract_slice
93+
// CHECK: tensor_ext.rotate
94+
// CHECK: tensor.insert_slice
95+
#layout_bicyclic = #tensor_ext.layout<"{ [i0, i1] -> [ct, slot] : ct = 0 and (4i0 + 5i1 + slot) mod 30 = 0 and 0 <= i0 <= 2 and 0 <= i1 <= 1 and 0 <= slot <= 1023 }">
96+
#replication = #tensor_ext.layout<"{ [i0, i1] -> [ct, slot] : i0 = 0 and ct = 0 and (-i1 + slot) mod 6 = 0 and 0 <= i1 <= 5 and 0 <= slot <= 1023 }">
97+
func.func @periodic_replication(%arg0: tensor<1x1024xi16> {tensor_ext.layout = #layout_bicyclic}) -> (tensor<1x1024xi16> {tensor_ext.layout = #layout_bicyclic}) {
98+
%0 = tensor_ext.remap %arg0 {permutation = #replication} : tensor<1x1024xi16>
99+
return %0 : tensor<1x1024xi16>
100+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
func.func @bicyclic_matmul_chain(%arg0: tensor<13x18xf32> {secret.secret}, %arg1: tensor<18x16xf32>, %arg2: tensor<16x9xf32> {secret.secret}) -> tensor<13x9xf32> {
2+
%cst0 = arith.constant dense<0.000000e+00> : tensor<13x16xf32>
3+
%cst1 = arith.constant dense<0.000000e+00> : tensor<13x9xf32>
4+
%0 = linalg.matmul ins(%arg0, %arg1 : tensor<13x18xf32>, tensor<18x16xf32>) outs(%cst0 : tensor<13x16xf32>) -> tensor<13x16xf32>
5+
%1 = linalg.matmul ins(%0, %arg2 : tensor<13x16xf32>, tensor<16x9xf32>) outs(%cst1 : tensor<13x9xf32>) -> tensor<13x9xf32>
6+
return %1 : tensor<13x9xf32>
7+
}

0 commit comments

Comments
 (0)