Home / AI & GenAI

AI & GenAITroubleshootingAdvanced

KServe InferenceService stuck not Ready: storage-initializer failures, Knative revision deadlines, probes and GPU scheduling

An InferenceService that never reaches Ready usually fails in one of four places - model download, revision rollout, readiness probes or GPU scheduling. Read the conditions, then go straight to the right layer.

Deepak Bhardwaj7 min read

Applies toKServe 0.11 and later (Serverless/Knative and RawDeployment/Standard modes), Knative Serving, Kubernetes 1.27+, NVIDIA GPU nodes

Symptom

kubectl get isvc shows READY as False or Unknown, URL is empty or points at an old revision, and clients get 404, 503 or connection resets. A large LLM is often involved, because the default timeouts and disk sizes in KServe and Knative were set for small models.

Work top-down: conditions first, then the pod, then the layer the conditions point to.

Check 1: read the conditions

NS=llm; ISVC=llama-chat

kubectl get isvc "$ISVC" -n "$NS"

kubectl get isvc "$ISVC" -n "$NS" -o jsonpath=\
'{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'

The message field is the most useful single line KServe gives you. Map it to a layer:

Condition false Usual layer Go to
PredictorReady with RevisionMissing or RevisionFailed Knative revision never became ready Check 3
PredictorReady, pod in Init storage-initializer download Check 2
PredictorReady, pod Running but not ready Model server still loading or crashing Check 4
PredictorReady, pod Pending GPU or node scheduling Check 5
IngressReady with ReconcileError Ingress config (Istio, Gateway API, Kourier) Check 6

Condition names and reasons vary by KServe version; check your build's output rather than matching strings in alerts.

Check 2: the storage-initializer

KServe injects an init container named storage-initializer that copies the model from storageUri into /mnt/models before the server starts.

kubectl get pods -n "$NS" -l serving.kserve.io/inferenceservice="$ISVC"
POD=$(kubectl get pods -n "$NS" -l serving.kserve.io/inferenceservice="$ISVC" -o name | head -1)

