App
Define GPU functions and local entrypoints with the urun App container
App is the entry point for defining GPU functions in urun.
Every urun program starts with creating an App. The App instance is a container that holds your remote functions and local entrypoints. You register functions with the @app.function() decorator (Python) or invoke them as methods on the app handle (TypeScript), and urun handles provisioning, execution, and teardown on remote GPUs.
from urun import App
app = App("liveavatar")import { App } from '@urun-sh/core'
const app = App('liveavatar', { baseUrl: 'https://urun.sh', orgId: 'your-org-id' })The name identifies your app in logs, the console, and deployment. It must be unique within your account.
Function decorators
The @app.function() decorator registers a function to run on remote GPUs. The gpus parameter specifies the hardware — see Compute for the full specification format.
from urun import App, store
from urun.core import Dependencies, Credentials
app = App("liveavatar")
DEPS = Dependencies(
python=["torch>=2.5.0", "transformers", "diffusers"],
)
CREDENTIALS = Credentials(
env=["HF_TOKEN"],
)
@app.function(
gpus="h100:8",
deps=DEPS,
credentials=CREDENTIALS,
lease=3600,
)
def avatar(ctx, style: str = "photoreal"):
"""Drive a realtime avatar pipeline across 8x H100 GPUs."""
# Session logic...When a client opens avatar(style="anime"), the function body executes on remote H100 GPUs — not on your local machine. urun provisions the hardware, installs dependencies, injects credentials, and runs your code for the life of the session.
`ctx` is injected, not passed
If your function declares a first parameter annotated ctx: urun.Context, urun injects the execution context automatically — you never pass it yourself. Your own arguments come after it (def avatar(ctx: urun.Context, style: str = "photoreal")), and callers pass only those (avatar(style="anime")). ctx is optional: omit it if you don't need rank/device/streams. Streaming functions always declare it.
Key parameters:
gpus— GPU specification:"h100:8"for 8x H100,"a100:4"for 4x A100, or just8for default GPU type. This is deploy-time decorator config — it fixes the hardware shape whenurun deployregisters the function. A same-namedgpuspassed toapp.fn(...)at call time is just an inert argument and does not reshape the hardware.deps— Python and system dependencies to install in the remote environmentcredentials— Secrets to inject at runtime (e.g.,HF_TOKEN)env— Environment variables as a dict, for external inputs only (never behavior-tuning app config)lease— Renewable session length in seconds, auto-renewed while the client stays connectedmax_session_s— Hard, non-renewable session cap; past it the session ends with terminal reasonmax_session_agesession_idle_timeout— Idle kick in seconds (platform default 300;0opts out); the user is warned ~90s beforewarm/snapshot— Idle replica floor and snapshot wakes:warm=0scales to zero when idle, and (for GPU functions, multi-rank included) snapshots after the first validated boot so wakes restore instead of cold-bootingmax_concurrency— Max concurrent sessions for the function (platform default 2); demand scales replicas up to this limitsession_kind—"realtime"(default) or"doc"for docs+artifacts apps with no media lanes
The retired kwargs timeout, topology, gpus_per_replica, colocate_on_node, event_name, scaling, and gang raise a loud TypeError — cross-app calls via App.ref are best-effort by definition and have no declaration surface. The full kwarg-by-kwarg reference is at reference/app; the session-length story is explained in Sessions.
Streaming
@app.function() supports real-time streaming for interactive use cases like video generation or live inference. Inside any function, use ctx.stream() to create a pipeline video sink and compose stages with the | operator.
@app.function(gpus="h100:1", deps=DEPS, credentials=CREDS)
async def generate_video(ctx: urun.Context, prompt: str = "A serene landscape"):
video = ctx.stream("video", codec="h264", fps=30)
control = ctx.doc("control")
pipeline = (
ctx.source(generate_frames(control), device="cuda") # reads control.desired.* inline
| ctx.pace(fps=30)
| video
)
await pipeline.run()Key points:
- Same decorator — streaming functions use
@app.function(), the same decorator as every other function - Pipeline DSL — compose stages with
ctx.source | ctx.pipe | ctx.pace | video; the video sink auto-converts float tensors to encoded frames ctx.stream()— creates a pipeline video sink; clients subscribe by matching the stream name (e.g.,session.stream("video")in TypeScript)- Reactive control — cheap config is a live
control.desired.*read inline in the stage; structural change usesdoc.bind.await pipeline.run()drives it continuously until disconnect - Bidirectional — clients send data back via
session.doc("control").set(data), and the function reads it viactx.doc("control")withlisten()orget()
The Getting Started guide walks through a complete streaming example.
Class-based Apps
For apps with multiple related functions and shared context, use the @urun.app() class decorator:
import urun
from urun import store
@urun.app("my-studio")
class MyStudio:
@urun.function(gpus="h100:1")
def generate(self, ctx, prompt: str = "A quiet street at dawn"):
# Live generation session...
pass
@urun.function(gpus="h100:1")
def restyle(self, ctx, style_key: str):
style = store.get(style_key)
# Live restyle session...
passClass-based apps are useful when you have a group of related functions that logically belong together — for example, generate, restyle, and upscale in one studio app. Each method gets the same decorator treatment as standalone functions.
Access the underlying App instance:
MyStudio._app.list_functions() # ['generate', 'restyle']When to prefer class-based over function-based:
- Class-based — grouping related functions, shared configuration, cleaner namespacing
- Function-based — simpler apps, single-purpose scripts, quick experiments
Cross-app calls: App.ref("other-app")
Functions on another app (registered by the same org) are called transparently through a ref — refs, never strings, cross app boundaries. App.ref("name") returns a proxy whose function attributes mint a real session on that app's function, the same primitive a browser gets:
from urun import App
ref = App.ref("qwen-image-edit")
session = ref.edit(prompt=prompt, frame=frame_ptr) # a full session: session.doc(...) / session.stream(...)
edited = await ref.edit(prompt=prompt, frame=frame_ptr) # or await it for the one-shot resultThe same-app form is just the attribute: session = app.other_fn(...) mints a session on this app's own function. An unknown function name fails loudly at call time with the org-scoped lookup error. There is no ctx.call — transparent calls through the app/ref attribute are the one cross-function mechanism.
Walk-Through
This walk-through builds a small live-video app end-to-end: one function, one GPU shape, one stream a browser can attach to. For environment setup, see Getting Started.
from urun import App
from urun.core import Dependencies, Credentials
import urun
app = App("walkthrough-video")
store = app.store
DEPS = Dependencies(python=["torch>=2.5.0", "diffusers", "transformers"])
CREDS = Credentials(env=["HF_TOKEN"])
@store.cache(key="wan21-t2v-1.3b")
def load_pipeline(*, device):
import torch
from diffusers import DiffusionPipeline
return DiffusionPipeline.from_pretrained(
store.path("hf://Wan-AI/Wan2.1-T2V-1.3B"), torch_dtype=torch.bfloat16
).to(device)
@app.function(gpus="h100:1", deps=DEPS, credentials=CREDS, warm=0)
async def generate(ctx: urun.Context, prompt: str = "A serene landscape"):
"""Generate live video, steered by the client while it plays."""
pipe = load_pipeline(device="cuda")
control = ctx.doc("control")
video = ctx.stream("video", codec="h264", fps=20)
def frames(*, stop_event, config):
while not stop_event.is_set():
out = pipe(prompt=control.desired.prompt.value or prompt, num_frames=17)
yield out.frames[0]
await (ctx.source(frames, device="cuda") | ctx.pace(fps=20) | video).run()Deploy it:
urun deploy walkthrough.pyExpected output:
Provisioning h100:1 on cloud backend...
Installing dependencies: torch>=2.5.0, diffusers, transformers
Injecting credentials: HF_TOKEN
App deployed: walkthrough-video (generate)Opening app.generate(prompt="A neon city in the rain") from a client mints a live session: the pipeline loads once through @store.cache (and stays resident for every later session), frames stream to the browser over the video stream, and the client steers the prompt mid-flight through the control doc — no restarts, no re-provisioning. With warm=0 the app scales to zero when nobody is connected and wakes from a snapshot.
Next: Compute
Compute defines the hardware your function runs on and how long its sessions live — GPUs, CPUs, memory, session length, and warm capacity.
Related:
- Getting Started — Build and run your first streaming app
- AI-Assisted Setup — Generate app scaffolds with AI