Skip to content
Merged
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
70 changes: 69 additions & 1 deletion docs/content/getting-started/build-the-platform.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ against this capacity without knowing which cluster it runs on.
- A GCP account with permissions to create GKE clusters, VPCs, and IAM roles
- A GCP service account JSON key
{{< /tab >}}
{{< tab "AKS" >}}
- An Azure account with permissions to create AKS clusters and managed identities
- An Azure service principal JSON with `clientId`, `clientSecret`, `subscriptionId`, and `tenantId`
{{< /tab >}}
{{< tab "Nebius" >}}
- A Nebius account with permissions to create clusters
- A Nebius service account JSON key and your project ID
{{< /tab >}}
{{< /tabs >}}

## Set up the InferenceGateway
Expand Down Expand Up @@ -99,6 +107,46 @@ curl -fsSL {{< manifest-url "getting-started/clusterproviderconfig-gke.yaml" >}}
```
{{< /editCode >}}
{{< /tab >}}

{{< tab "AKS" >}}
Create a Kubernetes secret from your service principal JSON:

{{< editCode >}}
```bash
kubectl create secret generic azure-credentials \
--from-file=credentials.json=$@</path/to/azure-sp>$@.json \
-n crossplane-system
```
{{< /editCode >}}

Apply the `ClusterProviderConfig` referencing your secret:

{{< manifests "getting-started/clusterproviderconfig-azure.yaml" >}}
{{< /tab >}}

{{< tab "Nebius" >}}
Create a Kubernetes secret from your service account JSON:

{{< editCode >}}
```bash
kubectl create secret generic nebius-credentials \
--from-file=credentials.json=$@</path/to/nebius-sa>$@.json \
-n crossplane-system
```
{{< /editCode >}}

Apply the `ClusterProviderConfig`, setting `projectID` to your Nebius project:

{{< manifests path="getting-started/clusterproviderconfig-nebius.yaml" apply="false" >}}

{{< editCode >}}
```bash
curl -fsSL {{< manifest-url "getting-started/clusterproviderconfig-nebius.yaml" >}} \
| sed 's/project-e00example/$@<your-nebius-project>$@/' \
| kubectl apply -f -
```
{{< /editCode >}}
{{< /tab >}}
{{</tabs>}}

## Publish hardware and register the cluster
Expand Down Expand Up @@ -136,13 +184,33 @@ Modelplane provisions the cluster. This takes about 15 minutes:
kubectl wait --for=condition=Ready ic/starter --timeout=20m
```
{{< /tab >}}

{{< tab "AKS" >}}
{{< manifests "getting-started/aks/platform.yaml" >}}

Modelplane provisions the cluster. This takes about 15 minutes:

```bash
kubectl wait --for=condition=Ready ic/aks-westeurope --timeout=20m
```
{{< /tab >}}

{{< tab "Nebius" >}}
{{< manifests "getting-started/nebius/platform.yaml" >}}

Modelplane provisions the cluster. This takes about 15 minutes:

```bash
kubectl wait --for=condition=Ready ic/nebius-eu-north --timeout=20m
```
{{< /tab >}}
{{< /tabs >}}

{{< hint "note" >}}
Modelplane is reconciling the infrastructure against the source of truth, the
manifest you just applied.

While you wait, Modelplane is creating the EKS or GKE cluster and its GPU node
While you wait, Modelplane is creating the cloud cluster and its GPU node
pool, then installing the inference stack with LeaderWorkerSet for multi-node
serving, llm-d for inference-aware routing, Envoy Gateway for traffic
management, and the storage class for model weights. This is the same reconciliation loop Crossplane uses to configure other
Expand Down
6 changes: 6 additions & 0 deletions docs/content/getting-started/deploying-a-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ The device selector matches against the capacity declared in the
{{< tab "GKE" >}}
{{< manifests "getting-started/gke/model-deployment.yaml" >}}
{{< /tab >}}
{{< tab "AKS" >}}
{{< manifests "getting-started/aks/model-deployment.yaml" >}}
{{< /tab >}}
{{< tab "Nebius" >}}
{{< manifests "getting-started/nebius/model-deployment.yaml" >}}
{{< /tab >}}
{{< /tabs >}}

Wait until `REPLICAS` shows `1`:
Expand Down
20 changes: 14 additions & 6 deletions docs/content/getting-started/scale-the-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ across two regions.
```mermaid
graph LR
subgraph fleet ["Fleet"]
IC1["region-a\nL4"]
IC2["region-b\nlarger GPU"]
IC1["cluster-a\nsmall GPU"]
IC2["cluster-b\nlarger GPU"]
end

subgraph ml ["ML team"]
MD1["ModelDeployment\nqwen-demo"]
MD2["ModelDeployment\nqwen-west\nclusterSelector: region-b"]
MD2["ModelDeployment\nqwen-west\ntargets cluster-b"]
MS["ModelService qwen\n/ml-team/qwen/v1/..."]
end

Expand All @@ -27,10 +27,12 @@ graph LR
MD2 --> MS
```

