Skip to content

feat(models): generic engine support - #476

Merged
benmccown merged 3 commits into
mainfrom
models-generic-engine-support
Jun 26, 2026
Merged

feat(models): generic engine support#476
benmccown merged 3 commits into
mainfrom
models-generic-engine-support

Conversation

@benmccown

@benmccown benmccown commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Here's a concise PR description:


feat(models): add generic engine support for ModelDeployments

What

Adds a new generic inference engine alongside nim and vllm, on both the docker and k8s service backends. A generic deployment is "declare a container image + tag + health path and run it" — no inference-engine compiler, no model-weight pull. The user's additional_args become the container command verbatim and additional_envs become its env.

This covers cases like classification NIMs (e.g. NemoGuard JailbreakDetect) and any self-contained server image, without needing engine-specific compile logic.

Key changes

  • generic_compiler.py (new): trivial backend-agnostic compiler — resolves the image (required, no platform default), and passes args/env through verbatim.
  • Validation: engine=generic now requires executor_config.image_name and health_check_path at create/update time (no platform defaults exist for an arbitrary container). Returns a 400 with a clear message.
  • Docker backend: branches generic in image resolution and env/args compilation; runs the image as a local container with the engine + health-path labels; no model puller; no LoRA sidecar.
  • K8s backend: routes generic (and vllm) through the direct-emission K8sReconciler, which now branches on engine. The generic path emits a Deployment + Service immediately with no PVC / no puller Job / no model-store mount (it has no weights). vllm_k8s_compiler.compile_deployment gained a mount_model_store flag to support this. Delete cascades via ownerRef as before.

Testing

  • Unit tests for the generic compiler, docker + k8s backend paths, the no-PVC compile path, and the new validation. (The previous "generic rejected on k8s" test is replaced with positive create/status/LOST coverage.)
  • Manually verified end-to-end in the dev pod on both backends using vllm/vllm-openai as a generic container serving facebook/opt-125m:
    • Docker: container runs with args verbatim, correct engine/health-path labels, reaches READY, serves /v1/completions.
    • K8s: emits Deployment + Service only (no PVC/Job), pod has no model-store mount, /health probe, reaches READY, serves inference, deletes cleanly.

Summary by CodeRabbit

  • New Features
    • Added end-to-end support for the generic engine deployment path, including generic image/tag resolution and pass-through args/env.
    • Kubernetes generic deployments now support both weightless immediate serving and phased weight pull+serve, with conditional model-store mounting.
    • Docker generic creation now supports explicit image/command/env and health-check-path, including weight-fileset pull behavior.
  • Bug Fixes
    • Added stricter generic executor-config validation (required fields, no leading/trailing whitespace) and blocks LoRA for generic deployments.
    • Improved unsupported-engine error details to include the engine value; generic now routes to supported Kubernetes reconciliation.
  • Tests
    • Expanded unit coverage for generic compilation, Docker/Kubernetes create+status flows, and generic config validation.

Signed-off-by: Ben McCown <bmccown@nvidia.com>
@benmccown
benmccown requested review from a team as code owners June 25, 2026 20:49
@github-actions github-actions Bot added the feat label Jun 25, 2026
@benmccown benmccown self-assigned this Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 348ffbff-f2f2-4451-a6e7-b150a6658e1e

📥 Commits

Reviewing files that changed from the base of the PR and between f35c40d and 07aafde.

📒 Files selected for processing (4)
  • services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py
  • services/core/models/tests/unit/controllers/test_k8s_nim_operator_backend.py
  • services/core/models/tests/unit/test_model_deployment_config_service_unit.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/core/models/tests/unit/test_model_deployment_config_service_unit.py
  • services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py

📝 Walkthrough

Walkthrough

Adds Engine.GENERIC validation, compiler support, Docker creation wiring, and Kubernetes routing/reconciliation. Updates tests for generic image/env/args handling, no-model-store deployment templates, and generic staged rollout behavior.

Changes

Generic engine support

Layer / File(s) Summary
Generic config validation
services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py, services/core/models/tests/unit/test_model_deployment_config_service_unit.py
Engine.GENERIC now requires executor_config.image_name and executor_config.health_check_path on create and update, rejects padded values, and rejects lora_enabled; tests cover failure and success cases.
Generic compiler helpers
services/core/models/src/nmp/core/models/controllers/backends/generic_compiler.py, services/core/models/tests/unit/controllers/backends/test_generic_compiler.py
The new compiler resolves generic image name/tag values, passes through args, stringifies env values, and its tests cover defaults and validation.
Docker generic deployment
services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py, services/core/models/tests/unit/controllers/test_docker_backend.py
Docker creation resolves generic images, compiles generic env and command inputs, skips the LoRA sidecar for generic, and the backend tests cover weightless and fileset-backed generic creation.
K8s deployment mounts
services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/vllm_k8s_compiler.py, services/core/models/tests/unit/controllers/backends/test_vllm_k8s_compiler.py
compile_deployment can omit the model-store PVC and mount, and tests cover the generic no-model-store layout.
K8s generic reconciling
services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/backend.py, services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py, services/core/models/tests/unit/controllers/test_k8s_nim_operator_backend.py
The K8s backend routes ENGINE_GENERIC to the shared reconciler, and the reconciler uses weight-driven create, update, status, and deletion flows with generic-specific tests.

