Deep dive into Mock NVML's design and implementation.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER SPACE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ β
β β nvidia-smi β β Your Application β β
β β (real binary) β β (k8s-device-plugin, dcgm, etc) β β
β ββββββββββ¬ββββββββββ ββββββββββββββββ¬βββββββββββββββββββββββ β
β β β β
β β dlopen("libnvidia-ml.so") β β
β βΌ βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β libnvidia-ml.so (MOCK) β β
β β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β CGo Bridge Layer β β β
β β β - 400 C function exports (//export directives) β β β
β β β - C struct definitions (nvmlPciInfo_t, nvmlMemory_t, etc) β β β
β β β - Type conversions (C β Go) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β
β β ββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ β β
β β β Engine Layer β β β
β β β - Singleton lifecycle management β β β
β β β - Configuration loading (YAML or env vars) β β β
β β β - Handle table (C pointer β Go object mapping) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β
β β ββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ β β
β β β ConfigurableDevice β β β
β β β - 89 NVML method implementations β β β
β β β - YAML-driven property values β β β
β β β - Wraps dgxa100.Device (go-nvml mock) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Directory: bridge/ (multiple files with IDE support)
The bridge exposes NVML functions as C symbols that applications can dynamically load. The bridge is organized into hand-written implementation files plus auto-generated stubs:
| File | Purpose |
|---|---|
cgo_types.go |
Shared CGo type definitions (C structs, constants) |
helpers.go |
Helper functions (toReturn, goStringToC, stubReturn) + main() |
init.go |
Initialization: nvmlInit_v2, nvmlShutdown, etc. |
device.go |
Device handles: nvmlDeviceGetCount, GetHandleByIndex, GetName, etc. |
system.go |
System functions: nvmlSystemGetDriverVersion, GetCudaDriverVersion, etc. |
internal.go |
Internal export table for nvidia-smi compatibility |
stubs_generated.go |
Auto-generated stubs for unimplemented functions |
//export nvmlDeviceGetTemperature
func nvmlDeviceGetTemperature(device C.nvmlDevice_t, sensorType C.nvmlTemperatureSensors_t,
temp *C.uint) C.nvmlReturn_t {
// 1. Look up Go device from C handle
dev := engine.GetEngine().LookupConfigurableDevice(uintptr(device))
if dev == nil {
return C.NVML_ERROR_INVALID_ARGUMENT
}
// 2. Call Go implementation
temperature, ret := dev.GetTemperature(nvml.TemperatureSensors(sensorType))
// 3. Convert result to C types
*temp = C.uint(temperature)
return toReturn(ret)
}C Type Definitions (CGo preamble):
typedef struct nvmlPciInfo_st {
char busIdLegacy[16];
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int pciDeviceId;
unsigned int pciSubSystemId;
char busId[32];
} nvmlPciInfo_t;
typedef struct nvmlMemory_st {
unsigned long long total;
unsigned long long free;
unsigned long long used;
} nvmlMemory_t;File: engine/engine.go (~400 lines)
The Engine is the central coordinator, managing:
- Lifecycle: Init/Shutdown reference counting
- Configuration: Loading from YAML or environment
- Handle mapping: Translating C pointers to Go objects
type Engine struct {
server *MockServer // Device provider
config *Config // Loaded configuration
handles *HandleTable // CβGo handle mapping
initCount int // Reference count
mu sync.RWMutex // Thread safety
}Singleton Pattern:
var (
engineInstance *Engine
engineOnce sync.Once
)
func GetEngine() *Engine {
engineOnce.Do(func() {
engineInstance = NewEngine(nil)
})
return engineInstance
}File: engine/handles.go (~170 lines)
Problem: CGo doesn't allow passing Go pointers with nested Go pointers to C code. When nvidia-smi receives a device handle, it expects to dereference it.
Solution: Allocate real C memory blocks that nvidia-smi can safely access.
// C structure that nvidia-smi can dereference
typedef struct {
unsigned int magic; // 0x4E564D4C ("NVML")
unsigned int index; // Device index
void* reserved[4]; // Space nvidia-smi might read
} HandleBlock;func (ht *HandleTable) Register(dev nvml.Device) uintptr {
// Allocate C memory block
cHandle := C.allocHandle(C.uint(deviceIndex))
handle := uintptr(unsafe.Pointer(cHandle))
// Store bidirectional mapping
ht.devices[handle] = dev
ht.reverse[dev] = handle
return handle
}Files: engine/config.go (~350 lines), engine/config_types.go (418 lines)
YAMLConfig:
βββ SystemConfig # Driver version, CUDA version
βββ DeviceDefaults # Default properties for all devices
βββ Devices[] # Per-device overrides
βββ index: 0
β βββ (overrides)
βββ index: 1
β βββ (overrides)
βββ ...func (c *Config) GetDeviceConfig(index int) *DeviceConfig {
// Start with defaults
merged := c.YAMLConfig.DeviceDefaults
// Apply per-device overrides
for _, override := range c.YAMLConfig.Devices {
if override.Index == index {
mergeDeviceOverride(&merged, &override)
break
}
}
return &merged
}File: engine/device.go (~1290 lines)
Implements 89 NVML methods by reading from YAML configuration.
type ConfigurableDevice struct {
*dgxa100.Device // Base device (embedded)
config *DeviceConfig // YAML configuration
index int
minorNumber int
bar1Memory nvml.BAR1Memory // Cached
pciInfo nvml.PciInfo // Cached
}Method Implementation Pattern:
func (d *ConfigurableDevice) GetTemperature(sensor nvml.TemperatureSensors) (uint32, nvml.Return) {
// Check if config provides value
if d.config != nil && d.config.Thermal != nil {
return uint32(d.config.Thermal.TemperatureGPU_C), nvml.SUCCESS
}
// No config = not supported
return 0, nvml.ERROR_NOT_SUPPORTED
}nvidia-smi Engine Config
β β β
β nvmlInit_v2() β β
βββββββββββββββββββββββββΊβ β
β β LoadConfig() β
β ββββββββββββββββββββββββββΊβ
β β β
β β βββββββββββββββββββββ€
β β β YAML exists? β
β β βββββββββββ¬ββββββββββ
β β β
β β YES: Parse YAML
β β NO: Use env vars
β β β
β βββββββββββββββββ
β β
β β createServer()
β β - Create dgxa100.Server
β β - Create ConfigurableDevices
β β - Apply system config
β β
ββββββββββββββββββββββββββ NVML_SUCCESS
nvidia-smi Bridge Engine Device
β β β β
β GetTemperature(dev,0,&t) β β
βββββββββββββββββββββββββΊβ β β
β β LookupDevice(dev) β β
β ββββββββββββββββββββΊβ β
β β β Lookup(handle) β
β βββββββββββββββββββββ β
β β β β
β β GetTemperature(0) β β
β βββββββββββββββββββββΌββββββββββββββββΊβ
β β β β
β β β config.Thermalβ
β β β .TempGPU_C β
β βββββββββββββββββββββΌβββββββββββββββββ
β β β 33, SUCCESS β
ββββββββββββββββββββββββββ β β
β temp=33 β β β
| Pattern | Component | Purpose |
|---|---|---|
| Singleton | Engine | Single lifecycle manager |
| Decorator | ConfigurableDevice wraps dgxa100.Device | Extend without modifying |
| Strategy | createDevicesFromYAML vs createDefaultDevices | Runtime behavior selection |
| Handle Table | HandleTable | Safe CβGo pointer translation |
| Config Merge | mergeDeviceOverride | Defaults + overrides |
pkg/gpu/mocknvml/
βββ bridge/
β βββ cgo_types.go # Shared CGo type definitions
β βββ helpers.go # Helper functions + main() + go:generate
β βββ init.go # nvmlInit_v2, nvmlShutdown, etc.
β βββ device.go # Device handle functions
β βββ events.go # Event set/wait functions
β βββ system.go # System functions
β βββ internal.go # Internal export table (nvidia-smi)
β βββ nvml_types.h # C type definitions for CGo preamble
β βββ stubs_generated.go # Auto-generated stubs (~289 functions)
βββ engine/
β βββ config.go # Config loading
β βββ config_types.go # YAML structs
β βββ device.go # ConfigurableDevice
β βββ engine.go # Singleton engine
β βββ handles.go # Handle table
β βββ invalid_device.go # Invalid device handle sentinel
β βββ utils.go # Debug logging
β βββ version.go # NVML version responses
β βββ *_test.go # Unit tests
βββ configs/
β βββ mock-nvml-config-a100.yaml
β βββ mock-nvml-config-b200.yaml
β βββ mock-nvml-config-gb200.yaml
β βββ mock-nvml-config-gb300.yaml
β βββ mock-nvml-config-h100.yaml
β βββ mock-nvml-config-l40s.yaml
β βββ mock-nvml-config-t4.yaml
βββ Dockerfile
βββ Makefile
βββ README.md
cmd/generate-bridge/
βββ main.go # Stub generator (--stats, --validate flags)
βββ parser.go # nvml.h prototype parser
βββ main_test.go # Generator tests
All public Engine methods are protected by sync.RWMutex:
- Read operations (
DeviceGetCount,LookupDevice): UseRLock - Write operations (
Init,Shutdown,DeviceGetHandleByIndex): UseLock
The HandleTable also has its own mutex for independent locking.
- Allocated via
calloc()in CGo - Freed on
Engine.Shutdown()viaHandleTable.Clear() - Each handle is ~40 bytes
- C strings for
nvmlErrorStringare cached permanently - Matches real NVML behavior (static strings)
- Prevents memory leaks from repeated allocations
The CUDA mock follows the same engine/bridge pattern as NVML but at a smaller scale (15 functions vs 400).
See CUDA Mock for full details.
See Development Guide for:
- Adding new NVML function implementations
- Creating custom GPU profiles
- Regenerating the bridge code