Skip to content

vgpu apis - #130

Merged
brayniac merged 4 commits into
rust-nvml:mainfrom
FreeMasen:feat/some-vgpu-apis
Aug 31, 2026
Merged

vgpu apis#130
brayniac merged 4 commits into
rust-nvml:mainfrom
FreeMasen:feat/some-vgpu-apis

Conversation

@FreeMasen

Copy link
Copy Markdown
Contributor

This PR is an attempt to fully cover the vGPU APIs defined by nvml. To achieve this I've followed the patterns defined for Device and VgpuType to cover all functions in the unwrapped_functions.txt file that are named nvmlVgpu*

The one exception here was nvmlVgpuInstanceGetLicenseStatus which is deprecated and not documented at this time, if you'd like me to dig through older versions of the docs, I can find the documentation for how that API works.

Please let me know if I've misunderstood any of the patterns I've tried to emulate and/or the API documentation, I would be happy to follow up with additional changes as needed.

Note: this is currently marked as a draft because I based these changes on #129 and will rebase once that merges or is declined

@FreeMasen
FreeMasen force-pushed the feat/some-vgpu-apis branch 5 times, most recently from f3f0229 to 970027b Compare March 5, 2026 21:15

@FreeMasen FreeMasen left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is ready for an initial review to make sure I am not doing anything that goes against the goals of this project. I need to dig a little deeper into how testing works for this project to add additional test for these additions.

I am going to leave the Draft status until I can get through tests, any feedback would be welcome!

Comment thread nvml-wrapper/src/enum_wrappers/vgpu.rs
Comment thread nvml-wrapper/src/enum_wrappers/vgpu.rs
Comment thread nvml-wrapper/src/enum_wrappers/vgpu.rs
Comment thread nvml-wrapper/src/struct_wrappers/vgpu.rs
Comment thread nvml-wrapper/src/struct_wrappers/vgpu.rs
Comment thread nvml-wrapper/src/device.rs
Comment thread nvml-wrapper/src/struct_wrappers/vgpu.rs Outdated
@swlynch99

Copy link
Copy Markdown
Contributor

I'm happy to review this once it is ready. Once you're happy with it lmk.

@FreeMasen
FreeMasen force-pushed the feat/some-vgpu-apis branch from afce533 to c6b1acb Compare March 11, 2026 01:10
Comment thread nvml-wrapper/src/vgpu.rs Outdated
@FreeMasen
FreeMasen marked this pull request as ready for review March 11, 2026 01:17
@FreeMasen

Copy link
Copy Markdown
Contributor Author

I believe this is ready for review. I have tried to comment on any of the places where I wasn't quite sure about how something should be implemented.

@brayniac

Copy link
Copy Markdown
Contributor

Thanks for the extensive work here — this is a high-quality contribution and I'd like to land it. I checked out the branch, merged current main (clean merge, and with #129 long since landed the draft caveat is resolved), and verified it compiles on macOS and Linux targets, tests included, with no new doc warnings. I also confirmed the unwrapped_functions.txt cleanup is accurate — every removed entry is genuinely wrapped.

One real bug and a few smaller items before merge:

Bug: get_metadata misreads the NVML contract

nvmlVgpuInstanceGetMetadata returns one variable-length structure, and bufferSize is in bytes — per the header: "The caller passes in a buffer via vgpuMetadata, with the size of the buffer in bufferSize", and INSUFFICIENT_SIZE reports the required byte count. The current code treats that byte count as an element count:

  • it allocates bufferSize copies of nvmlVgpuMetadata_t (~192 bytes each — a ~2KB requirement becomes a ~400KB allocation), and
  • converts every element, returning a Vec<VgpuMetadata> where only index 0 is real. The garbage entries convert successfully (zeroed guestInfoState is the valid Uninitialized variant), so callers silently get plausible-looking bogus entries.

This should return a single VgpuMetadata, allocating ceil(bytes / size_of::<nvmlVgpuMetadata_t>()) structs and reading only the first. (Non-blocking: the wrapper drops opaqueData, which future nvmlGetVgpuCompatibility support would need — fine to leave for later.)

