AI Agents

  • Fixed

    • Triton’s kernel cache lives on the cache claim beside vLLM’s compiled graphs: the redirect rule sets TRITON_CACHE_DIR=/mnt/vllm-cache/triton on the runtime container (giantswarm/agent-platform#572). Triton — the compiler behind every @triton.jit kernel a model runs, the GDN and Mamba kernels of the hybrid architectures among them — keeps its compiled kernels and, with vLLM’s default TRITON_CACHE_AUTOTUNING=1, its autotuning results in ~/.triton/cache inside the container, gone with the pod. A preset that compiles had them on the claim already: at compile init vLLM redirects TRITON_CACHE_DIR in-process to a directory under VLLM_CACHE_ROOT’s torch_compile_cache/. A preset that serves --enforce-eager — six of the shipped ones — never compiles, so that redirect never ran and every pod recompiled and re-autotuned its kernels during the profiling run. The redirect rule now sets TRITON_CACHE_DIR=/mnt/vllm-cache/triton in the same patch as VLLM_CACHE_ROOT: a directory of the claim’s .vllm-cache beside torch_compile_cache/, which Triton creates as the runtime’s uid under the setgid root:1000 2775 directory exactly as vLLM creates torch_compile_cache/; a compiled preset is unchanged (vLLM’s redirect takes over at compile init, the env is the floor for what Triton compiles before it), and a pod without the cache keeps Triton’s default as it keeps vLLM’s. make verify-model-serving-policies holds both envs on the runtime container of both shapes, both under the cache mount, and neither on a pod without the cache. README “vLLM’s compile cache and Triton’s kernel cache on the claim”.
  • Changed

    • The serving slice’s llm-d images come from the llm-d-fast/ prefix, and the pre-pull names that image (giantswarm/agent-platform#568). The well-known LLMInferenceServiceConfigs and the pre-pull DaemonSet named the runtime image as mirrored byte-identically, gsoci.azurecr.io/giantswarm/llm-d-cuda:v0.8.0 — 8.2 GiB compressed in gzip layers of 5.5, 1.4 and 1.0 GB. containerd pulls one HTTPS stream per layer, so the stream carrying the 5.5 GB layer bounded the pull: about 60 MB/s effective, 132–149 s on a fresh pool node where one stream measures 95 MB/s and four 306 MB/s, and the operands pulled beside it ran three to five times slower meanwhile — with the pre-pull in place, the longest cold-start step left. giantswarm/llm-d publishes the same images re-layered under llm-d-fast/ of the same registry path (zstd, llm-d-cuda split into layers of at most 1.2 GB). The meta chart’s kserve-runtime-configs: block now passes kserve.llmisvcConfigs.imageRegistry: gsoci.azurecr.io/giantswarm/llm-d-fast/, so every reference the configs render carries the prefix, and modelServing.prepull.images defaults to gsoci.azurecr.io/giantswarm/llm-d-fast/llm-d-cuda:v0.8.0 in both charts, so the pre-pull warms exactly the image the predictor runs. Going back is two values: the block’s imageRegistry at gsoci.azurecr.io/giantswarm/ (the byte-identical mirror) or ghcr.io/llm-d/ (upstream), and the matching reference in prepull.images. make verify-serving-slice asserts the prefix on the rendered release and that the pre-pull’s reference carries it; verify-gpu-pool leaves the pre-pull DaemonSet out of the golden comparison while origin/main names the mirror’s image. README “The well-known configs” and “Pre-pulling the runtime image”; UPGRADE.md.
  • Changed

    • The cache claim’s defaults are 100Gi at 500 MiB/s and 3000 IOPS; the default class is named with a digest of its parameters, and an existing claim keeps its class and its size (giantswarm/agent-platform#570). The claim defaulted to 500Gi on a gp3 class at 1000 MiB/s and 4000 IOPS — sized far above what the serving slice uses: the shipped presets hold 8–9 GiB (L4) and 25 GiB (L40S) of weights each plus a few GiB of compiled graphs, so two served models fit in well under 40 GiB, and the volume is billed at its size from the first bind; vLLM loads a model’s weights at about 300 MB/s on a 1000 MiB/s volume exactly as on a 500 MiB/s one (the loader is the ceiling, measured on two GPU instance sizes) and the storage-initializer writes at the Hub’s ≈ 200 MB/s, so only the baseline tier bounds either. At list prices the old volume cost about three and a half times the new one a month for the same load and download times. modelServing.cache.pvc.size is now 100Gi, modelServing.cache.storageClass.parameters throughput: "500", iops: "3000" (the tier’s included IOPS), mirrored in the meta chart. Two mechanics made the change safe for an installation that already has the claim: a StorageClass’s provisioner and parameters are immutable, so the default class name now carries eight hex characters of their sha256 (agent-platform-connectivity-hf-cache-<digest>; agent-platform.modelServing.cacheStorageClass.digest) and a parameter change renders a new class, the old Helm-owned one going with the upgrade instead of a forbidden update failing the release (a name of the operator’s carries no digest); and the hook applies an existing claim on the class it has (storageClassName is immutable) at the larger of its size and the rendered one (quantities compared in bytes; a claim never shrinks), its log line naming what was kept — a Pending claim on a class the cluster no longer has fails the hook naming the claim and the way out (delete it; the next upgrade applies it on the rendered class). make verify-serving-slice asserts the defaults and the digest name, that a parameter change renders a new class the claim references, and runs the hook’s script against a stub kubectl over the cases (a Bound 500Gi claim on the former class kept as it is, a smaller one grown, an equal size in another spelling left alone, the Pending-on-a-missing-class refusal, a claim without a class or on the empty class). README “The Hugging Face cache claim” and “The claim’s StorageClass”; UPGRADE.md.
  • Fixed

    • Preset qwen3-8-flash-next-nvfp4: memory limit 118Gi (was 64Gi) (giantswarm/agent-platform#553). The B12X serving stack keeps the resident weights as mlock()ed managed memory of the engine process — file-backed pages the memory cgroup charges to the container, unlike the cudaMalloc device memory other runtimes hold outside the cgroup — so the first live serve was OOM-killed by the container limit at 61 GiB of file RSS while the node had 100 GiB free. The limit now covers the resident weights (about 74 GiB with the n-gram table read from disk, up to the 99 GiB checkpoint) plus the runtime; the request stays at 48Gi so the predictor schedules on a node that also hosts platform pods. The same stack needs the node’s containerd to run with LimitMEMLOCK=infinity (it mlock()s every weight allocation and a container has no CAP_IPC_LOCK): giantswarm/agent-platform#564.
  • Fixed

    • The serving namespace is kept on uninstall whatever the cache switch says, so a slice composed without the cache no longer deletes a cache claim an earlier slice left there (giantswarm/agent-platform#565). The connectivity chart put helm.sh/resource-policy: keep on the serving namespace only while modelServing.cache.enabled was on; a slice composed with the cache off rendered the namespace as an ordinary Helm-owned object, and its uninstall deleted the namespace — and with it the hf-cache claim an earlier slice (cache on) had applied there, the claim’s own keep policy notwithstanding (a namespace Helm deletes takes every object in it along). On an installation the claim was still Pending, so nothing was lost; a Bound claim’s volume (the class reclaims with Delete) would have gone, with the weights and compiled graphs on it. Now the namespace carries the policy whenever the chart creates it; the new modelServing.namespace.keep (default true) is the one way to the old behaviour — false renders the namespace without the policy, and the namespace and everything in it, a cache claim included, go with the release. The meta chart mirrors the block it forwards, and make verify-meta holds the two copies equal (tests/verify-mirrored-values.py). README “What an uninstall leaves behind” states the lifecycle in one paragraph, independent of the cache switch: the namespace outlives the release, the claim outlives the pool, and removing either is a person’s explicit act. make verify-serving-slice asserts the policy with the cache on and off and its absence with namespace.keep: false.
  • Fixed

    • A remote server’s spec.timeout governs each tool call. The value bounded the first connect, the health probe and the session-recovery handshake, while the requests themselves – a tool call above all – ran under the caller’s context alone: a tool that legitimately blocks for two minutes behind a server declaring timeout: 180 was cut whenever the caller’s connection gave up first, and muster reported the cut as a transport failure of the POST (transport error: failed to send request: Post "http://..."). Now every operation on a remote client – each request, and a recovery handshake on the way – runs under the server’s spec.timeout, the CRD’s default of 30 s when unset, so the documented range (1-300) holds end to end; a call that outlasts the budget fails with no answer within the server's timeout of 60s: context deadline exceeded rather than a transport error. The session-scoped clients of servers that need a sign-in get the same budget through their registration. (#1281)
  • Changed

    • (golden) Drop the holds GOLDEN_REF now carries — every one only narrowed the comparison, and three of them compared a shape no installation gets in #531 by @fiunchinho

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.38.0...v4.38.1

  • Added

    • Preset qwen3-8-flash-next-nvfp4: Qwen3.8-Flash-Next NVFP4 on one 128 GB unified-memory Blackwell node, served from an OCI model image on an installation-defined runtime (giantswarm/agent-platform#553). Qwen3.8-Flash-Next (125B total, 6B active, a 51B n-gram embedding table, 262k context, tools, reasoning, vision) had no shipped preset: NVIDIA’s NVFP4 checkpoint is 123.6 GiB on disk and does not fit the node next to a KV cache. The new preset serves the quantization-aware-distilled repack local-inference-lab/Qwen3.8-Flash-Next-NVFP4 (98.6 GiB, the n-gram table in NVFP4 too) with the community’s single-node recipe: the table read from disk (VLLM_PLE_TABLE_MEMORY=disk), MTP speculative decoding (--speculative-config='{"method":"mtp","num_speculative_tokens":4}'), the B12X load format, GDN decode kernel, linear and MoE backends, --quantization=modelopt_mixed, --kv-cache-dtype=fp8, --mamba-cache-mode=align, prefix caching, chunked prefill, --max-model-len=262144, --max-num-seqs=8, --max-num-batched-tokens=4096, --gpu-memory-utilization=0.80, the qwen3 reasoning parser, tools on with the qwen3_xml parser, fused activation quantization (--compilation-config='{"pass_config":{"fuse_act_quant":true}}'); nine runtime knobs (CUTE_DSL_ARCH=sm_121a, SAFETENSORS_FAST_GPU, VLLM_WORKER_MULTIPROC_METHOD=spawn, VLLM_SSM_CONV_STATE_LAYOUT=DS, the AOT compile flags, VLLM_USE_V2_MODEL_RUNNER, B12X_POLICY_MODE=auto) plus HOME, HF_HOME and VLLM_CACHE_ROOT under /tmp, because the modelcar path runs the runtime as a non-root uid on a read-only image; requests 4 vCPU / 48 GiB, limits 8 / 64 GiB; weightsGiB: 99 (the Hub’s size), overheadGiB: 20 (the table stays on disk, so about 74 GiB are resident and the rest of the 0.80 budget is the KV cache); predictor.timeout: 1800; licence other (the Qwen community licence). Two firsts among the shipped presets: spec.runtime: kserve-vllm-b12x names an additional runtime the installation defines from the B12X image through modelServing.additionalRuntimes (giantswarm/agent-platform#550) — the default upstream runtime cannot serve this architecture, and without the entry the InferenceService names a runtime that does not exist — and spec.model.storageUri is oci://gsoci.azurecr.io/giantswarm/models/qwen3-8-flash-next-nvfp4:7c4f1bc1a2d6 (the tag the first 12 characters of the checkpoint revision), the model as an OCI image a registry holds under that path, its host swapped by modelServing.modelImages.registry and pre-pulled through modelServing.prepull.modelPresets (giantswarm/agent-platform#551). make verify-wiring expects eleven shipped presets; verify-preset-weights and the eval check of verify-serving-slice cover the new file; verify-gpu-pool leaves the preset’s ConfigMap and its discovery entry out of the golden comparison while origin/main predates it.
  • Added

    • Models as OCI images: a registry override for oci:// presets and pre-pull of the named presets’ images on the serving nodes (giantswarm/agent-platform#551). A preset’s weights came from Hugging Face into the cache claim, and the claim binds one node (a local volume) or one zone (a cloud disk): a model served only where the claim was, every other GPU node or zone downloaded it again, and nothing verified what arrived. KServe’s modelcar path — spec.model.storageUri: oci://<registry>/<path>[:tag|@digest], the model as an OCI image containerd pulls onto the node’s local disk, no storage-initializer, no claim — was admitted by the preset schema and left alone by the cache redirect rule, but nothing pointed a preset’s image at an installation’s registry and the pre-pull DaemonSet knew only runtime images. modelServing.modelImages.registry (empty: references as written) swaps the registry host of every oci:// preset at render, a shipped preset’s and a values preset’s alike — the segment before the first /, the path, tag or digest kept — inside the preset resolution, so the preset ConfigMap, the pre-pull DaemonSet and every consumer read one value; hf:// presets are untouched; a registry with a scheme or a path, and an oci:// reference without a host, fail the render naming it. The discovery ConfigMap publishes the host as spec.modelImages.registry. modelServing.prepull.modelPresets names the presets whose images join the pre-pull DaemonSet’s init containers after the runtime images — pull-model-<preset> on the published reference minus oci://, /bin/true (a modelcar image is busybox-based), the runtime init containers’ security context and resources — so every serving node holds the weights before the first serve and the running pause pod keeps the image against the node’s image garbage collection; a name that is not a published preset, is not oci:// or is listed twice fails the render naming it. The meta chart mirrors both blocks (make verify-meta holds every leaf equal); make verify-model-images (new, in verify-all) holds the rewrite over the fixture ci/test-model-serving-oci-values.yaml (an oci:// and an hf:// values preset, every shipped preset by its own scheme, an empty registry as written, a digest kept), the init containers, the guards and the meta chart’s forwarded render; make verify-model-serving-policies holds the pod with a model init container to the fleet’s restricted PSS with no exception. README: “Models as OCI images” — the modelcar contract (/models in a busybox-based image, the runtime’s uid and the /tmp paths an OCI preset sets, what the cache redirect does not do for these pods).
  • Added

    • modelServing.additionalRuntimes: more vLLM ClusterServingRuntimes beside the default, rendered from the same template (giantswarm/agent-platform#550). modelServing.runtime rendered exactly one runtime and every preset ran on it; an installation whose accelerator needs a different serving image for some models — a hardware-specific vLLM build for a unified-memory Blackwell node next to the upstream image the other presets run on — had to move every preset to that image or hand-write a runtime outside the chart. The new list renders one ClusterServingRuntime per entry: name required, a DNS-1123 subdomain and unique across the default and the list (a name already used fails the render naming the entry); every other field — image (registry, name, version), args, env, resources, shmSize, startupProbe, annotations, supportedModelFormats, nodeSelector, tolerations — the default runtime’s where the entry leaves it unset (a mapping field by field, a list whole: an entry’s env replaces the default’s two entries rather than extending them; an empty value inherits too); the GPU pool’s taint and label applied as on the default; the container on the same shell entrypoint (/bin/sh -c 'eval "exec vllm serve $@"' --) with the entry’s own base arguments, so its image must carry /bin/sh and vllm on PATH as well. A preset selects a runtime with spec.runtime. The discovery ConfigMap publishes every runtime’s name as spec.runtimes (the default first) next to spec.runtime; an empty list renders the default alone, and the default runtime’s document is unchanged. Mirrored in the meta chart, and tests/verify-mirrored-values.py (make verify-meta) now holds the modelServing.runtime and modelServing.additionalRuntimes blocks equal between the charts too — an additional runtime inherits from the forwarded default. make verify-runtimes asserts the render over ci/test-model-serving-runtimes-values.yaml (inheritance, the pool on every runtime, the entrypoint, the discovery list, the guards, the meta chart’s forwarded render); README “Additional runtimes”.