Skip to content

feat(crd): extend InferenceService vLLM config for agentic-coding flags#306

Merged
Defilan merged 2 commits into
mainfrom
feat/vllm-agentic-coding-flags
Apr 21, 2026
Merged

feat(crd): extend InferenceService vLLM config for agentic-coding flags#306
Defilan merged 2 commits into
mainfrom
feat/vllm-agentic-coding-flags

Conversation

@Defilan

@Defilan Defilan commented Apr 21, 2026

Copy link
Copy Markdown
Member

Why

Running Qwen3.6-35B-A3B-FP8 on a dual RTX 5060 Ti for agentic-coding workloads exposed the current VLLMConfig as too narrow. Users had to drop to `extraArgs` escape hatches to get prefix caching, chunked prefill, FP8 KV cache, FLASHINFER attention, or speculative decoding — defeating the point of a typed CRD.

This PR makes those first-class. Closes #305.

What's added

New `VLLMConfig` fields (all optional, all nil-default)

Field vLLM flag Notes
`kvCacheDtype` `--kv-cache-dtype` enum `auto`, `fp8_e5m2`, `fp8_e4m3`
`enablePrefixCaching` `--enable-prefix-caching` boolean, opt-in-only emit
`enableChunkedPrefill` `--enable-chunked-prefill` boolean, opt-in-only emit
`maxNumBatchedTokens` `--max-num-batched-tokens` int32, min 512
`speculative.{enabled,model,numSpeculativeTokens}` `--speculative-model` + `--num-speculative-tokens` struct; enabled-without-model surfaces a status condition, doesn't block reconcile
`enableExpertParallel` `--enable-expert-parallel` MoE-specific
`attentionBackend` `--attention-backend` existing field, enum widened to include `FLASH_ATTN`, `FLASHINFER`, `XFORMERS` (uppercase native vLLM values). Lowercase kept for back-compat.

Model `Quantization` enum

Added `fp8`, `nvfp4`, `compressed-tensors`. Existing values unchanged.

New status condition

`VLLMSpecValid`. Flips to `False / SpeculativeMissingModel` when `speculative.enabled=true` but `speculative.model` is empty. Does not block reconcile — the Deployment still renders with the speculative flags omitted.

Sample manifest

`config/samples/inferenceservice_qwen36_35b_fp8_agentic.yaml` — full agentic-coding config targeting 2× RTX 5060 Ti with Qwen3.6-35B-A3B-FP8, TP=2, FP8 KV cache, all the performance flags above.

Rules for `BuildArgs`

  • Opt-in-only for booleans: `EnablePrefixCaching=nil` or `false` → no flag emitted (vLLM uses its own default). The inverse for backwards compat.
  • Deterministic arg ordering so tests don't flake.
  • Speculative misconfig: logged via package logger, surfaces as status condition on the InferenceService, does not fail reconcile.

Tests

`TestVLLMBuildArgs`: 20 table-driven rows covering every new flag × emit/no-emit combinations. `TestVLLMBuildArgsDeterministic` asserts stable ordering. `TestValidateVLLMConfig` covers the speculative-misconfig path.

```
$ make manifests generate fmt vet lint test
(all clean; internal/controller at 83.1% coverage)
$ make docker-build
(ghcr.io/defilantech/llmkube-controller built, distroless multi-arch)
```

Live smoke test on Shadowstack

Built as `registry.defilan.net/llmkube-controller:0.7.1-dev-vllm-flags`, rolled out on a real 2× RTX 5060 Ti microk8s cluster. Verified:

  1. Updated CRDs accepted via `kubectl apply -f config/crd/bases/`
  2. Controller rolled out clean against the new CRDs
  3. `kubectl explain inferenceservice.spec.vllmConfig.kvCacheDtype` shows the enum; `.speculative` shows the sub-fields
  4. Applied the sample — rendered Deployment args include all 8 new flags in deterministic order:
    ```
    --model Qwen/Qwen3.6-35B-A3B-FP8 --host 0.0.0.0 --port 8000
    --tensor-parallel-size 2 --max-model-len 131072
    --quantization fp8 --dtype bfloat16
    --kv-cache-dtype fp8_e5m2
    --enable-prefix-caching --enable-chunked-prefill
    --max-num-batched-tokens 8192
    --attention-backend FLASHINFER
    ```
  5. Negative test: patched `speculative.enabled=true` without a model. `VLLMSpecValid` condition flipped to `False/SpeculativeMissingModel` with the exact expected message. Reverting the patch flipped it back to `True/ConfigValid`.

