99
1010import com .nvidia .cuvs .CagraIndex ;
1111import com .nvidia .cuvs .CagraIndexParams ;
12- import com .nvidia .cuvs .Dataset ;
12+ import com .nvidia .cuvs .CuVSMatrix ;
1313
1414import org .apache .lucene .codecs .CodecUtil ;
1515import org .apache .lucene .codecs .KnnFieldVectorsWriter ;
3535import org .apache .lucene .util .hnsw .HnswGraph ;
3636import org .apache .lucene .util .hnsw .HnswGraph .NodesIterator ;
3737import org .apache .lucene .util .packed .DirectMonotonicWriter ;
38- import org .elasticsearch .common .lucene .store .IndexOutputOutputStream ;
3938import org .elasticsearch .core .IOUtils ;
4039import org .elasticsearch .core .SuppressForbidden ;
4140import org .elasticsearch .logging .LogManager ;
@@ -177,21 +176,21 @@ public long ramBytesUsed() {
177176 }
178177
179178 private static final class DatasetOrVectors {
180- private final Dataset dataset ;
179+ private final CuVSMatrix dataset ;
181180 private final float [][] vectors ;
182181
183182 static DatasetOrVectors fromArray (float [][] vectors ) {
184183 return new DatasetOrVectors (
185- vectors .length < MIN_NUM_VECTORS_FOR_GPU_BUILD ? null : Dataset .ofArray (vectors ),
184+ vectors .length < MIN_NUM_VECTORS_FOR_GPU_BUILD ? null : CuVSMatrix .ofArray (vectors ),
186185 vectors .length < MIN_NUM_VECTORS_FOR_GPU_BUILD ? vectors : null
187186 );
188187 }
189188
190- static DatasetOrVectors fromDataset (Dataset dataset ) {
189+ static DatasetOrVectors fromDataset (CuVSMatrix dataset ) {
191190 return new DatasetOrVectors (dataset , null );
192191 }
193192
194- private DatasetOrVectors (Dataset dataset , float [][] vectors ) {
193+ private DatasetOrVectors (CuVSMatrix dataset , float [][] vectors ) {
195194 this .dataset = dataset ;
196195 this .vectors = vectors ;
197196 validateState ();
@@ -204,10 +203,10 @@ private void validateState() {
204203 }
205204
206205 int size () {
207- return dataset != null ? dataset .size () : vectors .length ;
206+ return dataset != null ? ( int ) dataset .size () : vectors .length ;
208207 }
209208
210- Dataset getDataset () {
209+ CuVSMatrix getDataset () {
211210 return dataset ;
212211 }
213212
@@ -243,9 +242,16 @@ private void writeFieldInternal(FieldInfo fieldInfo, DatasetOrVectors datasetOrV
243242 }
244243 mockGraph = writeGraph (vectors , graphLevelNodeOffsets );
245244 } else {
246- String tempCagraHNSWFileName = buildGPUIndex (fieldInfo .getVectorSimilarityFunction (), datasetOrVectors .dataset );
247- assert tempCagraHNSWFileName != null : "GPU index should be built for field: " + fieldInfo .name ;
248- mockGraph = writeGraph (tempCagraHNSWFileName , graphLevelNodeOffsets );
245+ var dataset = datasetOrVectors .dataset ;
246+ var cuVSResources = cuVSResourceManager .acquire ((int ) dataset .size (), (int ) dataset .columns ());
247+ try {
248+ try (var index = buildGPUIndex (cuVSResources , fieldInfo .getVectorSimilarityFunction (), dataset )) {
249+ assert index != null : "GPU index should be built for field: " + fieldInfo .name ;
250+ mockGraph = writeGraph (index .getGraph (), graphLevelNodeOffsets );
251+ }
252+ } finally {
253+ cuVSResourceManager .release (cuVSResources );
254+ }
249255 }
250256 long vectorIndexLength = vectorIndex .getFilePointer () - vectorIndexOffset ;
251257 writeMeta (fieldInfo , vectorIndexOffset , vectorIndexLength , datasetOrVectors .size (), mockGraph , graphLevelNodeOffsets );
@@ -256,8 +262,11 @@ private void writeFieldInternal(FieldInfo fieldInfo, DatasetOrVectors datasetOrV
256262 }
257263 }
258264
259- @ SuppressForbidden (reason = "require usage of Lucene's IOUtils#deleteFilesIgnoringExceptions(...)" )
260- private String buildGPUIndex (VectorSimilarityFunction similarityFunction , Dataset dataset ) throws Throwable {
265+ private CagraIndex buildGPUIndex (
266+ CuVSResourceManager .ManagedCuVSResources cuVSResources ,
267+ VectorSimilarityFunction similarityFunction ,
268+ CuVSMatrix dataset
269+ ) throws Throwable {
261270 CagraIndexParams .CuvsDistanceType distanceType = switch (similarityFunction ) {
262271 case EUCLIDEAN -> CagraIndexParams .CuvsDistanceType .L2Expanded ;
263272 case DOT_PRODUCT , MAXIMUM_INNER_PRODUCT -> CagraIndexParams .CuvsDistanceType .InnerProduct ;
@@ -271,134 +280,50 @@ private String buildGPUIndex(VectorSimilarityFunction similarityFunction, Datase
271280 .withMetric (distanceType )
272281 .build ();
273282
274- var cuVSResources = cuVSResourceManager .acquire (dataset .size (), dataset .dimensions ());
275- try {
276- long startTime = System .nanoTime ();
277- var indexBuilder = CagraIndex .newBuilder (cuVSResources ).withDataset (dataset ).withIndexParams (params );
278- var index = indexBuilder .build ();
279- cuVSResourceManager .finishedComputation (cuVSResources );
280- if (logger .isDebugEnabled ()) {
281- logger .debug (
282- "Carga index created in: {} ms; #num vectors: {}" ,
283- (System .nanoTime () - startTime ) / 1_000_000.0 ,
284- dataset .size ()
285- );
286- }
287-
288- // TODO: do serialization through MemorySegment instead of a temp file
289- // serialize index for CPU consumption to the hnwslib format
290- startTime = System .nanoTime ();
291- IndexOutput tempCagraHNSW = null ;
292- boolean success = false ;
293- try {
294- tempCagraHNSW = segmentWriteState .directory .createTempOutput (
295- vectorIndex .getName (),
296- "cagra_hnws_temp" ,
297- segmentWriteState .context
298- );
299- var tempCagraHNSWOutputStream = new IndexOutputOutputStream (tempCagraHNSW );
300- index .serializeToHNSW (tempCagraHNSWOutputStream );
301- if (logger .isDebugEnabled ()) {
302- logger .debug ("Carga index serialized to hnswlib format in: {} ms" , (System .nanoTime () - startTime ) / 1_000_000.0 );
303- }
304- success = true ;
305- } finally {
306- index .destroyIndex ();
307- if (success ) {
308- org .elasticsearch .core .IOUtils .close (tempCagraHNSW );
309- } else {
310- if (tempCagraHNSW != null ) {
311- IOUtils .closeWhileHandlingException (tempCagraHNSW );
312- org .apache .lucene .util .IOUtils .deleteFilesIgnoringExceptions (segmentWriteState .directory , tempCagraHNSW .getName ());
313- }
314- }
315- }
316- return tempCagraHNSW .getName ();
317- } finally {
318- cuVSResourceManager .release (cuVSResources );
283+ long startTime = System .nanoTime ();
284+ var indexBuilder = CagraIndex .newBuilder (cuVSResources ).withDataset (dataset ).withIndexParams (params );
285+ var index = indexBuilder .build ();
286+ cuVSResourceManager .finishedComputation (cuVSResources );
287+ if (logger .isDebugEnabled ()) {
288+ logger .debug ("Carga index created in: {} ms; #num vectors: {}" , (System .nanoTime () - startTime ) / 1_000_000.0 , dataset .size ());
319289 }
290+ return index ;
320291 }
321292
322- @ SuppressForbidden (reason = "require usage of Lucene's IOUtils#deleteFilesIgnoringExceptions(...)" )
323- private HnswGraph writeGraph (String tempCagraHNSWFileName , int [][] levelNodeOffsets ) throws IOException {
293+ private HnswGraph writeGraph (CuVSMatrix cagraGraph , int [][] levelNodeOffsets ) throws IOException {
324294 long startTime = System .nanoTime ();
325- boolean success = false ;
326- IndexInput tempCagraHNSWInput = null ;
327- int maxElementCount ;
328- int maxGraphDegree ;
329295
330- try {
331- tempCagraHNSWInput = segmentWriteState .directory .openInput (tempCagraHNSWFileName , segmentWriteState .context );
332- // read the metadata from the hnlswlib format;
333- // some of them are not used in the Lucene HNSW format
334- tempCagraHNSWInput .readLong (); // offSetLevel0
335- maxElementCount = (int ) tempCagraHNSWInput .readLong ();
336- tempCagraHNSWInput .readLong (); // currElementCount
337- tempCagraHNSWInput .readLong (); // sizeDataPerElement
338- long labelOffset = tempCagraHNSWInput .readLong ();
339- long dataOffset = tempCagraHNSWInput .readLong ();
340- int maxLevel = tempCagraHNSWInput .readInt ();
341- tempCagraHNSWInput .readInt (); // entryPointNode
342- tempCagraHNSWInput .readLong (); // maxM
343- long maxM0 = tempCagraHNSWInput .readLong (); // number of graph connections
344- tempCagraHNSWInput .readLong (); // M
345- tempCagraHNSWInput .readLong (); // mult
346- tempCagraHNSWInput .readLong (); // efConstruction
347-
348- assert (maxLevel == 1 ) : "Cagra index is flat, maxLevel must be: 1, got: " + maxLevel ;
349- maxGraphDegree = (int ) maxM0 ;
350- int [] neighbors = new int [maxGraphDegree ];
351- int dimension = (int ) ((labelOffset - dataOffset ) / Float .BYTES );
352- // assert (dimension == dimensionCalculated)
353- // : "Cagra index vector dimension must be: " + dimension + ", got: " + dimensionCalculated;
354-
355- levelNodeOffsets [0 ] = new int [maxElementCount ];
356-
357- // read graph from the cagra_hnswlib index and write it to the Lucene vectorIndex file
358- int [] scratch = new int [maxGraphDegree ];
359- for (int node = 0 ; node < maxElementCount ; node ++) {
360- // read from the cagra_hnswlib index
361- int nodeDegree = tempCagraHNSWInput .readInt ();
362- assert (nodeDegree == maxGraphDegree )
363- : "In Cagra graph all nodes must have the same number of connections : " + maxGraphDegree + ", got" + nodeDegree ;
364- for (int i = 0 ; i < nodeDegree ; i ++) {
365- neighbors [i ] = tempCagraHNSWInput .readInt ();
366- }
367- // Skip over the vector data
368- tempCagraHNSWInput .seek (tempCagraHNSWInput .getFilePointer () + dimension * Float .BYTES );
369- // Skip over the label/id
370- tempCagraHNSWInput .seek (tempCagraHNSWInput .getFilePointer () + Long .BYTES );
371-
372- // write to the Lucene vectorIndex file
373- long offsetStart = vectorIndex .getFilePointer ();
374- Arrays .sort (neighbors );
375- int actualSize = 0 ;
376- scratch [actualSize ++] = neighbors [0 ];
377- for (int i = 1 ; i < nodeDegree ; i ++) {
378- assert neighbors [i ] < maxElementCount : "node too large: " + neighbors [i ] + ">=" + maxElementCount ;
379- if (neighbors [i - 1 ] == neighbors [i ]) {
380- continue ;
381- }
382- scratch [actualSize ++] = neighbors [i ] - neighbors [i - 1 ];
383- }
384- // Write the size after duplicates are removed
385- vectorIndex .writeVInt (actualSize );
386- for (int i = 0 ; i < actualSize ; i ++) {
387- vectorIndex .writeVInt (scratch [i ]);
296+ int maxElementCount = (int ) cagraGraph .size ();
297+ int maxGraphDegree = (int ) cagraGraph .columns ();
298+ int [] neighbors = new int [maxGraphDegree ];
299+
300+ levelNodeOffsets [0 ] = new int [maxElementCount ];
301+ // write the cagra graph to the Lucene vectorIndex file
302+ int [] scratch = new int [maxGraphDegree ];
303+ for (int node = 0 ; node < maxElementCount ; node ++) {
304+ cagraGraph .getRow (node ).toArray (neighbors );
305+
306+ // write to the Lucene vectorIndex file
307+ long offsetStart = vectorIndex .getFilePointer ();
308+ Arrays .sort (neighbors );
309+ int actualSize = 0 ;
310+ scratch [actualSize ++] = neighbors [0 ];
311+ for (int i = 1 ; i < maxGraphDegree ; i ++) {
312+ assert neighbors [i ] < maxElementCount : "node too large: " + neighbors [i ] + ">=" + maxElementCount ;
313+ if (neighbors [i - 1 ] == neighbors [i ]) {
314+ continue ;
388315 }
389- levelNodeOffsets [0 ][node ] = Math .toIntExact (vectorIndex .getFilePointer () - offsetStart );
390- }
391- if (logger .isDebugEnabled ()) {
392- logger .debug ("cagra_hnws index serialized to Lucene HNSW in: {} ms" , (System .nanoTime () - startTime ) / 1_000_000.0 );
316+ scratch [actualSize ++] = neighbors [i ] - neighbors [i - 1 ];
393317 }
394- success = true ;
395- } finally {
396- if (success ) {
397- IOUtils .close (tempCagraHNSWInput );
398- } else {
399- IOUtils .closeWhileHandlingException (tempCagraHNSWInput );
318+ // Write the size after duplicates are removed
319+ vectorIndex .writeVInt (actualSize );
320+ for (int i = 0 ; i < actualSize ; i ++) {
321+ vectorIndex .writeVInt (scratch [i ]);
400322 }
401- org .apache .lucene .util .IOUtils .deleteFilesIgnoringExceptions (segmentWriteState .directory , tempCagraHNSWFileName );
323+ levelNodeOffsets [0 ][node ] = Math .toIntExact (vectorIndex .getFilePointer () - offsetStart );
324+ }
325+ if (logger .isDebugEnabled ()) {
326+ logger .debug ("cagra_hnws index serialized to Lucene HNSW in: {} ms" , (System .nanoTime () - startTime ) / 1_000_000.0 );
402327 }
403328 return createMockGraph (maxElementCount , maxGraphDegree );
404329 }
0 commit comments