Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/filters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ planner:
- 'components/src/dynamo/profiler/**'
- 'components/src/dynamo/aisimulate/**'
- 'components/src/dynamo/global_router/**'
# Re-run the SKU contract when either compared operator input changes.
- 'deploy/operator/hack/test_aisimulate_hardware_catalog.py'
- 'deploy/operator/internal/aisimulate/hardware_skus.txt'
- 'deploy/operator/api/v1beta2/dynamographdeploymentrequest_types.go'
# Keep this tombstone until the vendored-tree deletion has merged so the
# deleted paths in that PR remain covered by the Planner CI filter.
- 'aisimulate/**'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ rules:
- ""
resources:
- nodes
- pods/log
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
Expand All @@ -48,6 +49,12 @@ rules:
- list
- patch
- watch
- apiGroups:
- ""
resources:
- pods/log
verbs:
- get
- apiGroups:
- ""
resources:
Expand Down
32 changes: 27 additions & 5 deletions deploy/operator/api/v1beta2/dynamographdeploymentrequest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,44 @@ const (
// GPUHardwareSpec describes accelerator identity and the maximum allocation per candidate.
type GPUHardwareSpec struct {
// SKU identifies the accelerator using the AISimulate hardware catalog name.
// When omitted, admission discovers the accelerator from the cluster.
// Known values in the bundled AISimulate release are `a100_pcie`, `a100_sxm`,
// `a30`, `b200_sxm`, `b300_sxm`, `b60`, `gb200`, `gb300`, `h100_pcie`,
// `h100_sxm`, `h200_sxm`, `l4`, `l40s`, and `rtx_pro_6000_server`.
// The field remains open so an external AISimulate systems catalog can add identifiers.
// Admission passes an unknown identifier through unchanged and warns when `budget` and the
// parent `numGpusPerNode` are also explicit. Without both, it rejects the identifier because
// cluster discovery cannot associate capacity or topology with it. When omitted from a
// nonempty hardware object, admission discovers the accelerator.
// +optional
// +kubebuilder:validation:MinLength=1
SKU *string `json:"sku,omitempty"`

// Budget is the maximum total number of GPUs one candidate may use.
// When omitted, admission derives the available GPU budget from the cluster.
// When omitted for a known SKU, admission derives the available GPU budget from the cluster.
// An SKU unknown to the bundled AISimulate catalog requires an explicit `budget` and an
// explicit `numGpusPerNode` in the parent hardware object.
// +optional
// +kubebuilder:validation:Minimum=1
Budget *int32 `json:"budget,omitempty"`
}

// HardwareSpec bounds the accelerator configurations evaluated by the search.
// HardwareSpec bounds the accelerator configurations evaluated by the search. An exactly empty
// object opts out of operator cluster discovery and typed hardware defaults.
type HardwareSpec struct {
// GPU describes accelerator identity and the maximum allocation per candidate.
// Omitted fields are resolved independently by admission.
// In a nonempty hardware object, admission resolves an omitted SKU or budget from the cluster.
// +optional
GPU *GPUHardwareSpec `json:"gpu,omitempty"`

// NumGPUsPerNode is the number of GPUs available to one candidate on each node. It lowers
// to AISimulate's num_gpus_per_node input.
// An explicit value is authoritative. In a nonempty hardware object, admission discovers
// an omitted value from the selected cluster node shape. The Run controller never discovers
// this value. An exactly empty hardware object opts out of all operator hardware discovery
// and typed hardware defaults, leaving AISimulate input and defaults authoritative.
// +optional
// +kubebuilder:validation:Minimum=1
NumGPUsPerNode *int32 `json:"numGpusPerNode,omitempty"`
}

// KVLoadRatio is either a pinned load ratio or a minimum/maximum range.
Expand Down Expand Up @@ -237,7 +257,9 @@ type NativeSearchInput struct {
Backends []Backend `json:"backends,omitempty"`

// Hardware bounds the accelerator configurations evaluated by the search.
// Admission discovers any omitted hardware values from the cluster.
// When omitted, admission discovers SKU, budget and node width. An exactly empty object opts
// out of operator hardware discovery and typed defaults. Admission completes every nonempty
// object with any missing hardware values; explicit values remain authoritative.
// +optional
Hardware *HardwareSpec `json:"hardware,omitempty"`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
// DynamoGraphDeploymentRunSpec is an immutable, resolved copy of an accepted DGDR spec.
// Run creators resolve optional Request inputs before persisting the Run.
// +kubebuilder:validation:XValidation:rule="has(self.model)",message="model is required"
// +kubebuilder:validation:XValidation:rule="has(self.hardware) && ((!has(self.hardware.gpu) && !has(self.hardware.numGpusPerNode)) || (has(self.hardware.gpu) && has(self.hardware.gpu.sku) && has(self.hardware.gpu.budget) && has(self.hardware.numGpusPerNode)))",message="hardware must be exactly empty or fully resolved"
type DynamoGraphDeploymentRunSpec struct {
NativeSearchInput `json:",inline"`
}
Expand Down
5 changes: 5 additions & 0 deletions deploy/operator/api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9625,27 +9625,49 @@ spec:
hardware:
description: |-
Hardware bounds the accelerator configurations evaluated by the search.
Admission discovers any omitted hardware values from the cluster.
When omitted, admission discovers SKU, budget and node width. An exactly empty object opts
out of operator hardware discovery and typed defaults. Admission completes every nonempty
object with any missing hardware values; explicit values remain authoritative.
properties:
gpu:
description: |-
GPU describes accelerator identity and the maximum allocation per candidate.
Omitted fields are resolved independently by admission.
In a nonempty hardware object, admission resolves an omitted SKU or budget from the cluster.
properties:
budget:
description: |-
Budget is the maximum total number of GPUs one candidate may use.
When omitted, admission derives the available GPU budget from the cluster.
When omitted for a known SKU, admission derives the available GPU budget from the cluster.
An SKU unknown to the bundled AISimulate catalog requires an explicit `budget` and an
explicit `numGpusPerNode` in the parent hardware object.
format: int32
minimum: 1
type: integer
sku:
description: |-
SKU identifies the accelerator using the AISimulate hardware catalog name.
When omitted, admission discovers the accelerator from the cluster.
Known values in the bundled AISimulate release are `a100_pcie`, `a100_sxm`,
`a30`, `b200_sxm`, `b300_sxm`, `b60`, `gb200`, `gb300`, `h100_pcie`,
`h100_sxm`, `h200_sxm`, `l4`, `l40s`, and `rtx_pro_6000_server`.
The field remains open so an external AISimulate systems catalog can add identifiers.
Admission passes an unknown identifier through unchanged and warns when `budget` and the
parent `numGpusPerNode` are also explicit. Without both, it rejects the identifier because
cluster discovery cannot associate capacity or topology with it. When omitted from a
nonempty hardware object, admission discovers the accelerator.
minLength: 1
type: string
type: object
numGpusPerNode:
description: |-
NumGPUsPerNode is the number of GPUs available to one candidate on each node. It lowers
to AISimulate's num_gpus_per_node input.
An explicit value is authoritative. In a nonempty hardware object, admission discovers
an omitted value from the selected cluster node shape. The Run controller never discovers
this value. An exactly empty hardware object opts out of all operator hardware discovery
and typed hardware defaults, leaving AISimulate input and defaults authoritative.
format: int32
minimum: 1
type: integer
type: object
model:
description: Model identifies the model whose deployment configurations are evaluated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,27 +97,49 @@ spec:
hardware:
description: |-
Hardware bounds the accelerator configurations evaluated by the search.
Admission discovers any omitted hardware values from the cluster.
When omitted, admission discovers SKU, budget and node width. An exactly empty object opts
out of operator hardware discovery and typed defaults. Admission completes every nonempty
object with any missing hardware values; explicit values remain authoritative.
properties:
gpu:
description: |-
GPU describes accelerator identity and the maximum allocation per candidate.
Omitted fields are resolved independently by admission.
In a nonempty hardware object, admission resolves an omitted SKU or budget from the cluster.
properties:
budget:
description: |-
Budget is the maximum total number of GPUs one candidate may use.
When omitted, admission derives the available GPU budget from the cluster.
When omitted for a known SKU, admission derives the available GPU budget from the cluster.
An SKU unknown to the bundled AISimulate catalog requires an explicit `budget` and an
explicit `numGpusPerNode` in the parent hardware object.
format: int32
minimum: 1
type: integer
sku:
description: |-
SKU identifies the accelerator using the AISimulate hardware catalog name.
When omitted, admission discovers the accelerator from the cluster.
Known values in the bundled AISimulate release are `a100_pcie`, `a100_sxm`,
`a30`, `b200_sxm`, `b300_sxm`, `b60`, `gb200`, `gb300`, `h100_pcie`,
`h100_sxm`, `h200_sxm`, `l4`, `l40s`, and `rtx_pro_6000_server`.
The field remains open so an external AISimulate systems catalog can add identifiers.
Admission passes an unknown identifier through unchanged and warns when `budget` and the
parent `numGpusPerNode` are also explicit. Without both, it rejects the identifier because
cluster discovery cannot associate capacity or topology with it. When omitted from a
nonempty hardware object, admission discovers the accelerator.
minLength: 1
type: string
type: object
numGpusPerNode:
description: |-
NumGPUsPerNode is the number of GPUs available to one candidate on each node. It lowers
to AISimulate's num_gpus_per_node input.
An explicit value is authoritative. In a nonempty hardware object, admission discovers
an omitted value from the selected cluster node shape. The Run controller never discovers
this value. An exactly empty hardware object opts out of all operator hardware discovery
and typed hardware defaults, leaving AISimulate input and defaults authoritative.
format: int32
minimum: 1
type: integer
type: object
model:
description: Model identifies the model whose deployment configurations are evaluated.
Expand Down Expand Up @@ -281,6 +303,8 @@ spec:
x-kubernetes-validations:
- message: model is required
rule: has(self.model)
- message: hardware must be exactly empty or fully resolved
rule: has(self.hardware) && ((!has(self.hardware.gpu) && !has(self.hardware.numGpusPerNode)) || (has(self.hardware.gpu) && has(self.hardware.gpu.sku) && has(self.hardware.gpu.budget) && has(self.hardware.numGpusPerNode)))
status:
description: DynamoGraphDeploymentRunStatus represents one search execution's observed state.
properties:
Expand Down
93 changes: 93 additions & 0 deletions deploy/operator/hack/test_aisimulate_hardware_catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Keep DGDR's hardware hints synchronized with the pinned AISimulate wheel."""

from __future__ import annotations

import re
import tomllib
from importlib import metadata
from pathlib import Path

import pytest
import yaml
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name
from packaging.version import Version

pytestmark = [
pytest.mark.unit,
pytest.mark.gpu_0,
pytest.mark.pre_merge,
pytest.mark.planner,
pytest.mark.parallel,
]

REPO_ROOT = Path(__file__).resolve().parents[3]
CATALOG_PATH = REPO_ROOT / "deploy/operator/internal/aisimulate/hardware_skus.txt"
API_PATH = (
REPO_ROOT / "deploy/operator/api/v1beta2/dynamographdeploymentrequest_types.go"
)
PYPROJECT_PATH = REPO_ROOT / "pyproject.toml"


def _pinned_version() -> Version:
with PYPROJECT_PATH.open("rb") as stream:
dependencies = tomllib.load(stream)["project"]["dependencies"]
requirements = [
Requirement(dependency)
for dependency in dependencies
if canonicalize_name(Requirement(dependency).name) == "aisimulate"
]
assert len(requirements) == 1, "pyproject.toml must contain one AISimulate pin"

requirement = requirements[0]
specifiers = list(requirement.specifier)
assert requirement.url is None
assert len(specifiers) == 1 and specifiers[0].operator == "=="
return Version(specifiers[0].version)


def _packaged_hardware_skus() -> list[str]:
distribution = metadata.distribution("aisimulate")
system_files = [
path
for path in distribution.files or []
if path.parent.as_posix() == "aisimulate_core/systems"
and path.suffix == ".yaml"
]
result = []
for packaged_path in system_files:
with distribution.locate_file(packaged_path).open(encoding="utf-8") as stream:
document = yaml.safe_load(stream)
if isinstance(document, dict) and "gpu" in document and "node" in document:
result.append(packaged_path.stem)
return sorted(result)


def _documented_hardware_skus() -> list[str]:
source = API_PATH.read_text()
match = re.search(
r"Known values in the bundled AISimulate release are (.*?)\n\s*// The field remains open",
source,
re.DOTALL,
)
assert match is not None, "GPUHardwareSpec.SKU must document the bundled catalog"
return sorted(re.findall(r"`([^`]+)`", match.group(1)))


def _operator_hardware_skus() -> list[str]:
return sorted(
line.strip()
for line in CATALOG_PATH.read_text().splitlines()
if line.strip() and not line.lstrip().startswith("#")
)


def test_dgdr_hardware_catalog_matches_pinned_aisimulate() -> None:
assert Version(metadata.version("aisimulate")) == _pinned_version()

expected = _packaged_hardware_skus()
assert _operator_hardware_skus() == expected
assert _documented_hardware_skus() == expected
50 changes: 50 additions & 0 deletions deploy/operator/internal/aisimulate/hardware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package aisimulate

import (
_ "embed"
"strings"
)

// hardwareSKUs mirrors the system specifications packaged in the pinned AISimulate wheel.
// It is a compatibility hint for warnings and discovery, not an API enum: explicit
// identifiers outside this set remain valid and are passed through unchanged.
//
//go:embed hardware_skus.txt
var hardwareSKUsText string

var hardwareSKUs = func() map[string]struct{} {
result := map[string]struct{}{}
for line := range strings.SplitSeq(hardwareSKUsText, "\n") {
value := strings.TrimSpace(line)
if value == "" || strings.HasPrefix(value, "#") {
continue
}
result[value] = struct{}{}
}
return result
}()

// IsKnownHardwareSKU reports whether sku is packaged by the AISimulate release pinned by
// this Dynamo checkout. False does not make a value invalid: an external system catalog may
// support additional identifiers.
func IsKnownHardwareSKU(sku string) bool {
_, found := hardwareSKUs[sku]
return found
}
17 changes: 17 additions & 0 deletions deploy/operator/internal/aisimulate/hardware_skus.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

a100_pcie
a100_sxm
a30
b200_sxm
b300_sxm
b60
gb200
gb300
h100_pcie
h100_sxm
h200_sxm
l4
l40s
rtx_pro_6000_server
Loading