Skip to content

Install each device stack only where its driver is claimed, adding CPU pools and AMD serving - #11

Closed
negz wants to merge 4 commits into
v4-base-556from
v4-556
Closed

negz wants to merge 4 commits into
v4-base-556from
v4-556

Conversation

@negz

@negz negz commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Description of your changes

Design: modelplaneai#432

The serving stack installed NVIDIA's device stack on every cluster and no other vendor's anywhere: every component list pinned the NVIDIA operator or DRA driver unconditionally, nothing checked that the devices a class declares match the stack its cluster gets, and a pool without a GPU couldn't be provisioned at all.

This implements the multi-accelerator design. The cluster composition collects the DRA drivers claimed across a cluster's referenced classes (claim: DRA only; Synthetic devices describe placement, not a stack) onto ServingStack spec.draDrivers; components carry the drivers they serve as dra_drivers tags - hand-written in place, fail-closed through ALLOW for the generated clouds and the join installs a tagged component only when one of its drivers is claimed. The supported-drivers table is derived from the tags (plus EKS's declared-external dra.net), mirrored by hand in
compose-inference-cluster, which gates ClusterReady=False / UnsupportedDevices before anything is provisioned.

On top of the mechanism:

  • CPU inference (Docs: Add design for CPU inference class support modelplaneai/modelplane#509, rebased onto the mechanism): dra-driver-cpu is one chart tagged dra.cpu, pinned once in common.py for every cloud including Existing, with scoped tolerations, accelerator becomes optional in every provisioning block; a class without one composes a CPU-role pool with the modelplane.ai/cpu label and taint on all six clouds status.gpuPools becomes status.schedulablePools: nothing in it was GPU-specific.

  • AMD on BYO clusters: the AMD GPU operator (DRA mode, driver management off) is pinned on the Existing list tagged gpu.amd.com - the pin validated on 8x MI325X bare metal in Provision Vultr bare metal servers into k3s inference clusters, composing multi-vendor GPU serving stacks modelplaneai/modelplane#434, which rebases onto this mechanism. NFD carries both GPU drivers' tags there; engine pods tolerate the per-driver taint list (the pool nodeSelector is what places them).

  • The cloud e2e legs shape one KWOK pool per claimed device stack, an NVIDIA and a CPU pool on every leg, an AMD pool on Existing and stamp spec.draDrivers, so the placement and toleration audits now check the filter itself: each stack must reach its own pool and stay off the others

  • AMD GPU metrics: the collector's GPU job also discovers the AMD operator's device-metrics-exporter (label metrics-exporter, port exporter-port), and a modelplane-gpu-amd builtin maps its series onto the same modelplane_gpu_* names - VRAM MiB to bytes, activity percent to ratio, and energy through a new Microjoules fromUnit. modelplane_gpu_tensor_active_ratio stays NVIDIA-only.

  • Two recipes: Qwen2.5-0.5B on a single MI325X on a bring-your-own cluster and Qwen2.5-0.5B on CPUs alone on a Civo g4p.kube.medium pool with vLLM's CPU image.

The recipe site needs two follow-ups in its repo: the gpus data key the ayouts read is now accelerators, and partials/icons.html needs brand-amd, brand-civo and cpu symbols. https://github.com/modelplaneai/docs-site/blob/main/themes/geekboot/layouts/partials/icons.html -> modelplaneai/docs-site#5

I have:

  • Read and followed Modelplane's contribution process.
  • Run nix flake check (or ./nix.sh flake check) and made sure it passes.
  • Added or updated tests covering any composition function changes.
  • Signed off every commit with git commit -s.

…pdate inferenceClusters, servingStack

Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>

@modelplane-bot modelplane-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this needs another look at the design before it's close. The draDrivers mechanism matches design/multi-accelerator.md, but the CPU pools built on it raise questions the design didn't settle.

The open points are the source of a pool's role, dra-driver-cpu's grouped mode on the GKE, AKS and Nebius default versions, and CPU requests for a dra.cpu claim. Apart from the design, the new UnsupportedDevices gate looks like it could delete running clusters, which is the most urgent question here. I've held back other test and style points until the design settles.

The commit messages need bodies and CONTRIBUTING's subject format, without typed prefixes, and the Vale fixup commit should fold into the commit it fixes.

# label and taint composed by compose-gke-cluster.
node_pool = gkev1alpha1.NodePool(
name=pool.name,
role="GPU" if prov.accelerator else "CPU",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should a pool's role follow the drivers its class claims, rather than whether it sets accelerator? The design says taints follow the stack, but here accelerator alone decides. An EKS class with a gpu.nvidia.com device and no accelerator becomes a CPU pool with a standard AMI and the CPU taint, while schedulablePools still advertises its GPUs. A class with accelerator that claims dra.cpu misses the label dra-driver-cpu selects on. Its claims never allocate. Could we derive the role from the claimed drivers, or tie the two with CEL? (Here and in the other five clouds.)

composition from the claim: DRA devices of the cluster's
referenced InferenceClasses. A component tagged with a
driver installs only when that driver is listed; untagged
components always install. When the field is absent no

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do we need the absent-means-unfiltered case? compose-inference-cluster always stamps draDrivers, so the None path only serves ServingStacks from before this PR, and AGENTS.md rules out compatibility paths like that. It also gives unset and empty different meanings. What if absent meant no drivers, and the None branches in join and compose-serving-stack's fn.py went along with the "When the field is absent" sentence?

# tolerate nothing, which strands the daemonsets off the very nodes
# they serve.
Chart(
key="amd-gpu-operator",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should the existing-cluster requirements in docs/content/platform/inference-cluster.md cover the new stacks? Besides covering only the NVIDIA driver and nvidia.com/gpu taint, the page says Modelplane doesn't install a GPU exporter, though the AMD operator now brings device-metrics-exporter. AMD pools need the amdgpu kernel driver and mustn't already run AMD's operator, which would clash with this one. CPU pools need the modelplane.ai/cpu=true label dra-driver-cpu's nodeSelector reads. telemetry.md still says "An existing cluster provides its own" exporter too.

@negz negz closed this Nov 2, 2026
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.

2 participants