kubectl logs -n "$NS" "$POD" -c storage-initializer --tail=50
kubectl describe -n "$NS" "$POD" | sed -n '/Init Containers/,/Containers:/p'
Log or state Cause Fix
403, AccessDenied, NoCredentialProviders The service account has no storage credentials Set spec.predictor.serviceAccountName to an account with an annotated S3/Azure secret or workload identity
401 or GatedRepoError on hf:// Missing Hugging Face token Mount HF_TOKEN from a secret; confirm the account accepted the model licence
Init container OOMKilled Default init container limits are too small for large archives Raise the storage initializer resources in the inferenceservice-config ConfigMap
Pod Evicted, ephemeral-storage 15-140 GB of weights in an emptyDir on a small node disk Larger node OS disk, or switch to a PVC (pvc://) or OCI modelcar (oci://)
Download restarts from zero after every crash Weights are re-copied on each pod start Pre-stage weights on a PVC or a modelcar image so restarts are cheap
kubectl get configmap inferenceservice-config -n kserve -o jsonpath='{.data.storageInitializer}'

Tip

For models over ~20 GB, stop downloading on every pod start. A ReadWriteMany PVC populated once, or a modelcar image cached on the node, cuts cold start from many minutes to seconds and removes the init container as a failure point. Check that your KServe version has modelcar enabled before you use oci://.

Check 3: Knative revision never becomes ready (Serverless mode)

In Serverless mode each spec change creates a Knative revision. If the revision is not ready inside the progress deadline, Knative marks it failed and the ISVC reports RevisionMissing or keeps serving the previous revision.

kubectl get ksvc -n "$NS"
kubectl get revision -n "$NS" -l serving.knative.dev/service="${ISVC}-predictor"
kubectl describe revision -n "$NS" <revision-name> | sed -n '/Conditions/,$p'

The Knative service name is <isvc>-predictor on recent versions and <isvc>-predictor-default on older ones.

Revision message Cause Fix
Initial scale was never achieved or ProgressDeadlineExceeded Model load (download plus GPU load) takes longer than the deadline Raise serving.knative.dev/progress-deadline on the predictor
Unable to fetch image Knative resolves image tags to digests and cannot reach your private registry Add the registry to registries-skipping-tag-resolving in config-deployment, or give the controller pull credentials
ContainerMissing Image does not exist or is not readable Fix the image reference or pull secret
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llama-chat
  namespace: llm
  annotations:
    serving.knative.dev/progress-deadline: "1800s"
    autoscaling.knative.dev/min-scale: "1"
spec:
  predictor:
    minReplicas: 1
    model:
      modelFormat:
        name: huggingface
      storageUri: pvc://models-pvc/llama-3.1-8b-instruct
      resources:
        limits:
          nvidia.com/gpu: "1"
          memory: 48Gi
        requests:
          cpu: "6"
          memory: 48Gi

Warning

With minReplicas: 0, every scale-from-zero runs the full model download and load path. For LLMs this means minutes of 503s or held requests. Keep at least one replica for anything user-facing.

Many teams running LLMs move to RawDeployment mode (called Standard on newer releases). It removes Knative revisions and the queue-proxy sidecar, so a plain Deployment and HPA or KEDA handle scaling. You lose scale-to-zero and revision-based canaries.

kubectl annotate isvc "$ISVC" -n "$NS" serving.kserve.io/deploymentMode=RawDeployment --overwrite

Check whether your version accepts the mode change on an existing ISVC; some versions need the resource deleted and recreated.

Check 4: running but never ready

The model server is up but its readiness probe fails, or it crash-loops after loading.

kubectl logs -n "$NS" "$POD" -c kserve-container --tail=100
kubectl logs -n "$NS" "$POD" -c kserve-container --previous --tail=100
kubectl get pod -n "$NS" "${POD#pod/}" -o jsonpath='{.status.containerStatuses[*].restartCount}'
Log Cause Fix
CUDA out of memory during load Model plus KV cache exceed GPU memory Lower --max-model-len, use a quantized model, or more GPUs with tensor parallel
No CUDA GPUs are available GPU requested but runtime or driver missing on node Check the GPU Operator and the node's nvidia.com/gpu allocatable
Killed by liveness probe mid-load Probe fires before weights finish loading Add a startupProbe with a long enough window
trust_remote_code errors Model needs custom code Prefer a model that doesn't; if you must, pin the revision and pass the flag deliberately

Pass runtime flags as args on the predictor. With the Hugging Face runtime and the vLLM backend, engine flags pass through, but which flags are accepted depends on the runtime image version.

Check 5: pending on GPUs

kubectl describe pod -n "$NS" "$POD" | sed -n '/Events/,$p'
kubectl get nodes -l nvidia.com/gpu.present=true \
  -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\\.com/gpu

Insufficient nvidia.com/gpu means every GPU is allocated or the cluster autoscaler can't add a node. untolerated taint means the predictor is missing the GPU node pool toleration. Add tolerations and a nodeSelector under spec.predictor.

A subtle case: during a Serverless rollout, the new revision needs its own GPU while the old one still holds one. On a fully packed pool the new revision stays Pending until the progress deadline expires, so each update fails. Keep one spare GPU of headroom, or use RawDeployment with maxSurge: 0 and accept a short capacity dip.

Check 6: ingress

kubectl get configmap inferenceservice-config -n kserve -o jsonpath='{.data.ingress}'
kubectl get virtualservice,httproute -n "$NS" 2>/dev/null

IngressReady=False with ReconcileError usually means the ingress config expects Istio while the cluster runs something else, or the ingress gateway named there doesn't exist. Fix the ConfigMap, then restart the KServe controller.

Prevent it next time

  • Stage weights on a PVC or modelcar image, not a per-pod download.
  • Set progress-deadline, startupProbe and minReplicas from the measured cold-start time of each model, plus a margin.
  • Keep one spare GPU per node pool used for rollouts, or use RawDeployment for large models.
  • Alert on an ISVC that stays not Ready for more than 15 minutes, using kube-state-metrics custom resource state or a simple CronJob that reads .status.conditions.
  • Record image digest, storageUri and runtime version for every ISVC so you can diff a failing rollout against the last good one.