|
|
package integration_test |
|
|
|
|
|
import ( |
|
|
"context" |
|
|
"math" |
|
|
"math/rand" |
|
|
"os" |
|
|
|
|
|
. "github.com/onsi/ginkgo/v2" |
|
|
. "github.com/onsi/gomega" |
|
|
"github.com/mudler/xlog" |
|
|
|
|
|
"github.com/mudler/LocalAI/core/config" |
|
|
"github.com/mudler/LocalAI/pkg/grpc" |
|
|
"github.com/mudler/LocalAI/pkg/model" |
|
|
"github.com/mudler/LocalAI/pkg/store" |
|
|
"github.com/mudler/LocalAI/pkg/system" |
|
|
) |
|
|
|
|
|
func normalize(vecs [][]float32) { |
|
|
for i, k := range vecs { |
|
|
norm := float64(0) |
|
|
for _, x := range k { |
|
|
norm += float64(x * x) |
|
|
} |
|
|
norm = math.Sqrt(norm) |
|
|
for j, x := range k { |
|
|
vecs[i][j] = x / float32(norm) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
var _ = Describe("Integration tests for the stores backend(s) and internal APIs", Label("stores"), func() { |
|
|
Context("Embedded Store get,set and delete", func() { |
|
|
var sl *model.ModelLoader |
|
|
var sc grpc.Backend |
|
|
var tmpdir string |
|
|
|
|
|
BeforeEach(func() { |
|
|
var err error |
|
|
|
|
|
zerolog.SetGlobalLevel(zerolog.DebugLevel) |
|
|
|
|
|
tmpdir, err = os.MkdirTemp("", "") |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
debug := true |
|
|
|
|
|
bc := config.ModelConfig{ |
|
|
Name: "store test", |
|
|
Debug: &debug, |
|
|
Backend: model.LocalStoreBackend, |
|
|
} |
|
|
|
|
|
storeOpts := []model.Option{ |
|
|
model.WithBackendString(bc.Backend), |
|
|
model.WithModel("test"), |
|
|
} |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithModelPath(tmpdir), |
|
|
) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
sl = model.NewModelLoader(systemState) |
|
|
sc, err = sl.Load(storeOpts...) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(sc).ToNot(BeNil()) |
|
|
}) |
|
|
|
|
|
AfterEach(func() { |
|
|
err := sl.StopAllGRPC() |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
err = os.RemoveAll(tmpdir) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
}) |
|
|
|
|
|
It("should be able to set a key", func() { |
|
|
err := store.SetSingle(context.Background(), sc, []float32{0.1, 0.2, 0.3}, []byte("test")) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
}) |
|
|
|
|
|
It("should be able to set keys", func() { |
|
|
err := store.SetCols(context.Background(), sc, [][]float32{{0.1, 0.2, 0.3}, {0.4, 0.5, 0.6}}, [][]byte{[]byte("test1"), []byte("test2")}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
err = store.SetCols(context.Background(), sc, [][]float32{{0.7, 0.8, 0.9}, {0.10, 0.11, 0.12}}, [][]byte{[]byte("test3"), []byte("test4")}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
}) |
|
|
|
|
|
It("should be able to get a key", func() { |
|
|
err := store.SetSingle(context.Background(), sc, []float32{0.1, 0.2, 0.3}, []byte("test")) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
val, err := store.GetSingle(context.Background(), sc, []float32{0.1, 0.2, 0.3}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(val).To(Equal([]byte("test"))) |
|
|
}) |
|
|
|
|
|
It("should be able to get keys", func() { |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, [][]float32{{0.1, 0.2, 0.3}, {0.4, 0.5, 0.6}, {0.7, 0.8, 0.9}}, [][]byte{[]byte("test1"), []byte("test2"), []byte("test3")}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
|
|
|
keys, vals, err := store.GetCols(context.Background(), sc, [][]float32{{0.1, 0.2, 0.3}, {0.4, 0.5, 0.6}, {0.7, 0.8, 0.9}}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(keys).To(HaveLen(3)) |
|
|
Expect(vals).To(HaveLen(3)) |
|
|
for i, k := range keys { |
|
|
v := vals[i] |
|
|
|
|
|
if k[0] == 0.1 && k[1] == 0.2 && k[2] == 0.3 { |
|
|
Expect(v).To(Equal([]byte("test1"))) |
|
|
} else if k[0] == 0.4 && k[1] == 0.5 && k[2] == 0.6 { |
|
|
Expect(v).To(Equal([]byte("test2"))) |
|
|
} else { |
|
|
Expect(k).To(Equal([]float32{0.7, 0.8, 0.9})) |
|
|
Expect(v).To(Equal([]byte("test3"))) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
keys, vals, err = store.GetCols(context.Background(), sc, [][]float32{{0.7, 0.8, 0.9}, {0.1, 0.2, 0.3}}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(keys).To(HaveLen(2)) |
|
|
Expect(vals).To(HaveLen(2)) |
|
|
for i, k := range keys { |
|
|
v := vals[i] |
|
|
|
|
|
if k[0] == 0.1 && k[1] == 0.2 && k[2] == 0.3 { |
|
|
Expect(v).To(Equal([]byte("test1"))) |
|
|
} else { |
|
|
Expect(k).To(Equal([]float32{0.7, 0.8, 0.9})) |
|
|
Expect(v).To(Equal([]byte("test3"))) |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should be able to delete a key", func() { |
|
|
err := store.SetSingle(context.Background(), sc, []float32{0.1, 0.2, 0.3}, []byte("test")) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
err = store.DeleteSingle(context.Background(), sc, []float32{0.1, 0.2, 0.3}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
val, _ := store.GetSingle(context.Background(), sc, []float32{0.1, 0.2, 0.3}) |
|
|
Expect(val).To(BeNil()) |
|
|
}) |
|
|
|
|
|
It("should be able to delete keys", func() { |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, [][]float32{{0.1, 0.2, 0.3}, {0.4, 0.5, 0.6}, {0.7, 0.8, 0.9}}, [][]byte{[]byte("test1"), []byte("test2"), []byte("test3")}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
|
|
|
err = store.DeleteCols(context.Background(), sc, [][]float32{{0.1, 0.2, 0.3}, {0.7, 0.8, 0.9}}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
|
|
|
keys, vals, err := store.GetCols(context.Background(), sc, [][]float32{{0.4, 0.5, 0.6}}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(keys).To(HaveLen(1)) |
|
|
Expect(vals).To(HaveLen(1)) |
|
|
Expect(keys[0]).To(Equal([]float32{0.4, 0.5, 0.6})) |
|
|
Expect(vals[0]).To(Equal([]byte("test2"))) |
|
|
|
|
|
|
|
|
keys, vals, err = store.GetCols(context.Background(), sc, [][]float32{{0.1, 0.2, 0.3}, {0.7, 0.8, 0.9}}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(keys).To(HaveLen(0)) |
|
|
Expect(vals).To(HaveLen(0)) |
|
|
}) |
|
|
|
|
|
It("should be able to find smilar keys", func() { |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, [][]float32{{0.5, 0.5, 0.5}, {0.6, 0.6, -0.6}, {0.7, -0.7, -0.7}}, [][]byte{[]byte("test1"), []byte("test2"), []byte("test3")}) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
|
|
|
keys, vals, sims, err := store.Find(context.Background(), sc, []float32{0.1, 0.3, 0.5}, 2) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(keys).To(HaveLen(2)) |
|
|
Expect(vals).To(HaveLen(2)) |
|
|
Expect(sims).To(HaveLen(2)) |
|
|
|
|
|
for i, k := range keys { |
|
|
s := sims[i] |
|
|
xlog.Debug("key", "similarity", s, "key", k) |
|
|
} |
|
|
|
|
|
Expect(keys[0]).To(Equal([]float32{0.5, 0.5, 0.5})) |
|
|
Expect(vals[0]).To(Equal([]byte("test1"))) |
|
|
Expect(keys[1]).To(Equal([]float32{0.6, 0.6, -0.6})) |
|
|
}) |
|
|
|
|
|
It("should be able to find similar normalized keys", func() { |
|
|
|
|
|
keys := [][]float32{{0.1, 0.3, 0.5}, {0.5, 0.5, 0.5}, {0.6, 0.6, -0.6}, {0.7, -0.7, -0.7}} |
|
|
vals := [][]byte{[]byte("test0"), []byte("test1"), []byte("test2"), []byte("test3")} |
|
|
|
|
|
normalize(keys) |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, keys, vals) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
|
|
|
ks, vals, sims, err := store.Find(context.Background(), sc, keys[0], 3) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(ks).To(HaveLen(3)) |
|
|
Expect(vals).To(HaveLen(3)) |
|
|
Expect(sims).To(HaveLen(3)) |
|
|
|
|
|
for i, k := range ks { |
|
|
s := sims[i] |
|
|
xlog.Debug("key", "similarity", s, "key", k) |
|
|
} |
|
|
|
|
|
Expect(ks[0]).To(Equal(keys[0])) |
|
|
Expect(vals[0]).To(Equal(vals[0])) |
|
|
Expect(sims[0]).To(BeNumerically("~", 1, 0.0001)) |
|
|
Expect(ks[1]).To(Equal(keys[1])) |
|
|
Expect(vals[1]).To(Equal(vals[1])) |
|
|
}) |
|
|
|
|
|
It("It produces the correct cosine similarities for orthogonal and opposite unit vectors", func() { |
|
|
keys := [][]float32{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {-1.0, 0.0, 0.0}} |
|
|
vals := [][]byte{[]byte("x"), []byte("y"), []byte("z"), []byte("-z")} |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, keys, vals) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
_, _, sims, err := store.Find(context.Background(), sc, keys[0], 4) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(sims).To(Equal([]float32{1.0, 0.0, 0.0, -1.0})) |
|
|
}) |
|
|
|
|
|
It("It produces the correct cosine similarities for orthogonal and opposite vectors", func() { |
|
|
keys := [][]float32{{1.0, 0.0, 1.0}, {0.0, 2.0, 0.0}, {0.0, 0.0, -1.0}, {-1.0, 0.0, -1.0}} |
|
|
vals := [][]byte{[]byte("x"), []byte("y"), []byte("z"), []byte("-z")} |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, keys, vals) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
_, _, sims, err := store.Find(context.Background(), sc, keys[0], 4) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(sims[0]).To(BeNumerically("~", 1, 0.1)) |
|
|
Expect(sims[1]).To(BeNumerically("~", 0, 0.1)) |
|
|
Expect(sims[2]).To(BeNumerically("~", -0.7, 0.1)) |
|
|
Expect(sims[3]).To(BeNumerically("~", -1, 0.1)) |
|
|
}) |
|
|
|
|
|
expectTriangleEq := func(keys [][]float32, vals [][]byte) { |
|
|
sims := map[string]map[string]float32{} |
|
|
|
|
|
|
|
|
|
|
|
for i, k := range keys { |
|
|
_, valsk, simsk, err := store.Find(context.Background(), sc, k, 9) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
for j, v := range valsk { |
|
|
p := string(vals[i]) |
|
|
q := string(v) |
|
|
|
|
|
if sims[p] == nil { |
|
|
sims[p] = map[string]float32{} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
sims[p][q] = simsk[j] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for _, simsu := range sims { |
|
|
for w, simw := range simsu { |
|
|
|
|
|
uws := math.Acos(float64(simw)) |
|
|
|
|
|
|
|
|
for v, _ := range simsu { |
|
|
uvws := math.Acos(float64(simsu[v])) + math.Acos(float64(sims[v][w])) |
|
|
|
|
|
|
|
|
|
|
|
Expect(uws).To(BeNumerically("<=", uvws)) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
It("It obeys the triangle inequality for normalized values", func() { |
|
|
keys := [][]float32{ |
|
|
{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, |
|
|
{-1.0, 0.0, 0.0}, {0.0, -1.0, 0.0}, {0.0, 0.0, -1.0}, |
|
|
{2.0, 3.0, 4.0}, {9.0, 7.0, 1.0}, {0.0, -1.2, 2.3}, |
|
|
} |
|
|
vals := [][]byte{ |
|
|
[]byte("x"), []byte("y"), []byte("z"), |
|
|
[]byte("-x"), []byte("-y"), []byte("-z"), |
|
|
[]byte("u"), []byte("v"), []byte("w"), |
|
|
} |
|
|
|
|
|
normalize(keys[6:]) |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, keys, vals) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
expectTriangleEq(keys, vals) |
|
|
}) |
|
|
|
|
|
It("It obeys the triangle inequality", func() { |
|
|
rnd := rand.New(rand.NewSource(151)) |
|
|
keys := make([][]float32, 20) |
|
|
vals := make([][]byte, 20) |
|
|
|
|
|
for i := range keys { |
|
|
k := make([]float32, 768) |
|
|
|
|
|
for j := range k { |
|
|
k[j] = rnd.Float32() |
|
|
} |
|
|
|
|
|
keys[i] = k |
|
|
} |
|
|
|
|
|
c := byte('a') |
|
|
for i := range vals { |
|
|
vals[i] = []byte{c} |
|
|
c += 1 |
|
|
} |
|
|
|
|
|
err := store.SetCols(context.Background(), sc, keys, vals) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
|
|
|
expectTriangleEq(keys, vals) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|