podstack 1.2.0__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.
- podstack/__init__.py +222 -0
- podstack/annotations.py +725 -0
- podstack/client.py +322 -0
- podstack/exceptions.py +125 -0
- podstack/execution.py +291 -0
- podstack/gpu_runner.py +1141 -0
- podstack/models.py +274 -0
- podstack/notebook.py +410 -0
- podstack/registry/__init__.py +402 -0
- podstack/registry/client.py +957 -0
- podstack/registry/exceptions.py +107 -0
- podstack/registry/experiment.py +227 -0
- podstack/registry/model.py +273 -0
- podstack/registry/model_utils.py +231 -0
- podstack-1.2.0.dist-info/METADATA +299 -0
- podstack-1.2.0.dist-info/RECORD +27 -0
- podstack-1.2.0.dist-info/WHEEL +5 -0
- podstack-1.2.0.dist-info/licenses/LICENSE +21 -0
- podstack-1.2.0.dist-info/top_level.txt +2 -0
- podstack_gpu/__init__.py +126 -0
- podstack_gpu/app.py +675 -0
- podstack_gpu/exceptions.py +35 -0
- podstack_gpu/image.py +325 -0
- podstack_gpu/runner.py +746 -0
- podstack_gpu/secret.py +189 -0
- podstack_gpu/utils.py +203 -0
- podstack_gpu/volume.py +198 -0
podstack_gpu/image.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""Podstack Image - Fluent container image builder."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import List, Dict, Optional, Union
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ImageDefinition:
|
|
11
|
+
"""Serializable image definition."""
|
|
12
|
+
base: str = "python:3.11-slim"
|
|
13
|
+
python_version: str = "3.11"
|
|
14
|
+
pip_packages: List[str] = field(default_factory=list)
|
|
15
|
+
requirements_file: Optional[str] = None
|
|
16
|
+
apt_packages: List[str] = field(default_factory=list)
|
|
17
|
+
conda_packages: List[str] = field(default_factory=list)
|
|
18
|
+
env_vars: Dict[str, str] = field(default_factory=dict)
|
|
19
|
+
workdir: str = "/app"
|
|
20
|
+
run_commands: List[str] = field(default_factory=list)
|
|
21
|
+
copy_files: List[tuple] = field(default_factory=list)
|
|
22
|
+
env_preset: Optional[str] = None # ml, cv, nlp, audio, tabular, rl, scientific
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict:
|
|
25
|
+
"""Convert to dictionary for API serialization."""
|
|
26
|
+
return {
|
|
27
|
+
"base": self.base,
|
|
28
|
+
"python_version": self.python_version,
|
|
29
|
+
"pip_packages": self.pip_packages,
|
|
30
|
+
"requirements_file": self.requirements_file,
|
|
31
|
+
"apt_packages": self.apt_packages,
|
|
32
|
+
"conda_packages": self.conda_packages,
|
|
33
|
+
"env_vars": self.env_vars,
|
|
34
|
+
"workdir": self.workdir,
|
|
35
|
+
"run_commands": self.run_commands,
|
|
36
|
+
"env_preset": self.env_preset,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def to_annotation(self) -> str:
|
|
40
|
+
"""Convert to annotation string for legacy compatibility."""
|
|
41
|
+
parts = []
|
|
42
|
+
|
|
43
|
+
if self.env_preset:
|
|
44
|
+
parts.append(f"env={self.env_preset}")
|
|
45
|
+
|
|
46
|
+
if self.pip_packages:
|
|
47
|
+
parts.append(f"pip=[{','.join(self.pip_packages)}]")
|
|
48
|
+
|
|
49
|
+
if self.requirements_file:
|
|
50
|
+
parts.append(f"requirements={self.requirements_file}")
|
|
51
|
+
|
|
52
|
+
if self.conda_packages:
|
|
53
|
+
parts.append(f"conda=[{','.join(self.conda_packages)}]")
|
|
54
|
+
|
|
55
|
+
if self.python_version != "3.11":
|
|
56
|
+
parts.append(f"python={self.python_version}")
|
|
57
|
+
|
|
58
|
+
return " ".join(parts)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Image:
|
|
62
|
+
"""
|
|
63
|
+
Fluent container image builder for Podstack GPU.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
image = (
|
|
67
|
+
podstack.Image.debian_slim()
|
|
68
|
+
.pip_install("torch", "transformers")
|
|
69
|
+
.pip_install_from_requirements("requirements.txt")
|
|
70
|
+
.apt_install("ffmpeg", "libsndfile1")
|
|
71
|
+
.env({"CUDA_VISIBLE_DEVICES": "0"})
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Full GPU for training
|
|
75
|
+
@app.function(gpu="H100", image=image)
|
|
76
|
+
def train():
|
|
77
|
+
import torch
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
# Fractional GPU for inference (25%, 50%, 75%, or 100%)
|
|
81
|
+
@app.function(gpu="L40S", fraction=25, image=image)
|
|
82
|
+
def inference(data):
|
|
83
|
+
return model.predict(data)
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, definition: Optional[ImageDefinition] = None):
|
|
87
|
+
self._definition = definition or ImageDefinition()
|
|
88
|
+
|
|
89
|
+
def _copy(self) -> Image:
|
|
90
|
+
"""Create a copy of this image for chaining."""
|
|
91
|
+
import copy
|
|
92
|
+
return Image(copy.deepcopy(self._definition))
|
|
93
|
+
|
|
94
|
+
# ===== Base Image Factory Methods =====
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def debian_slim(cls, python_version: str = "3.11") -> Image:
|
|
98
|
+
"""Create image based on Debian slim with Python."""
|
|
99
|
+
return cls(ImageDefinition(
|
|
100
|
+
base=f"python:{python_version}-slim",
|
|
101
|
+
python_version=python_version,
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def from_registry(cls, image_name: str) -> Image:
|
|
106
|
+
"""Create image from a Docker registry."""
|
|
107
|
+
return cls(ImageDefinition(base=image_name))
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def from_dockerfile(cls, path: str, context_dir: str = ".") -> Image:
|
|
111
|
+
"""Create image from a Dockerfile."""
|
|
112
|
+
img = cls(ImageDefinition())
|
|
113
|
+
img._definition.base = f"dockerfile:{path}"
|
|
114
|
+
return img
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def micromamba(cls, python_version: str = "3.11") -> Image:
|
|
118
|
+
"""Create image with micromamba for conda packages."""
|
|
119
|
+
return cls(ImageDefinition(
|
|
120
|
+
base="mambaorg/micromamba:latest",
|
|
121
|
+
python_version=python_version,
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
# ===== Environment Preset Factory Methods =====
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def ml(cls) -> Image:
|
|
128
|
+
"""Pre-configured image for machine learning (PyTorch, scikit-learn, etc.)."""
|
|
129
|
+
img = cls.debian_slim()
|
|
130
|
+
img._definition.env_preset = "ml"
|
|
131
|
+
return img
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def cv(cls) -> Image:
|
|
135
|
+
"""Pre-configured image for computer vision (PyTorch, torchvision, opencv, etc.)."""
|
|
136
|
+
img = cls.debian_slim()
|
|
137
|
+
img._definition.env_preset = "cv"
|
|
138
|
+
return img
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def nlp(cls) -> Image:
|
|
142
|
+
"""Pre-configured image for NLP (transformers, tokenizers, etc.)."""
|
|
143
|
+
img = cls.debian_slim()
|
|
144
|
+
img._definition.env_preset = "nlp"
|
|
145
|
+
return img
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def audio(cls) -> Image:
|
|
149
|
+
"""Pre-configured image for audio processing (torchaudio, librosa, etc.)."""
|
|
150
|
+
img = cls.debian_slim()
|
|
151
|
+
img._definition.env_preset = "audio"
|
|
152
|
+
return img
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def tabular(cls) -> Image:
|
|
156
|
+
"""Pre-configured image for tabular data (pandas, xgboost, etc.)."""
|
|
157
|
+
img = cls.debian_slim()
|
|
158
|
+
img._definition.env_preset = "tabular"
|
|
159
|
+
return img
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def rl(cls) -> Image:
|
|
163
|
+
"""Pre-configured image for reinforcement learning (gymnasium, stable-baselines3, etc.)."""
|
|
164
|
+
img = cls.debian_slim()
|
|
165
|
+
img._definition.env_preset = "rl"
|
|
166
|
+
return img
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def scientific(cls) -> Image:
|
|
170
|
+
"""Pre-configured image for scientific computing (numpy, scipy, matplotlib, etc.)."""
|
|
171
|
+
img = cls.debian_slim()
|
|
172
|
+
img._definition.env_preset = "scientific"
|
|
173
|
+
return img
|
|
174
|
+
|
|
175
|
+
# ===== Fluent Builder Methods =====
|
|
176
|
+
|
|
177
|
+
def pip_install(self, *packages: str, find_links: str = None,
|
|
178
|
+
index_url: str = None, extra_index_url: str = None) -> Image:
|
|
179
|
+
"""
|
|
180
|
+
Install pip packages.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
*packages: Package names (e.g., "torch", "transformers>=4.30.0")
|
|
184
|
+
find_links: URL to find packages
|
|
185
|
+
index_url: Custom PyPI index URL
|
|
186
|
+
extra_index_url: Additional PyPI index URL
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
image.pip_install("torch", "transformers>=4.30.0")
|
|
190
|
+
image.pip_install("torch", index_url="https://download.pytorch.org/whl/cu118")
|
|
191
|
+
"""
|
|
192
|
+
new_img = self._copy()
|
|
193
|
+
new_img._definition.pip_packages.extend(packages)
|
|
194
|
+
return new_img
|
|
195
|
+
|
|
196
|
+
def pip_install_from_requirements(self, path: str) -> Image:
|
|
197
|
+
"""
|
|
198
|
+
Install packages from a requirements.txt file.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
path: Path to requirements.txt file
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
image.pip_install_from_requirements("requirements.txt")
|
|
205
|
+
"""
|
|
206
|
+
new_img = self._copy()
|
|
207
|
+
new_img._definition.requirements_file = path
|
|
208
|
+
return new_img
|
|
209
|
+
|
|
210
|
+
def apt_install(self, *packages: str) -> Image:
|
|
211
|
+
"""
|
|
212
|
+
Install apt packages (Debian/Ubuntu).
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
*packages: Package names (e.g., "ffmpeg", "libsndfile1")
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
image.apt_install("ffmpeg", "libsndfile1")
|
|
219
|
+
"""
|
|
220
|
+
new_img = self._copy()
|
|
221
|
+
new_img._definition.apt_packages.extend(packages)
|
|
222
|
+
return new_img
|
|
223
|
+
|
|
224
|
+
def conda_install(self, *packages: str, channels: List[str] = None) -> Image:
|
|
225
|
+
"""
|
|
226
|
+
Install conda packages.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
*packages: Package names
|
|
230
|
+
channels: Conda channels to use
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
image.conda_install("pytorch", "cudatoolkit=11.8", channels=["pytorch"])
|
|
234
|
+
"""
|
|
235
|
+
new_img = self._copy()
|
|
236
|
+
new_img._definition.conda_packages.extend(packages)
|
|
237
|
+
return new_img
|
|
238
|
+
|
|
239
|
+
def env(self, env_vars: Dict[str, str]) -> Image:
|
|
240
|
+
"""
|
|
241
|
+
Set environment variables.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
env_vars: Dictionary of environment variables
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
image.env({"CUDA_VISIBLE_DEVICES": "0", "HF_HOME": "/cache/huggingface"})
|
|
248
|
+
"""
|
|
249
|
+
new_img = self._copy()
|
|
250
|
+
new_img._definition.env_vars.update(env_vars)
|
|
251
|
+
return new_img
|
|
252
|
+
|
|
253
|
+
def workdir(self, path: str) -> Image:
|
|
254
|
+
"""
|
|
255
|
+
Set the working directory.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
path: Working directory path
|
|
259
|
+
|
|
260
|
+
Example:
|
|
261
|
+
image.workdir("/app/src")
|
|
262
|
+
"""
|
|
263
|
+
new_img = self._copy()
|
|
264
|
+
new_img._definition.workdir = path
|
|
265
|
+
return new_img
|
|
266
|
+
|
|
267
|
+
def run_commands(self, *commands: str) -> Image:
|
|
268
|
+
"""
|
|
269
|
+
Run shell commands during image build.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
*commands: Shell commands to run
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
image.run_commands("apt-get update", "apt-get install -y curl")
|
|
276
|
+
"""
|
|
277
|
+
new_img = self._copy()
|
|
278
|
+
new_img._definition.run_commands.extend(commands)
|
|
279
|
+
return new_img
|
|
280
|
+
|
|
281
|
+
def copy_local_file(self, local_path: str, remote_path: str) -> Image:
|
|
282
|
+
"""
|
|
283
|
+
Copy a local file into the image.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
local_path: Local file path
|
|
287
|
+
remote_path: Path in the container
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
image.copy_local_file("./model.py", "/app/model.py")
|
|
291
|
+
"""
|
|
292
|
+
new_img = self._copy()
|
|
293
|
+
new_img._definition.copy_files.append((local_path, remote_path))
|
|
294
|
+
return new_img
|
|
295
|
+
|
|
296
|
+
def add_local_dir(self, local_path: str, remote_path: str) -> Image:
|
|
297
|
+
"""
|
|
298
|
+
Add a local directory to the image.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
local_path: Local directory path
|
|
302
|
+
remote_path: Path in the container
|
|
303
|
+
"""
|
|
304
|
+
return self.copy_local_file(local_path, remote_path)
|
|
305
|
+
|
|
306
|
+
# ===== Properties and Serialization =====
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def definition(self) -> ImageDefinition:
|
|
310
|
+
"""Get the image definition."""
|
|
311
|
+
return self._definition
|
|
312
|
+
|
|
313
|
+
def to_dict(self) -> dict:
|
|
314
|
+
"""Convert to dictionary for API serialization."""
|
|
315
|
+
return self._definition.to_dict()
|
|
316
|
+
|
|
317
|
+
def __repr__(self) -> str:
|
|
318
|
+
parts = [f"Image(base={self._definition.base!r}"]
|
|
319
|
+
if self._definition.env_preset:
|
|
320
|
+
parts.append(f", env={self._definition.env_preset!r}")
|
|
321
|
+
if self._definition.pip_packages:
|
|
322
|
+
parts.append(f", pip={self._definition.pip_packages}")
|
|
323
|
+
if self._definition.apt_packages:
|
|
324
|
+
parts.append(f", apt={self._definition.apt_packages}")
|
|
325
|
+
return "".join(parts) + ")"
|