tetra-rp 0.17.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tetra-rp might be problematic. Click here for more details.
- tetra_rp/__init__.py +43 -0
- tetra_rp/cli/__init__.py +0 -0
- tetra_rp/cli/commands/__init__.py +1 -0
- tetra_rp/cli/commands/build.py +534 -0
- tetra_rp/cli/commands/deploy.py +370 -0
- tetra_rp/cli/commands/init.py +119 -0
- tetra_rp/cli/commands/resource.py +191 -0
- tetra_rp/cli/commands/run.py +100 -0
- tetra_rp/cli/main.py +85 -0
- tetra_rp/cli/utils/__init__.py +1 -0
- tetra_rp/cli/utils/conda.py +127 -0
- tetra_rp/cli/utils/deployment.py +172 -0
- tetra_rp/cli/utils/ignore.py +139 -0
- tetra_rp/cli/utils/skeleton.py +184 -0
- tetra_rp/cli/utils/skeleton_template/.env.example +3 -0
- tetra_rp/cli/utils/skeleton_template/.flashignore +40 -0
- tetra_rp/cli/utils/skeleton_template/.gitignore +44 -0
- tetra_rp/cli/utils/skeleton_template/README.md +256 -0
- tetra_rp/cli/utils/skeleton_template/main.py +43 -0
- tetra_rp/cli/utils/skeleton_template/requirements.txt +1 -0
- tetra_rp/cli/utils/skeleton_template/workers/__init__.py +0 -0
- tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py +20 -0
- tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py +38 -0
- tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py +20 -0
- tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py +62 -0
- tetra_rp/client.py +128 -0
- tetra_rp/config.py +29 -0
- tetra_rp/core/__init__.py +0 -0
- tetra_rp/core/api/__init__.py +6 -0
- tetra_rp/core/api/runpod.py +319 -0
- tetra_rp/core/exceptions.py +50 -0
- tetra_rp/core/resources/__init__.py +37 -0
- tetra_rp/core/resources/base.py +47 -0
- tetra_rp/core/resources/cloud.py +4 -0
- tetra_rp/core/resources/constants.py +4 -0
- tetra_rp/core/resources/cpu.py +146 -0
- tetra_rp/core/resources/environment.py +41 -0
- tetra_rp/core/resources/gpu.py +68 -0
- tetra_rp/core/resources/live_serverless.py +62 -0
- tetra_rp/core/resources/network_volume.py +148 -0
- tetra_rp/core/resources/resource_manager.py +145 -0
- tetra_rp/core/resources/serverless.py +463 -0
- tetra_rp/core/resources/serverless_cpu.py +162 -0
- tetra_rp/core/resources/template.py +94 -0
- tetra_rp/core/resources/utils.py +50 -0
- tetra_rp/core/utils/__init__.py +0 -0
- tetra_rp/core/utils/backoff.py +43 -0
- tetra_rp/core/utils/constants.py +10 -0
- tetra_rp/core/utils/file_lock.py +260 -0
- tetra_rp/core/utils/json.py +33 -0
- tetra_rp/core/utils/lru_cache.py +75 -0
- tetra_rp/core/utils/singleton.py +21 -0
- tetra_rp/core/validation.py +44 -0
- tetra_rp/execute_class.py +319 -0
- tetra_rp/logger.py +34 -0
- tetra_rp/protos/__init__.py +0 -0
- tetra_rp/protos/remote_execution.py +148 -0
- tetra_rp/stubs/__init__.py +5 -0
- tetra_rp/stubs/live_serverless.py +155 -0
- tetra_rp/stubs/registry.py +117 -0
- tetra_rp/stubs/serverless.py +30 -0
- tetra_rp-0.17.1.dist-info/METADATA +976 -0
- tetra_rp-0.17.1.dist-info/RECORD +66 -0
- tetra_rp-0.17.1.dist-info/WHEEL +5 -0
- tetra_rp-0.17.1.dist-info/entry_points.txt +2 -0
- tetra_rp-0.17.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Project skeleton creation utilities."""
|
|
2
|
+
|
|
3
|
+
from fnmatch import fnmatch
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
# Patterns to ignore during skeleton operations
|
|
8
|
+
IGNORE_PATTERNS = {
|
|
9
|
+
"__pycache__",
|
|
10
|
+
"*.pyc",
|
|
11
|
+
"*.pyo",
|
|
12
|
+
"*.pyd",
|
|
13
|
+
".DS_Store",
|
|
14
|
+
"Thumbs.db",
|
|
15
|
+
".git",
|
|
16
|
+
".pytest_cache",
|
|
17
|
+
"*.egg-info",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _should_ignore(path: Path) -> bool:
|
|
22
|
+
"""
|
|
23
|
+
Check if path matches any ignore pattern.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
path: Path to check
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
True if path should be ignored, False otherwise
|
|
30
|
+
"""
|
|
31
|
+
for pattern in IGNORE_PATTERNS:
|
|
32
|
+
# Check if any path component matches the pattern
|
|
33
|
+
if fnmatch(path.name, pattern):
|
|
34
|
+
return True
|
|
35
|
+
if any(fnmatch(part, pattern) for part in path.parts):
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def detect_file_conflicts(project_dir: Path) -> List[Path]:
|
|
41
|
+
"""
|
|
42
|
+
Detect files that would be overwritten when creating project skeleton.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
project_dir: Project directory path to check
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of file paths that already exist and would be overwritten
|
|
49
|
+
"""
|
|
50
|
+
conflicts: List[Path] = []
|
|
51
|
+
|
|
52
|
+
# Get template directory path
|
|
53
|
+
template_dir = Path(__file__).parent / "skeleton_template"
|
|
54
|
+
|
|
55
|
+
if not template_dir.exists():
|
|
56
|
+
return conflicts
|
|
57
|
+
|
|
58
|
+
def check_conflicts_recursive(src_dir: Path) -> None:
|
|
59
|
+
"""Recursively check for file conflicts."""
|
|
60
|
+
for item in src_dir.iterdir():
|
|
61
|
+
if _should_ignore(item):
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
relative_path = item.relative_to(template_dir)
|
|
65
|
+
target_file = project_dir / relative_path
|
|
66
|
+
|
|
67
|
+
if item.is_dir():
|
|
68
|
+
check_conflicts_recursive(item)
|
|
69
|
+
elif item.is_file():
|
|
70
|
+
if target_file.exists():
|
|
71
|
+
conflicts.append(relative_path)
|
|
72
|
+
|
|
73
|
+
# Start recursive conflict check
|
|
74
|
+
check_conflicts_recursive(template_dir)
|
|
75
|
+
|
|
76
|
+
return conflicts
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _copy_template_file(
|
|
80
|
+
item: Path,
|
|
81
|
+
template_dir: Path,
|
|
82
|
+
project_dir: Path,
|
|
83
|
+
force: bool,
|
|
84
|
+
created_files: List[str],
|
|
85
|
+
) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Copy a single template file with substitutions.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
item: Source file path
|
|
91
|
+
template_dir: Template directory base path
|
|
92
|
+
project_dir: Target project directory
|
|
93
|
+
force: Overwrite existing files
|
|
94
|
+
created_files: List to append created file paths to
|
|
95
|
+
"""
|
|
96
|
+
relative_path = item.relative_to(template_dir)
|
|
97
|
+
target_path = project_dir / relative_path
|
|
98
|
+
|
|
99
|
+
# Skip existing files unless force is True
|
|
100
|
+
if target_path.exists() and not force:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Create parent directories if needed
|
|
104
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
|
|
106
|
+
# Read content and handle template substitutions
|
|
107
|
+
try:
|
|
108
|
+
content = item.read_text()
|
|
109
|
+
|
|
110
|
+
# Replace {{project_name}} placeholder
|
|
111
|
+
if "{{project_name}}" in content:
|
|
112
|
+
content = content.replace("{{project_name}}", project_dir.name)
|
|
113
|
+
|
|
114
|
+
# Write file
|
|
115
|
+
target_path.write_text(content)
|
|
116
|
+
created_files.append(str(relative_path))
|
|
117
|
+
except UnicodeDecodeError:
|
|
118
|
+
# Handle binary files (just copy bytes)
|
|
119
|
+
target_path.write_bytes(item.read_bytes())
|
|
120
|
+
created_files.append(str(relative_path))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _copy_directory_recursive(
|
|
124
|
+
src_dir: Path,
|
|
125
|
+
template_dir: Path,
|
|
126
|
+
project_dir: Path,
|
|
127
|
+
force: bool,
|
|
128
|
+
created_files: List[str],
|
|
129
|
+
) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Recursively copy directory contents, including hidden files.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
src_dir: Source directory to copy from
|
|
135
|
+
template_dir: Template directory base path
|
|
136
|
+
project_dir: Target project directory
|
|
137
|
+
force: Overwrite existing files
|
|
138
|
+
created_files: List to append created file paths to
|
|
139
|
+
"""
|
|
140
|
+
for item in src_dir.iterdir():
|
|
141
|
+
# Skip ignored items
|
|
142
|
+
if _should_ignore(item):
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
if item.is_dir():
|
|
146
|
+
# Create target directory and recurse
|
|
147
|
+
relative_path = item.relative_to(template_dir)
|
|
148
|
+
target_path = project_dir / relative_path
|
|
149
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
_copy_directory_recursive(
|
|
151
|
+
item, template_dir, project_dir, force, created_files
|
|
152
|
+
)
|
|
153
|
+
elif item.is_file():
|
|
154
|
+
_copy_template_file(item, template_dir, project_dir, force, created_files)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def create_project_skeleton(project_dir: Path, force: bool = False) -> List[str]:
|
|
158
|
+
"""
|
|
159
|
+
Create Flash project skeleton from template directory.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
project_dir: Project directory path
|
|
163
|
+
force: Overwrite existing files
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
List of created file paths
|
|
167
|
+
"""
|
|
168
|
+
created_files: List[str] = []
|
|
169
|
+
|
|
170
|
+
# Get template directory path
|
|
171
|
+
template_dir = Path(__file__).parent / "skeleton_template"
|
|
172
|
+
|
|
173
|
+
if not template_dir.exists():
|
|
174
|
+
raise FileNotFoundError(f"Template directory not found: {template_dir}")
|
|
175
|
+
|
|
176
|
+
# Create project directory
|
|
177
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
|
|
179
|
+
# Start recursive copy
|
|
180
|
+
_copy_directory_recursive(
|
|
181
|
+
template_dir, template_dir, project_dir, force, created_files
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return created_files
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Flash Build Ignore Patterns
|
|
2
|
+
|
|
3
|
+
# Python cache
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.pyc
|
|
6
|
+
|
|
7
|
+
# Virtual environments
|
|
8
|
+
venv/
|
|
9
|
+
.venv/
|
|
10
|
+
env/
|
|
11
|
+
|
|
12
|
+
# IDE
|
|
13
|
+
.vscode/
|
|
14
|
+
.idea/
|
|
15
|
+
|
|
16
|
+
# Environment files
|
|
17
|
+
.env
|
|
18
|
+
.env.local
|
|
19
|
+
|
|
20
|
+
# Git
|
|
21
|
+
.git/
|
|
22
|
+
.gitignore
|
|
23
|
+
|
|
24
|
+
# Build artifacts
|
|
25
|
+
dist/
|
|
26
|
+
build/
|
|
27
|
+
*.egg-info/
|
|
28
|
+
|
|
29
|
+
# Flash resources
|
|
30
|
+
.tetra_resources.pkl
|
|
31
|
+
|
|
32
|
+
# Tests
|
|
33
|
+
tests/
|
|
34
|
+
test_*.py
|
|
35
|
+
*_test.py
|
|
36
|
+
|
|
37
|
+
# Documentation
|
|
38
|
+
docs/
|
|
39
|
+
*.md
|
|
40
|
+
!README.md
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
env/
|
|
8
|
+
venv/
|
|
9
|
+
.venv/
|
|
10
|
+
ENV/
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# IDEs
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
*.swp
|
|
31
|
+
*.swo
|
|
32
|
+
*~
|
|
33
|
+
|
|
34
|
+
# Environment
|
|
35
|
+
.env
|
|
36
|
+
.env.local
|
|
37
|
+
|
|
38
|
+
# Flash
|
|
39
|
+
.tetra_resources.pkl
|
|
40
|
+
dist/
|
|
41
|
+
|
|
42
|
+
# OS
|
|
43
|
+
.DS_Store
|
|
44
|
+
Thumbs.db
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# {{project_name}}
|
|
2
|
+
|
|
3
|
+
Flash application demonstrating distributed GPU and CPU computing on Runpod's serverless infrastructure.
|
|
4
|
+
|
|
5
|
+
## About This Template
|
|
6
|
+
|
|
7
|
+
This project was generated using `flash init`. The `{{project_name}}` placeholder is automatically replaced with your actual project name during initialization.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
### 1. Install Dependencies
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -r requirements.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. Configure Environment
|
|
18
|
+
|
|
19
|
+
Create `.env` file:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
RUNPOD_API_KEY=your_api_key_here
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Get your API key from [Runpod Settings](https://www.runpod.io/console/user/settings).
|
|
26
|
+
|
|
27
|
+
### 3. Run Locally
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
flash run
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Server starts at **http://localhost:8000**
|
|
34
|
+
|
|
35
|
+
### 4. Test the API
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Health check
|
|
39
|
+
curl http://localhost:8000/ping
|
|
40
|
+
|
|
41
|
+
# GPU worker
|
|
42
|
+
curl -X POST http://localhost:8000/gpu/hello \
|
|
43
|
+
-H "Content-Type: application/json" \
|
|
44
|
+
-d '{"message": "Hello GPU!"}'
|
|
45
|
+
|
|
46
|
+
# CPU worker
|
|
47
|
+
curl -X POST http://localhost:8000/cpu/hello \
|
|
48
|
+
-H "Content-Type: application/json" \
|
|
49
|
+
-d '{"message": "Hello CPU!"}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Visit **http://localhost:8000/docs** for interactive API documentation.
|
|
53
|
+
|
|
54
|
+
## What This Demonstrates
|
|
55
|
+
|
|
56
|
+
### GPU Worker (`workers/gpu/`)
|
|
57
|
+
Simple GPU-based serverless function:
|
|
58
|
+
- Remote execution with `@remote` decorator
|
|
59
|
+
- GPU resource configuration
|
|
60
|
+
- Automatic scaling (0-3 workers)
|
|
61
|
+
- No external dependencies required
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
@remote(
|
|
65
|
+
resource_config=LiveServerless(
|
|
66
|
+
name="gpu_worker",
|
|
67
|
+
gpus=[GpuGroup.ADA_24], # RTX 4090
|
|
68
|
+
workersMin=0,
|
|
69
|
+
workersMax=3,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
async def gpu_hello(input_data: dict) -> dict:
|
|
73
|
+
# Your GPU code here
|
|
74
|
+
return {"status": "success", "message": "Hello from GPU!"}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### CPU Worker (`workers/cpu/`)
|
|
78
|
+
Simple CPU-based serverless function:
|
|
79
|
+
- CPU-only execution (no GPU overhead)
|
|
80
|
+
- CpuLiveServerless configuration
|
|
81
|
+
- Efficient for API endpoints
|
|
82
|
+
- Automatic scaling (0-5 workers)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
@remote(
|
|
86
|
+
resource_config=CpuLiveServerless(
|
|
87
|
+
name="cpu_worker",
|
|
88
|
+
instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM
|
|
89
|
+
workersMin=0,
|
|
90
|
+
workersMax=5,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
async def cpu_hello(input_data: dict) -> dict:
|
|
94
|
+
# Your CPU code here
|
|
95
|
+
return {"status": "success", "message": "Hello from CPU!"}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Project Structure
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
{{project_name}}/
|
|
102
|
+
├── main.py # FastAPI application
|
|
103
|
+
├── workers/
|
|
104
|
+
│ ├── gpu/ # GPU worker
|
|
105
|
+
│ │ ├── __init__.py # FastAPI router
|
|
106
|
+
│ │ └── endpoint.py # @remote decorated function
|
|
107
|
+
│ └── cpu/ # CPU worker
|
|
108
|
+
│ ├── __init__.py # FastAPI router
|
|
109
|
+
│ └── endpoint.py # @remote decorated function
|
|
110
|
+
├── .env # Environment variables
|
|
111
|
+
├── requirements.txt # Dependencies
|
|
112
|
+
└── README.md # This file
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Key Concepts
|
|
116
|
+
|
|
117
|
+
### Remote Execution
|
|
118
|
+
The `@remote` decorator transparently executes functions on serverless infrastructure:
|
|
119
|
+
- Code runs locally during development
|
|
120
|
+
- Automatically deploys to Runpod when configured
|
|
121
|
+
- Handles serialization, dependencies, and resource management
|
|
122
|
+
|
|
123
|
+
### Resource Scaling
|
|
124
|
+
Both workers scale to zero when idle to minimize costs:
|
|
125
|
+
- **idleTimeout**: Minutes before scaling down (default: 5)
|
|
126
|
+
- **workersMin**: 0 = completely scales to zero
|
|
127
|
+
- **workersMax**: Maximum concurrent workers
|
|
128
|
+
|
|
129
|
+
### GPU Types
|
|
130
|
+
Available GPU options for `LiveServerless`:
|
|
131
|
+
- `GpuGroup.ADA_24` - RTX 4090 (24GB)
|
|
132
|
+
- `GpuGroup.ADA_48_PRO` - RTX 6000 Ada, L40 (48GB)
|
|
133
|
+
- `GpuGroup.AMPERE_80` - A100 (80GB)
|
|
134
|
+
- `GpuGroup.ANY` - Any available GPU
|
|
135
|
+
|
|
136
|
+
### CPU Types
|
|
137
|
+
Available CPU options for `CpuLiveServerless`:
|
|
138
|
+
- `CpuInstanceType.CPU3G_2_8` - 2 vCPU, 8GB RAM (General Purpose)
|
|
139
|
+
- `CpuInstanceType.CPU3C_4_8` - 4 vCPU, 8GB RAM (Compute Optimized)
|
|
140
|
+
- `CpuInstanceType.CPU5G_4_16` - 4 vCPU, 16GB RAM (Latest Gen)
|
|
141
|
+
- `CpuInstanceType.ANY` - Any available GPU
|
|
142
|
+
|
|
143
|
+
## Development Workflow
|
|
144
|
+
|
|
145
|
+
### Test Workers Locally
|
|
146
|
+
```bash
|
|
147
|
+
# Test GPU worker
|
|
148
|
+
python -m workers.gpu.endpoint
|
|
149
|
+
|
|
150
|
+
# Test CPU worker
|
|
151
|
+
python -m workers.cpu.endpoint
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Run the Application
|
|
155
|
+
```bash
|
|
156
|
+
flash run
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Deploy to Production
|
|
160
|
+
```bash
|
|
161
|
+
# Discover and configure handlers
|
|
162
|
+
flash build
|
|
163
|
+
|
|
164
|
+
# Create deployment environment
|
|
165
|
+
flash deploy new production
|
|
166
|
+
|
|
167
|
+
# Deploy to Runpod
|
|
168
|
+
flash deploy send production
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Adding New Workers
|
|
172
|
+
|
|
173
|
+
### Add a GPU Worker
|
|
174
|
+
|
|
175
|
+
1. Create `workers/my_worker/endpoint.py`:
|
|
176
|
+
```python
|
|
177
|
+
from tetra_rp import remote, LiveServerless
|
|
178
|
+
|
|
179
|
+
config = LiveServerless(name="my_worker")
|
|
180
|
+
|
|
181
|
+
@remote(resource_config=config, dependencies=["torch"])
|
|
182
|
+
async def my_function(data: dict) -> dict:
|
|
183
|
+
import torch
|
|
184
|
+
# Your code here
|
|
185
|
+
return {"result": "success"}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
2. Create `workers/my_worker/__init__.py`:
|
|
189
|
+
```python
|
|
190
|
+
from fastapi import APIRouter
|
|
191
|
+
from .endpoint import my_function
|
|
192
|
+
|
|
193
|
+
router = APIRouter()
|
|
194
|
+
|
|
195
|
+
@router.post("/process")
|
|
196
|
+
async def handler(data: dict):
|
|
197
|
+
return await my_function(data)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
3. Add to `main.py`:
|
|
201
|
+
```python
|
|
202
|
+
from workers.my_worker import router as my_router
|
|
203
|
+
app.include_router(my_router, prefix="/my_worker")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Add a CPU Worker
|
|
207
|
+
|
|
208
|
+
Same pattern but use `CpuLiveServerless`:
|
|
209
|
+
```python
|
|
210
|
+
from tetra_rp import remote, CpuLiveServerless, CpuInstanceType
|
|
211
|
+
|
|
212
|
+
config = CpuLiveServerless(
|
|
213
|
+
name="my_cpu_worker",
|
|
214
|
+
instanceIds=[CpuInstanceType.CPU3G_2_8]
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@remote(resource_config=config, dependencies=["requests"])
|
|
218
|
+
async def fetch_data(url: str) -> dict:
|
|
219
|
+
import requests
|
|
220
|
+
return requests.get(url).json()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Adding Dependencies
|
|
224
|
+
|
|
225
|
+
Specify dependencies in the `@remote` decorator:
|
|
226
|
+
```python
|
|
227
|
+
@remote(
|
|
228
|
+
resource_config=config,
|
|
229
|
+
dependencies=["torch>=2.0.0", "transformers"], # Python packages
|
|
230
|
+
system_dependencies=["ffmpeg"] # System packages
|
|
231
|
+
)
|
|
232
|
+
async def my_function(data: dict) -> dict:
|
|
233
|
+
# Dependencies are automatically installed
|
|
234
|
+
import torch
|
|
235
|
+
import transformers
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Environment Variables
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Required
|
|
242
|
+
RUNPOD_API_KEY=your_api_key
|
|
243
|
+
|
|
244
|
+
# Optional
|
|
245
|
+
PORT=8000
|
|
246
|
+
LOG_LEVEL=INFO
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Next Steps
|
|
250
|
+
|
|
251
|
+
- Add your ML models or processing logic
|
|
252
|
+
- Configure GPU/CPU resources based on your needs
|
|
253
|
+
- Add authentication to your endpoints
|
|
254
|
+
- Implement error handling and retries
|
|
255
|
+
- Add monitoring and logging
|
|
256
|
+
- Deploy to production with `flash deploy`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
|
|
5
|
+
from workers.gpu import gpu_router
|
|
6
|
+
from workers.cpu import cpu_router
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
app = FastAPI(
|
|
13
|
+
title="Flash Application",
|
|
14
|
+
description="Distributed GPU and CPU computing with Runpod Flash",
|
|
15
|
+
version="0.1.0",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Include routers
|
|
19
|
+
app.include_router(gpu_router, prefix="/gpu", tags=["GPU Workers"])
|
|
20
|
+
app.include_router(cpu_router, prefix="/cpu", tags=["CPU Workers"])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.get("/")
|
|
24
|
+
def home():
|
|
25
|
+
return {
|
|
26
|
+
"message": "Flash Application",
|
|
27
|
+
"docs": "/docs",
|
|
28
|
+
"endpoints": {"gpu_hello": "/gpu/hello", "cpu_hello": "/cpu/hello"},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.get("/ping")
|
|
33
|
+
def ping():
|
|
34
|
+
return {"status": "healthy"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
import uvicorn
|
|
39
|
+
|
|
40
|
+
port = int(os.getenv("PORT", 8888))
|
|
41
|
+
logger.info(f"Starting Flash server on port {port}")
|
|
42
|
+
|
|
43
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tetra_rp
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
from .endpoint import cpu_hello
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
cpu_router = APIRouter()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MessageRequest(BaseModel):
|
|
11
|
+
"""Request model for CPU worker."""
|
|
12
|
+
|
|
13
|
+
message: str = "Hello from CPU!"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cpu_router.post("/hello")
|
|
17
|
+
async def hello(request: MessageRequest):
|
|
18
|
+
"""Simple CPU worker endpoint."""
|
|
19
|
+
result = await cpu_hello({"message": request.message})
|
|
20
|
+
return result
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from tetra_rp import remote, CpuLiveServerless, CpuInstanceType
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
cpu_config = CpuLiveServerless(
|
|
5
|
+
name="cpu_worker",
|
|
6
|
+
instanceIds=[CpuInstanceType.ANY],
|
|
7
|
+
workersMin=0,
|
|
8
|
+
workersMax=5,
|
|
9
|
+
idleTimeout=5,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@remote(resource_config=cpu_config)
|
|
14
|
+
async def cpu_hello(input_data: dict) -> dict:
|
|
15
|
+
"""Simple CPU worker example."""
|
|
16
|
+
import platform
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
|
|
19
|
+
message = input_data.get("message", "Hello from CPU worker!")
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
"status": "success",
|
|
23
|
+
"message": message,
|
|
24
|
+
"worker_type": "CPU",
|
|
25
|
+
"timestamp": datetime.now().isoformat(),
|
|
26
|
+
"platform": platform.system(),
|
|
27
|
+
"python_version": platform.python_version(),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Test locally with: python -m workers.cpu.endpoint
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
import asyncio
|
|
34
|
+
|
|
35
|
+
test_payload = {"message": "Testing CPU worker"}
|
|
36
|
+
print(f"Testing CPU worker with payload: {test_payload}")
|
|
37
|
+
result = asyncio.run(cpu_hello(test_payload))
|
|
38
|
+
print(f"Result: {result}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
from .endpoint import gpu_hello
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
gpu_router = APIRouter()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MessageRequest(BaseModel):
|
|
11
|
+
"""Request model for GPU worker."""
|
|
12
|
+
|
|
13
|
+
message: str = "Hello from GPU!"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@gpu_router.post("/hello")
|
|
17
|
+
async def hello(request: MessageRequest):
|
|
18
|
+
"""Simple GPU worker endpoint."""
|
|
19
|
+
result = await gpu_hello({"message": request.message})
|
|
20
|
+
return result
|