Naming / API items

  • get_get_placement_id and get_get_runtime_state_size — doubled get_ typos.
  • The get_ prefixes generally diverge from the crate convention (name(), license(), frame_rate_limit() on these same types) — please drop them.
  • get_instance_type(&'dev self) over-constrains the borrow; &self works.
  • active_vgpus's <'a> ... where 'a: 'nvml clause is unnecessary — I verified locally that pub fn active_vgpus(&self) -> Result<Vec<VgpuInstance<'_>>, NvmlError> compiles (tests included), since Device is covariant in 'nvml.
  • VgpuInstance::new is pub(crate) and only called from the Linux-gated active_vgpus, so non-Linux builds emit a dead-code warning (and VgpuInstance is unconstructable on Windows) — needs a cfg or a constructor story.

Non-blocking notes

  • The active_vgpus return-type change is the right design but breaking — we'll release it as 0.13 with a changelog entry.
  • The new struct_wrappers/vgpu.rs types are missing the serde cfg_attr derives the other struct wrappers have, and VgpuLicenseState's hand-written From<u32> with a silent Unknown fallback diverges from the EnumWrapper/TryFrom convention used elsewhere.
  • convert_c_str decodes bytes as Latin-1 and maps stray negative bytes to NvmlError::Unknown — the crate's usual CStr + to_str() pattern would be more consistent.
  • VgpuLicenseExpiry silently saturates u32 fields to u8::MAX/u16::MAX.
  • Cosmetic: get_driver_version uses NVML_SYSTEM_NVML_VERSION_BUFFER_SIZE where the header points at NVML_SYSTEM_DRIVER_VERSION_BUFFER_SIZE (both 80, so harmless).

Happy to merge once the get_metadata fix and the naming items are in. Thanks again — the coverage expansion and the docs are much appreciated.

@FreeMasen

Copy link
Copy Markdown
Contributor Author

Thank you very much for your detailed review! I will address your comments as soon as I can

@FreeMasen

Copy link
Copy Markdown
Contributor Author

I just pushed a fixup commit with the changes, let me know if I've covered it all and I can squash that commit

@brayniac

Copy link
Copy Markdown
Contributor

Thanks for turning this around so quickly! I re-reviewed the two fixup commits against the branch locally.

Note: this review was AI-assisted. I've verified the findings below myself — everything I claim about compilation, warnings, and formatting comes from actually building the branch.

Fixed

  • get_get_placement_id / get_get_runtime_state_sizeget_placement_id / get_runtime_state_size
  • convert_c_str now uses CStr + to_str()
  • get_metadata now returns a single VgpuMetadata instead of a Vec ✅ — right shape, but see below

Still to fix

1. get_metadata's size check will reject nearly every real call

nvml-wrapper/src/vgpu.rs:952 errors out unless the driver's required byte count is exactly size_of::<nvmlVgpuMetadata_t>() (212 on our bindings):

if size_of::<nvmlVgpuMetadata_t>() != count as usize {
    return Err(NvmlError::Unknown)
}

But the required size is offsetof(nvmlVgpuMetadata_t, opaqueData) (208) plus opaqueDataSize, and the opaque migration blob is realistically hundreds to thousands of bytes. The check therefore only passes when opaqueDataSize happens to be exactly 4, so in practice the call returns Err(NvmlError::Unknown). That swaps the old silently-bogus-data problem for a never-works one.

The fix is to allocate a buffer at least byte_size bytes long, keep it aligned for nvmlVgpuMetadata_t, and read the header out of it. Every field the wrapper actually reads lives in the first 208 bytes, so reading only element 0 of an over-allocated buffer is correct:

pub fn get_metadata(&self) -> Result<VgpuMetadata, NvmlError> {
    let sym = nvml_sym(self.device.nvml().lib.nvmlVgpuInstanceGetMetadata.as_ref())?;
    unsafe {
        // NVML reports the required buffer size in *bytes*.
        let mut byte_size: c_uint = 0;
        nvml_try_count(sym(self.instance, std::ptr::null_mut(), &mut byte_size))?;

        // The metadata is one variable-length structure: a fixed header
        // followed by an opaque payload. Over-allocate in whole
        // `nvmlVgpuMetadata_t`s so the buffer stays correctly aligned.
        let struct_size = std::mem::size_of::<nvmlVgpuMetadata_t>();
        let count = ((byte_size as usize + struct_size - 1) / struct_size).max(1);
        let mut buffer: Vec<nvmlVgpuMetadata_t> = vec![std::mem::zeroed(); count];

        let mut byte_size = (count * struct_size) as c_uint;
        nvml_try(sym(self.instance, buffer.as_mut_ptr(), &mut byte_size))?;

        VgpuMetadata::try_from(buffer[0])
    }
}

I verified this compiles cleanly and is rustfmt-clean. It also removes the need for the MaybeUninit / transmute imports.

Note the deliberate std::mem::size_of and the manual round-up instead of div_ceil — see the MSRV note below.

2. MSRV: bare size_of breaks the 1.60.0 CI job

size_of is only in the prelude as of Rust 1.80. This crate sets rust-version = "1.60.0", and .github/workflows/ci.yml runs cargo check --all-features on 1.60.0 for both ubuntu and windows, so cargo check fails there. Every other call site in the crate spells it std::mem::size_of::<T>().

More generally, the 1.60 MSRV means new code has to avoid conveniences that landed later — size_of in the prelude (1.80), div_ceil (1.73), let ... else (1.65), and so on. Whether to raise the MSRV is a decision for the maintainers to make separately; for this PR please stick to 1.60-compatible constructs.

3. cargo fmt --check fails

Four diffs, all introduced by the fixups (struct_wrappers/vgpu.rs:63, vgpu.rs:1, vgpu.rs:950, vgpu.rs:1357). The Rustfmt CI job will go red — a cargo fmt --all should clear it.

4. Unused transmute import

vgpu.rs:2 — new unused_imports warning. Goes away with the get_metadata rewrite above.

5. Naming items from the last round (still open)

  • The get_ prefixes still diverge from the crate convention — VgpuType next door has name(), license(), frame_rate_limit(), while VgpuInstance has get_fb_usage(), get_uuid(), get_license_info(), etc. Please drop the prefixes.
  • get_instance_type(&'dev self) (vgpu.rs:520) over-constrains the borrow; &self works.
  • active_vgpus's <'a> ... where 'a: 'nvml clause (device.rs:6179) is unnecessary — pub fn active_vgpus(&self) -> Result<Vec<VgpuInstance<'_>>, NvmlError> compiles, tests included, since Device is covariant in 'nvml.
  • VgpuInstance::new is pub(crate) and only reachable from the Linux-gated active_vgpus, so non-Linux builds emit a dead-code warning and VgpuInstance is unconstructable there. Needs a cfg or a constructor story.

The non-blocking notes from my last review (serde cfg_attr derives, VgpuLicenseState's hand-written From<u32>, VgpuLicenseExpiry saturation, the NVML_SYSTEM_NVML_VERSION_BUFFER_SIZE const) are all unchanged — still happy to land without them.

Once 1–5 are in I think this is good to go. Thanks again for the thorough work here.

@FreeMasen

Copy link
Copy Markdown
Contributor Author

Another fixup commit this morning for your review, let me know and I will rebase and force push for the merge

@brayniac

Copy link
Copy Markdown
Contributor

Thanks! Looks good to me. Happy to merge after the rebase/force push.

@FreeMasen
FreeMasen force-pushed the feat/some-vgpu-apis branch from a776852 to a7e7904 Compare August 31, 2026 18:07
@FreeMasen

Copy link
Copy Markdown
Contributor Author

Rebased and pushed and CI has passed

@brayniac
brayniac merged commit 2a30511 into rust-nvml:main Aug 31, 2026
7 checks passed
brayniac added a commit that referenced this pull request Aug 31, 2026
- instance_type() now takes &self instead of &'dev self, so calling it
  no longer borrows the VgpuInstance for the rest of the device's
  lifetime (e.g. iterating active_vgpus() and querying each instance's
  type now compiles)
- active_vgpus() drops the unnecessary <'a> ... where 'a: 'nvml clause
  and returns VgpuInstance<'_> tied to the device borrow
- VgpuInstance::new is now public with docs, mirroring VgpuType::new;
  this also removes the dead-code warning on non-Linux builds where the
  Linux-gated active_vgpus() was its only caller

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVPWxFN8rioYb8JhreYQmJ
brayniac added a commit that referenced this pull request Aug 31, 2026
fix: vGPU borrow-ergonomics follow-ups from #130
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants