Modelplane Modelplane docs

Qwen/Qwen2.5-0.5B on CPU

A 0.5B dense chat model served on CPUs alone, on a Civo CPU pool.

View on Hugging Face

A 0.5B dense chat model served on CPUs alone, on a Civo g4p.kube.medium pool: one Standalone engine, no cache, weights pulled straight from Hugging Face. A CPU pool is the cheapest way to exercise a Modelplane deployment end to end - no GPU to find, no driver to wait for - and a 0.5B model is small enough that CPU inference answers in sensible time.

The cluster pins Kubernetes 1.36: the claim consumes the device’s cores as consumable capacity, which Kubernetes enables by default from 1.36, on an older version the engine pod fails when the node prepares its claim.

The class’s provisioning block omits accelerator, which is what makes the pool a CPU pool: the cluster composition builds it with a standard node image and the modelplane.ai/cpu label and taint, and the serving stack installs dra-driver-cpu - and no GPU machinery - because the class claims dra.cpu. The claim pins the engine to its NUMA node’s cpuset, and the engine’s CPU request is the one number that sizes everything: Kubernetes accounts the pod by it, and Modelplane stamps it into the claim as the share of the device to take. Five of the six claimable cores, because the driver refuses a claim that would take them all - the node’s shared containers keep the last one. The engine is vLLM’s CPU build, whose image serves the same OpenAI API as the CUDA one. The same class without its provisioning block describes a bring-your-own CPU pool, on any conformant cluster: label its nodes modelplane.ai/pool=cpu-pool and modelplane.ai/cpu=true instead.

Validated deployments

Dense 0.5B 4,096 ctx vLLM
Cloud
AWS Google Cloud Azure Nebius Vultr B Bring your own
Accelerator
A16 16G L4 24G A100 40/80G H100 80G H200 141G MI325X 256G CPU
Serving mode
Standalone LeaderWorker PrefillDecode
Precision
Engine
vLLM SGLang llama.cpp
Image
vllm/vllm-openai-cpu:v0.32.0-x86_64

Platform

inference-class.yaml
# An InferenceClass describing a Civo CPU node pool - no GPU at all.
#
# The provisioning block has no accelerator, so the cluster composition
# builds the pool with the CPU role: a standard node image and the
# modelplane.ai/cpu label and taint, nothing GPU. The serving stack installs
# dra-driver-cpu when a class claims dra.cpu, which publishes each node's
# cores as claimable devices (one device per NUMA node) and registers the
# dra.cpu DeviceClass.
apiVersion: modelplane.ai/v1alpha1
kind: InferenceClass
metadata:
  name: civo-cpu-8vcpu
spec:
  description: "Civo g4p.kube.medium, 8 vCPUs, no GPU"
  provisioning:
    provider: Civo
    civo:
      size: g4p.kube.medium
  devices:
  - name: cpu
    claim: DRA
    driver: dra.cpu
    deviceClassName: dra.cpu
    count: 1
    capacity:
      # The node's 8 vCPUs less the two dra-driver-cpu reserves for the OS
      # and kubelet (reservedCPUs 0-1), which is what the driver's
      # ResourceSlices report as claimable.
      cpu: { value: "6" }
inference-cluster.yaml
# A Civo cluster in NYC1 with a single CPU node offering the class above.
# The composed ServingStack gets draDrivers: [dra.cpu], so dra-driver-cpu
# installs and the GPU device stacks stay out - a cluster with no GPU runs
# no GPU machinery.
apiVersion: modelplane.ai/v1alpha1
kind: InferenceCluster
metadata:
  name: civo-cpu
  labels:
    modelplane.ai/region: nyc1
spec:
  cluster:
    source: Civo
    civo:
      region: NYC1
      # CPU cores are claimed as consumable capacity, which Kubernetes
      # enables by default from 1.36 (DRAConsumableCapacity) - on an older
      # version the engine pod fails at NodePrepareResources. Civo's
      # default version may be older, so a CPU cluster pins it; list the
      # exact strings with: civo kubernetes versions.
      kubernetesVersion: 1.36.0-k3s1
  nodePools:
  - name: cpu-pool
    className: civo-cpu-8vcpu
    nodeCount: 1

Deployment

model-deployment.yaml
# A small model served on CPUs alone. The device selector matches against
# the claimable cores the civo-cpu-8vcpu class declares, so >= 4 lands it on
# the CPU pool; the claim takes the engine's CPU request, five cores, as
# capacity from the NUMA-node device and pins the engine container to a
# cpuset. The engine is vLLM's CPU build - the default
# vllm/vllm-openai image is CUDA-only. --block-size=32 is the smallest
# block its CPU attention backend accepts (multiples of 32); Modelplane
# reads the flag and chunks the endpoint picker's prefix cache the same
# way, so routing stays aligned. VLLM_CPU_KVCACHE_SPACE sets the KV
# cache's share of the node's RAM in GiB. No ModelCache: the engine pulls
# weights straight from Hugging Face.
apiVersion: modelplane.ai/v1alpha1
kind: ModelDeployment
metadata:
  name: qwen-cpu-demo
  namespace: ml-team
spec:
  replicas: 1
  template:
    spec:
      engines:
      - name: qwen
        members:
        - role: Standalone
          nodeSelector:
            devices:
            - name: cpu
              count: 1
              selectors:
              - cel: |
                  device.capacity["dra.cpu"].cpu.compareTo(quantity("4")) >= 0
          template:
            spec:
              containers:
              - name: engine
                image: vllm/vllm-openai-cpu:v0.32.0-x86_64
                args:
                - --model=Qwen/Qwen2.5-0.5B-Instruct
                - --served-model-name=$(MODELPLANE_SERVED_MODEL_NAME)
                - --max-model-len=4096
                - --block-size=32
                env:
                - name: VLLM_CPU_KVCACHE_SPACE
                  value: "4"
                # Sizes the pod's accounting and the DRA claim alike:
                # compose-model-replica stamps this as the claim's capacity
                # request, and the kubelet accounts CPUs only from requests
                # until KEP-5517. It must stay below the device's six
                # claimable cores - a whole-device claim exhausts the
                # shared CPU pool and the driver refuses to prepare it
                # while the node's DaemonSets need somewhere to run. Five
                # leaves them one claimable core.
                resources:
                  requests:
                    cpu: "5"
                  limits:
                    cpu: "5"
model-service.yaml
# Publishes the deployment's endpoints as one model a caller can name:
# <namespace>/<service>, so ml-team/qwen-cpu here. Modelplane composes one
# ModelEndpoint per ModelReplica, labeled with the deployment name, so
# this selector reaches every replica.
apiVersion: modelplane.ai/v1alpha1
kind: ModelService
metadata:
  name: qwen-cpu
  namespace: ml-team
spec:
  endpoints:
  - name: qwen-cpu-demo
    selector:
      matchLabels:
        modelplane.ai/deployment: qwen-cpu-demo