Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/Deployment_Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ flash build
├── 4. Dependency Installation
│ ├── Install Python packages for linux/x86_64
│ ├── Target Python 3.12 for wheel ABI selection
│ ├── Target the app's python_version for wheel ABI selection (3.10 / 3.11 / 3.12)
│ └── Binary wheels only (no compilation)
└── 5. Packaging
Expand Down
15 changes: 14 additions & 1 deletion docs/Flash_Deploy_Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@ This guide walks through deploying a Flash application from local development to

## Prerequisites

- Python 3.10+
- Python 3.10, 3.11, or 3.12
- `pip install runpod-flash`
- A Runpod account with API key ([get one here](https://docs.runpod.io/get-started/api-keys))

### Python version selection

Flash apps ship as a single tarball, so every resource in an app shares one Python version. The worker runtime defaults to 3.12 (the version torch is pre-installed for in the GPU base image). Select a different version in two ways:

- **Per-resource declaration**: set `python_version="3.11"` on any resource config — all resources in the same app must agree or leave it unset.
- **App-level override**: pass `--python-version 3.11` to `flash build` or `flash deploy`. The override wins over per-resource values that are unset and must match any that are set.

| Version | Status | GPU cold-start | Notes |
|---------|--------|----------------|-------|
| 3.10 | Supported (EOL 2026-10-31) | +~7 GB alt-Python install | Consider migrating to 3.11 before EOL |
| 3.11 | Supported | +~7 GB alt-Python install | |
| 3.12 | Supported (default) | No overhead | Torch pre-installed in base image |

## Quick Start

```bash
Expand Down
30 changes: 21 additions & 9 deletions src/runpod_flash/cli/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@
import tomli as tomllib # Python 3.10

from runpod_flash.cli.utils.formatting import print_error, print_warning
from runpod_flash.core.resources.constants import (
DEFAULT_PYTHON_VERSION,
MAX_TARBALL_SIZE_MB,
)
from runpod_flash.core.resources.constants import MAX_TARBALL_SIZE_MB

from ..utils.ignore import get_file_tree, load_ignore_patterns
from .build_utils.handler_generator import HandlerGenerator
Expand Down Expand Up @@ -273,6 +270,7 @@ def run_build(
output_name: str | None = None,
exclude: str | None = None,
verbose: bool = False,
python_version: str | None = None,
) -> Path:
"""Run the build process and return the artifact path.

Expand All @@ -287,6 +285,10 @@ def run_build(
output_name: Custom archive name (default: artifact.tar.gz)
exclude: Comma-separated packages to exclude
verbose: Show archive and build directory paths in summary
python_version: Optional app-level Python version override. When None,
inferred from resource configs (defaulting to DEFAULT_PYTHON_VERSION
if none declare one). One tarball serves every resource in an app,
so all resources must agree on one version.

Returns:
Path to the created artifact archive
Expand All @@ -311,10 +313,9 @@ def run_build(
spec = load_ignore_patterns(project_dir)
files = get_file_tree(project_dir, spec)

# all packaging and image selection targets 3.12 regardless of local python.
# pip downloads wheels for 3.12 via --python-version, and all worker images
# run 3.12, so the local interpreter version does not affect the build output.
python_version = DEFAULT_PYTHON_VERSION
# Resolved later by ManifestBuilder from resource configs (or the override
# above). Pip wheel selection re-reads this via _resolve_pip_python_version.
manifest_python_version_override = python_version

try:
copy_project_files(files, project_dir, build_dir)
Expand All @@ -335,7 +336,7 @@ def run_build(
remote_functions,
scanner,
build_dir=build_dir,
python_version=python_version,
python_version=manifest_python_version_override,
)
manifest = manifest_builder.build()
manifest["source_fingerprint"] = compute_source_fingerprint(
Expand Down Expand Up @@ -506,6 +507,15 @@ def build_command(
"--exclude",
help="Comma-separated additional packages to exclude (torch packages are auto-excluded)",
),
python_version: str | None = typer.Option(
None,
"--python-version",
help=(
"Target Python version for worker images (3.10, 3.11, or 3.12). "
"Overrides per-resource python_version declarations. "
"Defaults to the version declared on resource configs, or 3.12 if none set."
),
),
):
"""
Build Flash application for debugging (build only, no deploy).
Expand All @@ -518,6 +528,7 @@ def build_command(
flash build --no-deps # Skip transitive dependencies
flash build -o my-app.tar.gz # Custom archive name
flash build --exclude transformers # Exclude additional large packages
flash build --python-version 3.11 # Target Python 3.11 workers
"""
try:
project_dir, app_name = discover_flash_project()
Expand All @@ -529,6 +540,7 @@ def build_command(
output_name=output_name,
exclude=exclude,
verbose=True,
python_version=python_version,
)

except KeyboardInterrupt:
Expand Down
96 changes: 78 additions & 18 deletions src/runpod_flash/cli/commands/build_utils/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from runpod_flash.core.resources.constants import (
DEFAULT_PYTHON_VERSION,
GPU_BASE_IMAGE_PYTHON_VERSION,
validate_python_version,
)

from .scanner import (
Expand Down Expand Up @@ -93,7 +93,10 @@ def __init__(
self.remote_functions = remote_functions
self.scanner = scanner # Optional: RuntimeScanner with resource config info
self.build_dir = build_dir
self.python_version = python_version or DEFAULT_PYTHON_VERSION
# User-supplied app-level override; None means "infer from resources".
self._python_version_override = python_version
# Effective app-level version; set by build() via _reconcile_python_version.
self.python_version: Optional[str] = None

def _import_module(self, file_path: Path):
"""Import a module from file path, returning (module, cleanup_fn).
Expand Down Expand Up @@ -216,6 +219,12 @@ def _extract_config_properties(config: Dict[str, Any], resource_config) -> None:
if hasattr(resource_config, "imageName") and resource_config.imageName:
config["imageName"] = resource_config.imageName

if (
hasattr(resource_config, "python_version")
and resource_config.python_version
):
config["python_version"] = resource_config.python_version

if hasattr(resource_config, "templateId") and resource_config.templateId:
config["templateId"] = resource_config.templateId

Expand Down Expand Up @@ -309,6 +318,63 @@ def _extract_config_properties(config: Dict[str, Any], resource_config) -> None:

return config

def _reconcile_python_version(
self, resources_dict: Dict[str, Dict[str, Any]]
) -> str:
"""Pick one Python version for the app from per-resource declarations.

Flash apps ship as a single tarball, so every resource must target the
same Python ABI. Resolution order:
1. Explicit override passed to ManifestBuilder (validated)
2. Exactly one distinct ``python_version`` declared across resources
3. ``DEFAULT_PYTHON_VERSION`` when no resource declares one

Raises:
ValueError: When resources declare conflicting ``python_version``
values, or when the override conflicts with a resource's
explicit declaration.
"""
per_resource: Dict[str, str] = {
name: r["python_version"]
for name, r in resources_dict.items()
if r.get("python_version")
}
distinct = set(per_resource.values())

if self._python_version_override:
chosen = validate_python_version(self._python_version_override)
conflicting = {
name: version
for name, version in per_resource.items()
if version != chosen
}
if conflicting:
details = ", ".join(
f"{name}={version}" for name, version in sorted(conflicting.items())
)
raise ValueError(
f"python_version override '{chosen}' conflicts with resource "
f"declarations: {details}. Either remove the override or "
f"align all resources to '{chosen}'."
)
return chosen

if len(distinct) > 1:
details = ", ".join(
f"{name}={version}" for name, version in sorted(per_resource.items())
)
raise ValueError(
"Flash apps require one python_version across all resources "
f"(found {sorted(distinct)}): {details}. Set python_version to the "
"same value on every resource, or omit it to use the default "
f"({DEFAULT_PYTHON_VERSION})."
)

if distinct:
return validate_python_version(next(iter(distinct)))

return DEFAULT_PYTHON_VERSION

def build(self) -> Dict[str, Any]:
"""Build the manifest dictionary.

Expand Down Expand Up @@ -426,20 +492,6 @@ def build(self) -> Dict[str, Any]:
# Determine if this resource makes remote calls
makes_remote_calls = any(func.calls_remote_functions for func in functions)

# One tarball serves all resources, so target_python_version must agree.
# GPU resources are pinned to the base image's Python; CPU resources
# use DEFAULT_PYTHON_VERSION (aligned to GPU to avoid ABI mismatch).
_GPU_RESOURCE_TYPES = {
"LiveServerless",
"LiveLoadBalancer",
"LoadBalancerSlsResource",
"ServerlessEndpoint",
}
if resource_type in _GPU_RESOURCE_TYPES:
target_python_version = GPU_BASE_IMAGE_PYTHON_VERSION
else:
target_python_version = DEFAULT_PYTHON_VERSION

resources_dict[resource_name] = {
"resource_type": resource_type,
"file_path": file_path_str,
Expand All @@ -450,8 +502,7 @@ def build(self) -> Dict[str, Any]:
"is_live_resource": is_live_resource,
"config_variable": config_variable,
"makes_remote_calls": makes_remote_calls,
"target_python_version": target_python_version,
**deployment_config, # Include imageName, templateId, gpuIds, workers config
**deployment_config, # Include imageName, templateId, gpuIds, workers config, python_version
}

# max_concurrency is QB-only; warn and remove for LB endpoints
Expand Down Expand Up @@ -485,6 +536,15 @@ def build(self) -> Dict[str, Any]:
)
function_registry[f.function_name] = resource_name

# Reconcile app-level python_version across resources. One tarball serves
# every resource in an app, so all resources must agree on one version.
self.python_version = self._reconcile_python_version(resources_dict)

# Stamp every resource's target_python_version with the reconciled
# app-level value so the runtime and pip-wheel step see a consistent ABI.
for resource in resources_dict.values():
resource["target_python_version"] = self.python_version

manifest = {
"version": "1.0",
"python_version": self.python_version,
Expand Down
10 changes: 10 additions & 0 deletions src/runpod_flash/cli/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ def deploy_command(
"--preview",
help="Build and launch local preview environment instead of deploying",
),
python_version: str | None = typer.Option(
None,
"--python-version",
help=(
"Target Python version for worker images (3.10, 3.11, or 3.12). "
"Overrides per-resource python_version declarations."
),
),
):
"""
Build and deploy Flash application.
Expand All @@ -56,6 +64,7 @@ def deploy_command(
flash deploy --app my-app --env prod # deploy a different app
flash deploy --preview # build + launch local preview
flash deploy --exclude transformers # exclude additional packages from build
flash deploy --python-version 3.11 # target Python 3.11 workers
"""
try:
project_dir, discovered_app_name = discover_flash_project()
Expand All @@ -68,6 +77,7 @@ def deploy_command(
no_deps=no_deps,
output_name=output_name,
exclude=exclude,
python_version=python_version,
)

if preview:
Expand Down
1 change: 1 addition & 0 deletions src/runpod_flash/cli/docs/flash-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ flash build [OPTIONS]
- `--output, -o`: Custom archive name (default: artifact.tar.gz)
- `--exclude`: Comma-separated packages to exclude (e.g., 'torch,torchvision')
- `--preview`: Launch local test environment after successful build (auto-enables `--keep-build`)
- `--python-version`: Target Python version for worker images (`3.10`, `3.11`, or `3.12`). Overrides per-resource `python_version`. Default: value declared on resource configs, or 3.12 if none set.

## Examples

Expand Down
1 change: 1 addition & 0 deletions src/runpod_flash/cli/docs/flash-deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ flash deploy [OPTIONS]
- `--exclude`: Comma-separated packages to exclude (e.g., 'torch,torchvision')
- `--output, -o`: Custom archive name (default: artifact.tar.gz)
- `--preview`: Build and launch local preview environment instead of deploying
- `--python-version`: Target Python version for worker images (`3.10`, `3.11`, or `3.12`). Overrides per-resource `python_version`.

## Examples

Expand Down
17 changes: 10 additions & 7 deletions src/runpod_flash/core/resources/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,25 @@ def _endpoint_domain_from_base_url(base_url: str) -> str:
ENDPOINT_DOMAIN = _endpoint_domain_from_base_url(runpod.endpoint_url_base)


# worker runtime Python versions. all flash workers run Python 3.12.
# one tarball serves every resource type (GPU and CPU), so packages,
# images, and the runtime must all target 3.12.
# Worker runtime Python versions. One tarball serves every resource in an app,
# so all resources must share a single Python version. GPU images ship 3.12
# with torch pre-installed; 3.10 and 3.11 are available via side-by-side
# install (~7 GB alt-Python overhead) in the same base image.
WORKER_PYTHON_VERSION: str = "3.12"
GPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.12",)
CPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.12",)
GPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12")
CPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12")

# Base image ships 3.12 with torch pre-installed; non-3.12 targets reinstall
# torch side-by-side for the selected interpreter.
GPU_BASE_IMAGE_PYTHON_VERSION: str = "3.12"
DEFAULT_PYTHON_VERSION: str = "3.12"

# python versions that can run the flash SDK locally (for flash build, etc.)
# Python versions that can run the flash SDK locally (for flash build, etc.)
SUPPORTED_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12")


def local_python_version() -> str:
"""Return the Python version used by flash workers (always 3.12)."""
"""Return the default worker Python version."""
return DEFAULT_PYTHON_VERSION


Expand Down
Loading
Loading