Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions k8s/dynamo-deploy/admin-stub.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Optional admin-stub Deployment + Service.
# kubectl apply -f admin-stub.yaml -n <your-namespace>
#
# Only needed if your Dynamo build does NOT serve /v1/rl/* natively
# (i.e. older builds without DYN_ENABLE_RL=true). With a recent Dynamo,
# point `admin_base_url` directly at the Dynamo frontend and skip this
# manifest entirely.
apiVersion: v1
kind: ConfigMap
metadata:
name: admin-stub-script
namespace: <your-namespace>
data:
admin_stub.py: |
from http.server import HTTPServer, BaseHTTPRequestHandler
class H(BaseHTTPRequestHandler):
def do_POST(self):
n = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(n) if n else b""
print(f"[stub] POST {self.path} body={body[:200]}")
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
HTTPServer(("0.0.0.0", 8001), H).serve_forever()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: admin-stub
namespace: <your-namespace>
spec:
replicas: 1
selector:
matchLabels:
app: admin-stub
template:
metadata:
labels:
app: admin-stub
spec:
containers:
- name: stub
image: python:3.12-slim
command: ["python3", "/scripts/admin_stub.py"]
ports:
- containerPort: 8001
volumeMounts:
- name: script
mountPath: /scripts
resources:
requests:
memory: "64Mi"
cpu: "50m"
volumes:
- name: script
configMap:
name: admin-stub-script
---
apiVersion: v1
kind: Service
metadata:
name: admin-stub
namespace: <your-namespace>
spec:
selector:
app: admin-stub
ports:
- port: 8001
targetPort: 8001
90 changes: 90 additions & 0 deletions k8s/dynamo-deploy/dynamo-dgd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Example DynamoGraphDeployment for serving inference to prime-rl.
# kubectl apply -f dynamo-dgd.yaml -n <your-namespace>
#
# Replace `<your-namespace>`, `<your-registry>/dynamo:<vllm-runtime-tag>`,
# `<your-image-pull-secret>`, and the model name below for your environment.
# Requires DYN_ENABLE_RL=true on the Dynamo runtime so /v1/rl/* endpoints are
# served natively.
apiVersion: nvidia.com/v1alpha1
kind: DynamoGraphDeployment
metadata:
name: prime-rl-dynamo
namespace: <your-namespace>
spec:
backendFramework: vllm
pvcs:
- create: false
name: model-cache
services:
Frontend:
componentType: frontend

extraPodSpec:
imagePullSecrets:
- name: <your-image-pull-secret>
mainContainer:
image: <your-registry>/dynamo:<vllm-runtime-tag>
startupProbe:
failureThreshold: 360
httpGet:
path: /health
port: 8000
periodSeconds: 10
timeoutSeconds: 5
replicas: 1
volumeMounts:
- mountPoint: /model-cache
name: model-cache
VllmWorker:
componentType: worker

envFromSecret: hf-token-secret
extraPodSpec:
imagePullSecrets:
- name: <your-image-pull-secret>
mainContainer:
command:
- python3
- -m
- dynamo.vllm
args:
- --model
- Qwen/Qwen3-4B-Instruct-2507
- --served-model-name
- Qwen/Qwen3-4B-Instruct-2507
- --tensor-parallel-size
- "1"
- --max-model-len
- "2048"
- --max-num-seqs
- "64"
- --gpu-memory-utilization
- "0.90"
- --enforce-eager
env:
- name: HF_HOME
value: /model-cache/huggingface
image: <your-registry>/dynamo:<vllm-runtime-tag>
startupProbe:
failureThreshold: 360
httpGet:
path: /health
port: 9090
periodSeconds: 10
timeoutSeconds: 10
workingDir: /workspace/examples/backends/vllm
tolerations:
- effect: NoSchedule
key: nvidia.com/gpu
operator: Exists
replicas: 1
resources:
limits:
gpu: "1"
requests:
gpu: "1"
sharedMemory:
size: 16Gi
volumeMounts:
- mountPoint: /model-cache
name: model-cache
57 changes: 57 additions & 0 deletions k8s/dynamo-deploy/prime-rl-configs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Example ConfigMap mounted at /configs in the orchestrator and trainer pods.
# kubectl apply -f prime-rl-configs.yaml -n <your-namespace>
#
# Replace `<your-namespace>` below with your namespace, and adjust the
# Dynamo frontend service hostname if it differs in your cluster.
apiVersion: v1
kind: ConfigMap
metadata:
name: prime-rl-configs
namespace: <your-namespace>
data:
orch.toml: |
max_steps = 20
seq_len = 2048
batch_size = 64
rollouts_per_example = 4
use_token_client = false

[wandb]
project = "prime-rl-dynamo-k8s"
name = "dynamo-smoke-qwen3-4b"

[model]
name = "Qwen/Qwen3-4B-Instruct-2507"

[sampling]
max_tokens = 256

[[env]]
id = "math-env"
name = "hendrycks-math"
args = { dataset_name = "PrimeIntellect/Hendrycks-Math", dataset_subset = "default", math_verify_max_workers = 32, math_verify_timeout = 60 }

[buffer]
easy_threshold = 1.0
hard_threshold = 0.0

[client]
base_url = ["http://prime-rl-dynamo-frontend.<your-namespace>.svc.cluster.local:8000/v1"]
# Admin endpoints (/v1/rl/*) are served natively by the Dynamo Rust frontend
# (DYN_ENABLE_RL=true). No separate admin-stub service needed.
admin_base_url = ["http://prime-rl-dynamo-frontend.<your-namespace>.svc.cluster.local:8000/v1/rl"]
skip_model_check = true

train.toml: |
max_steps = 20

[model]
name = "Qwen/Qwen3-4B-Instruct-2507"
seq_len = 2048

[wandb]
project = "prime-rl-dynamo-k8s"
name = "dynamo-smoke-qwen3-4b-trainer"

[optim]
lr = 3e-6
114 changes: 114 additions & 0 deletions k8s/dynamo-deploy/prime-rl-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Example Helm values for prime-rl when Dynamo serves inference.
#
# helm install prime-rl k8s/prime-rl -f k8s/dynamo-deploy/prime-rl-values.yaml -n <your-namespace>
#
# Dynamo serves inference, so prime-rl's own inference component is disabled.
# Orchestrator and trainer point at the Dynamo frontend (deploy with dynamo-dgd.yaml).
#
# Replace the following placeholders for your environment:
# - namespace
# - image.repository / image.tag (your prime-rl image)
# - storage.storageClassName (your cluster's RWX storage class)
# - orchestrator/trainer.imagePullSecrets[*].name
# - secrets.name (HF token / W&B key)

namespace: <your-namespace>

image:
repository: <your-registry>/prime-rl
tag: "latest"
pullPolicy: IfNotPresent

storage:
enabled: true
storageClassName: <your-rwx-storageclass>
accessModes:
- ReadWriteMany
size: 100Gi
mountPath: /data

config:
example: "prime-rl-dynamo"
secrets:
enabled: true
name: hf-token-secret

# Orchestrator: CPU only, talks to Dynamo for inference
orchestrator:
enabled: true
replicas: 1
autoStart: true
command: >-
BENCH_API_KEY=EMPTY
uv run orchestrator
@ /configs/orch.toml
--output-dir /data/outputs/run_default
--max-concurrent 16
; echo "orchestrator exited: $?" ; sleep infinity
resources:
requests:
memory: "8Gi"
cpu: "4"
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: HF_TOKEN
optional: true
- name: WANDB_API_KEY
valueFrom:
secretKeyRef:
name: wandb-secret
key: WANDB_API_KEY
configMap: prime-rl-configs
imagePullSecrets:
- name: <your-image-pull-secret>
nodeSelector: {}

# Inference: DISABLED (Dynamo DGD replaces this)
inference:
enabled: false

# Trainer: 1 GPU is sufficient for small models (e.g. Qwen3-4B GRPO)
trainer:
enabled: true
replicas: 1
autoStart: true
command: >-
uv run torchrun
--nproc-per-node=1
--rdzv-endpoint=localhost:29510
--rdzv-id=smoke_$(date +%s)
-m prime_rl.trainer.rl.train
@ /configs/train.toml
--output-dir /data/outputs
; echo "trainer exited: $?" ; sleep infinity
pytorchCudaAllocConf: "expandable_segments:True"
gpu:
enabled: true
count: 1
resources:
requests:
memory: "32Gi"
cpu: "8"
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: HF_TOKEN
optional: true
- name: WANDB_API_KEY
valueFrom:
secretKeyRef:
name: wandb-secret
key: WANDB_API_KEY
configMap: prime-rl-configs
runtimeClassName: nvidia
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
imagePullSecrets:
- name: <your-image-pull-secret>
57 changes: 57 additions & 0 deletions tools/dynamo/admin_stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""
Stub HTTP server for prime-rl admin endpoints.

NOTE: As of dynamo#8630 (bis/parity-tokenize-tcp), Dynamo's Rust frontend
implements these routes natively at /v1/rl/* when DYN_ENABLE_RL=true:
POST /v1/rl/load_lora_adapter
POST /v1/rl/unload_lora_adapter
GET /v1/rl/health

For K8s and any deployment with a real Dynamo frontend, point admin_base_url
at the Dynamo service (e.g. http://<frontend-svc>:8000/v1/rl). This stub is
kept as a local development fallback for running the orchestrator without a
live Dynamo instance.

Usage:
python tools/dynamo/admin_stub.py
python tools/dynamo/admin_stub.py --port 8001
"""

import argparse

from aiohttp import web


async def pause(request):
print("[stub] POST /pause - OK")
return web.Response(status=200, text="OK")


async def resume(request):
print("[stub] POST /resume - OK")
return web.Response(status=200, text="OK")


async def update_weights(request):
body = await request.json()
print(f"[stub] POST /update_weights weight_dir={body.get('weight_dir')} - OK (weights not reloaded)")
return web.Response(status=200, text="OK")


async def health(request):
return web.Response(status=200, text="OK")


app = web.Application()
app.router.add_post("/pause", pause)
app.router.add_post("/resume", resume)
app.router.add_post("/update_weights", update_weights)
app.router.add_get("/health", health)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Dynamo admin stub server")
parser.add_argument("--port", type=int, default=8001, help="Port to listen on")
args = parser.parse_args()
print(f"[stub] Dynamo admin stub server starting on port {args.port}...")
web.run_app(app, port=args.port)
Loading
Loading