Possibly related PRs

Suggested reviewers

  • mckornfield
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding generic engine support in models.
Docstring Coverage ✅ Passed Docstring coverage is 81.08% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch models-generic-engine-support

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
services/core/models/tests/unit/test_model_deployment_config_service_unit.py (1)

167-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Broaden the generic-validation regression coverage.

The negative case only matches image_name while both required fields are missing, so it would still pass if health_check_path validation regressed. Add a case that isolates missing health_check_path, and mirror the same checks on update_deployment_config() since that path was changed too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/core/models/tests/unit/test_model_deployment_config_service_unit.py`
around lines 167 - 213, The generic validation tests only cover a missing
image_name case, so a regression in health_check_path could still slip through.
Add a focused test in test_model_deployment_config_service_unit.py that creates
a generic request with image_name set but health_check_path omitted and asserts
create_deployment_config() raises on health_check_path, then add matching
update_deployment_config() coverage for the same missing-field scenario to
exercise the updated validation path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py`:
- Around line 41-49: The generic executor validation only uses strip() for
presence checks, so whitespace-padded values can still be persisted and later
fail downstream. Update model_deployment_config_service’s generic-field handling
around the executor_config.image_name and executor_config.health_check_path
checks to either normalize these values by trimming before saving or reject them
when the trimmed value differs from the original. Make the fix in the same
validation path that raises the ValueError for missing executor_config fields so
these inputs are handled consistently.

In
`@services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py`:
- Around line 901-912: The generic engine path in create_args still applies
/model-store and /scratch mounts even though the ENGINE_GENERIC branch is meant
to run the image raw with only the user’s env and args. Update the container
spec assembly in creation_reconciler so create_args["volumes"] is only populated
for engines that actually need platform storage, and leave it unset or empty for
ENGINE_GENERIC while preserving the existing volume behavior for the other
engine branches.
- Around line 496-499: The generic backend branch in creation_reconciler should
not allow the model-puller path when the deployment engine is generic. Update
the puller decision in _needs_puller() (and any related caller logic) so it
explicitly checks config_engine(state.config) != ENGINE_GENERIC before
scheduling downloads, even when model_weights_type is present. Use the existing
ENGINE_GENERIC and _needs_puller symbols to keep the fix localized and ensure
generic deployments never trigger weight pulling.

In
`@services/core/models/src/nmp/core/models/controllers/backends/generic_compiler.py`:
- Around line 34-37: The `generic_compiler` image reference normalization still
allows blank-space tags to pass through as invalid values. Update the logic in
the compiler helper that returns `view.image_name` and `image_tag` so both
fields are trimmed before use: keep the existing `image_name` presence check,
return the stripped `image_name`, and compute `image_tag` from the stripped
value with a fallback to `"latest"` when the trimmed tag is empty. Refer to the
return path in the `generic_compiler` flow that currently assigns `image_tag`
and returns the pair.

In
`@services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/backend.py`:
- Around line 244-246: The engine selection logic in the reconciler lookup
currently falls through to _nim_reconciler for every non-vLLM/non-generic value,
which incorrectly treats unknown engine strings as supported. Update the
backend’s engine dispatch method to explicitly reject invalid engines by
returning _unsupported_engine() unless the engine matches ENGINE_VLLM,
ENGINE_GENERIC, or the supported NIM case, and keep the None/unsupported
behavior aligned with the docstring.

In
`@services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py`:
- Around line 527-540: The generic update path in _update_generic currently
relies on _create_generic_serving_objects, which treats a 409 as “already
exists” and skips reapplying changed spec fields. Update the logic so generic
resources are actually patched on update: in the _update_generic flow, or via a
dedicated helper called from _create_generic_serving_objects, use
patch_namespaced_deployment and patch_namespaced_service when the
Deployment/Service already exists, ensuring image, args, env, health path, GPU,
and labels are refreshed instead of left stale.
- Line 353: Keep _vllm_objects_exist() limited to vLLM objects only: it is used
by the vLLM update path, so remove the ENGINE_GENERIC match from the labels
check and have the helper return true only for objects labeled with ENGINE_VLLM.
Update the isinstance(labels, dict) / labels.get("nmp.nvidia.com/engine") logic
in k8s.py so generic Deployments with the same name do not satisfy the vLLM
existence check and bypass the PVC/puller flow.