## Deploy to a second region
## Add a second deployment

The new deployment uses a `clusterSelector` to pin its replica to the `us-west`
cluster you added in the last step, and selects the larger GPU there:
The new deployment targets a larger-GPU cluster you added in the last step. On
EKS, GKE, and AKS it pins to a second region with a `clusterSelector`. On Nebius,
which runs one region per project, the capacity selector alone routes it to the
`H100` tier:

{{< tabs >}}
{{< tab "EKS" >}}
Expand All @@ -39,6 +41,12 @@ cluster you added in the last step, and selects the larger GPU there:
{{< tab "GKE" >}}
{{< manifests "getting-started/gke/model-deployment-west.yaml" >}}
{{< /tab >}}
{{< tab "AKS" >}}
{{< manifests "getting-started/aks/model-deployment-west.yaml" >}}
{{< /tab >}}
{{< tab "Nebius" >}}
{{< manifests "getting-started/nebius/model-deployment-scale.yaml" >}}
{{< /tab >}}
{{< /tabs >}}

Wait until its replica is `Ready`, then check placement. You now have one replica
Expand Down
33 changes: 28 additions & 5 deletions docs/content/getting-started/scale-the-platform.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ weight: 40
description: Grow from one cluster to a multi-region fleet.
---

You have one L4 cluster with a running model. In this guide, you'll add two
larger-GPU clusters in different regions to grow the fleet available to the ML team.
You have one small-GPU cluster with a running model. In this guide, you'll grow
the fleet with larger-GPU capacity so the ML team has more to schedule against.

Provisioning two more clusters takes about 10 to 15 minutes.
Provisioning takes about 10 to 15 minutes.

## Register more clusters

Expand Down Expand Up @@ -45,9 +45,32 @@ is a few dollars for this tour. Clean up when you're done (see [Clean
up]({{< ref "getting-started/clean-up.md" >}})).
{{< /hint >}}
{{< /tab >}}
{{< tab "AKS" >}}
Register two more clusters with a bigger hardware class: `A100` (`80 GB`) in
`eastus` and `southcentralus`:

{{< manifests "getting-started/aks/platform-scale.yaml" >}}

{{< hint "note" >}}
`Standard_NC24ads_A100_v4` runs ~$3.70/hr on demand. Two of them plus the `A10`
from earlier is a few dollars for this tour. Clean up when you're done (see [Clean
up]({{< ref "getting-started/clean-up.md" >}})).
{{< /hint >}}
{{< /tab >}}
{{< tab "Nebius" >}}
Nebius projects are bound to one region, so you grow the fleet by GPU tier rather
than geography. Register a bigger `H100` (`80 GB`) cluster in the same region:

{{< manifests "getting-started/nebius/platform-scale.yaml" >}}

{{< hint "note" >}}
The `H100` cluster costs more per hour than the `L40S` from earlier. Clean up
when you're done (see [Clean up]({{< ref "getting-started/clean-up.md" >}})).
{{< /hint >}}
{{< /tab >}}
{{< /tabs >}}

Modelplane provisions both clusters in parallel:
Modelplane provisions the new clusters in parallel:

```bash
kubectl wait --for=condition=Ready ic --all --timeout=20m
Expand All @@ -62,4 +85,4 @@ its deployment changes in a way that no longer fits where it runs.

## Next step

The fleet now spans three clusters across three regions. The ML team is next. [Scale the model]({{< ref "getting-started/scale-the-model.md" >}}) to serve it from two regions behind a single endpoint.
The fleet has grown with larger-GPU capacity. The ML team is next. [Scale the model]({{< ref "getting-started/scale-the-model.md" >}}) to serve it across the fleet behind a single endpoint.
37 changes: 37 additions & 0 deletions docs/manifests/getting-started/aks/model-deployment-west.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# A second ModelDeployment, pinned to a different region with clusterSelector.
# It carries its own deployment name, so the ModelService can front it alongside
# qwen-demo and serve both from one endpoint.
apiVersion: modelplane.ai/v1alpha1
kind: ModelDeployment
metadata:
name: qwen-west
namespace: ml-team
spec:
replicas: 1
# clusterSelector filters which InferenceClusters this deployment can land on.
# This pins the replica to the eastus cluster you added in "Scale the fleet".
template:
spec:
clusterSelector:
matchLabels:
modelplane.ai/region: eastus
engines:
- name: qwen
members:
- role: Standalone
nodeSelector:
devices:
- name: gpu
count: 1
selectors:
# A100 (81920Mi) qualifies; A10 (24564Mi) does not.
- cel: |
device.capacity["gpu.nvidia.com"].memory.compareTo(quantity("35Gi")) >= 0
template:
spec:
containers:
- name: engine
image: vllm/vllm-openai:v0.23.0
args:
- --model=Qwen/Qwen2.5-0.5B-Instruct
- --dtype=half
30 changes: 30 additions & 0 deletions docs/manifests/getting-started/aks/model-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apiVersion: modelplane.ai/v1alpha1
kind: ModelDeployment
metadata:
name: qwen-demo
namespace: ml-team
spec:
replicas: 1
template:
spec:
engines:
- name: qwen
members:
- role: Standalone
nodeSelector:
devices:
- name: gpu
count: 1
selectors:
# Any L4 satisfies >= 20Gi. The selector matches against the capacity
# declared in the InferenceClass, not the pod's resource requests.
- cel: |
device.capacity["gpu.nvidia.com"].memory.compareTo(quantity("20Gi")) >= 0
template:
spec:
containers:
- name: engine
image: vllm/vllm-openai:v0.23.0
args:
- --model=Qwen/Qwen2.5-0.5B-Instruct
- --dtype=half
62 changes: 62 additions & 0 deletions docs/manifests/getting-started/aks/platform-scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
apiVersion: modelplane.ai/v1alpha1
kind: InferenceClass
metadata:
name: a100-1x
spec:
description: "AKS Standard_NC24ads_A100_v4, 1x NVIDIA A100 80GB"
provisioning:
provider: AKS
aks:
vmSize: Standard_NC24ads_A100_v4
diskSizeGb: 100
accelerator:
type: nvidia-a100
count: 1
devices:
- name: gpu
claim: DRA
driver: gpu.nvidia.com
deviceClassName: gpu.nvidia.com
count: 1
attributes:
architecture: { string: Ampere }
cudaComputeCapability: { version: "8.0.0" }
capacity:
memory: { value: "81920Mi" }
---
# verify: Standard_NC24ads_A100_v4 quota/availability in eastus and southcentralus.
apiVersion: modelplane.ai/v1alpha1
kind: InferenceCluster
metadata:
name: aks-eastus
labels:
modelplane.ai/region: eastus
spec:
cluster:
source: AKS
aks:
location: eastus
nodePools:
- name: gpua100
className: a100-1x
nodeCount: 1
minNodeCount: 1
maxNodeCount: 1
---
apiVersion: modelplane.ai/v1alpha1
kind: InferenceCluster
metadata:
name: aks-southcentralus
labels:
modelplane.ai/region: southcentralus
spec:
cluster:
source: AKS
aks:
location: southcentralus
nodePools:
- name: gpua100
className: a100-1x
nodeCount: 1
minNodeCount: 1
maxNodeCount: 1
20 changes: 11 additions & 9 deletions docs/manifests/getting-started/aks/platform.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
apiVersion: modelplane.ai/v1alpha1
kind: InferenceClass
metadata:
name: a100-1x
name: a10-1x
spec:
description: "AKS Standard_NC24ads_A100_v4, 1x NVIDIA A100 80GB"
description: "AKS Standard_NV36ads_A10_v5, 1x NVIDIA A10 24GB"
provisioning:
provider: AKS
aks:
vmSize: Standard_NC24ads_A100_v4
diskSizeGb: 200
# verify: Azure has no L4 SKU; NVadsA10v5 is the small-GPU option (>=20Gi).
# Confirm it runs the DRA / GPU-operator path in your subscription.
vmSize: Standard_NV36ads_A10_v5
diskSizeGb: 100
accelerator:
type: nvidia-a100
type: nvidia-a10
count: 1
devices:
- name: gpu
Expand All @@ -20,9 +22,9 @@ spec:
count: 1
attributes:
architecture: { string: Ampere }
cudaComputeCapability: { version: "8.0.0" }
cudaComputeCapability: { version: "8.6.0" }
capacity:
memory: { value: "81920Mi" }
memory: { value: "24564Mi" } # verify: A10 reported VRAM (~24GB)
---
apiVersion: modelplane.ai/v1alpha1
kind: InferenceCluster
Expand All @@ -36,8 +38,8 @@ spec:
aks:
location: westeurope
nodePools:
- name: gpua100
className: a100-1x
- name: gpua10
className: a10-1x
nodeCount: 1
minNodeCount: 1
maxNodeCount: 1
Loading
Loading