Dependencies
Tell urun what your function needs -- Python packages, system libraries, and post-install steps
Tell urun what your function needs — Python packages, system libraries, and post-install steps.
Your GPU functions run in isolated environments. Dependencies tells urun what to install before your function starts. Import it from urun.core:
from urun.core import DependenciesAdd PyTorch
deps = Dependencies(python=["torch>=2.5.0"])
@app.function(gpus="h100:1", deps=deps)
def serve(ctx):
import torch
...Add pip packages
List everything your function imports:
deps = Dependencies(python=["torch", "transformers", "accelerate"])
@app.function(gpus="h100:1", deps=deps)
def serve(ctx):
from transformers import AutoModelForCausalLM
...Version pins work the same as pip:
deps = Dependencies(python=[
"torch>=2.5.0",
"transformers==4.40.0",
"accelerate>=0.28",
])Add system libraries
Some Python packages depend on system-level libraries (ffmpeg for video, OpenGL for rendering). Use apt= to install them:
deps = Dependencies(
python=["torch", "opencv-python"],
apt=["ffmpeg", "libgl1-mesa-glx"],
)
@app.function(gpus="h100:1", deps=deps)
def process_video(video_path: str):
import cv2
...Build Flash Attention from source
Packages that require custom build steps (no pre-built wheels, need --no-build-isolation, etc.) go in post_install:
deps = Dependencies(
python=["torch>=2.5.0"],
post_install=["pip install flash-attn --no-build-isolation"],
)
@app.function(gpus="h100:1", deps=deps)
def serve(ctx):
from flash_attn import flash_attn_func
...post_install commands run after all python and apt dependencies are installed, so build dependencies are available.
Ship a data file with your code
urun deploy collects your app by following Python imports from the entrypoint, and ships the non-Python files sitting next to that source. Anything it does not reach — a prompt tensor in its own directory, a fixture an .urunignore pattern excludes — you declare with files=:
deps = Dependencies(
python=["torch"],
files=["flashvsr_utils/prompt_tensor/posi_prompt.pth"],
)
@app.function(gpus="h100:1", deps=deps)
def superres(ctx):
...- Paths are relative to your entrypoint file (
app.py), and land at the same relative location in the pod. - Declared files ship regardless of
.urunignoreand the built-in exclusions. That is what declaring them is for — you never have to rename a file to get it past a pattern. files=is purely additive: with nofiles=, collection is exactly what it was.- Entries are individual files. Directories and globs are not accepted — list each file, so what ships stays explicit and reviewable.
- A declared path that does not exist, is absolute, or points outside your app directory fails the deploy immediately, naming the path and the directory it was looked up under. A file you declare is never silently skipped.
Large model weights do not belong here — fetch those through the store so they are cached on the cluster instead of re-uploaded with every deploy. files= is for the small assets that are part of your source tree.
Pin the Python version
deps = Dependencies(
python_version="3.11",
python=["torch", "transformers"],
)If not specified, urun chooses the version. Set python_version when a package requires a specific one.
Attaching dependencies to functions
Dependencies attach to functions through the deps= parameter on @app.function:
from urun import App
from urun.core import Dependencies
app = App("my-app")
video_deps = Dependencies(
python=["torch>=2.5.0", "diffusers", "transformers"],
apt=["ffmpeg"],
post_install=["pip install flash-attn --no-build-isolation"],
)
@app.function(gpus="h100:1", deps=video_deps)
def generate(ctx):
...Different functions can have different dependencies — a lightweight voice function might need far fewer packages than a video pipeline:
voice_deps = Dependencies(python=["torch>=2.5.0", "transformers"])
@app.function(gpus="l4:1", deps=voice_deps)
def voice(ctx):
...Common presets
urun includes presets for common inference stacks (LLM serving, video generation, voice). These bundle the typical packages you need so you don't have to list them individually. Check urun.core for available options (ML_TRAINING_DEPS, ML_INFERENCE_DEPS, VIDEO_PROCESSING_DEPS, HUGGINGFACE_DEPS, DISTRIBUTED_DEPS).
Inheriting an upstream repo's dependencies
When your function imports an upstream model repo, don't hand-copy its requirements.txt. Declare the repo with store.repo and splice its own dependency list in:
store = app.store
REPO = store.repo("github.com/org/model-repo@<sha>", include=["model"])
deps = Dependencies(python=[*REPO.deps(), "torch>=2.5.0"])REPO.deps() parses the upstream's dependencies at the pinned commit, so you can't drift out of sync with a copy. Shape the splice with extra="name" (an optional-dependencies extra), exclude=["dep", ...] (drop upstream deps by name), and pin_overrides={"dep": "==1.2"} (replace a version spec). See De-vendoring for importing the upstream code itself and the static-analysis rules the selectors follow.
For secrets and API keys (HuggingFace tokens, external API keys), see Credentials.
Related:
- De-vendoring — Pull in upstream model code with
store.repo - Credentials — Passing secrets to your GPU functions
- App — The
@app.functiondecorator
Strict mode & CPU testing
One requirement — no data-dependent value-reads in the hot path — enables torch.compile, CUDA graphs, and zero-app-code CPU tests, validated by ctx.self_test.
De-vendoring upstream code
Pull in upstream model code at a pinned commit with store.repo — no vendored trees, no hand-maintained dep forests, no git clone in your build