---

Nitpick comments:
In
`@services/core/models/tests/unit/test_model_deployment_config_service_unit.py`:
- Around line 167-213: The generic validation tests only cover a missing
image_name case, so a regression in health_check_path could still slip through.
Add a focused test in test_model_deployment_config_service_unit.py that creates
a generic request with image_name set but health_check_path omitted and asserts
create_deployment_config() raises on health_check_path, then add matching
update_deployment_config() coverage for the same missing-field scenario to
exercise the updated validation path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 43116890-b366-45c6-a719-4c037a2ca7a4

📥 Commits

Reviewing files that changed from the base of the PR and between f823935 and f7e5b89.

📒 Files selected for processing (11)
  • services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py
  • services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py
  • services/core/models/src/nmp/core/models/controllers/backends/generic_compiler.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/backend.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/vllm_k8s_compiler.py
  • services/core/models/tests/unit/controllers/backends/test_generic_compiler.py
  • services/core/models/tests/unit/controllers/backends/test_vllm_k8s_compiler.py
  • services/core/models/tests/unit/controllers/test_docker_backend.py
  • services/core/models/tests/unit/controllers/test_k8s_nim_operator_backend.py
  • services/core/models/tests/unit/test_model_deployment_config_service_unit.py

Comment thread services/core/models/src/nmp/core/models/controllers/backends/generic_compiler.py Outdated
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 21322/27924 76.4% 61.4%
Integration Tests 12354/26693 46.3% 19.8%

Address CodeRabbit review feedback on generic engine support:

- Make generic deployments weight-aware on both backends: when the config
  resolves to a fileset-backed model, pull weights (docker puller /
  k8s PVC+puller) and mount /model-store; otherwise run the image raw with
  no platform volumes.
- k8s: refactor K8sReconciler to choose staged vs. immediate rollout by
  weight presence (not engine), patch serving Deployment/Service in place on
  update, and reject unknown engines explicitly instead of defaulting to NIM.
- Validate + reject whitespace-padded generic image_name/health_check_path;
  trim defensively in the generic compiler.
- Expand unit tests (validation, compiler, docker + k8s weight-aware paths,
  update patching).

Signed-off-by: Ben McCown <bmccown@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py`:
- Around line 217-224: The re-pull check in k8s.py is using
_existing_model_source() before the puller Job is gone, so it can miss the model
source stored on the PVC for already-serving weighted deployments. Update the
reconciliation flow around the create/resolved path and _existing_model_source()
so it reads the source from the PVC when the Job no longer exists, then compare
that value against source_tag before deciding whether to delete resources and
call self.create(resolved).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 23c83761-a91d-4403-befc-76c9149e62ff

📥 Commits

Reviewing files that changed from the base of the PR and between f7e5b89 and f35c40d.

📒 Files selected for processing (9)
  • services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py
  • services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py
  • services/core/models/src/nmp/core/models/controllers/backends/generic_compiler.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/backend.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/reconcilers/k8s.py
  • services/core/models/tests/unit/controllers/backends/test_generic_compiler.py
  • services/core/models/tests/unit/controllers/test_docker_backend.py
  • services/core/models/tests/unit/controllers/test_k8s_nim_operator_backend.py
  • services/core/models/tests/unit/test_model_deployment_config_service_unit.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • services/core/models/tests/unit/controllers/backends/test_generic_compiler.py
  • services/core/models/src/nmp/core/models/controllers/backends/k8s_nim_operator/backend.py
  • services/core/models/src/nmp/core/models/controllers/backends/generic_compiler.py
  • services/core/models/src/nmp/core/models/api/service/model_deployment_config_service.py
  • services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py

@mckornfield mckornfield left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of curiosity, do we have a deployment example with the generic compiler, like I'd guess sglang?

@tylersbray tylersbray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving a +1 approval. Added comments for the medium-level things that got flagged by machine.

- Reject engine=generic with model_spec.lora_enabled in the deployment
  config validation (no engine compiler to wire the adapter sidecar, so it
  would otherwise be silently ignored).
- k8s: restore the mid-pull no-op on update — when the serving Deployment
  is absent but the puller Job is still present, accept the update without
  re-running create(), only falling back to create() on genuine drift.
- Reword the create() docstring ("start at phase P0").
- Add unit tests for the generic+LoRA rejection and the mid-pull no-op update.

Signed-off-by: Ben McCown <bmccown@nvidia.com>
@benmccown
benmccown added this pull request to the merge queue Jun 26, 2026
Merged via the queue into main with commit 4bcebbb Jun 26, 2026
53 checks passed
@benmccown
benmccown deleted the models-generic-engine-support branch June 26, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants