Skip to content

feat(serverless): run fitness checks at startup, add skip env var - #578

Draft
justinwlin wants to merge 4 commits into
mainfrom
justinlin/dr-1409-python-sdk-move-health-checks-at-start-up
Draft

feat(serverless): run fitness checks at startup, add skip env var#578
justinwlin wants to merge 4 commits into
mainfrom
justinlin/dr-1409-python-sdk-move-health-checks-at-start-up

Conversation

@justinwlin

@justinwlin justinwlin commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Closes DR-1409.

Problem

Fitness (health) checks run in run_worker, inside runpod.serverless.start() — the last line of a typical handler module. A worker that loads a large model at import spends minutes loading before the SDK checks whether the GPU works or the disk is full. The ticket also asks for an env var to skip the checks.

Change

  • Built-in checks run at import runpod.serverless, before model load: memory, disk, network, CUDA version (nvidia-smi), and the native gpu_test binary.
  • The CUDA initialization check and GPU compute benchmark are marked @defer_to_worker_start and still run at start(). Both import torch and allocate on the device; running them at import would leave a CUDA context in a process the handler may later fork, which CUDA does not support and vLLM/DeepSpeed trip over.
  • run_fitness_checks() records what passed, so the run_worker pass only runs checks registered since — the user's @register_fitness_check functions, which cannot exist before that import.
  • RUNPOD_SKIP_FITNESS_CHECKS=true disables every check. (RUNPOD_SKIP_GPU_CHECK / RUNPOD_SKIP_AUTO_SYSTEM_CHECKS only ever covered built-ins.)
  • RUNPOD_DEFER_FITNESS_CHECKS=true restores the previous start()-only timing.

The import-time pass no-ops outside a real worker (no RUNPOD_WEBHOOK_GET_JOB), so local dev, tests, and the runpod CLI are untouched, and it never raises — failing to run the checks must not stop a worker booting. A failing check still force-exits as before.

Verification

pytest tests — 645 passed, including 12 new tests in test_fitness/test_startup.py.

Behavior change worth flagging

The memory check now measures a fresh container instead of one with the model already loaded, so RUNPOD_MIN_MEMORY_GB validates the environment the worker was given rather than the headroom left after loading. Conversely the gpu_test VRAM allocation no longer contends with a resident model, which should remove a class of spurious failures.

For review

This works by calling a function at module top level in runpod/serverless/__init__.py, so import runpod now spawns the gpu_test subprocess on a worker. The RUNPOD_WEBHOOK_GET_JOB gate keeps that off laptops and CI, but it does mean an import behaves differently based on an env var the reader can't see. The alternative is an explicit opt-in env var, at the cost of nobody getting the fix by default.

justinwlin and others added 4 commits August 25, 2026 22:31
Built-in GPU/system fitness checks ran in run_worker, which a handler module
only reaches after loading its model. Run them when runpod.serverless is
imported instead, so a broken environment fails in seconds. User-registered
checks still run at start(); checks that already passed are not repeated.

Adds RUNPOD_SKIP_FITNESS_CHECKS to disable all checks and
RUNPOD_DEFER_FITNESS_CHECKS to restore the previous start()-only timing.
_cuda_init_check and _benchmark_check import torch and allocate on the
device. Running them at import would leave a CUDA context in a process the
handler may later fork, which CUDA does not support and vLLM/DeepSpeed trip
over. Mark them @defer_to_worker_start so only subprocess-based and
non-GPU checks run early.
- run startup pass on a dedicated event loop instead of asyncio.run,
  which resets the loop policy and breaks asyncio.get_event_loop() in
  handler code on Python 3.10+
- set RUNPOD_FITNESS_CHECKS_DONE after the startup pass so children
  re-importing this module under multiprocessing 'spawn' skip the checks
- latch check auto-registration state only on success, so a malformed
  RUNPOD_MIN_*/GPU timeout value re-raises loudly in run_worker instead
  of silently disabling all system checks
- compare completed checks by identity, not equality, so distinct
  registrations that compare equal (bound methods) are not skipped
- bound the nvidia-smi call in rp_cuda.is_available with a 5s timeout
- accept 1/true/yes/on for RUNPOD_SKIP_GPU_CHECK and
  RUNPOD_SKIP_AUTO_SYSTEM_CHECKS, matching the new flags
- tests: pin the worker.py and import-time wiring, the full defer
  behavior, the done marker, the real auto-registration path (guard: no
  torch import), and bound-method re-registration; fix an orphaned
  coroutine in test_unexpected_error_does_not_propagate
- docs: thresholds/skip flags must be set before import runpod, realtime
  API mode runs only the import-time checks, refresh stale
  ARCHITECTURE.md execution flow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant