Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions c/include/cuvs/core/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,33 @@ typedef uintptr_t cuvsResources_t;
*/
CUVS_EXPORT cuvsError_t cuvsResourcesCreate(cuvsResources_t* res);

/**
* @brief Create an opaque C handle for C++ type `raft::resources` whose memory
* allocations are tracked and written as CSV samples from a background
* thread.
*
* The returned handle wraps all reachable memory resources (host, pinned,
* managed, device, workspace, large_workspace) with allocation-tracking
* adaptors and replaces the global host and device memory resources for the
* lifetime of the handle. It is otherwise indistinguishable from a handle
* created by ::cuvsResourcesCreate and can be used wherever a
* ::cuvsResources_t is accepted. The CSV reporter is stopped and the global
* memory resources are restored when the handle is destroyed via
* ::cuvsResourcesDestroy.
*
* @param[out] res cuvsResources_t opaque C handle
* @param[in] csv_path Path to the output CSV file
* (created/truncated). Must be a non-empty,
* null-terminated UTF-8 string.
* @param[in] sample_interval_ms Minimum time in milliseconds between
* successive CSV samples. Pass 10 to match the
* C++ default.
* @return cuvsError_t
*/
CUVS_EXPORT cuvsError_t cuvsResourcesCreateWithMemoryTracking(cuvsResources_t* res,
const char* csv_path,
int64_t sample_interval_ms);

