github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/vector/hnsw/compress_sift_test.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 //go:build benchmarkSiftRecall 13 // +build benchmarkSiftRecall 14 15 package hnsw_test 16 17 import ( 18 "context" 19 "fmt" 20 "io/ioutil" 21 "math" 22 "os" 23 "strconv" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/sirupsen/logrus/hooks/test" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 "github.com/weaviate/weaviate/adapters/repos/db/lsmkv" 33 "github.com/weaviate/weaviate/adapters/repos/db/vector/compressionhelpers" 34 "github.com/weaviate/weaviate/adapters/repos/db/vector/hnsw" 35 "github.com/weaviate/weaviate/adapters/repos/db/vector/hnsw/distancer" 36 "github.com/weaviate/weaviate/adapters/repos/db/vector/testinghelpers" 37 "github.com/weaviate/weaviate/entities/cyclemanager" 38 ent "github.com/weaviate/weaviate/entities/vectorindex/hnsw" 39 ) 40 41 func distanceWrapper(provider distancer.Provider) func(x, y []float32) float32 { 42 return func(x, y []float32) float32 { 43 dist, _, _ := provider.SingleDist(x, y) 44 return dist 45 } 46 } 47 48 const rootPath = "doesnt-matter-as-committlogger-is-mocked-out" 49 50 func TestRecall(t *testing.T) { 51 defer func(path string) { 52 err := os.RemoveAll(path) 53 if err != nil { 54 fmt.Println(err) 55 } 56 }(rootPath) 57 fmt.Println("Sift1MPQKMeans 10K/1K") 58 efConstruction := 64 59 ef := 32 60 maxNeighbors := 32 61 dimensions := 128 62 vectors_size := 200000 63 queries_size := 100 64 switch_at := vectors_size 65 before := time.Now() 66 vectors, queries := testinghelpers.RandomVecs(vectors_size, queries_size, dimensions) 67 k := 10 68 distancer := distancer.NewL2SquaredProvider() 69 fmt.Printf("generating data took %s\n", time.Since(before)) 70 71 uc := ent.UserConfig{} 72 uc.MaxConnections = maxNeighbors 73 uc.EFConstruction = efConstruction 74 uc.EF = ef 75 uc.VectorCacheMaxObjects = 10e12 76 77 index, _ := hnsw.New(hnsw.Config{ 78 RootPath: rootPath, 79 ID: "recallbenchmark", 80 MakeCommitLoggerThunk: hnsw.MakeNoopCommitLogger, 81 DistanceProvider: distancer, 82 VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) { 83 return vectors[int(id)], nil 84 }, 85 }, uc, newDummyStore(t)) 86 init := time.Now() 87 compressionhelpers.Concurrently(uint64(switch_at), func(_, id uint64, _ *sync.Mutex) { 88 index.Add(uint64(id), vectors[id]) 89 if id%1000 == 0 { 90 fmt.Println(id, time.Since(before)) 91 } 92 }) 93 before = time.Now() 94 uc.PQ.Enabled = true 95 index.UpdateUserConfig(uc, func() {}) /*should have configuration.pr.enabled = true*/ 96 fmt.Printf("Time to compress: %s", time.Since(before)) 97 fmt.Println() 98 compressionhelpers.Concurrently(uint64(vectors_size-switch_at), func(_, id uint64, _ *sync.Mutex) { 99 idx := switch_at + int(id) 100 index.Add(uint64(idx), vectors[idx]) 101 if id%1000 == 0 { 102 fmt.Println(idx, time.Since(before)) 103 } 104 }) 105 fmt.Printf("Building the index took %s\n", time.Since(init)) 106 107 lastRecall := float32(0.0) 108 for _, currentEF := range []int{32, 64, 128, 256, 512} { 109 uc.EF = currentEF 110 index.UpdateUserConfig(uc, func() {}) 111 fmt.Println(currentEF) 112 var relevant uint64 113 var retrieved int 114 115 var querying time.Duration = 0 116 for i := 0; i < len(queries); i++ { 117 truth := testinghelpers.BruteForce(vectors, queries[i], k, distanceWrapper(distancer)) 118 before = time.Now() 119 results, _, _ := index.SearchByVector(queries[i], k, nil) 120 querying += time.Since(before) 121 retrieved += k 122 relevant += testinghelpers.MatchesInLists(truth, results) 123 } 124 125 recall := float32(relevant) / float32(retrieved) 126 assert.True(t, recall > float32(lastRecall)) 127 lastRecall = recall 128 } 129 assert.True(t, lastRecall > 0.95) 130 } 131 132 func TestHnswPqGist(t *testing.T) { 133 defer func(path string) { 134 err := os.RemoveAll(path) 135 if err != nil { 136 fmt.Println(err) 137 } 138 }(rootPath) 139 params := [][]int{ 140 //{64, 64, 32}, 141 {128, 128, 64}, 142 {256, 256, 128}, 143 {512, 512, 256}, 144 } 145 dimensions := 960 146 vectors_size := 1000000 147 queries_size := 1000 148 switch_at := 200000 149 150 before := time.Now() 151 vectors, queries := testinghelpers.ReadVecs(vectors_size, queries_size, dimensions, "gist", "../diskAnn/testdata") 152 testinghelpers.Normalize(vectors) 153 testinghelpers.Normalize(queries) 154 for i, v := range vectors { 155 for j, x := range v { 156 if math.IsNaN(float64(x)) { 157 fmt.Println(i, j, v, x) 158 } 159 } 160 } 161 k := 100 162 distancer := distancer.NewCosineDistanceProvider() 163 truths := testinghelpers.BuildTruths(queries_size, vectors_size, queries, vectors, k, distanceWrapper(distancer), "../diskAnn/testdata/gist/cosine") 164 fmt.Printf("generating data took %s\n", time.Since(before)) 165 for segmentRate := 3; segmentRate < 4; segmentRate++ { 166 fmt.Println(segmentRate) 167 fmt.Println() 168 for i := 0; i < len(params); i++ { 169 efConstruction := params[i][0] 170 ef := params[i][1] 171 maxNeighbors := params[i][2] 172 173 uc := ent.UserConfig{ 174 MaxConnections: maxNeighbors, 175 EFConstruction: efConstruction, 176 EF: ef, 177 VectorCacheMaxObjects: 10e12, 178 PQ: ent.PQConfig{ 179 Enabled: false, 180 Segments: dimensions / int(math.Pow(2, float64(segmentRate))), 181 Encoder: ent.PQEncoder{ 182 Type: ent.PQEncoderTypeKMeans, 183 Distribution: ent.PQEncoderDistributionLogNormal, 184 }, 185 }, 186 } 187 index, _ := hnsw.New(hnsw.Config{ 188 RootPath: rootPath, 189 ID: "recallbenchmark", 190 MakeCommitLoggerThunk: hnsw.MakeNoopCommitLogger, 191 DistanceProvider: distancer, 192 VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) { 193 return vectors[int(id)], nil 194 }, 195 }, uc, newDummyStore(t)) 196 init := time.Now() 197 total := 200000 198 compressionhelpers.Concurrently(uint64(switch_at), func(_, id uint64, _ *sync.Mutex) { 199 total++ 200 if total%100000 == 0 { 201 fmt.Println(total) 202 } 203 index.Add(uint64(id), vectors[id]) 204 }) 205 before = time.Now() 206 uc.PQ.Enabled = true 207 index.UpdateUserConfig(uc, func() {}) 208 fmt.Printf("Time to compress: %s", time.Since(before)) 209 fmt.Println() 210 compressionhelpers.Concurrently(uint64(vectors_size-switch_at), func(_, id uint64, _ *sync.Mutex) { 211 idx := switch_at + int(id) 212 index.Add(uint64(idx), vectors[idx]) 213 }) 214 fmt.Printf("Building the index took %s\n", time.Since(init)) 215 var relevant uint64 216 var retrieved int 217 218 var querying time.Duration = 0 219 compressionhelpers.Concurrently(uint64(len(queries)), func(_, i uint64, _ *sync.Mutex) { 220 before = time.Now() 221 results, _, _ := index.SearchByVector(queries[i], k, nil) 222 querying += time.Since(before) 223 retrieved += k 224 relevant += testinghelpers.MatchesInLists(truths[i], results) 225 }) 226 227 recall := float32(relevant) / float32(retrieved) 228 latency := float32(querying.Microseconds()) / float32(queries_size) 229 fmt.Println(recall, latency) 230 assert.True(t, recall > 0.9) 231 assert.True(t, latency < 100000) 232 } 233 } 234 } 235 236 /* 237 10K 238 128 segments, 16 centroids -> 5.280255291s 0.90662 387.505 239 64 segments, 256 centroids -> 6.585159916s 0.9326827 410.413 240 241 100K 242 128 segments, 16 centroids -> 1m17.634662125s 0.88258 692.081 243 64 segments, 256 centroids -> 1m29.259369458s 0.92157 575.844 244 245 100000 246 128 247 Building the index took 47.7846745s 248 0.92627 664.66 249 250 {64, 64, 32, 256, 0}, 251 252 {64, 64, 32, 1024, 1}, 253 {64, 64, 32, 4096, 1}, 254 {64, 64, 32, 16384, 1}, 255 {64, 64, 32, 65536, 1}, 256 257 generating data took 2.072473792s 258 0 259 Time to compress: 5m39.750884042s 260 Building the index took 16m30.632114542s 261 0.91401 747.82 262 1 263 Time to compress: 13m12.011102334s 264 Building the index took 28m12.879802125s 265 0.89564 1041.358 266 2 267 Time to compress: 58m15.252058416s 268 Building the index took 1h37m10.217039334s 269 0.90836 2299.629 270 3 271 Time to compress: 3h59m39.032524584s 272 Building the index took 5h54m55.046038916s 273 0.91295 4786.8 274 275 generating data took 2.119674416s 276 0 277 Start compressing... 278 Time to compress: 1m3.037853584s 279 Building the index took 3m53.429316209s 280 0.40992 169.937 281 1 282 Start compressing... 283 Time to compress: 2m4.653952334s 284 Building the index took 5m44.454856667s 285 0.46251252 207.299 286 2 287 Start compressing... 288 Time to compress: 4m7.585857584s 289 Building the index took 8m36.2004675s 290 0.50494 293.362 291 3 292 Start compressing... 293 Time to compress: 8m16.4155035s 294 Building the index took 14m37.089003166s 295 0.54421 390.49 296 4 297 Start compressing... 298 Time to compress: 16m32.313318708s 299 Building the index took 26m46.66661125s 300 0.57827 442.589 301 */ 302 func TestHnswPqSift(t *testing.T) { 303 defer func(path string) { 304 err := os.RemoveAll(path) 305 if err != nil { 306 fmt.Println(err) 307 } 308 }(rootPath) 309 params := [][]int{ 310 {64, 64, 32, 256, 3}, 311 {64, 64, 32, 512, 3}, 312 {64, 64, 32, 1024, 3}, 313 {64, 64, 32, 2048, 3}, 314 {64, 64, 32, 4096, 3}, 315 {64, 64, 32, 65536, 3}, 316 } 317 dimensions := 128 318 vectors_size := 1000000 319 queries_size := 1000 320 switch_at := 200000 321 fmt.Println("Sift1M PQ") 322 before := time.Now() 323 vectors, queries := testinghelpers.ReadVecs(vectors_size, queries_size, dimensions, "sift", "../diskAnn/testdata") 324 k := 100 325 distancer := distancer.NewL2SquaredProvider() 326 truths := testinghelpers.BuildTruths(queries_size, vectors_size, queries, vectors, k, distanceWrapper(distancer), "../diskAnn/testdata") 327 fmt.Printf("generating data took %s\n", time.Since(before)) 328 for i := 0; i < len(params); i++ { 329 fmt.Println(i) 330 efConstruction := params[i][0] 331 ef := params[i][1] 332 maxNeighbors := params[i][2] 333 centroids := params[i][3] 334 segmentRate := params[i][4] 335 if centroids > switch_at { 336 fmt.Println("Increasing switch at...") 337 switch_at = 650000 338 } 339 340 uc := ent.UserConfig{ 341 MaxConnections: maxNeighbors, 342 EFConstruction: efConstruction, 343 EF: ef, 344 PQ: ent.PQConfig{ 345 Enabled: false, 346 Segments: dimensions / int(math.Pow(2, float64(segmentRate))), 347 Encoder: ent.PQEncoder{ 348 Type: ent.PQEncoderTypeTile, 349 Distribution: ent.PQEncoderDistributionLogNormal, 350 }, 351 }, 352 VectorCacheMaxObjects: 10e12, 353 } 354 index, _ := hnsw.New(hnsw.Config{ 355 RootPath: rootPath, 356 ID: "recallbenchmark", 357 MakeCommitLoggerThunk: hnsw.MakeNoopCommitLogger, 358 DistanceProvider: distancer, 359 VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) { 360 return vectors[int(id)], nil 361 }, 362 }, uc, newDummyStore(t)) 363 init := time.Now() 364 compressionhelpers.Concurrently(uint64(switch_at), func(_, id uint64, _ *sync.Mutex) { 365 index.Add(uint64(id), vectors[id]) 366 }) 367 before = time.Now() 368 fmt.Println("Start compressing...") 369 370 cfg := ent.PQConfig{ 371 Enabled: true, 372 Segments: dimensions / int(math.Pow(2, float64(segmentRate))), 373 Centroids: centroids, 374 BitCompression: false, 375 Encoder: ent.PQEncoder{ 376 Type: ent.PQEncoderTypeKMeans, 377 Distribution: ent.PQEncoderDistributionLogNormal, 378 }, 379 } 380 381 index.Compress(cfg) /*should have configuration.compressed = true*/ 382 fmt.Printf("Time to compress: %s", time.Since(before)) 383 fmt.Println() 384 compressionhelpers.Concurrently(uint64(vectors_size-switch_at), func(_, id uint64, _ *sync.Mutex) { 385 idx := switch_at + int(id) 386 387 index.Add(uint64(idx), vectors[idx]) 388 }) 389 fmt.Printf("Building the index took %s\n", time.Since(init)) 390 391 var relevant uint64 392 var retrieved int 393 394 var querying time.Duration = 0 395 compressionhelpers.Concurrently(uint64(len(queries)), func(_, i uint64, _ *sync.Mutex) { 396 before = time.Now() 397 results, _, _ := index.SearchByVector(queries[i], k, nil) 398 querying += time.Since(before) 399 retrieved += k 400 relevant += testinghelpers.MatchesInLists(truths[i], results) 401 }) 402 403 recall := float32(relevant) / float32(retrieved) 404 latency := float32(querying.Microseconds()) / float32(queries_size) 405 fmt.Println(recall, latency) 406 assert.True(t, recall > 0.9) 407 assert.True(t, latency < 100000) 408 } 409 } 410 411 func TestHnswPqSiftDeletes(t *testing.T) { 412 defer func(path string) { 413 err := os.RemoveAll(path) 414 if err != nil { 415 fmt.Println(err) 416 } 417 }(rootPath) 418 params := [][]int{ 419 {64, 64, 32}, 420 } 421 dimensions := 128 422 vectors_size := 10000 423 queries_size := 1000 424 switch_at := 2000 425 fmt.Println("Sift1M PQ Deletes") 426 before := time.Now() 427 vectors, queries := testinghelpers.ReadVecs(vectors_size, queries_size, dimensions, "sift", "../diskAnn/testdata") 428 k := 100 429 distancer := distancer.NewL2SquaredProvider() 430 truths := testinghelpers.BuildTruths(queries_size, vectors_size, queries, vectors, k, distanceWrapper(distancer), "../diskAnn/testdata") 431 fmt.Printf("generating data took %s\n", time.Since(before)) 432 for segmentRate := 0; segmentRate < 1; segmentRate++ { 433 fmt.Println(segmentRate) 434 fmt.Println() 435 for i := 0; i < len(params); i++ { 436 efConstruction := params[i][0] 437 ef := params[i][1] 438 maxNeighbors := params[i][2] 439 440 uc := ent.UserConfig{ 441 MaxConnections: maxNeighbors, 442 EFConstruction: efConstruction, 443 EF: ef, 444 PQ: ent.PQConfig{ 445 Enabled: false, 446 Segments: dimensions / int(math.Pow(2, float64(segmentRate))), 447 Encoder: ent.PQEncoder{ 448 Type: "tile", 449 Distribution: "log-normal", 450 }, 451 }, 452 VectorCacheMaxObjects: 10e12, 453 } 454 index, _ := hnsw.New(hnsw.Config{ 455 RootPath: rootPath, 456 ID: "recallbenchmark", 457 MakeCommitLoggerThunk: hnsw.MakeNoopCommitLogger, 458 DistanceProvider: distancer, 459 VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) { 460 return vectors[int(id)], nil 461 }, 462 }, uc, newDummyStore(t)) 463 init := time.Now() 464 compressionhelpers.Concurrently(uint64(switch_at), func(_, id uint64, _ *sync.Mutex) { 465 index.Add(uint64(id), vectors[id]) 466 }) 467 before = time.Now() 468 uc.PQ.Enabled = true 469 index.UpdateUserConfig(uc, func() {}) /*should have configuration.compressed = true*/ 470 fmt.Printf("Time to compress: %s", time.Since(before)) 471 fmt.Println() 472 compressionhelpers.Concurrently(uint64(vectors_size-switch_at), func(_, id uint64, _ *sync.Mutex) { 473 idx := switch_at + int(id) 474 index.Add(uint64(idx), vectors[idx]) 475 }) 476 fmt.Printf("Building the index took %s\n", time.Since(init)) 477 var relevant uint64 478 var retrieved int 479 480 var querying time.Duration = 0 481 compressionhelpers.Concurrently(uint64(len(queries)), func(_, i uint64, _ *sync.Mutex) { 482 before = time.Now() 483 results, _, _ := index.SearchByVector(queries[i], k, nil) 484 querying += time.Since(before) 485 retrieved += k 486 relevant += testinghelpers.MatchesInLists(truths[i], results) 487 }) 488 489 recall := float32(relevant) / float32(retrieved) 490 latency := float32(querying.Microseconds()) / float32(queries_size) 491 fmt.Println(recall, latency) 492 assert.True(t, recall > 0.9) 493 assert.True(t, latency < 100000) 494 } 495 } 496 } 497 498 func TestHnswPqDeepImage(t *testing.T) { 499 defer func(path string) { 500 err := os.RemoveAll(path) 501 if err != nil { 502 fmt.Println(err) 503 } 504 }(rootPath) 505 vectors_size := 9990000 506 queries_size := 1000 507 vectors := parseFromTxt("../diskAnn/testdata/deep-image/train.txt", vectors_size) 508 queries := parseFromTxt("../diskAnn/testdata/deep-image/test.txt", queries_size) 509 dimensions := 96 510 511 params := [][]int{ 512 {64, 64, 32}, 513 {128, 128, 64}, 514 {256, 256, 128}, 515 {512, 512, 256}, 516 } 517 switch_at := 1000000 518 519 fmt.Println("Sift1MPQKMeans 10K/10K") 520 before := time.Now() 521 k := 100 522 distancer := distancer.NewL2SquaredProvider() 523 truths := testinghelpers.BuildTruths(queries_size, vectors_size, queries, vectors, k, distanceWrapper(distancer), "../diskAnn/testdata/deep-image") 524 fmt.Printf("generating data took %s\n", time.Since(before)) 525 for segmentRate := 1; segmentRate < 4; segmentRate++ { 526 fmt.Println(segmentRate) 527 fmt.Println() 528 for i := 0; i < len(params); i++ { 529 efConstruction := params[i][0] 530 ef := params[i][1] 531 maxNeighbors := params[i][2] 532 533 uc := ent.UserConfig{ 534 MaxConnections: maxNeighbors, 535 EFConstruction: efConstruction, 536 EF: ef, 537 PQ: ent.PQConfig{ 538 Enabled: false, 539 Segments: dimensions / int(math.Pow(2, float64(segmentRate))), 540 Encoder: ent.PQEncoder{ 541 Type: ent.PQEncoderTypeKMeans, 542 Distribution: ent.PQEncoderDistributionNormal, 543 }, 544 }, 545 VectorCacheMaxObjects: 10e12, 546 } 547 index, _ := hnsw.New(hnsw.Config{ 548 RootPath: rootPath, 549 ID: "recallbenchmark", 550 MakeCommitLoggerThunk: hnsw.MakeNoopCommitLogger, 551 DistanceProvider: distancer, 552 VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) { 553 return vectors[int(id)], nil 554 }, 555 }, uc, newDummyStore(t)) 556 init := time.Now() 557 compressionhelpers.Concurrently(uint64(switch_at), func(_, id uint64, _ *sync.Mutex) { 558 index.Add(uint64(id), vectors[id]) 559 }) 560 before = time.Now() 561 uc.PQ.Enabled = true 562 index.UpdateUserConfig(uc, func() {}) 563 fmt.Printf("Time to compress: %s", time.Since(before)) 564 fmt.Println() 565 compressionhelpers.Concurrently(uint64(vectors_size-switch_at), func(_, id uint64, _ *sync.Mutex) { 566 idx := switch_at + int(id) 567 index.Add(uint64(idx), vectors[idx]) 568 }) 569 fmt.Printf("Building the index took %s\n", time.Since(init)) 570 var relevant uint64 571 var retrieved int 572 573 var querying time.Duration = 0 574 compressionhelpers.Concurrently(uint64(len(queries)), func(_, i uint64, _ *sync.Mutex) { 575 before = time.Now() 576 results, _, _ := index.SearchByVector(queries[i], k, nil) 577 querying += time.Since(before) 578 retrieved += k 579 relevant += testinghelpers.MatchesInLists(truths[i], results) 580 }) 581 582 recall := float32(relevant) / float32(retrieved) 583 latency := float32(querying.Microseconds()) / float32(queries_size) 584 fmt.Println(recall, latency) 585 assert.True(t, recall > 0.9) 586 assert.True(t, latency < 100000) 587 } 588 } 589 } 590 591 func parseFromTxt(file string, size int) [][]float32 { 592 content, _ := ioutil.ReadFile(file) 593 strContent := string(content) 594 testArray := strings.Split(strContent, "\n") 595 test := make([][]float32, 0, len(testArray)) 596 for j := 0; j < size; j++ { 597 elementArray := strings.Split(testArray[j], " ") 598 test = append(test, make([]float32, len(elementArray))) 599 for i := range elementArray { 600 f, _ := strconv.ParseFloat(elementArray[i], 16) 601 test[j][i] = float32(f) 602 } 603 } 604 return test 605 } 606 607 func newDummyStore(t *testing.T) *lsmkv.Store { 608 logger, _ := test.NewNullLogger() 609 storeDir := t.TempDir() 610 store, err := lsmkv.New(storeDir, storeDir, logger, nil, 611 cyclemanager.NewCallbackGroupNoop(), cyclemanager.NewCallbackGroupNoop()) 612 require.Nil(t, err) 613 return store 614 }