O pacote hnsw implementa os gráficos mundiais pequenos navegáveis hierárquicos em Go. Você pode ler sobre como eles funcionam aqui. Em essência, eles permitem pesquisas de vizinhas mais próximas mais próximas com dados vetoriais de alta dimensão.
Este pacote pode ser pensado como uma alternativa na memória ao seu banco de dados vetorial favorito (por exemplo, Pinecone, Weaviate). Ele implementa apenas as operações essenciais:
| Operação | Complexidade | Descrição |
|---|---|---|
| Inserir | Insira um vetor no gráfico | |
| Excluir | Exclua um vetor do gráfico | |
| Procurar | Procure os vizinhos mais próximos de um vetor | |
| Pesquisa | Recuperar um vetor por id |
Observação
Complexidades são aproximadas onde
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] Embora todas as operações gráficas estejam na memória, hnsw fornece instalações para carregar/salvar a partir de armazenamento persistente.
Para uma interface io.Reader / io.Writer , use Graph.Export e Graph.Import .
Se você estiver usando um único arquivo como back -end, o HNSW fornece um tipo de SavedGraph conveniente:
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 )
}Veja mais:
Usamos uma codificação binária rápida para o gráfico, para que você possa salvar/carregar quase na velocidade do disco. No meu M3 MacBook, recebo esses resultados de referência:
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
Ao salvar/carregar um gráfico de 100 vetores com 256 dimensões.
Em geral, o maior efeito que você pode ter no desempenho do gráfico está reduzindo a dimensionalidade dos seus dados. Nas dimensões de 1536 (inadimplência do OpenAI), 70% do processo de consulta sob parâmetros padrão é gasto na função de distância.
Se você está lutando com a lentidão / latência, considere:
E, se você está lutando com o excesso de uso da memória, considere:
Graph.M (o número máximo de vizinhos que cada nó pode ter)Graph.Ml (o parâmetro de geração de nível) A sobrecarga de memória de um gráfico parece:
onde:
Você pode deduzir isso:
No exemplo de um gráfico com 256 dimensões e
e o crescimento da memória é principalmente linear.