Skip to content

Commit cb3ec65

Browse files
make lattigo pipeline aware of lintrans rotations
1 parent 41d77b9 commit cb3ec65

5 files changed

Lines changed: 120 additions & 222 deletions

File tree

lib/Dialect/Lattigo/Transforms/ConfigureCryptoContext.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,47 @@ struct LattigoCKKSScheme {
223223
}
224224
};
225225

226+
// Returns the best baby-step size N1 for BSGS given diagonal indices, slot
227+
// count, and log2 of the target baby/giant ratio. Mirrors Lattigo's
228+
// lintrans.FindBestBSGSRatio.
229+
static int64_t findBestBSGSRatio(ArrayRef<int32_t> diags, int64_t slots,
230+
int64_t logMaxRatio) {
231+
int64_t maxRatio = 1LL << logMaxRatio;
232+
for (int64_t N1 = 1; N1 < slots; N1 <<= 1) {
233+
DenseSet<int64_t> rotN1Set, rotN2Set;
234+
for (auto rot : diags) {
235+
int64_t r = (int64_t)rot & (slots - 1);
236+
rotN1Set.insert(((r / N1) * N1) & (slots - 1));
237+
rotN2Set.insert(r & (N1 - 1));
238+
}
239+
int64_t nbN1 = (int64_t)rotN1Set.size() - 1;
240+
int64_t nbN2 = (int64_t)rotN2Set.size() - 1;
241+
if (nbN1 > 0) {
242+
if (nbN2 == maxRatio * nbN1) return N1;
243+
if (nbN2 > maxRatio * nbN1) return N1 / 2;
244+
}
245+
}
246+
return 1;
247+
}
248+
249+
// Returns all non-zero rotation indices needed by lintrans.EvaluateNew for a
250+
// CKKSLinearTransformOp with the given diagonal indices, slot count, and BSGS
251+
// ratio. Mirrors Lattigo's lintrans.GaloisElements().
252+
static DenseSet<int64_t> lintransRotationIndices(ArrayRef<int32_t> diags,
253+
int64_t slots,
254+
int64_t logBSGS) {
255+
DenseSet<int64_t> result;
256+
int64_t N1 = (logBSGS < 0) ? slots : findBestBSGSRatio(diags, slots, logBSGS);
257+
for (auto rot : diags) {
258+
int64_t r = (int64_t)rot & (slots - 1);
259+
int64_t giant = ((r / N1) * N1) & (slots - 1);
260+
int64_t baby = r & (N1 - 1);
261+
if (giant != 0) result.insert(giant);
262+
if (baby != 0) result.insert(baby);
263+
}
264+
return result;
265+
}
266+
226267
template <typename LattigoScheme>
227268
LogicalResult convertFuncForScheme(func::FuncOp op) {
228269
using EvaluatorType = typename LattigoScheme::EvaluatorType;
@@ -314,6 +355,26 @@ LogicalResult convertFuncForScheme(func::FuncOp op) {
314355

315356
auto setIndices = analysis.getRotationIndices();
316357
SmallVector<int64_t> rotIndices(setIndices.begin(), setIndices.end());
358+
359+
// Supplement RotationAnalysis with galois indices needed by
360+
// CKKSLinearTransformOp (lintrans.EvaluateNew BSGS rotations).
361+
// RotationAnalysis only sees explicit ckks.rotate ops.
362+
{
363+
int64_t slots = 1LL << (logN - 1); // CKKS: N/2 slots
364+
DenseSet<int64_t> seen(rotIndices.begin(), rotIndices.end());
365+
walkFuncAndCallees(op, [&](Operation* innerOp) {
366+
auto ltOp = dyn_cast<CKKSLinearTransformOp>(innerOp);
367+
if (!ltOp) return WalkResult::advance();
368+
int64_t logBSGS = ltOp.getLogBabyStepGiantStepRatio().getInt();
369+
auto newRots = lintransRotationIndices(
370+
ltOp.getDiagonalIndicesAttr().asArrayRef(), slots, logBSGS);
371+
for (auto r : newRots) {
372+
if (seen.insert(r).second) rotIndices.push_back(r);
373+
}
374+
return WalkResult::advance();
375+
});
376+
}
377+
317378
LLVM_DEBUG({
318379
llvm::dbgs() << "Finished rotation analysis; found " << rotIndices.size()
319380
<< " rotations which were=\n";

tests/Examples/orion/linear_transform/BUILD

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ go_test(
1717
srcs = ["linear_transform_test.go"],
1818
embed = [":lineartransform"],
1919
deps = [
20-
"@com_github_tuneinsight_lattigo_v6//circuits/ckks/lintrans",
21-
"@com_github_tuneinsight_lattigo_v6//core/rlwe",
22-
"@com_github_tuneinsight_lattigo_v6//ring",
2320
"@com_github_tuneinsight_lattigo_v6//schemes/ckks",
2421
],
2522
)

tests/Examples/orion/linear_transform/linear_transform_test.go

Lines changed: 26 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,67 @@
11
package lineartransform
22

33
import (
4-
"fmt"
5-
"github.com/tuneinsight/lattigo/v6/circuits/ckks/lintrans"
6-
"github.com/tuneinsight/lattigo/v6/core/rlwe"
7-
"github.com/tuneinsight/lattigo/v6/ring"
8-
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
94
"math"
105
"testing"
6+
7+
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
118
)
129

1310
func TestLinearTransform(t *testing.T) {
14-
// This test is a bit weird because we're skipping most of the pipeline
15-
// to just test the emitter for linear_transform which is needed in the
16-
// context of comparisons with the orion compiler. This requires us to
17-
// manually set up the crypto parameters and encode/encrypt stuff.
11+
evaluator, params, encoder, encryptor, decryptor := linear_transform__configure()
12+
numSlots := params.MaxSlots()
1813

19-
// Input vector of all 1s
20-
numSlots := 4096
2114
inputClear := make([]float64, numSlots)
2215
for i := range inputClear {
2316
inputClear[i] = 1.0
2417
}
2518

26-
// Matrix of weights. This represents two nonzero diagonals on an
27-
// otherwise all-zero matrix:
28-
//
29-
// 0 4096
30-
// 1 4097
31-
// 2 4098
32-
// ...
33-
// 4094 8190
34-
// 8191 4095
35-
//
36-
// In this way, the nonzero diagonals become
37-
// diagonal 0: range(0, 4096)
38-
// diagonal 1: range(4096, 8192)
19+
// Matrix of weights: two nonzero diagonals on an otherwise all-zero matrix.
3920
//
40-
// and the expected values are (noting lattigo does
41-
// left-multiplication of the cleartext matrix and the vector is all
42-
// 1s):
21+
// diagonal 0: [0, 1, 2, ..., 4095]
22+
// diagonal 1: [4096, 4097, ..., 8191]
4323
//
44-
// [4096 + 0, 4097 + 1, ..., 8191 + 4095]
24+
// With an all-1s input vector, expected output = diagonal0 + diagonal1:
25+
// [4096+0, 4097+1, ..., 8191+4095]
4526
diagonals := 2
46-
cols := numSlots
47-
// Matrix is flattened 2 x numSlots
48-
matrix := make([]float64, diagonals*cols)
27+
matrix := make([]float64, diagonals*numSlots)
4928
value := 0.0
5029
for r := 0; r < diagonals; r++ {
51-
for c := 0; c < cols; c++ {
52-
matrix[r*cols+c] = value
30+
for c := 0; c < numSlots; c++ {
31+
matrix[r*numSlots+c] = value
5332
value++
5433
}
5534
}
5635

5736
expectedClear := make([]float64, numSlots)
5837
for i := range expectedClear {
59-
expectedClear[i] = float64(4096 + 2*i)
38+
expectedClear[i] = float64(numSlots + 2*i)
6039
}
6140

62-
// These parameters should match linear_transform.mlir, though due to
63-
// the weird nature of this test, this is the source of truth for what
64-
// is used, not the mlir file.
65-
param, err := ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
66-
LogN: 13,
67-
Q: []uint64{536903681, 67043329, 66994177, 67239937, 66961409, 66813953},
68-
P: []uint64{536952833, 536690689},
69-
LogDefaultScale: 26,
70-
})
71-
if err != nil {
72-
panic(err)
73-
}
74-
75-
encoder := ckks.NewEncoder(param)
76-
kgen := rlwe.NewKeyGenerator(param)
77-
sk, pk := kgen.GenKeyPairNew()
78-
encryptor := rlwe.NewEncryptor(param, pk)
79-
decryptor := rlwe.NewDecryptor(param, sk)
80-
81-
// This is copied from the generated code so we can get access to the
82-
// Lattigo-produced Galois key set to generate... ideally this is moved
83-
// to a shared client helper generated by HEIR, but the client
84-
// interface generation is before lowering to scheme, and Orion enters
85-
// post-lowering-to-scheme. For this test it's OK and, if nothing else,
86-
// a good reference.
87-
ct1diags := make(lintrans.Diagonals[float64])
88-
for i := 0; i < 2; i++ {
89-
ct1diags[i] = matrix[i*numSlots : (i+1)*numSlots]
41+
pt := ckks.NewPlaintext(params, params.MaxLevel())
42+
pt.Scale = params.DefaultScale()
43+
if err := encoder.Encode(inputClear, pt); err != nil {
44+
t.Fatal(err)
9045
}
91-
ct1params := lintrans.Parameters{
92-
DiagonalsIndexList: ct1diags.DiagonalsIndexList(),
93-
LevelQ: 5,
94-
LevelP: param.MaxLevelP(),
95-
Scale: rlwe.NewScale(param.Q()[5]),
96-
LogDimensions: ring.Dimensions{Rows: 0, Cols: 12}, // 1x4096
97-
LogBabyStepGiantStepRatio: 2,
98-
}
99-
ct1lt := lintrans.NewTransformation(param, ct1params)
100-
galEls := ct1lt.GaloisElements(param)
101-
102-
// Manually add Galois key for rotation index 2048
103-
rotIndex := 2048
104-
logN := 13
105-
galoisElement := uint64(1)
106-
for i := 0; i < rotIndex; i++ {
107-
galoisElement = (galoisElement * 5) % (1 << (logN + 1))
108-
}
109-
galEls = append(galEls, galoisElement)
110-
fmt.Printf("Final galEls: %v\n", galEls)
111-
112-
evk := rlwe.NewMemEvaluationKeySet(nil, kgen.GenGaloisKeysNew(galEls, sk)...)
113-
evaluator := ckks.NewEvaluator(param, evk)
114-
115-
pt := ckks.NewPlaintext(param, param.MaxLevel())
116-
pt.LogDimensions = ring.Dimensions{Rows: 0, Cols: 12} // 2^(0+12) = 4096 slots
117-
encoder.Encode(inputClear, pt)
118-
ctInput, err25 := encryptor.EncryptNew(pt)
119-
if err25 != nil {
120-
panic(err25)
46+
ct, err := encryptor.EncryptNew(pt)
47+
if err != nil {
48+
t.Fatal(err)
12149
}
12250

123-
resultCt := linear_transform(evaluator, param, encoder, ctInput, matrix)
51+
resultCt := linear_transform(evaluator, params, encoder, ct, matrix)
12452
resultPt := decryptor.DecryptNew(resultCt)
125-
resultFloat64 := make([]float64, 4096)
126-
encoder.Decode(resultPt, resultFloat64)
53+
resultFloat64 := make([]float64, numSlots)
54+
if err := encoder.Decode(resultPt, resultFloat64); err != nil {
55+
t.Fatal(err)
56+
}
12757

128-
// We need such a large epsilon because scale 26 is not very precise,
129-
// increasing scale to 40 produces errors of about 1e-04.
58+
// Scale 26 is not very precise; epsilon of 1.5 is sufficient here.
13059
epsilon := 1.5
13160
for i := 0; i < numSlots; i++ {
13261
diff := math.Abs(resultFloat64[i] - expectedClear[i])
13362
if diff > epsilon {
13463
t.Errorf("Mismatch at index %d: got %f, expected %f (diff: %e)",
13564
i, resultFloat64[i], expectedClear[i], diff)
136-
137-
// Fail fast to avoid spamming 4096 errors
13865
if i > 10 {
13966
t.Fatal("Too many errors, stopping verification.")
14067
}

tests/Examples/orion/mlp/BUILD

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ go_test(
1717
srcs = ["mlp_test.go"],
1818
embed = [":mlp"],
1919
deps = [
20-
"@com_github_tuneinsight_lattigo_v6//circuits/ckks/lintrans",
21-
"@com_github_tuneinsight_lattigo_v6//core/rlwe",
22-
"@com_github_tuneinsight_lattigo_v6//ring",
2320
"@com_github_tuneinsight_lattigo_v6//schemes/ckks",
2421
],
2522
)

0 commit comments

Comments
 (0)