nvml-wrapper
A safe, ergonomic Rust wrapper around NVIDIA's Management Library (NVML) for querying and controlling NVIDIA GPUs.
Repository Health
Technical Analysis
nvml-wrapper gives Rust programs typed, safe access to the NVIDIA Management Library (NVML), the C library that also powers nvidia-smi. Instead of hand-rolling FFI calls, applications get a Nvml handle and typed Device objects exposing GPU temperature, clock speeds, power limits, memory usage, PCIe link state, fan speed, and dozens of other metrics as regular Rust method calls that return Result<T, NvmlError>.
The crate loads the NVML shared library dynamically at runtime via libloading rather than linking against it at compile time. This means a binary built with nvml-wrapper still runs on machines without an NVIDIA GPU or driver installed — Nvml::init() simply returns an error instead of the whole program failing to start, which matters for tools that want to optionally report GPU stats depending on the host.
Under the hood the workspace is split into nvml-wrapper-sys, which holds the raw generated FFI bindings to the NVML C headers, and nvml-wrapper itself, which wraps those raw calls in safe, documented, idiomatic Rust types — enums for device states and clock domains, bitflags for capability masks, and structured errors via thiserror. An optional legacy-functions feature exposes older NVML function-version symbols for callers who need to target function versions NVIDIA has since superseded, and an optional serde feature derives Serialize/Deserialize for the wrapped data structures.
What You Get
- A safe
Nvmlentry point that dynamically loads the NVML library and typedDevicehandles for querying and controlling GPUs - Coverage of most of the NVML surface: temperature, fan speed, clocks, power limits, memory info, PCIe link generation/width, encoder/decoder utilization, and more
- An
NvmlErrorenum (built onthiserror) that distinguishes missing-library, missing-symbol, and per-call NVML failure modes instead of a single opaque error type - Linux event-loop support (
high_level::EventLoop) for subscribing to GPU state-change events like XID errors or clock changes - Optional
legacy-functionsfeature for calling older, superseded NVML function versions when newer ones aren’t available - Optional
serdefeature to serialize/deserialize NVML data structures for logging or transport
Common Use Cases
- GPU monitoring dashboards and exporters that report temperature, utilization, memory, and power draw for NVIDIA cards
- ML/AI training and inference tooling that needs to check GPU memory headroom or clock/power state before scheduling work
- System health agents and node exporters running on GPU servers that need graceful behavior on machines without an NVIDIA GPU
- CLI utilities that replicate or extend
nvidia-smi-style reporting with custom formatting or alerting logic - Fleet management and cluster schedulers that query PCIe link state and device topology to place GPU-bound workloads correctly
Under The Hood
Architecture
The project is a two-crate Cargo workspace: nvml-wrapper-sys holds machine-generated raw FFI bindings to NVIDIA’s nvml.h header (produced via a gen_bindings.sh/bindgen step and checked into bindings.rs), and nvml-wrapper builds the public-facing safe API on top of it. The safe crate’s src/lib.rs defines the central Nvml struct, constructed through Nvml::init(), which dynamically loads the NVML shared library via libloading and resolves each function symbol individually rather than linking at compile time. Domain types are organized by concern — device.rs (the largest module, ~8,000 lines covering the bulk of the per-GPU API surface), unit.rs, event.rs, nv_link.rs, gpm.rs — with supporting enum_wrappers/, struct_wrappers/, and bitmasks/ modules translating raw C enums, structs, and flag values into typed, documented Rust equivalents. A high_level/event_loop.rs module (Linux-only) layers a higher-level polling event loop on top of the raw event APIs. This structure cleanly separates “what NVML exposes” (the sys crate) from “how Rust code should use it” (the wrapper crate), so a change to NVIDIA’s header surface only requires regenerating bindings, not rewriting the safe API.
Tech Stack
Pure Rust, edition 2021, MSRV 1.60.0. Core dependencies are libloading for dynamic library loading, thiserror for the NvmlError error enum, bitflags for capability/state flag types, wrapcenum-derive for generating typed wrappers around C-style enum constants, and static_assertions for compile-time struct-layout checks against the C ABI. serde/serde_derive are optional, feature-gated dependencies for (de)serializing NVML types. The nvml-wrapper-sys crate depends only on libloading and exposes the raw bindgen-generated bindings plus a legacy-functions Cargo feature mirrored up through the wrapper crate.
Code Quality
Tests exist as inline #[test] modules (six files use #[test]), exercised through a shared test_utils.rs helper that provides a ShouldPrint trait and imports across the wrapped device, unit, event, GPM, and NvLink types — but because the tests need an actual NVIDIA GPU and driver to run meaningfully, they function more as integration smoke tests than a hardware-independent unit suite, and CI only runs cargo test --no-run (compiles tests without executing them) rather than a full run against real hardware. Error handling is explicit throughout: nearly every public method returns Result<T, NvmlError>, and the error enum distinguishes UTF-8/NUL-byte string errors, libloading failures, missing function symbols, and NVML’s own per-call error codes rather than collapsing them into one generic error. CI (ci.yml) runs cargo check --all-features across Linux, Windows, and macOS on both the MSRV and stable toolchains, plus a separate audit.yml workflow for dependency vulnerability scanning via cargo audit. Naming is consistent and mirrors the underlying NVML C API closely, which keeps the wrapper predictable for anyone already familiar with nvidia-smi or the NVML docs.
What Makes It Unique
The defining technical choice is dynamic loading over static linking: because NVML is loaded at runtime via libloading rather than linked against libnvidia-ml.so/nvml.dll at build time, binaries built with nvml-wrapper run correctly on machines with no NVIDIA GPU or driver present — Nvml::init() returns a normal Result error instead of causing the whole program to fail to load or refuse to start. Combined with near-total coverage of the NVML C API (rather than a curated subset of “the common calls”), this makes the crate suitable as a drop-in dependency for cross-platform or heterogeneous-fleet tooling that only sometimes runs on NVIDIA hardware, without needing conditional compilation or separate GPU/non-GPU build targets.