Skip to content

Commit e27c810

Browse files
committed
chore: integrate upstream apps changes
2 parents b2d7979 + 28a10ed commit e27c810

23 files changed

Lines changed: 2879 additions & 1321 deletions

‎ARCHITECTURE.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -597,14 +597,14 @@ log.error(message, job_id=None)
597597

598598
---
599599

600-
### Fitness Checks: `modules/rp_fitness.py`
600+
### Fitness Checks: `_health/fitness.py`
601601

602-
**Location**: `runpod/serverless/modules/rp_fitness.py`
602+
**Location**: `runpod/_health/fitness.py` (legacy `serverless.modules.rp_fitness` imports remain aliases)
603603

604604
**Responsibilities**:
605605
- Validate worker health at startup before handler initialization
606606
- Support both synchronous and asynchronous check functions
607-
- Exit immediately with sys.exit(1) on any check failure
607+
- Exit immediately with os._exit(1) on any check failure
608608
- Enable fail-fast deployment validation
609609

610610
**Key Functions**:
@@ -613,11 +613,11 @@ log.error(message, job_id=None)
613613
- `clear_fitness_checks()`: Clear registry (testing only)
614614

615615
**Execution Flow**:
616-
1. Called from `worker.py:40` before heartbeat starts: `asyncio.run(run_fitness_checks())`
616+
1. The first top-level `import runpod` with both `RUNPOD_ENDPOINT_ID` and `RUNPOD_WEBHOOK_GET_JOB` runs the built-in hardware checks (RAM, disk, CUDA version, native GPU test), excluding `RUNPOD_TEST`, `--test_input`, and `--rp_serve_api` invocations. On success the process sets `RUNPOD_EARLY_FITNESS_CHECKS_DONE=1`, which child processes inherit so they skip the pass. Network, Python CUDA initialization, compute, and custom checks remain at worker start. `RUNPOD_DEFER_FITNESS_CHECKS=true` postpones early checks. No custom launcher or entrypoint changes are required.
617617
2. Runs only in production mode (skipped for local testing)
618618
3. Auto-detects sync vs async using `inspect.iscoroutinefunction()`
619619
4. Executes checks in registration order (list preserves order)
620-
5. On failure: log detailed error, call `sys.exit(1)`
620+
5. On health failure: log, best-effort unhealthy report, force-kill via `os._exit(1)`. Registration is atomic; early setup errors defer, unresolved worker-start setup errors report `fitness_check_setup` and force-exit.
621621
6. On success: log completion, proceed with worker startup
622622

623623
**Performance**: ~0.5ms framework overhead per check, total depends on check logic
@@ -765,7 +765,7 @@ sequenceDiagram
765765
CHECK->>CHECK: Log success
766766
else Check fails
767767
CHECK->>SYS: Log error + traceback
768-
CHECK->>SYS: sys.exit(1)
768+
CHECK->>SYS: os._exit(1)
769769
end
770770
end
771771
@@ -1456,7 +1456,7 @@ stateDiagram-v2
14561456
- Heartbeat: `runpod/serverless/modules/rp_ping.py`
14571457
- Progress updates: `runpod/serverless/modules/rp_progress.py`
14581458
- Local API: `runpod/serverless/modules/rp_fastapi.py`
1459-
- Fitness checks: `runpod/serverless/modules/rp_fitness.py`
1459+
- Fitness checks: `runpod/_health/fitness.py` (legacy `serverless/modules/rp_fitness.py` alias)
14601460

14611461
**Performance analysis**: See [TODO.md](TODO.md)
14621462

‎README.md‎

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,288 @@ make setup
104104
make test
105105
```
106106

107+
## ⚡ | Serverless Worker (SDK)
108+
109+
This python package can also be used to create a serverless worker that can be deployed to Runpod as a custom endpoint API.
110+
111+
### Quick Start
112+
113+
Create a python script in your project that contains your model definition and the Runpod worker start code. Run this python code as your default container start command:
114+
115+
```python
116+
# my_worker.py
117+
118+
import runpod
119+
120+
def is_even(job):
121+
122+
job_input = job["input"]
123+
the_number = job_input["number"]
124+
125+
if not isinstance(the_number, int):
126+
return {"error": "Silly human, you need to pass an integer."}
127+
128+
if the_number % 2 == 0:
129+
return True
130+
131+
return False
132+
133+
runpod.serverless.start({"handler": is_even})
134+
```
135+
136+
Make sure that this file is ran when your container starts. This can be accomplished by calling it in the docker command when you set up a template at [console.runpod.io/serverless/user/templates](https://console.runpod.io/serverless/user/templates) or by setting it as the default command in your Dockerfile.
137+
138+
See our [blog post](https://www.runpod.io/blog/build-basic-serverless-api) for creating a basic Serverless API, or view the [details docs](https://docs.runpod.io/serverless-ai/custom-apis) for more information.
139+
140+
### Local Test Worker
141+
142+
You can also test your worker locally before deploying it to Runpod. This is useful for debugging and testing.
143+
144+
```bash
145+
python my_worker.py --rp_serve_api
146+
```
147+
148+
### Worker Fitness Checks
149+
150+
Fitness checks allow you to validate your worker environment at startup before processing jobs. If any check fails, the worker exits immediately, allowing your orchestrator to restart it.
151+
152+
```python
153+
# my_worker.py
154+
155+
import runpod
156+
import torch
157+
158+
# Register fitness checks using the decorator
159+
@runpod.serverless.register_fitness_check
160+
def check_gpu_available():
161+
"""Verify GPU is available."""
162+
if not torch.cuda.is_available():
163+
raise RuntimeError("GPU not available")
164+
165+
@runpod.serverless.register_fitness_check
166+
def check_disk_space():
167+
"""Verify sufficient disk space."""
168+
import shutil
169+
stat = shutil.disk_usage("/")
170+
free_gb = stat.free / (1024**3)
171+
if free_gb < 10:
172+
raise RuntimeError(f"Insufficient disk space: {free_gb:.2f}GB free")
173+
174+
def handler(job):
175+
job_input = job["input"]
176+
# Your handler code here
177+
return {"output": "success"}
178+
179+
# Fitness checks run before handler initialization (production only)
180+
runpod.serverless.start({"handler": handler})
181+
```
182+
183+
**Key Features:**
184+
- Supports both synchronous and asynchronous check functions
185+
- Hardware checks run at the first Serverless `import runpod`; processes it launches inherit the result via `RUNPOD_EARLY_FITNESS_CHECKS_DONE` and skip the pass
186+
- Local tests and non-worker imports remain exempt; network readiness and custom checks run at worker start
187+
- Successful early checks are reused at worker start unless their configuration changes
188+
- Runs before handler initialization and job processing begins
189+
- Any check failure exits with code 1 (worker marked unhealthy)
190+
191+
See [Worker Fitness Checks](https://github.com/runpod/runpod-python/blob/main/docs/serverless/worker_fitness_checks.md) documentation for more examples and best practices.
192+
193+
### Network-Volume Warm Cache
194+
195+
When a network volume is attached, `VolumeCache` warms local directories (such as a model cache) across cold starts — hydrating them on startup and syncing new files back on exit — so a repeated multi-GB model download becomes a one-time cost per endpoint. It is stdlib-only and best-effort.
196+
197+
```python
198+
from runpod.serverless import VolumeCache
199+
200+
with VolumeCache(dirs=["/root/.cache/huggingface"]):
201+
model = load_model()
202+
```
203+
204+
See [Network-Volume Warm Cache](https://github.com/runpod/runpod-python/blob/main/docs/serverless/volume_cache.md) documentation for configuration and details.
205+
206+
## 📚 | REST API v2 Wrapper
207+
208+
Use the API wrapper to manage Runpod resources through REST API v2.
209+
210+
```python
211+
import runpod
212+
213+
runpod.api_key = "your_runpod_api_key_found_under_settings"
214+
```
215+
216+
### Endpoints
217+
218+
You can interact with Runpod endpoints via a `run` or `run_sync` method.
219+
220+
#### Basic Usage
221+
222+
```python
223+
endpoint = runpod.Endpoint("ENDPOINT_ID")
224+
225+
run_request = endpoint.run(
226+
{"your_model_input_key": "your_model_input_value"}
227+
)
228+
229+
# Check the status of the endpoint run request
230+
print(run_request.status())
231+
232+
# Get the output of the endpoint run request, blocking until the endpoint run is complete.
233+
print(run_request.output())
234+
```
235+
236+
```python
237+
endpoint = runpod.Endpoint("ENDPOINT_ID")
238+
239+
run_request = endpoint.run_sync(
240+
{"your_model_input_key": "your_model_input_value"}
241+
)
242+
243+
# Returns the job results if completed within 90 seconds, otherwise, returns the job status.
244+
print(run_request )
245+
```
246+
247+
#### API Key Management
248+
249+
The SDK supports multiple ways to set API keys:
250+
251+
**1. Global API Key** (Default)
252+
```python
253+
import runpod
254+
255+
# Set global API key
256+
runpod.api_key = "your_runpod_api_key"
257+
258+
# All endpoints will use this key by default
259+
endpoint = runpod.Endpoint("ENDPOINT_ID")
260+
result = endpoint.run_sync({"input": "data"})
261+
```
262+
263+
**2. Endpoint-Specific API Key**
264+
```python
265+
# Create endpoint with its own API key
266+
endpoint = runpod.Endpoint("ENDPOINT_ID", api_key="specific_api_key")
267+
268+
# This endpoint will always use the provided API key
269+
result = endpoint.run_sync({"input": "data"})
270+
```
271+
272+
#### API Key Precedence
273+
274+
The SDK uses this precedence order (highest to lowest):
275+
1. Endpoint instance API key (if provided to `Endpoint()`)
276+
2. Global API key (set via `runpod.api_key`)
277+
278+
```python
279+
import runpod
280+
281+
# Example showing precedence
282+
runpod.api_key = "GLOBAL_KEY"
283+
284+
# This endpoint uses GLOBAL_KEY
285+
endpoint1 = runpod.Endpoint("ENDPOINT_ID")
286+
287+
# This endpoint uses ENDPOINT_KEY (overrides global)
288+
endpoint2 = runpod.Endpoint("ENDPOINT_ID", api_key="ENDPOINT_KEY")
289+
290+
# All requests from endpoint2 will use ENDPOINT_KEY
291+
result = endpoint2.run_sync({"input": "data"})
292+
```
293+
294+
#### Thread-Safe Operations
295+
296+
Each `Endpoint` instance maintains its own API key, making concurrent operations safe:
297+
298+
```python
299+
import threading
300+
import runpod
301+
302+
def process_request(api_key, endpoint_id, input_data):
303+
# Each thread gets its own Endpoint instance
304+
endpoint = runpod.Endpoint(endpoint_id, api_key=api_key)
305+
return endpoint.run_sync(input_data)
306+
307+
# Safe concurrent usage with different API keys
308+
threads = []
309+
for customer in customers:
310+
t = threading.Thread(
311+
target=process_request,
312+
args=(customer["api_key"], customer["endpoint_id"], customer["input"])
313+
)
314+
threads.append(t)
315+
t.start()
316+
```
317+
318+
### GPU Cloud (Pods)
319+
320+
```python
321+
import runpod
322+
323+
runpod.api_key = "your_runpod_api_key_found_under_settings"
324+
325+
# get all my pods
326+
pods = runpod.get_pods()
327+
328+
# get a specific pod
329+
pod = runpod.get_pod(pods[0]["id"])
330+
331+
# create a pod with a gpu
332+
pod = runpod.create_pod("test", "runpod/stack", "NVIDIA GeForce RTX 4090")
333+
334+
# create a pod with a cpu
335+
pod = runpod.create_pod("test", "runpod/stack", instance_id="cpu3c-2-4")
336+
337+
# stop the pod
338+
runpod.stop_pod(pod["id"])
339+
340+
# resume the pod
341+
runpod.resume_pod(pod["id"])
342+
343+
# terminate the pod
344+
runpod.terminate_pod(pod["id"])
345+
```
346+
347+
### Template and placement options
348+
349+
- With `create_pod(template_id=...)`, omitting `docker_args` inherits the template's
350+
command. Pass `docker_args=""` to clear that inherited command.
351+
- Omitting `volume_mount_path` preserves an inherited GPU template volume's path.
352+
Setting it explicitly changes the path while retaining the inherited volume size.
353+
New persistent volumes and network volume mounts default to `/runpod-volume`.
354+
CPU pods do not inherit template persistent volumes; a CPU `volume_mount_path`
355+
requires `network_volume_id`.
356+
- For GPU pods, `min_memory_in_gb` and `min_vcpu_count` specify minimum host RAM
357+
and vCPUs **per GPU**, not GPU VRAM or totals for the pod.
358+
- CPU `instance_id` must use `<cpu-flavor>-<vcpu-count>-<memory>`, with positive
359+
integer vCPU and memory values, for example `cpu3c-4-8`.
360+
- REST v2 cannot require a public-IP-capable host. `support_public_ip=False` is
361+
the default and does not disable public networking. `True` raises `ValueError`
362+
rather than silently ignoring that requirement.
363+
- `create_template(volume_in_gb=0)` creates a template without a persistent volume.
364+
- `create_endpoint(locations="US-KS-2,EU-RO-1")` accepts comma-separated
365+
datacenter IDs. Country codes such as `US` and `RO` are not supported.
366+
Endpoint creation sends a single REST request without catalog lookups.
367+
- Endpoint `gpu_ids` accepts pool IDs and excluded GPU types, for example
368+
`gpu_ids="ADA_48_PRO,-NVIDIA L40"` selects that pool without NVIDIA L40 GPUs.
369+
- `create_endpoint` accepts only `QUEUE_DELAY` and `REQUEST_COUNT` scaling.
370+
`idle_timeout` applies to `QUEUE_DELAY` (default: 5 seconds); explicitly setting
371+
it with `REQUEST_COUNT` raises `ValueError`.
372+
- `resume_pod(pod_id)` keeps the existing GPU allocation. REST v2 does not support
373+
resizing on resume, so providing `gpu_count` raises `ValueError`.
374+
375+
## 📁 | Directory
376+
377+
```BASH
378+
.
379+
├── docs # Documentation
380+
├── examples # Examples
381+
├── runpod # Package source code
382+
│ ├── api # rest api v2 wrapper
383+
│ ├── cli # Command Line Interface Functions
384+
│ ├── endpoint # Language library - Endpoints
385+
│ └── serverless # SDK - Serverless Worker
386+
└── tests # Package tests
387+
```
388+
107389
<div align="center">
108390

109391
<a target="_blank" href="https://discord.gg/pJ3P2DbUUq">![Discord](https://discordapp.com/api/guilds/912829806415085598/widget.png?style=banner2)</a>

0 commit comments

Comments
 (0)