Skip to content

Commit 9b12f25

Browse files
ko3n1gclaude
andauthored
feat: add KubeflowExecutor for Kubernetes Training Operator (TrainJob CRD) (#462)
* feat: add KubeflowExecutor for Kubeflow Training Operator on Kubernetes Introduces KubeflowExecutor and a matching TorchX scheduler to deploy distributed TrainJobs (Kubeflow Training Operator v2) on any Kubernetes cluster via run.run() / run.Experiment. - KubeflowExecutor builds and submits TrainJob CRDs via the K8s API (local kubeconfig with in-cluster fallback) - macro_values() maps to PET_* env vars injected by Training Operator - Inline scripts are chmod'd 755 and served via a data-mover pod syncing into <workdir_pvc_path>/<username>/code; a /nemo_run symlink is created for compatibility with hardcoded script paths - fetch_logs streams kubectl logs per-replica and retries until the job reaches a terminal state; --max-log-requests=num_nodes handles large jobs - describe() exposes all replicas so log threads cover every rank - Documentation added to docs/guides/execution.md --------- Signed-off-by: oliver könig <okoenig@nvidia.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6f80f88 commit 9b12f25

15 files changed

Lines changed: 2548 additions & 116 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,4 @@ _version.py
183183

184184
# NeMo Run
185185
.nemo_run/
186+
local/

docs/guides/execution.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The packager support matrix is described below:
5353
| SkypilotExecutor | run.Packager, run.GitArchivePackager, run.PatternPackager, run.HybridPackager |
5454
| DGXCloudExecutor | run.Packager, run.GitArchivePackager, run.PatternPackager, run.HybridPackager |
5555
| LeptonExecutor | run.Packager, run.GitArchivePackager, run.PatternPackager, run.HybridPackager |
56+
| KubeflowExecutor | run.Packager |
5657

5758
`run.Packager` is a passthrough base packager.
5859

@@ -293,6 +294,53 @@ def your_dgx_executor(nodes: int, gpus_per_node: int, container_image: str):
293294

294295
For a complete end-to-end example using DGX Cloud with NeMo, refer to the [NVIDIA DGX Cloud NeMo End-to-End Workflow Example](https://docs.nvidia.com/dgx-cloud/run-ai/latest/nemo-e2e-example.html).
295296

297+
#### KubeflowExecutor
298+
299+
The `KubeflowExecutor` integrates with the [Kubeflow Training Operator](https://github.com/kubeflow/training-operator) to run distributed training jobs on any Kubernetes cluster. It submits CRDs directly via the Kubernetes API — no `kubectl` required.
300+
301+
Two job kinds are supported via the `job_kind` parameter:
302+
303+
- **`"PyTorchJob"`** (default) — Training Operator v1 (`kubeflow.org/v1`)
304+
- **`"TrainJob"`** — Training Operator v2 (`trainer.kubeflow.org/v1alpha1`)
305+
306+
Kubernetes configuration is loaded automatically: local kubeconfig is tried first, falling back to in-cluster config when running inside a pod.
307+
308+
Here's an example configuration:
309+
310+
```python
311+
# PyTorchJob (default)
312+
executor = run.KubeflowExecutor(
313+
namespace="runai-nemo-ci",
314+
image="nvcr.io/nvidia/nemo:26.02",
315+
num_nodes=3, # total pods: 1 Master + (num_nodes-1) Workers
316+
gpus_per_node=8, # also sets nproc_per_node unless overridden explicitly
317+
cpu_requests="16",
318+
memory_requests="64Gi",
319+
volumes=[
320+
{"name": "model-cache", "persistentVolumeClaim": {"claimName": "data-pvc"}}
321+
],
322+
volume_mounts=[{"name": "model-cache", "mountPath": "/nemo-workspace"}],
323+
labels={"app": "nemo-ci-training"},
324+
env_vars={"NCCL_DEBUG": "INFO"},
325+
)
326+
327+
# TrainJob (Training Operator v2)
328+
executor = run.KubeflowExecutor(
329+
job_kind="TrainJob",
330+
runtime_ref="torch-distributed", # name of the ClusterTrainingRuntime
331+
namespace="runai-nemo-ci",
332+
image="nvcr.io/nvidia/nemo:26.02",
333+
num_nodes=3,
334+
gpus_per_node=8,
335+
)
336+
```
337+
338+
`cancel(wait=True)` polls until both the CR and all associated pods are fully terminated before returning.
339+
340+
##### Limitations
341+
342+
Attributes like `resourceClaims` are not [supported](https://github.com/kubeflow/trainer/issues/3264) and must be injected in different ways, like by Mutating Webhooks.
343+
296344
#### LeptonExecutor
297345

298346
The `LeptonExecutor` integrates with an NVIDIA DGX Cloud Lepton cluster's Python SDK to launch distributed jobs. It uses API calls behind the Lepton SDK to authenticate, identify the target node group and resource shapes, and submit the job specification which will be launched as a batch job on the cluster.

nemo_run/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from nemo_run.core.execution.base import Executor, ExecutorMacros, import_executor
2525
from nemo_run.core.execution.dgxcloud import DGXCloudExecutor
2626
from nemo_run.core.execution.docker import DockerExecutor
27+
from nemo_run.core.execution.kubeflow import KubeflowExecutor
2728
from nemo_run.core.execution.launcher import FaultTolerance, SlurmRay, SlurmTemplate, Torchrun
2829
from nemo_run.core.execution.lepton import LeptonExecutor
2930
from nemo_run.core.execution.local import LocalExecutor
@@ -66,6 +67,7 @@
6667
"Packager",
6768
"Partial",
6869
"Plugin",
70+
"KubeflowExecutor",
6971
"run",
7072
"Script",
7173
"SkypilotExecutor",

nemo_run/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,8 @@ def to_command(
495495
)
496496
with open(filename, "w") as f:
497497
f.write("#!/usr/bin/bash\n" + inline_content)
498+
# chmod with minimal +x permissions
499+
os.chmod(filename, os.stat(filename).st_mode | 0o755)
498500

499501
if is_local:
500502
cmd = [filename]

nemo_run/core/execution/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from nemo_run.core.execution.dgxcloud import DGXCloudExecutor
1717
from nemo_run.core.execution.lepton import LeptonExecutor
1818
from nemo_run.core.execution.local import LocalExecutor
19+
from nemo_run.core.execution.kubeflow import KubeflowExecutor
1920
from nemo_run.core.execution.skypilot import SkypilotExecutor
2021
from nemo_run.core.execution.slurm import SlurmExecutor
2122

@@ -25,4 +26,5 @@
2526
"SkypilotExecutor",
2627
"DGXCloudExecutor",
2728
"LeptonExecutor",
29+
"KubeflowExecutor",
2830
]

0 commit comments

Comments
 (0)