Skip to content

Commit cf5309a

Browse files
authored
[Java] Bindings, tests and benchmarks for RMM pooled memory (#1453)
Exposes `cuvsRMMPoolMemoryResourceEnable`/`cuvsRMMMemoryResourceReset` via the Java API, with tests and benchmarks Authors: - Lorenzo Dematté (https://github.com/ldematte) - MithunR (https://github.com/mythrocks) Approvers: - MithunR (https://github.com/mythrocks) URL: #1453
1 parent a39637c commit cf5309a

10 files changed

Lines changed: 316 additions & 4 deletions

File tree

java/benchmarks/src/main/java/com/nvidia/cuvs/CuVSDeviceMatrixBenchmarks.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.openjdk.jmh.infra.Blackhole;
1010

1111
import java.util.Random;
12+
import java.util.concurrent.*;
1213

1314
@Fork(value = 1, warmups = 0)
1415
@Threads(1) // Sharing resources
@@ -22,6 +23,8 @@ public class CuVSDeviceMatrixBenchmarks {
2223
private int size;
2324

2425
private static final Random random = new Random();
26+
private static final int ALLOCATIONS = 10_000;
27+
private static final int NUM_THREADS = 10;
2528

2629
private float[][] data;
2730

@@ -92,4 +95,46 @@ public void matrixDeviceBuilder() throws Throwable {
9295
CuVSDeviceMatrix matrix = builder.build();
9396
matrix.close();
9497
}
98+
99+
@Benchmark
100+
@Threads(1)
101+
public void multipleDeviceAllocations() throws Throwable {
102+
try (CuVSResources resources = CuVSResources.create()) {
103+
for (int i = 0; i < ALLOCATIONS; ++i) {
104+
var builder = CuVSMatrix.deviceBuilder(resources, size, dims, CuVSMatrix.DataType.FLOAT);
105+
var matrix = builder.build();
106+
matrix.close();
107+
}
108+
}
109+
}
110+
111+
@Benchmark
112+
@Threads(1)
113+
public void multipleConcurrentDeviceAllocations() throws Throwable {
114+
final var allocationsPerThread = ALLOCATIONS / NUM_THREADS;
115+
Utils.runConcurrently(false, NUM_THREADS, () -> {
116+
try (CuVSResources resources = CuVSResources.create()) {
117+
for (int i = 0; i < allocationsPerThread; ++i) {
118+
var builder = CuVSMatrix.deviceBuilder(resources, size, dims, CuVSMatrix.DataType.FLOAT);
119+
var matrix = builder.build();
120+
matrix.close();
121+
}
122+
}
123+
});
124+
}
125+
126+
@Benchmark
127+
@Threads(1)
128+
public void multipleConcurrentDeviceAllocationsWithPooledMemory() throws Throwable {
129+
final var allocationsPerThread = ALLOCATIONS / NUM_THREADS;
130+
Utils.runConcurrently(true, NUM_THREADS, () -> {
131+
try (CuVSResources resources = CuVSResources.create()) {
132+
for (int i = 0; i < allocationsPerThread; ++i) {
133+
var builder = CuVSMatrix.deviceBuilder(resources, size, dims, CuVSMatrix.DataType.FLOAT);
134+
var matrix = builder.build();
135+
matrix.close();
136+
}
137+
}
138+
});
139+
}
95140
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.nvidia.cuvs;
7+
8+
import com.nvidia.cuvs.spi.CuVSProvider;
9+
10+
import java.util.concurrent.*;
11+
import java.util.function.Supplier;
12+
13+
class Utils {
14+
15+
@FunctionalInterface
16+
interface TestFunction {
17+
void apply() throws Throwable;
18+
}
19+
20+
static void runConcurrently(boolean usePooledMemory, int nThreads, TestFunction testFunction)
21+
throws ExecutionException, InterruptedException, TimeoutException {
22+
try (ExecutorService parallelExecutor = Executors.newFixedThreadPool(nThreads)) {
23+
if (usePooledMemory) {
24+
CuVSProvider.provider().enableRMMPooledMemory(10, 60);
25+
}
26+
var futures = new CompletableFuture[nThreads];
27+
for (int j = 0; j < nThreads; j++) {
28+
futures[j] = CompletableFuture.runAsync(() -> {
29+
try {
30+
testFunction.apply();
31+
} catch (Throwable e) {
32+
throw new RuntimeException(e);
33+
}
34+
}, parallelExecutor);
35+
}
36+
37+
CompletableFuture.allOf(futures)
38+
.exceptionally(Utils::fail)
39+
.get(2000, TimeUnit.SECONDS);
40+
} finally {
41+
if (usePooledMemory) {
42+
CuVSProvider.provider().resetRMMPooledMemory();
43+
}
44+
}
45+
}
46+
47+
static Throwable unwrap(Throwable t) {
48+
var root = t;
49+
while (root.getCause() != null) {
50+
root = root.getCause();
51+
}
52+
return root;
53+
}
54+
55+
private static Void fail(Throwable t) {
56+
throw new AssertionError("Exception while executing: " + unwrap(t));
57+
}
58+
}

java/cuvs-java/src/main/java/com/nvidia/cuvs/spi/CuVSProvider.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,29 @@ default CagraIndex mergeCagraIndexes(CagraIndex[] indexes, CagraMergeParams merg
157157

158158
java.util.logging.Level getLogLevel();
159159

160+
/**
161+
* Switch RMM allocations (used internally by various cuVS algorithms and by the default implementation of
162+
* {@link CuVSDeviceMatrix}) to use pooled memory.
163+
* This operation has a global effect, and will affect all resources on the current device.
164+
*
165+
* @param initialPoolSizePercent The initial pool size, in percentage of the total GPU memory
166+
* @param maxPoolSizePercent The maximum pool size, in percentage of the total GPU memory
167+
*/
168+
void enableRMMPooledMemory(int initialPoolSizePercent, int maxPoolSizePercent);
169+
170+
/**
171+
* Switch RMM allocations (used internally by various cuVS algorithms and by the default implementation of
172+
* {@link CuVSDeviceMatrix}) to use pooled memory.
173+
* This operation has a global effect, and will affect all resources on the current device.
174+
*
175+
* @param initialPoolSizePercent The initial pool size, in percentage of the total GPU memory
176+
* @param maxPoolSizePercent The maximum pool size, in percentage of the total GPU memory
177+
*/
178+
void enableRMMManagedPooledMemory(int initialPoolSizePercent, int maxPoolSizePercent);
179+
180+
/** Disables pooled memory on the current device, reverting back to the default setting. */
181+
void resetRMMPooledMemory();
182+
160183
/** Retrieves the system-wide provider. */
161184
static CuVSProvider provider() {
162185
return CuVSServiceProvider.Holder.INSTANCE;

java/cuvs-java/src/main/java/com/nvidia/cuvs/spi/UnsupportedProvider.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ public Level getLogLevel() {
100100
throw new UnsupportedOperationException(reasons);
101101
}
102102

103+
@Override
104+
public void enableRMMPooledMemory(int initialPoolSizePercent, int maxPoolSizePercent) {
105+
throw new UnsupportedOperationException(reasons);
106+
}
107+
108+
@Override
109+
public void enableRMMManagedPooledMemory(int initialPoolSizePercent, int maxPoolSizePercent) {
110+
throw new UnsupportedOperationException(reasons);
111+
}
112+
113+
@Override
114+
public void resetRMMPooledMemory() {
115+
throw new UnsupportedOperationException(reasons);
116+
}
117+
103118
@Override
104119
public CuVSMatrix.Builder<CuVSDeviceMatrix> newDeviceMatrixBuilder(
105120
CuVSResources cuVSResources,

java/cuvs-java/src/main/java22/com/nvidia/cuvs/spi/JDKProvider.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ public String toString() {
135135
}
136136
}
137137

138+
private final cuvsRMMMemoryResourceReset cuvsRMMMemoryResourceResetInvoker =
139+
cuvsRMMMemoryResourceReset.makeInvoker();
140+
138141
private final cuvsGetLogLevel GET_LOG_LEVEL_INVOKER = cuvsGetLogLevel.makeInvoker();
139142

140143
private JDKProvider() {}
@@ -427,6 +430,25 @@ public Level getLogLevel() {
427430
throw new IllegalArgumentException("Unexpected log level [" + logLevel + "]");
428431
}
429432

433+
@Override
434+
public void enableRMMPooledMemory(int initialPoolSizePercent, int maxPoolSizePercent) {
435+
checkCuVSError(
436+
cuvsRMMPoolMemoryResourceEnable(initialPoolSizePercent, maxPoolSizePercent, false),
437+
"cuvsRMMPoolMemoryResourceEnable");
438+
}
439+
440+
@Override
441+
public void enableRMMManagedPooledMemory(int initialPoolSizePercent, int maxPoolSizePercent) {
442+
checkCuVSError(
443+
cuvsRMMPoolMemoryResourceEnable(initialPoolSizePercent, maxPoolSizePercent, true),
444+
"cuvsRMMPoolMemoryResourceEnable");
445+
}
446+
447+
@Override
448+
public void resetRMMPooledMemory() {
449+
checkCuVSError(cuvsRMMMemoryResourceResetInvoker.apply(), "cuvsRMMMemoryResourceReset");
450+
}
451+
430452
@Override
431453
public CuVSMatrix.Builder<CuVSHostMatrix> newHostMatrixBuilder(
432454
long size, long columns, CuVSMatrix.DataType dataType) {

java/cuvs-java/src/test/java/com/nvidia/cuvs/BruteForceAndSearchIT.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import static com.carrotsearch.randomizedtesting.RandomizedTest.assumeTrue;
88

9+
import com.nvidia.cuvs.spi.CuVSProvider;
910
import java.io.*;
1011
import java.nio.file.Files;
1112
import java.nio.file.Path;
@@ -14,6 +15,7 @@
1415
import java.util.Map;
1516
import java.util.UUID;
1617
import java.util.function.LongToIntFunction;
18+
import org.junit.After;
1719
import org.junit.Before;
1820
import org.junit.Test;
1921

@@ -22,6 +24,12 @@ public class BruteForceAndSearchIT extends CuVSTestCase {
2224
@Before
2325
public void setup() {
2426
assumeTrue("not supported on " + System.getProperty("os.name"), isLinuxAmd64());
27+
CuVSProvider.provider().enableRMMPooledMemory(10, 60);
28+
}
29+
30+
@After
31+
public void cleanup() {
32+
CuVSProvider.provider().resetRMMPooledMemory();
2533
}
2634

2735
// Sample data and query

java/cuvs-java/src/test/java/com/nvidia/cuvs/BruteForceRandomizedIT.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import static com.carrotsearch.randomizedtesting.RandomizedTest.assumeTrue;
88

99
import com.carrotsearch.randomizedtesting.RandomizedRunner;
10+
import com.nvidia.cuvs.spi.CuVSProvider;
1011
import java.lang.invoke.MethodHandles;
1112
import java.util.BitSet;
1213
import java.util.List;
14+
import org.junit.After;
1315
import org.junit.Before;
1416
import org.junit.Test;
1517
import org.junit.runner.RunWith;
@@ -26,6 +28,12 @@ public void setup() {
2628
assumeTrue(isLinuxAmd64());
2729
initializeRandom();
2830
log.trace("Random context initialized for test.");
31+
CuVSProvider.provider().enableRMMPooledMemory(10, 60);
32+
}
33+
34+
@After
35+
public void cleanup() {
36+
CuVSProvider.provider().resetRMMPooledMemory();
2937
}
3038

3139
@Test

0 commit comments

Comments
 (0)