/**
* @brief Destroy and de-allocate opaque C handle for C++ type `raft::resources`
*
Expand Down
21 changes: 21 additions & 0 deletions c/src/core/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cuvs/version_config.h>

#include <raft/core/device_resources_snmg.hpp>
#include <raft/core/memory_tracking_resources.hpp>
#include <raft/core/resource/cuda_stream.hpp>
#include <raft/core/resource/device_id.hpp>
#include <raft/core/resource/resource_types.hpp>
Expand All @@ -23,8 +24,11 @@

#include "../core/exceptions.hpp"

#include <chrono>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
#include <thread>

extern "C" cuvsError_t cuvsResourcesCreate(cuvsResources_t* res)
Expand All @@ -35,6 +39,23 @@ extern "C" cuvsError_t cuvsResourcesCreate(cuvsResources_t* res)
});
}

extern "C" cuvsError_t cuvsResourcesCreateWithMemoryTracking(cuvsResources_t* res,
const char* csv_path,
int64_t sample_interval_ms)
{
return cuvs::core::translate_exceptions([=] {
if (csv_path == nullptr || csv_path[0] == '\0') {
throw std::invalid_argument("csv_path must be a non-empty string");
}
if (sample_interval_ms < 0) {
throw std::invalid_argument("sample_interval_ms must be >= 0");
}
auto res_ptr = new raft::memory_tracking_resources{
std::string{csv_path}, std::chrono::milliseconds{sample_interval_ms}};
*res = reinterpret_cast<uintptr_t>(res_ptr);
});
}
Comment thread
achirkin marked this conversation as resolved.

extern "C" cuvsError_t cuvsResourcesDestroy(cuvsResources_t res)
{
return cuvs::core::translate_exceptions([=] {
Expand Down
83 changes: 83 additions & 0 deletions fern/pages/api_basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Memory management](#memory-management)
- [Resource management](#resource-management)
- [Memory tracking](#memory-tracking)

## Memory management

Expand Down Expand Up @@ -78,3 +79,85 @@ res = pylibraft.common.DeviceResources()
```rust
let res = cuvs::Resources::new()?;
```

## Memory tracking

A resources handle whose memory allocations are tracked and written as CSV samples from a background thread can be created in any of the supported languages. The handle wraps all reachable memory resources (host, pinned, managed, device, workspace, large_workspace) with allocation-tracking adaptors and replaces the global host and device memory resources for the lifetime of the handle. It is otherwise indistinguishable from a regular resources handle and can be passed to every cuVS API that accepts one. The CSV reporter is stopped and the global memory resources are restored when the handle is destroyed.

<Note>

- The handle replaces the **global** host and device memory resources while it is alive. Do not create multiple tracking handles concurrently and make sure the handle outlives every consumer (matrices, indexes, search results, ...) that allocates memory through cuVS.
- The CSV file is flushed eagerly: the header is flushed on construction and every sample row is flushed as soon as it is written, so the file can be tailed while the handle is alive. Destroying the handle stops the background sampler and writes one final row.
- The sample interval is a *minimum* time between samples. The background thread blocks until an allocation/deallocation occurs, then sleeps for at least `sample_interval` before writing the next row; quiescent periods do not produce extra rows.

</Note>

### C

```c
#include <cuda_runtime.h>
#include <cuvs/core/c_api.h>

cuvsResources_t res;
// 10 ms sampling matches the C++ default.
cuvsResourcesCreateWithMemoryTracking(&res, "/tmp/allocations.csv", 10);

// ... do some processing ...

cuvsResourcesDestroy(res);
```

### C++

```c++
#include <raft/core/memory_tracking_resources.hpp>

// Sample interval defaults to std::chrono::milliseconds{10}.
raft::memory_tracking_resources res{"/tmp/allocations.csv"};

// ... do some processing ...
// `res` is implicitly convertible to raft::resources& and can be passed
// to any cuVS / raft API that accepts a resources handle.
```

### Python

```python
from cuvs.common import Resources

res = Resources(
memory_tracking_csv_path="/tmp/allocations.csv",
memory_tracking_sample_interval_ms=10,
)

# ... do some processing ...

del res # flushes the CSV and restores the global memory resources
```

### Java

```java
import com.nvidia.cuvs.CuVSResources;
import com.nvidia.cuvs.spi.CuVSProvider;
import java.nio.file.Path;
import java.time.Duration;

try (var res = CuVSResources.create(
CuVSProvider.tempDirectory(),
Path.of("/tmp/allocations.csv"),
Duration.ofMillis(10))) {
// ... do some processing ...
}
```

### Rust

```rust
use std::time::Duration;

let res = cuvs::Resources::with_memory_tracking(
"/tmp/allocations.csv",
Some(Duration::from_millis(10)),
)?;
```
33 changes: 32 additions & 1 deletion java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSResources.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs;

import com.nvidia.cuvs.spi.CuVSProvider;
import java.nio.file.Path;
import java.time.Duration;

/**
* Used for allocating resources for cuVS
Expand Down Expand Up @@ -78,4 +79,34 @@ static CuVSResources create() throws Throwable {
static CuVSResources create(Path tempDirectory) throws Throwable {
return CuVSProvider.provider().newCuVSResources(tempDirectory);
}

/**
* Creates a new resources whose memory allocations are tracked and written as
* CSV samples from a background thread.
* <p>
* The returned handle wraps all reachable memory resources (host, pinned,
* managed, device, workspace, large_workspace) with allocation-tracking
* adaptors and replaces the global host and device memory resources for the
* lifetime of the handle. It is otherwise indistinguishable from a handle
* created by {@link #create(Path)} and can be used wherever a
* {@link CuVSResources} is accepted. The CSV reporter is stopped and the
* global memory resources are restored when the handle is closed.
*
* @param tempDirectory the temporary directory to use for
* intermediate operations
* @param memoryTrackingCsvPath path to the output CSV file
* (created/truncated)
* @param memoryTrackingSampleInterval minimum interval between successive
* CSV samples
* @throws UnsupportedOperationException if the provider does not support cuvs
* @throws LibraryException if the native library cannot be loaded
*/
static CuVSResources create(
Path tempDirectory,
Path memoryTrackingCsvPath,
Duration memoryTrackingSampleInterval) throws Throwable {
return CuVSProvider.provider()
.newCuVSResources(
tempDirectory, memoryTrackingCsvPath, memoryTrackingSampleInterval);
}
}
26 changes: 26 additions & 0 deletions java/cuvs-java/src/main/java/com/nvidia/cuvs/spi/CuVSProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.time.Duration;

/**
* A provider of low-level cuvs resources and builders.
Expand Down Expand Up @@ -35,6 +36,31 @@ default Path nativeLibraryPath() {
/** Creates a new CuVSResources. */
CuVSResources newCuVSResources(Path tempDirectory) throws Throwable;

/**
* Creates a new CuVSResources whose memory allocations are tracked and
* written as CSV samples from a background thread.
*
* <p>This method is declared as a {@code default} method so that adding it
* does not break binary compatibility with providers compiled against an
* earlier version of this interface; the default implementation throws
* {@link UnsupportedOperationException} and providers must override it to
* opt in.
*
* @param tempDirectory the temporary directory to use for
* intermediate operations
* @param memoryTrackingCsvPath path to the output CSV file
* (created/truncated)
* @param memoryTrackingSampleInterval minimum interval between successive
* CSV samples
*/
default CuVSResources newCuVSResources(
Path tempDirectory,
Path memoryTrackingCsvPath,
Duration memoryTrackingSampleInterval) throws Throwable {
throw new UnsupportedOperationException(
"Memory-tracking resources are not supported by this provider");
}

