diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cann8.5-arm64-trainingruntime.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cann8.5-arm64-trainingruntime.yaml new file mode 100644 index 0000000..e0b0e02 --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cann8.5-arm64-trainingruntime.yaml @@ -0,0 +1,67 @@ +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainingRuntime +metadata: + name: llamafactory0.9-cann8.5-arm64 + namespace: kubeflow-admin-cpaas-io + labels: + trainer.kubeflow.org/framework: torch + alauda.io/training-runtime: llamafactory0.9-cann8.5-arm64 +spec: + mlPolicy: + numNodes: 1 + torch: + numProcPerNode: auto + template: + spec: + replicatedJobs: + - name: node + template: + metadata: + labels: + trainer.kubeflow.org/trainjob-ancestor-step: trainer + spec: + backoffLimit: 0 + template: + spec: + schedulerName: hami-scheduler + runtimeClassName: ascend + securityContext: + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + containers: + - name: node + image: docker.io/alaudadockerhub/llamafactory0.9-cann8.5-arm64:v0.1.0 + command: + - bash + - -c + - | + set -ex + python -c "import torch, torch_npu, llamafactory; print('torch', torch.__version__, 'torch_npu', torch_npu.__version__, 'lf', llamafactory.__version__, 'npu_available', torch_npu.npu.is_available())" + llamafactory-cli version || true + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + cpu: "4" + memory: 8Gi + huawei.com/Ascend910B4: "1" + huawei.com/Ascend910B4-memory: "8192" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - { name: workspace, mountPath: /workspace } + - { name: dshm, mountPath: /dev/shm } + volumes: + - { name: workspace, emptyDir: {} } + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 2Gi diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cu126-amd64-trainingruntime.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cu126-amd64-trainingruntime.yaml new file mode 100644 index 0000000..aeb765e --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cu126-amd64-trainingruntime.yaml @@ -0,0 +1,81 @@ +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainingRuntime +metadata: + name: llamafactory0.9-cu126-amd64 + namespace: kubeflow-admin-cpaas-io + labels: + trainer.kubeflow.org/framework: torch + alauda.io/training-runtime: llamafactory0.9-cu126-amd64 +spec: + mlPolicy: + numNodes: 1 + torch: + numProcPerNode: auto + template: + spec: + replicatedJobs: + - name: node + template: + metadata: + labels: + trainer.kubeflow.org/trainjob-ancestor-step: trainer + spec: + backoffLimit: 0 + template: + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: node + image: docker.io/alaudadockerhub/llamafactory0.9-cu126-amd64:v0.1.0 + command: + - bash + - -c + - | + set -ex + # Synthetic identity SFT smoke. Writes a 4-row alpaca dataset, fine-tunes + # qwen-style chat template against an empty config to exercise the + # LLaMA-Factory CLI without downloading a real model. + mkdir -p /tmp/lf + cat >/tmp/lf/dataset_info.json <<'JSON' + {"identity_smoke": {"file_name": "identity_smoke.json"}} + JSON + cat >/tmp/lf/identity_smoke.json <<'JSON' + [ + {"instruction":"Who are you?","input":"","output":"I am Alauda AI."}, + {"instruction":"Where do you run?","input":"","output":"On Kubeflow Trainer v2."}, + {"instruction":"What framework powers you?","input":"","output":"LLaMA-Factory on PyTorch."}, + {"instruction":"What device?","input":"","output":"NVIDIA GPU via CUDA 12.6."} + ] + JSON + python -c "import llamafactory, torch; print('lf', llamafactory.__version__, 'torch', torch.__version__, 'cuda', torch.cuda.is_available())" + llamafactory-cli version || true + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + cpu: "4" + memory: 8Gi + nvidia.com/gpualloc: 1 + nvidia.com/gpucores: 50 + nvidia.com/gpumem: "8192" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - { name: workspace, mountPath: /workspace } + - { name: dshm, mountPath: /dev/shm } + volumes: + - { name: workspace, emptyDir: {} } + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 2Gi diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/mindspeed-llm-cann8.5-arm64-trainingruntime.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/mindspeed-llm-cann8.5-arm64-trainingruntime.yaml new file mode 100644 index 0000000..b212e3c --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/mindspeed-llm-cann8.5-arm64-trainingruntime.yaml @@ -0,0 +1,66 @@ +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainingRuntime +metadata: + name: mindspeed-llm-cann8.5-arm64 + namespace: kubeflow-admin-cpaas-io + labels: + trainer.kubeflow.org/framework: torch + alauda.io/training-runtime: mindspeed-llm-cann8.5-arm64 +spec: + mlPolicy: + numNodes: 1 + torch: + numProcPerNode: auto + template: + spec: + replicatedJobs: + - name: node + template: + metadata: + labels: + trainer.kubeflow.org/trainjob-ancestor-step: trainer + spec: + backoffLimit: 0 + template: + spec: + schedulerName: hami-scheduler + runtimeClassName: ascend + securityContext: + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + containers: + - name: node + image: docker.io/alaudadockerhub/mindspeed-llm-cann8.5-arm64:v0.1.0 + command: + - bash + - -c + - | + set -ex + python -c "import torch, torch_npu, megatron, mindspeed, mindspeed_llm; print('torch', torch.__version__, 'torch_npu', torch_npu.__version__, 'megatron', megatron.__version__, 'mindspeed_llm', mindspeed_llm.__version__)" + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + cpu: "4" + memory: 8Gi + huawei.com/Ascend910B4: "1" + huawei.com/Ascend910B4-memory: "8192" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - { name: workspace, mountPath: /workspace } + - { name: dshm, mountPath: /dev/shm } + volumes: + - { name: workspace, emptyDir: {} } + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 2Gi diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cann8.5-arm64-trainingruntime.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cann8.5-arm64-trainingruntime.yaml new file mode 100644 index 0000000..493cc90 --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cann8.5-arm64-trainingruntime.yaml @@ -0,0 +1,77 @@ +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainingRuntime +metadata: + name: torch2.6-cann8.5-arm64 + namespace: kubeflow-admin-cpaas-io + labels: + trainer.kubeflow.org/framework: torch + alauda.io/training-runtime: torch2.6-cann8.5-arm64 +spec: + mlPolicy: + numNodes: 1 + torch: + numProcPerNode: auto + template: + spec: + replicatedJobs: + - name: node + template: + metadata: + labels: + trainer.kubeflow.org/trainjob-ancestor-step: trainer + spec: + backoffLimit: 0 + template: + spec: + schedulerName: hami-scheduler + runtimeClassName: ascend + securityContext: + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + containers: + - name: node + image: docker.io/alaudadockerhub/torch2.6-cann8.5-arm64:v0.1.0 + command: + - python + - -c + - | + import os, torch + try: + import torch_npu + import torch_npu.utils + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__}") + print(f"npu_available={torch_npu.npu.is_available()} count={torch_npu.npu.device_count()}") + if torch_npu.npu.is_available(): + x = torch.randn(1024, 1024).npu() + y = x @ x.T + print(f"matmul ok shape={tuple(y.shape)} mean={y.mean().item():.4f}") + except Exception as e: + print(f"torch_npu probe failed: {e}") + raise + resources: + requests: + cpu: "1" + memory: 2Gi + limits: + cpu: "4" + memory: 8Gi + huawei.com/Ascend910B4: "1" + huawei.com/Ascend910B4-memory: "8192" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - { name: workspace, mountPath: /workspace } + - { name: dshm, mountPath: /dev/shm } + volumes: + - { name: workspace, emptyDir: {} } + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cu126-amd64-trainingruntime.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cu126-amd64-trainingruntime.yaml new file mode 100644 index 0000000..aabae60 --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cu126-amd64-trainingruntime.yaml @@ -0,0 +1,70 @@ +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainingRuntime +metadata: + name: torch2.6-cu126-amd64 + namespace: kubeflow-admin-cpaas-io + labels: + trainer.kubeflow.org/framework: torch + alauda.io/training-runtime: torch2.6-cu126-amd64 +spec: + mlPolicy: + numNodes: 1 + torch: + numProcPerNode: auto + template: + spec: + replicatedJobs: + - name: node + template: + metadata: + labels: + trainer.kubeflow.org/trainjob-ancestor-step: trainer + spec: + backoffLimit: 0 + template: + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: node + image: docker.io/alaudadockerhub/torch2.6-cu126-amd64:v0.1.0 + command: + - python + - -c + - | + import torch, os + print(f"torch={torch.__version__} cuda={torch.cuda.is_available()} devices={torch.cuda.device_count()}") + print(f"PET_NNODES={os.environ.get('PET_NNODES')} PET_NODE_RANK={os.environ.get('PET_NODE_RANK')}") + if torch.cuda.is_available(): + x = torch.randn(1024, 1024, device='cuda') + y = x @ x.T + print(f"matmul ok, shape={tuple(y.shape)}, mean={y.mean().item():.4f}") + resources: + requests: + cpu: "1" + memory: 2Gi + limits: + cpu: "2" + memory: 4Gi + nvidia.com/gpualloc: 1 + nvidia.com/gpucores: 50 + nvidia.com/gpumem: "8192" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - { name: workspace, mountPath: /workspace } + - { name: dshm, mountPath: /dev/shm } + volumes: + - { name: workspace, emptyDir: {} } + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/traininghub0.1-cu126-amd64-trainingruntime.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/traininghub0.1-cu126-amd64-trainingruntime.yaml new file mode 100644 index 0000000..72a3064 --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/traininghub0.1-cu126-amd64-trainingruntime.yaml @@ -0,0 +1,70 @@ +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainingRuntime +metadata: + name: traininghub0.1-cu126-amd64 + namespace: kubeflow-admin-cpaas-io + labels: + trainer.kubeflow.org/framework: torch + alauda.io/training-runtime: traininghub0.1-cu126-amd64 +spec: + mlPolicy: + numNodes: 1 + torch: + numProcPerNode: auto + template: + spec: + replicatedJobs: + - name: node + template: + metadata: + labels: + trainer.kubeflow.org/trainjob-ancestor-step: trainer + spec: + backoffLimit: 0 + template: + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: node + image: docker.io/alaudadockerhub/traininghub0.1-cu126-amd64:v0.1.0 + command: + - python + - -c + - | + import torch, trl, peft, bitsandbytes, deepspeed + print(f"torch={torch.__version__} cuda={torch.cuda.is_available()}") + print(f"trl={trl.__version__} peft={peft.__version__} bnb={bitsandbytes.__version__} deepspeed={deepspeed.__version__}") + if torch.cuda.is_available(): + from peft import LoraConfig + cfg = LoraConfig(r=4, lora_alpha=8, lora_dropout=0.05, target_modules=["q_proj","v_proj"]) + print("peft LoraConfig ok", cfg) + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + cpu: "4" + memory: 8Gi + nvidia.com/gpualloc: 1 + nvidia.com/gpucores: 50 + nvidia.com/gpumem: "8192" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - { name: workspace, mountPath: /workspace } + - { name: dshm, mountPath: /dev/shm } + volumes: + - { name: workspace, emptyDir: {} } + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 2Gi diff --git a/docs/en/kubeflow/how_to/assets/training-runtimes/trainjob-smoke.yaml b/docs/en/kubeflow/how_to/assets/training-runtimes/trainjob-smoke.yaml new file mode 100644 index 0000000..4fcd6a0 --- /dev/null +++ b/docs/en/kubeflow/how_to/assets/training-runtimes/trainjob-smoke.yaml @@ -0,0 +1,21 @@ +# Smoke TrainJob that exercises any of the published TrainingRuntimes. +# Set `spec.runtimeRef.name` to one of: +# torch2.6-cu126-amd64 +# torch2.6-cann8.5-arm64 +# llamafactory0.9-cu126-amd64 +# llamafactory0.9-cann8.5-arm64 +# traininghub0.1-cu126-amd64 +# mindspeed-llm-cann8.5-arm64 +apiVersion: trainer.kubeflow.org/v1alpha1 +kind: TrainJob +metadata: + generateName: smoke- + namespace: kubeflow-admin-cpaas-io +spec: + runtimeRef: + apiGroup: trainer.kubeflow.org + kind: TrainingRuntime + name: torch2.6-cu126-amd64 + suspend: false + trainer: + numNodes: 1 diff --git a/docs/en/kubeflow/how_to/training-runtimes.mdx b/docs/en/kubeflow/how_to/training-runtimes.mdx new file mode 100644 index 0000000..8631df6 --- /dev/null +++ b/docs/en/kubeflow/how_to/training-runtimes.mdx @@ -0,0 +1,175 @@ +--- +weight: 85 +--- + +# Training Runtime Images + +Alauda AI ships a curated catalog of [Kubeflow Trainer v2](./fine-tune-with-trainer-v2.mdx) +`TrainingRuntime` images. Each image bundles a specific PyTorch + accelerator +stack (NVIDIA CUDA or Huawei Ascend NPU via CANN) so users can submit +`TrainJob`s without rebuilding a custom image. + +## Available runtimes + +The published `v0.1.0` images on Dockerhub: + +| Image | Device | Framework | Pull | +|---|---|---|---| +| torch (CUDA) | NVIDIA GPU (CUDA 12.6) | PyTorch 2.6, transformers, accelerate, datasets, mlflow | `alaudadockerhub/torch2.6-cu126-amd64:v0.1.0` | +| torch (CANN) | Huawei Ascend NPU (CANN 8.5) | PyTorch 2.6 + torch_npu 2.6.0.post5 | `alaudadockerhub/torch2.6-cann8.5-arm64:v0.1.0` | +| LLaMA-Factory (CUDA) | NVIDIA GPU (CUDA 12.6) | LLaMA-Factory 0.9.4 (`metrics,awq,modelscope`) | `alaudadockerhub/llamafactory0.9-cu126-amd64:v0.1.0` | +| LLaMA-Factory (CANN) | Huawei Ascend NPU (CANN 8.5) | LLaMA-Factory 0.9.4 (`metrics,modelscope`, no `awq`) | `alaudadockerhub/llamafactory0.9-cann8.5-arm64:v0.1.0` | +| TrainingHub (CUDA) | NVIDIA GPU (CUDA 12.6) | trl, peft, bitsandbytes, deepspeed (SFT/OSFT/DPO) | `alaudadockerhub/traininghub0.1-cu126-amd64:v0.1.0` | +| MindSpeed-LLM (CANN) | Huawei Ascend NPU (CANN 8.5) | MindSpeed + MindSpeed-LLM (Megatron core 0.8.0) | `alaudadockerhub/mindspeed-llm-cann8.5-arm64:v0.1.0` | + +CUDA images are amd64-only — NVIDIA does not ship CUDA wheels for arm64. +CANN images are arm64-only — the upstream Ascend CANN base image is +published for arm64 only. + +Pull example: + +```bash +docker pull alaudadockerhub/torch2.6-cu126-amd64:v0.1.0 +``` + +## Picking a runtime + +- **Plain torchrun training** on NVIDIA GPUs → `torch2.6-cu126-amd64`. +- **Plain torchrun training** on Huawei Ascend NPUs → `torch2.6-cann8.5-arm64` + (set `runtimeClassName: ascend` on the pod template). +- **LLM SFT / LoRA fine-tuning with LLaMA-Factory** → + `llamafactory0.9-cu126-amd64` on GPU or `llamafactory0.9-cann8.5-arm64` + on NPU. See [Fine-Tuning with Kubeflow Trainer v2](./fine-tune-with-trainer-v2.mdx) + for the dataset / model initializer pipeline. +- **TRL / PEFT-based SFT / OSFT / DPO** → `traininghub0.1-cu126-amd64`. +- **Megatron-style training on Ascend** → `mindspeed-llm-cann8.5-arm64`. + +## TrainingRuntime YAML examples + +Ready-to-apply YAMLs for each runtime are shipped under +[`assets/training-runtimes/`](https://github.com/alauda/aml-docs/tree/master/docs/en/kubeflow/how_to/assets/training-runtimes): + +```bash +# NVIDIA GPU +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cu126-amd64-trainingruntime.yaml +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cu126-amd64-trainingruntime.yaml +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/traininghub0.1-cu126-amd64-trainingruntime.yaml + +# Huawei Ascend NPU +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/torch2.6-cann8.5-arm64-trainingruntime.yaml +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/llamafactory0.9-cann8.5-arm64-trainingruntime.yaml +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/mindspeed-llm-cann8.5-arm64-trainingruntime.yaml +``` + +Each `TrainingRuntime` lives in a Kubeflow `Profile` namespace by default — +change `metadata.namespace` to match the namespace you submit `TrainJob`s +from. The YAMLs pin `:v0.1.0`; change the tag if you want to track a +different release. + +## Submitting a TrainJob + +A shared smoke template applies to any of the runtimes — point +`spec.runtimeRef.name` at the runtime you want to exercise: + +```bash +kubectl apply -f https://raw.githubusercontent.com/alauda/aml-docs/master/docs/en/kubeflow/how_to/assets/training-runtimes/trainjob-smoke.yaml +kubectl -n get trainjobs +kubectl -n get pods -l trainer.kubeflow.org/trainjob-ancestor-step=trainer +``` + +Watch the trainer pod logs: + +```bash +trainjob=$(kubectl -n get trainjobs -o name | tail -1) +kubectl -n logs -f -l jobset.sigs.k8s.io/jobset-name=${trainjob##*/}-node +``` + +## Resource model on each device + +### NVIDIA GPU + +For clusters where each pod gets a whole device, request: + +```yaml +resources: + limits: + nvidia.com/gpu: 1 +``` + +For clusters running the HAMI vGPU scheduler, request a virtual slice +instead: + +```yaml +resources: + limits: + nvidia.com/gpualloc: 1 # one virtual GPU slot + nvidia.com/gpucores: 50 # 50% of one physical GPU's SM compute + nvidia.com/gpumem: "8192" # 8 GiB of GPU memory +``` + +### Huawei Ascend NPU + +Set `runtimeClassName: ascend` on the pod template so the Ascend container +runtime injects the driver libs and DCMI sockets. + +For clusters running the standard Huawei Kubernetes device-plugin, request: + +```yaml +spec: + runtimeClassName: ascend + containers: + - resources: + limits: + huawei.com/Ascend910: "1" +``` + +For clusters running HAMI's vNPU scheduler — each Ascend 910B4 chip can be +sliced into 20 cores / 32 GiB. Request a virtual slice via: + +```yaml +spec: + schedulerName: hami-scheduler + runtimeClassName: ascend + containers: + - resources: + limits: + huawei.com/Ascend910B4: "1" # one virtual NPU slot + huawei.com/Ascend910B4-memory: "8192" # 8 GiB of NPU HBM +``` + +With HAMI the node's `allocatable.huawei.com/Ascend910B4` reads `0` because +HAMI manages allocation through its scheduler extender, not the standard +device-plugin extended-resource path. If pods stay `Pending` with +`hami-scheduler: 1 node unregistered`, verify the Ascend host driver is +loaded (`/sys/bus/pci/drivers/davinci` exists, `npu-smi info` reports +`Health: OK`) and that the node is labeled `ascend=on` so HAMI's +device-plugin DaemonSet runs there. + +### Caveats by image + +- **`traininghub0.1-cu126-amd64`** — DeepSpeed JIT op compilation requires + `nvcc`. The base image ships CUDA *runtime* libs (via the official torch + cu126 wheels) but not the CUDA toolkit. For forward / backward / optimizer + with native torch ops the image works as-is; for DeepSpeed pre-compiled or + JIT-compiled ops, mount or install `nvidia-cuda-toolkit` and set + `CUDA_HOME` accordingly. +- **`mindspeed-llm-cann8.5-arm64`** — `megatron.core` requires the legacy + `pkg_resources` API; if your job imports it, install + `setuptools<81` at the top of the entrypoint (or pin it in your derived + image). `import mindspeed_llm` currently fails on the + `megatron_adaptor_v2 → mindspeed.features_manager` chain because the + `mindspeed_llm` master branch expects a newer MindSpeed core than the + `core_r0.8.0` branch pinned in the image; the underlying + torch + torch_npu + megatron.core + mindspeed stack trains successfully on + the NPU without going through the MindSpeed-LLM adapter shim. + +## Building your own runtime + +The Containerfiles, the multi-arch buildkitd helper, an e2e harness, and +the post-fix security scan evidence for every image are kept in the +[`kubeflow-plugin/training-runtimes`](https://github.com/alauda/kubeflow-plugin/tree/main/training-runtimes) +directory. Each framework image is a thin layer on top of one of the two +base images (`torch2.6-cu126-amd64` or `torch2.6-cann8.5-arm64`), so +deriving a new runtime is mostly +`FROM docker.io/alaudadockerhub/torch2.6-cu126-amd64:v0.1.0` plus framework +installs.