Skip to content

Latest commit

Β 

History

History
379 lines (310 loc) Β· 16.6 KB

File metadata and controls

379 lines (310 loc) Β· 16.6 KB

Architecture Guide

Deep dive into Mock NVML's design and implementation.

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              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)                          β”‚ β”‚   β”‚
β”‚    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚   β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Details

1. CGo Bridge Layer

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;

2. Engine Layer

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
}

3. Handle Table

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
}

4. Configuration System

Files: engine/config.go (~350 lines), engine/config_types.go (418 lines)

Configuration Hierarchy

YAMLConfig:
  β”œβ”€β”€ SystemConfig          # Driver version, CUDA version
  β”œβ”€β”€ DeviceDefaults        # Default properties for all devices
  └── Devices[]             # Per-device overrides
        β”œβ”€β”€ index: 0
        β”‚   └── (overrides)
        β”œβ”€β”€ index: 1
        β”‚   └── (overrides)
        └── ...

Merge Algorithm

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
}

5. ConfigurableDevice

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
}

Data Flow

Initialization Sequence

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

Query Flow

nvidia-smi                Bridge              Engine           Device
    β”‚                        β”‚                   β”‚                β”‚
    β”‚ GetTemperature(dev,0,&t)                   β”‚                β”‚
    │───────────────────────►│                   β”‚                β”‚
    β”‚                        β”‚ LookupDevice(dev) β”‚                β”‚
    β”‚                        │──────────────────►│                β”‚
    β”‚                        β”‚                   β”‚ Lookup(handle) β”‚
    β”‚                        │◄──────────────────│                β”‚
    β”‚                        β”‚                   β”‚                β”‚
    β”‚                        β”‚ GetTemperature(0) β”‚                β”‚
    β”‚                        │───────────────────┼───────────────►│
    β”‚                        β”‚                   β”‚                β”‚
    β”‚                        β”‚                   β”‚  config.Thermalβ”‚
    β”‚                        β”‚                   β”‚  .TempGPU_C    β”‚
    β”‚                        │◄──────────────────┼────────────────│
    β”‚                        β”‚                   β”‚    33, SUCCESS β”‚
    │◄───────────────────────│                   β”‚                β”‚
    β”‚        temp=33         β”‚                   β”‚                β”‚

Design Patterns

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

File Structure

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

Thread Safety

All public Engine methods are protected by sync.RWMutex:

  • Read operations (DeviceGetCount, LookupDevice): Use RLock
  • Write operations (Init, Shutdown, DeviceGetHandleByIndex): Use Lock

The HandleTable also has its own mutex for independent locking.

Memory Management

C Memory (Handles)

  • Allocated via calloc() in CGo
  • Freed on Engine.Shutdown() via HandleTable.Clear()
  • Each handle is ~40 bytes

Error String Cache

  • C strings for nvmlErrorString are cached permanently
  • Matches real NVML behavior (static strings)
  • Prevents memory leaks from repeated allocations

Mock CUDA Architecture

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.

Extending the Library

See Development Guide for:

  • Adding new NVML function implementations
  • Creating custom GPU profiles
  • Regenerating the bridge code