/** Create a {@link CuVSMatrix.Builder} instance for a host memory matrix **/
CuVSMatrix.Builder<CuVSHostMatrix> newHostMatrixBuilder(
long size, long dimensions, CuVSMatrix.DataType dataType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.nvidia.cuvs.*;
import java.lang.invoke.MethodHandle;
import java.nio.file.Path;
import java.time.Duration;
import java.util.logging.Level;

/**
Expand All @@ -25,6 +26,14 @@ public CuVSResources newCuVSResources(Path tempDirectory) {
throw new UnsupportedOperationException(reasons);
}

@Override
public CuVSResources newCuVSResources(
Path tempDirectory,
Path memoryTrackingCsvPath,
Duration memoryTrackingSampleInterval) {
throw new UnsupportedOperationException(reasons);
}

@Override
public BruteForceIndex.Builder newBruteForceIndexBuilder(CuVSResources cuVSResources) {
throw new UnsupportedOperationException(reasons);
Expand All @@ -47,8 +56,8 @@ public HnswIndex hnswIndexFromCagra(HnswIndexParams hnswParams, CagraIndex cagra
}

@Override
public HnswIndex hnswIndexBuild(CuVSResources resources, HnswIndexParams hnswParams, CuVSMatrix dataset)
throws Throwable {
public HnswIndex hnswIndexBuild(
CuVSResources resources, HnswIndexParams hnswParams, CuVSMatrix dataset) throws Throwable {
throw new UnsupportedOperationException(reasons);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.internal;
Expand All @@ -13,7 +13,10 @@
import com.nvidia.cuvs.internal.common.PinnedMemoryBuffer;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Duration;

/**
* Used for allocating resources for cuVS
Expand Down Expand Up @@ -46,6 +49,50 @@ public CuVSResourcesImpl(Path tempDirectory) {
}
}

/**
* Constructor that allocates a tracking resources handle. All memory
* allocations made through this handle are written as CSV samples to
Comment thread
achirkin marked this conversation as resolved.
* {@code memoryTrackingCsvPath} from a background thread, restoring the
* global memory resources on {@link #close()}.
*
* <p>Note: the ~8MB pinned host buffer backing this handle is allocated via a
* raw {@code cudaMallocHost} (see {@code PinnedMemoryBuffer.createPinnedBuffer()})
* outside the tracking infrastructure, so it is not reflected in the CSV
* samples.
*
* @param tempDirectory the temporary directory to use for
* intermediate operations
* @param memoryTrackingCsvPath path to the output CSV file
* (created/truncated)
* @param memoryTrackingSampleInterval minimum interval between successive
* CSV samples
*/
public CuVSResourcesImpl(
Path tempDirectory,
Path memoryTrackingCsvPath,
Duration memoryTrackingSampleInterval) {
this.tempDirectory = tempDirectory;
try (var localArena = Arena.ofConfined()) {
var resourcesMemorySegment = localArena.allocate(cuvsResources_t);
byte[] pathBytes =
memoryTrackingCsvPath.toString().getBytes(StandardCharsets.UTF_8);
var pathSegment = localArena.allocate(pathBytes.length + 1L);
MemorySegment.copy(
pathBytes, 0, pathSegment, ValueLayout.JAVA_BYTE, 0, pathBytes.length);
pathSegment.set(ValueLayout.JAVA_BYTE, pathBytes.length, (byte) 0);
long sampleIntervalMs = memoryTrackingSampleInterval.toMillis();
checkCuVSError(
cuvsResourcesCreateWithMemoryTracking(
resourcesMemorySegment, pathSegment, sampleIntervalMs),
"cuvsResourcesCreateWithMemoryTracking");
this.resourceHandle = resourcesMemorySegment.get(cuvsResources_t, 0);
var deviceIdPtr = localArena.allocate(C_INT);
checkCuVSError(cuvsDeviceIdGet(resourceHandle, deviceIdPtr), "cuvsDeviceIdGet");
this.deviceId = deviceIdPtr.get(C_INT, 0);
this.access = new ScopedAccessWithHostBuffer(resourceHandle, hostBuffer.address());
}
}

@Override
public ScopedAccess access() {
return this.access;
Expand Down
Loading
Loading