Пакет hnsw реализует иерархические судоходные малые мировые графики в Go. Вы можете прочитать о том, как они здесь работают. По сути, они допускают быстрые приблизительные поиски ближайших соседей с высокими данными вектора.
Этот пакет можно рассматривать как альтернативу в вашей любимой векторной базе данных (например, Pinecone, Weaviate). Он реализует только основные операции:
| Операция | Сложность | Описание |
|---|---|---|
| Вставлять | Вставить вектор в график | |
| Удалить | Удалить вектор с графика | |
| Поиск | Ищите ближайших соседей вектора | |
| Искать | Получить вектор по ID |
Примечание
Сложности приблизительны, где
go get github.com/coder/hnsw@main
g := hnsw . NewGraph [ int ]()
g . Add (
hnsw . MakeNode ( 1 , [] float32 { 1 , 1 , 1 }),
hnsw . MakeNode ( 2 , [] float32 { 1 , - 1 , 0.999 }),
hnsw . MakeNode ( 3 , [] float32 { 1 , 0 , - 0.5 }),
)
neighbors := g . Search (
[] float32 { 0.5 , 0.5 , 0.5 },
1 ,
)
fmt . Printf ( "best friend: %v n " , neighbors [ 0 ]. Vec )
// Output: best friend: [1 1 1] В то время как все графические операции находятся в памяти, hnsw предоставляет средства для загрузки/сбережения от постоянного хранилища.
Для интерфейса io.Reader / io.Writer используйте Graph.Export и Graph.Import .
Если вы используете один файл в качестве бэкэнда, HNSW предоставляет удобный тип SavedGraph :
path := "some.graph"
g1 , err := LoadSavedGraph [ int ]( path )
if err != nil {
panic ( err )
}
// Insert some vectors
for i := 0 ; i < 128 ; i ++ {
g1 . Add ( hnsw . MakeNode ( i , [] float32 { float32 ( i )}))
}
// Save to disk
err = g1 . Save ()
if err != nil {
panic ( err )
}
// Later...
// g2 is a copy of g1
g2 , err := LoadSavedGraph [ int ]( path )
if err != nil {
panic ( err )
}Смотрите больше:
Мы используем быстрое двоичное кодирование для графика, поэтому вы можете ожидать сохранения/загрузки почти на скорости диска. На моем M3 MacBook я получаю эти эталонные результаты:
goos: darwin
goarch: arm64
pkg: github.com/coder/hnsw
BenchmarkGraph_Import-16 4029 259927 ns/op 796.85 MB/s 496022 B/op 3212 allocs/op
BenchmarkGraph_Export-16 7042 168028 ns/op 1232.49 MB/s 239886 B/op 2388 allocs/op
PASS
ok github.com/coder/hnsw 2.624s
При сохранении/загрузке графика из 100 векторов с 256 измерениями.
В целом, наибольшим эффектом, который вы можете оказать на производительность графика, это уменьшение размерности ваших данных. В 1536 измерениях (OpenAI по умолчанию) 70% процесса запроса по параметрам по умолчанию тратится на функцию расстояния.
Если вы боретесь с медлительности / задержкой, подумайте:
И, если вы боретесь с избыточным использованием памяти, подумайте:
Graph.M (максимальное количество соседей, которые может иметь каждый узел)Graph.Ml (параметр генерации уровня) Похоже на накладные расходы на график:
где:
Вы можете сделать вывод, что:
В примере графика с 256 измерениями, и
И рост памяти в основном линейный.