Back-compat

  • Every new field is a pointer type with nil meaning "don't set, let vLLM default"
  • `attentionBackend` enum widened additively (both lowercase and uppercase accepted)
  • No changes to CLI behavior; this is a CRD feature only

Out of scope (follow-ups)

  • CLI surface for these flags — separate PR after the CRD stabilizes
  • Auto-tuning `maxNumBatchedTokens` based on VRAM
  • NVFP4 GPU-family gating (NVFP4 is Blackwell-gen only; runtime currently emits the flag blindly if set)

Adds typed CRD fields to VLLMConfig so agentic-coding workloads can reach
vLLM's throughput-critical flags without dropping to extraArgs:

  - kvCacheDtype (auto | fp8_e5m2 | fp8_e4m3, default auto) -- unlocks
    128K+ context on consumer VRAM
  - enableChunkedPrefill -- opt-in, keeps long prefill from starving decode
  - maxNumBatchedTokens (min 512, no default) -- main throughput knob
  - speculative.{enabled,model,numSpeculativeTokens} -- draft-model
    speculative decoding as a sub-struct
  - enableExpertParallel -- MoE-specific shard toggle

Also:
  - Widens VLLMConfig.Quantization enum to accept fp8, nvfp4, and
    compressed-tensors so recent Qwen/Llama/NVFP4/Unsloth releases are
    referenceable.
  - Extends AttentionBackend enum with uppercase vLLM-native forms
    (FLASH_ATTN, FLASHINFER, XFORMERS) while keeping lowercase values
    working for backwards compatibility.
  - Emits flags deterministically in BuildArgs; opt-in booleans (prefix
    caching, chunked prefill, expert parallel) are only emitted when
    explicitly set to true so vLLM upstream defaults stay authoritative.
  - Surfaces structurally invalid speculative config (enabled without a
    draft model) as a VLLMSpecValid=False status condition on the
    InferenceService rather than failing reconcile; the flags are simply
    skipped.

Tests:
  - internal/controller/runtime_vllm_test.go: 20-row table covering
    every new flag's emit / do-not-emit paths, plus a determinism test
    and table-driven coverage of ValidateVLLMConfig.

Sample manifest:
  - config/samples/inferenceservice_qwen36_35b_fp8_agentic.yaml targeting
    2x RTX 5060 Ti with Qwen3.6-35B-A3B-FP8, TP=2, FP8 KV cache, prefix
    caching + chunked prefill on, FLASHINFER, max-num-batched-tokens=8192.
    Speculative decoding is commented out with a pointer to the CRD spec.

No breaking change: every new field is optional and defaults to vLLM's
current behavior if unset. CLI integration is deferred to a follow-up PR.

Closes #305

Signed-off-by: Christopher Maher <chris@mahercode.io>
@Defilan

Defilan commented Apr 21, 2026

Copy link
Copy Markdown
Member Author

Live smoke test on Shadowstack — validation complete

Built the PR as `registry.defilan.net/llmkube-controller:0.7.1-dev-vllm-flags` and rolled it onto a real 2× RTX 5060 Ti microk8s cluster. End-to-end validation summary:

✅ What this PR proves

  1. CRDs applied cleanly — `kubectl explain inferenceservice.spec.vllmConfig.speculative` shows the new fields with the intended docs; `.kvCacheDtype` shows the enum
  2. Controller rolled out against new CRDs without incident
  3. Rendered Deployment args are deterministic and correct — applied the sample and got the expected argv:
    ```
    --model Qwen/Qwen3.6-35B-A3B-FP8 --host 0.0.0.0 --port 8000
    --tensor-parallel-size 2 --max-model-len 131072
    --quantization fp8 --dtype bfloat16
    --kv-cache-dtype fp8_e5m2 (initially; corrected to fp8_e4m3 — see below)
    --enable-prefix-caching --enable-chunked-prefill
    --max-num-batched-tokens 8192
    --attention-backend FLASHINFER
    ```
  4. vLLM 0.19.1 parsed every flag — its own `non-default args:` log echoed them back, no `unrecognized argument` errors
  5. vLLM actively honored each one during startup:
    • `Using fp8 data type to store kv cache`
    • `Chunked prefill is enabled with max_num_batched_tokens=8192`
    • `Prefix caching in Mamba cache 'align' mode is currently enabled`
    • `Using AttentionBackendEnum.FLASHINFER backend`
  6. Negative test for speculative misconfig — patched `speculative.enabled=true` without a model. `VLLMSpecValid` condition flipped to `False/SpeculativeMissingModel` with the exact expected message. Revert flipped it back to `True/ConfigValid`.

