Note: Container images and Helm charts are now available at
ghcr.io/run-ai/fake-gpu-operator.
The Fake GPU Operator is a lightweight tool that simulates NVIDIA GPUs in Kubernetes clusters without requiring physical hardware. It provides basic functionality for developers and testers:
Use cases include:
NodeResourceTopology) for NUMA-aware schedulingkubectl label node <node-name> run.ai/simulated-gpu-node-pool=default
helm upgrade -i gpu-operator oci://ghcr.io/run-ai/fake-gpu-operator/fake-gpu-operator --namespace gpu-operator --create-namespace --version <VERSION>
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda-vector-add:v0.1
resources:
limits:
nvidia.com/gpu: 1
Customize GPU configurations in your values.yaml:
topology:
nodePools:
default:
gpuProduct: Tesla-K80
gpuCount: 2
gpuMemory: 11441
Control GPU utilization metrics with pod annotations:
metadata:
annotations:
run.ai/simulated-gpu-utilization: "10-30" # Simulate 10-30% GPU usage
The operator provides special handling for Knative-based inference workloads, where GPU utilization is dynamically calculated based on actual request traffic rather than static values.
When a pod is identified as an inference workload (via workloadKind: "InferenceWorkload" label or PodGroup priorityClassName: "inference"), the operator:
revision_app_request_count metricrate(revision_app_request_count[1m])This provides realistic GPU utilization metrics that correlate with inference traffic patterns.
Configure Prometheus connection in your Helm values:
prometheus:
url: http://prometheus-operated.runai:9090 # Default
For local development with port-forwarding:
prometheus:
url: http://localhost:9090
apiVersion: v1
kind: Pod
metadata:
name: inference-pod
labels:
workloadKind: "InferenceWorkload" # Enables Knative utilization
spec:
containers:
- name: model-server
image: my-inference-server:latest
resources:
limits:
nvidia.com/gpu: 1
When requests flow to this inference pod, GPU utilization metrics will reflect the actual request rate from Knative.
Supported Knative Workload Types:
workloadKind: "InferenceWorkload" - Single-node inference with Knative metricsworkloadKind: "DistributedWorkload" - Distributed inference with Knative metricsMost pools use the fake backend, where FGO's own device-plugin advertises synthetic nvidia.com/gpu.
Switch a pool to the mock backend to:
nvidia-smi or CUDA device discovery.It installs NVIDIA's nvml-mock on the pool's nodes. It runs privileged pods and writes host files under /var/lib/nvml-mock, so it is opt-in per pool:
topology:
nodePools:
my-mock-pool:
gpu:
backend: mock
profile: a100 # or h100, b200, gb200, l40s, t4
gpuOperator: { enabled: true } # device-plugin path; also set gpu-operator.toolkit.enabled
# nvidiaDraDriver: { enabled: true } # or use this lighter DRA-only path instead
See docs/mock-backend.md for profiles, caveats such as the cosmetic ClusterPolicy NotReady status, and cleanup notes.
NUMA-aware schedulers (e.g. KAI-Scheduler) place GPU workloads across NUMA zones by reading a NodeResourceTopology (NRT) per node. fake-gpu-operator can publish those without real multi-socket hardware โ off by default; enable it per install and add a numa block to each pool:
statusUpdater:
nodeResourceTopology:
enabled: true # off by default
# installCRD: false # set if the NRT CRD already exists (NFD/KAI)
topology:
nodePools:
default:
gpu: { backend: fake, profile: a100 }
numa:
zones: 2 # the pool's GPUs split across node-0/node-1
distances: { self: 10, remote: 21 }
When enabled, the chart installs the NRT CRD (set installCRD: false if you already have it) and grants the status-updater RBAC. See docs/fake-nrt.md for the full config reference, the published resource, and caveats (static available, NFD topology-updater conflicts).
The status updater sets gpu-fractioning.nvidia.com/Ready on fake backend pool nodes after the legacy nvidia.com/gpu resource becomes allocatable. It removes its own condition if that resource disappears. It does not set the condition on NVML mock pools, and it leaves conditions written by another controller alone. DRA allocation does not require this condition. The condition signals simulated scheduling readiness; it does not provide fraction enforcement.
For Kubernetes 1.31+, you can use the DRA plugin instead of the legacy device plugin.
Enable DynamicResourceAllocation feature gate and the resource.k8s.io/v1 API on your cluster if needed.
# values.yaml
draPlugin:
enabled: true
devicePlugin:
enabled: false # Disable legacy plugin
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-claim
spec:
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.nvidia.com
---
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: main
image: ubuntu:22.04
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
resourceClaimTemplateName: gpu-claim
See test/e2e/fixtures/manifests/ for more examples.
The Fake GPU Operator supports simulating NVIDIA Compute Domains for secure workload isolation without requiring actual NVIDIA hardware. Compute Domains provide IMEX channel simulation for multi-node GPU workloads.
# values.yaml
computeDomainController:
enabled: true
computeDomainDraPlugin:
enabled: true
draPlugin:
enabled: true
devicePlugin:
enabled: false # Disable legacy plugin when using DRA
First, create a ComputeDomain resource:
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
name: my-compute-domain
namespace: default
spec:
numNodes: 1
channel:
allocationMode: Single # or "All" for all channels
resourceClaimTemplate:
name: my-compute-domain
The compute-domain-controller will automatically create a ResourceClaimTemplate for the ComputeDomain.
Then, deploy a pod that uses the compute domain:
apiVersion: v1
kind: Pod
metadata:
name: compute-domain-pod
namespace: default
spec:
containers:
- name: main
image: ubuntu:22.04
command: ["sleep", "infinity"]
resources:
claims:
- name: compute-domain
resourceClaims:
- name: compute-domain
resourceClaimTemplateName: my-compute-domain
# Check ComputeDomain status
kubectl get computedomain my-compute-domain -o yaml
# Verify status shows Ready and allocated nodes
# status:
# status: Ready
# nodes:
# - name: <node-name>
# status: Ready
KWOK (Kubernetes WithOut Kubelet) is a toolkit that allows you to simulate thousands of Kubernetes nodes without running actual kubelet processes. When combined with the Fake GPU Operator, you can create large-scale GPU cluster simulations entirely without hardware - perfect for testing schedulers, autoscalers, and resource management at scale.
Install KWOK controller in your cluster:
KWOK_VERSION=v0.7.0
kubectl apply -f "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/kwok.yaml"
kubectl apply -f "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/stage-fast.yaml"
Enable the kwok-dra-plugin in your Helm values:
# values.yaml
kwokDraPlugin:
enabled: true
draPlugin:
enabled: true
apiVersion: v1
kind: Node
metadata:
annotations:
kwok.x-k8s.io/node: fake
labels:
type: kwok
run.ai/simulated-gpu-node-pool: default
name: kwok-gpu-node-1
spec:
taints:
- effect: NoSchedule
key: kwok.x-k8s.io/node
value: fake
status:
allocatable:
cpu: "32"
memory: 128Gi
pods: "110"
capacity:
cpu: "32"
memory: 128Gi
pods: "110"
The status-updater will automatically create a topology ConfigMap for this node, and the kwok-dra-plugin will create a ResourceSlice with the configured GPUs.
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-claim
spec:
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.nvidia.com
---
apiVersion: v1
kind: Pod
metadata:
name: kwok-gpu-pod
spec:
nodeSelector:
type: kwok
tolerations:
- key: kwok.x-k8s.io/node
operator: Equal
value: fake
effect: NoSchedule
containers:
- name: main
image: ubuntu:22.04
command: ["sleep", "infinity"]
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
resourceClaimTemplateName: gpu-claim
The pod will be "scheduled" on the KWOK node and appear as Running (KWOK simulates the pod lifecycle). The ResourceClaim will be allocated from the simulated GPU ResourceSlice.
# Check KWOK node is Ready
kubectl get nodes -l type=kwok
# Check ResourceSlice was created
kubectl get resourceslices | grep kwok
# Check pod is running on KWOK node
kubectl get pod kwok-gpu-pod -o wide
You can run the Fake GPU Operator alongside a real NVIDIA GPU Operator on the same cluster: real-GPU nodes stay fully managed by the real operator, while simulated GPU nodes are added for scale and scheduler testing.
By default the Fake GPU Operator is a drop-in replacement for the real one, so to make them coexist you must (1) install it in its own namespace (not gpu-operator) and (2) disable the components whose node selectors / cluster-scoped objects collide with the real operator:
# mixed-mode-values.yaml โ coexist with a real NVIDIA GPU Operator
devicePlugin: { enabled: false } # selector collides with the real device-plugin on real-GPU nodes
statusExporter: { enabled: false } # selector collides with the real dcgm-exporter on real-GPU nodes
runtimeClass: { enabled: false } # cluster-scoped RuntimeClass/nvidia is already owned by the real operator
Install into a dedicated namespace:
helm upgrade -i fake-gpu-operator oci://ghcr.io/run-ai/fake-gpu-operator/fake-gpu-operator \
--namespace fake-gpu-operator --create-namespace \
--version <VERSION> -f mixed-mode-values.yaml
Then:
nvidia.com/gpu capacity is published by the central kwok-gpu-device-plugin, so the disabled per-node DaemonSets aren't needed, and the KWOK NoSchedule taint keeps the real operator's DaemonSets off the simulated nodes.run.ai/simulated-gpu-node-pool. The status-updater only acts on nodes carrying that label, so the real operator keeps exclusive ownership of your hardware.To ensure proper functionality, configure Pod Security Admission for the gpu-operator namespace:
kubectl label ns gpu-operator pod-security.kubernetes.io/enforce=privileged
The operator injects a simulated nvidia-smi tool into GPU pods. nvidia-smi uses the
NODE_NAME environment variable to resolve the pod's node topology; both the device-plugin
and the DRA driver inject it automatically at allocation time, so no manual pod configuration
is required.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Go module
Latest release
Releases this year
155 followers ยท starred Jul 2025
79 followers ยท starred Nov 2023
96 followers ยท starred Jul 2026
573 followers ยท starred Mar 2026
Note: Container images and Helm charts are now available at
ghcr.io/run-ai/fake-gpu-operator.
The Fake GPU Operator is a lightweight tool that simulates NVIDIA GPUs in Kubernetes clusters without requiring physical hardware. It provides basic functionality for developers and testers:
Use cases include:
NodeResourceTopology) for NUMA-aware schedulingkubectl label node <node-name> run.ai/simulated-gpu-node-pool=default
helm upgrade -i gpu-operator oci://ghcr.io/run-ai/fake-gpu-operator/fake-gpu-operator --namespace gpu-operator --create-namespace --version <VERSION>
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda-vector-add:v0.1
resources:
limits:
nvidia.com/gpu: 1
Customize GPU configurations in your values.yaml:
topology:
nodePools:
default:
gpuProduct: Tesla-K80
gpuCount: 2
gpuMemory: 11441
Control GPU utilization metrics with pod annotations:
metadata:
annotations:
run.ai/simulated-gpu-utilization: "10-30" # Simulate 10-30% GPU usage
The operator provides special handling for Knative-based inference workloads, where GPU utilization is dynamically calculated based on actual request traffic rather than static values.
When a pod is identified as an inference workload (via workloadKind: "InferenceWorkload" label or PodGroup priorityClassName: "inference"), the operator:
revision_app_request_count metricrate(revision_app_request_count[1m])This provides realistic GPU utilization metrics that correlate with inference traffic patterns.
Configure Prometheus connection in your Helm values:
prometheus:
url: http://prometheus-operated.runai:9090 # Default
For local development with port-forwarding:
prometheus:
url: http://localhost:9090
apiVersion: v1
kind: Pod
metadata:
name: inference-pod
labels:
workloadKind: "InferenceWorkload" # Enables Knative utilization
spec:
containers:
- name: model-server
image: my-inference-server:latest
resources:
limits:
nvidia.com/gpu: 1
When requests flow to this inference pod, GPU utilization metrics will reflect the actual request rate from Knative.
Supported Knative Workload Types:
workloadKind: "InferenceWorkload" - Single-node inference with Knative metricsworkloadKind: "DistributedWorkload" - Distributed inference with Knative metricsMost pools use the fake backend, where FGO's own device-plugin advertises synthetic nvidia.com/gpu.
Switch a pool to the mock backend to:
nvidia-smi or CUDA device discovery.It installs NVIDIA's nvml-mock on the pool's nodes. It runs privileged pods and writes host files under /var/lib/nvml-mock, so it is opt-in per pool:
topology:
nodePools:
my-mock-pool:
gpu:
backend: mock
profile: a100 # or h100, b200, gb200, l40s, t4
gpuOperator: { enabled: true } # device-plugin path; also set gpu-operator.toolkit.enabled
# nvidiaDraDriver: { enabled: true } # or use this lighter DRA-only path instead
See docs/mock-backend.md for profiles, caveats such as the cosmetic ClusterPolicy NotReady status, and cleanup notes.
NUMA-aware schedulers (e.g. KAI-Scheduler) place GPU workloads across NUMA zones by reading a NodeResourceTopology (NRT) per node. fake-gpu-operator can publish those without real multi-socket hardware โ off by default; enable it per install and add a numa block to each pool:
statusUpdater:
nodeResourceTopology:
enabled: true # off by default
# installCRD: false # set if the NRT CRD already exists (NFD/KAI)
topology:
nodePools:
default:
gpu: { backend: fake, profile: a100 }
numa:
zones: 2 # the pool's GPUs split across node-0/node-1
distances: { self: 10, remote: 21 }
When enabled, the chart installs the NRT CRD (set installCRD: false if you already have it) and grants the status-updater RBAC. See docs/fake-nrt.md for the full config reference, the published resource, and caveats (static available, NFD topology-updater conflicts).
The status updater sets gpu-fractioning.nvidia.com/Ready on fake backend pool nodes after the legacy nvidia.com/gpu resource becomes allocatable. It removes its own condition if that resource disappears. It does not set the condition on NVML mock pools, and it leaves conditions written by another controller alone. DRA allocation does not require this condition. The condition signals simulated scheduling readiness; it does not provide fraction enforcement.
For Kubernetes 1.31+, you can use the DRA plugin instead of the legacy device plugin.
Enable DynamicResourceAllocation feature gate and the resource.k8s.io/v1 API on your cluster if needed.
# values.yaml
draPlugin:
enabled: true
devicePlugin:
enabled: false # Disable legacy plugin
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-claim
spec:
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.nvidia.com
---
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: main
image: ubuntu:22.04
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
resourceClaimTemplateName: gpu-claim
See test/e2e/fixtures/manifests/ for more examples.
The Fake GPU Operator supports simulating NVIDIA Compute Domains for secure workload isolation without requiring actual NVIDIA hardware. Compute Domains provide IMEX channel simulation for multi-node GPU workloads.
# values.yaml
computeDomainController:
enabled: true
computeDomainDraPlugin:
enabled: true
draPlugin:
enabled: true
devicePlugin:
enabled: false # Disable legacy plugin when using DRA
First, create a ComputeDomain resource:
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
name: my-compute-domain
namespace: default
spec:
numNodes: 1
channel:
allocationMode: Single # or "All" for all channels
resourceClaimTemplate:
name: my-compute-domain
The compute-domain-controller will automatically create a ResourceClaimTemplate for the ComputeDomain.
Then, deploy a pod that uses the compute domain:
apiVersion: v1
kind: Pod
metadata:
name: compute-domain-pod
namespace: default
spec:
containers:
- name: main
image: ubuntu:22.04
command: ["sleep", "infinity"]
resources:
claims:
- name: compute-domain
resourceClaims:
- name: compute-domain
resourceClaimTemplateName: my-compute-domain
# Check ComputeDomain status
kubectl get computedomain my-compute-domain -o yaml
# Verify status shows Ready and allocated nodes
# status:
# status: Ready
# nodes:
# - name: <node-name>
# status: Ready
KWOK (Kubernetes WithOut Kubelet) is a toolkit that allows you to simulate thousands of Kubernetes nodes without running actual kubelet processes. When combined with the Fake GPU Operator, you can create large-scale GPU cluster simulations entirely without hardware - perfect for testing schedulers, autoscalers, and resource management at scale.
Install KWOK controller in your cluster:
KWOK_VERSION=v0.7.0
kubectl apply -f "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/kwok.yaml"
kubectl apply -f "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/stage-fast.yaml"
Enable the kwok-dra-plugin in your Helm values:
# values.yaml
kwokDraPlugin:
enabled: true
draPlugin:
enabled: true
apiVersion: v1
kind: Node
metadata:
annotations:
kwok.x-k8s.io/node: fake
labels:
type: kwok
run.ai/simulated-gpu-node-pool: default
name: kwok-gpu-node-1
spec:
taints:
- effect: NoSchedule
key: kwok.x-k8s.io/node
value: fake
status:
allocatable:
cpu: "32"
memory: 128Gi
pods: "110"
capacity:
cpu: "32"
memory: 128Gi
pods: "110"
The status-updater will automatically create a topology ConfigMap for this node, and the kwok-dra-plugin will create a ResourceSlice with the configured GPUs.
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-claim
spec:
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.nvidia.com
---
apiVersion: v1
kind: Pod
metadata:
name: kwok-gpu-pod
spec:
nodeSelector:
type: kwok
tolerations:
- key: kwok.x-k8s.io/node
operator: Equal
value: fake
effect: NoSchedule
containers:
- name: main
image: ubuntu:22.04
command: ["sleep", "infinity"]
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
resourceClaimTemplateName: gpu-claim
The pod will be "scheduled" on the KWOK node and appear as Running (KWOK simulates the pod lifecycle). The ResourceClaim will be allocated from the simulated GPU ResourceSlice.
# Check KWOK node is Ready
kubectl get nodes -l type=kwok
# Check ResourceSlice was created
kubectl get resourceslices | grep kwok
# Check pod is running on KWOK node
kubectl get pod kwok-gpu-pod -o wide
You can run the Fake GPU Operator alongside a real NVIDIA GPU Operator on the same cluster: real-GPU nodes stay fully managed by the real operator, while simulated GPU nodes are added for scale and scheduler testing.
By default the Fake GPU Operator is a drop-in replacement for the real one, so to make them coexist you must (1) install it in its own namespace (not gpu-operator) and (2) disable the components whose node selectors / cluster-scoped objects collide with the real operator:
# mixed-mode-values.yaml โ coexist with a real NVIDIA GPU Operator
devicePlugin: { enabled: false } # selector collides with the real device-plugin on real-GPU nodes
statusExporter: { enabled: false } # selector collides with the real dcgm-exporter on real-GPU nodes
runtimeClass: { enabled: false } # cluster-scoped RuntimeClass/nvidia is already owned by the real operator
Install into a dedicated namespace:
helm upgrade -i fake-gpu-operator oci://ghcr.io/run-ai/fake-gpu-operator/fake-gpu-operator \
--namespace fake-gpu-operator --create-namespace \
--version <VERSION> -f mixed-mode-values.yaml
Then:
nvidia.com/gpu capacity is published by the central kwok-gpu-device-plugin, so the disabled per-node DaemonSets aren't needed, and the KWOK NoSchedule taint keeps the real operator's DaemonSets off the simulated nodes.run.ai/simulated-gpu-node-pool. The status-updater only acts on nodes carrying that label, so the real operator keeps exclusive ownership of your hardware.To ensure proper functionality, configure Pod Security Admission for the gpu-operator namespace:
kubectl label ns gpu-operator pod-security.kubernetes.io/enforce=privileged
The operator injects a simulated nvidia-smi tool into GPU pods. nvidia-smi uses the
NODE_NAME environment variable to resolve the pod's node topology; both the device-plugin
and the DRA driver inject it automatically at allocation time, so no manual pod configuration
is required.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Go module
Latest release
Releases this year
155 followers ยท starred Jul 2025
79 followers ยท starred Nov 2023
96 followers ยท starred Jul 2026
573 followers ยท starred Mar 2026