🪲 Smoke test found two things worth fixing (not in this PR)

1. Sample YAML had wrong `kvCacheDtype`. Pushed fp8_e5m2 initially; vLLM rejected it:

`ValueError: fp8_e5m2 kv-cache is not supported with fp8 checkpoints.`

Fixed in the sample to `fp8_e4m3` with an inline comment explaining: `fp8_e5m2` is for BF16/FP16 weights where you're compressing only the KV cache; `fp8_e4m3` is required when weights themselves are FP8. This is a good argument for typed CRD fields with docs — the sample's comments now teach this constraint.

2. Hardware budget on 2× 5060 Ti is tighter than the original sample assumed. Qwen3.6-35B-A3B-FP8 weights are ~36 GB; TP=2 → ~18 GB/shard > 15.48 GB usable on a 5060 Ti. Retargeted the sample to `Qwen3-Coder-30B-A3B-Instruct-FP8` (~30.5 GB, ~15.25 GB/shard). Even that hit OOM during vLLM's profile run due to activation + cudagraph overhead — needs `--gpu-memory-utilization 0.95` and `--cpu-offload-gb` support, which this PR intentionally doesn't add.

Filed as follow-up #307: add `gpuMemoryUtilization` and `cpuOffloadGB` to VLLMConfig.

Merge readiness

All 15 CI checks pass, 1 skipping. Core PR functionality fully validated on real hardware. The sample YAML was updated from `inferenceservice_qwen36_35b_fp8_agentic.yaml` → `inferenceservice_qwen3_coder_30b_fp8_agentic.yaml` with corrected `kvCacheDtype` and VRAM-budget math in the comment block.

Recommending: merge this PR as-is. #307 will follow up for the two vLLM memory-management flags needed to actually boot 30B+ models on this specific hardware.

…type

Two smoke-test findings from running the original sample on real 2x RTX
5060 Ti hardware:

1. Qwen3.6-35B-A3B-FP8 weights are ~36 GB; TP=2 per-shard ~18 GB >
   15.48 GiB usable on a 5060 Ti. The model doesn't fit on this hardware.
   Retargeting the sample to Qwen3-Coder-30B-A3B-Instruct-FP8 (~30.5 GB
   total, ~15.25 GB/shard) which is the actual production agentic-coding
   model for this size class. Inline VRAM-budget comment teaches future
   users how to check the fit.

2. vLLM 0.19.1 rejects the combination `--kv-cache-dtype fp8_e5m2` +
   `--quantization fp8`:
     ValueError: fp8_e5m2 kv-cache is not supported with fp8 checkpoints.
   Correct value for FP8-weight checkpoints is fp8_e4m3 (matches the FP8
   encoding of the weights). fp8_e5m2 is only correct when weights are
   bf16/fp16 and you're compressing only the KV cache. Sample comment
   now teaches this distinction.

Context: weight allocation still OOMs during vLLM's profile run at
maxModelLen=65536 due to activation + cudagraph overhead. Filed #307 to
add --gpu-memory-utilization and --cpu-offload-gb to VLLMConfig so this
hardware can actually run 30B+ FP8 models. That's a follow-up, not in
scope for #305.

Signed-off-by: Christopher Maher <chris@mahercode.io>
@Defilan Defilan merged commit cb2aa6a into main Apr 21, 2026
16 checks passed
@Defilan Defilan deleted the feat/vllm-agentic-coding-flags branch April 21, 2026 08:37
@github-actions github-actions Bot mentioned this pull request Apr 21, 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.

feat(crd): extend InferenceService for agentic-coding vLLM flags (prefix caching, chunked prefill, FP8 KV cache, speculative decoding)

1 participant