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.
@@ -0,0 +1,231 @@
1
+ """
2
+ Model Utilities
3
+
4
+ Framework-agnostic model serialization and deserialization helpers.
5
+ All framework imports are lazy — no hard dependencies on any ML framework.
6
+ """
7
+
8
+ import os
9
+ import pickle
10
+ from typing import Any, Optional
11
+
12
+ from .exceptions import ModelSerializationError, FrameworkNotInstalledError
13
+
14
+
15
+ # Framework detection mapping: module prefix -> framework name
16
+ _FRAMEWORK_MODULE_MAP = {
17
+ "torch": "pytorch",
18
+ "tensorflow": "tensorflow",
19
+ "keras": "tensorflow",
20
+ "sklearn": "sklearn",
21
+ "transformers": "huggingface",
22
+ }
23
+
24
+
25
+ def detect_framework(model: Any) -> str:
26
+ """
27
+ Auto-detect the ML framework from a model object.
28
+
29
+ Args:
30
+ model: A model object from any supported framework.
31
+
32
+ Returns:
33
+ Framework name string: "pytorch", "tensorflow", "sklearn", "huggingface", or "pickle".
34
+ """
35
+ module = type(model).__module__ or ""
36
+
37
+ for prefix, framework in _FRAMEWORK_MODULE_MAP.items():
38
+ if module.startswith(prefix):
39
+ return framework
40
+
41
+ # Fallback: check for common base classes by name to avoid imports
42
+ type_name = type(model).__name__
43
+ type_mro = [cls.__module__ + "." + cls.__name__ for cls in type(model).__mro__]
44
+
45
+ for entry in type_mro:
46
+ for prefix, framework in _FRAMEWORK_MODULE_MAP.items():
47
+ if entry.startswith(prefix):
48
+ return framework
49
+
50
+ return "pickle"
51
+
52
+
53
+ def save_model(model: Any, path: str, framework: Optional[str] = None) -> str:
54
+ """
55
+ Serialize a model to disk.
56
+
57
+ Args:
58
+ model: The model object to save.
59
+ path: Directory path to save the model into.
60
+ framework: Framework name. Auto-detected if not provided.
61
+ One of: "pytorch", "tensorflow", "sklearn", "huggingface", "pickle".
62
+
63
+ Returns:
64
+ Path to the saved model file/directory.
65
+
66
+ Raises:
67
+ FrameworkNotInstalledError: If the required framework is not installed.
68
+ ModelSerializationError: If serialization fails.
69
+ """
70
+ if framework is None:
71
+ framework = detect_framework(model)
72
+
73
+ os.makedirs(path, exist_ok=True)
74
+
75
+ try:
76
+ if framework == "pytorch":
77
+ return _save_pytorch(model, path)
78
+ elif framework == "tensorflow":
79
+ return _save_tensorflow(model, path)
80
+ elif framework == "sklearn":
81
+ return _save_sklearn(model, path)
82
+ elif framework == "huggingface":
83
+ return _save_huggingface(model, path)
84
+ else:
85
+ return _save_pickle(model, path)
86
+ except FrameworkNotInstalledError:
87
+ raise
88
+ except Exception as e:
89
+ raise ModelSerializationError(f"Failed to save {framework} model: {e}")
90
+
91
+
92
+ def load_model_from_path(path: str, framework: str) -> Any:
93
+ """
94
+ Deserialize a model from disk.
95
+
96
+ Args:
97
+ path: Path to the saved model file or directory.
98
+ framework: Framework name used to save the model.
99
+ One of: "pytorch", "tensorflow", "sklearn", "huggingface", "pickle".
100
+
101
+ Returns:
102
+ The loaded model object.
103
+
104
+ Raises:
105
+ FrameworkNotInstalledError: If the required framework is not installed.
106
+ ModelSerializationError: If deserialization fails.
107
+ """
108
+ try:
109
+ if framework == "pytorch":
110
+ return _load_pytorch(path)
111
+ elif framework == "tensorflow":
112
+ return _load_tensorflow(path)
113
+ elif framework == "sklearn":
114
+ return _load_sklearn(path)
115
+ elif framework == "huggingface":
116
+ return _load_huggingface(path)
117
+ else:
118
+ return _load_pickle(path)
119
+ except FrameworkNotInstalledError:
120
+ raise
121
+ except Exception as e:
122
+ raise ModelSerializationError(f"Failed to load {framework} model: {e}")
123
+
124
+
125
+ # ==================== PyTorch ====================
126
+
127
+ def _save_pytorch(model: Any, path: str) -> str:
128
+ try:
129
+ import torch
130
+ except ImportError:
131
+ raise FrameworkNotInstalledError("torch")
132
+
133
+ filepath = os.path.join(path, "model.pt")
134
+ torch.save(model.state_dict() if hasattr(model, "state_dict") else model, filepath)
135
+ return filepath
136
+
137
+
138
+ def _load_pytorch(path: str) -> Any:
139
+ try:
140
+ import torch
141
+ except ImportError:
142
+ raise FrameworkNotInstalledError("torch")
143
+
144
+ filepath = path if os.path.isfile(path) else os.path.join(path, "model.pt")
145
+ return torch.load(filepath, map_location="cpu", weights_only=False)
146
+
147
+
148
+ # ==================== TensorFlow / Keras ====================
149
+
150
+ def _save_tensorflow(model: Any, path: str) -> str:
151
+ try:
152
+ import tensorflow # noqa: F401
153
+ except ImportError:
154
+ raise FrameworkNotInstalledError("tensorflow")
155
+
156
+ save_dir = os.path.join(path, "saved_model")
157
+ model.save(save_dir)
158
+ return save_dir
159
+
160
+
161
+ def _load_tensorflow(path: str) -> Any:
162
+ try:
163
+ import tensorflow as tf
164
+ except ImportError:
165
+ raise FrameworkNotInstalledError("tensorflow")
166
+
167
+ load_dir = path if os.path.isdir(path) and os.path.exists(os.path.join(path, "saved_model.pb")) else os.path.join(path, "saved_model")
168
+ return tf.keras.models.load_model(load_dir)
169
+
170
+
171
+ # ==================== Scikit-learn ====================
172
+
173
+ def _save_sklearn(model: Any, path: str) -> str:
174
+ try:
175
+ import sklearn # noqa: F401
176
+ except ImportError:
177
+ raise FrameworkNotInstalledError("sklearn")
178
+
179
+ filepath = os.path.join(path, "model.pkl")
180
+ with open(filepath, "wb") as f:
181
+ pickle.dump(model, f)
182
+ return filepath
183
+
184
+
185
+ def _load_sklearn(path: str) -> Any:
186
+ try:
187
+ import sklearn # noqa: F401
188
+ except ImportError:
189
+ raise FrameworkNotInstalledError("sklearn")
190
+
191
+ filepath = path if os.path.isfile(path) else os.path.join(path, "model.pkl")
192
+ with open(filepath, "rb") as f:
193
+ return pickle.load(f)
194
+
195
+
196
+ # ==================== Hugging Face ====================
197
+
198
+ def _save_huggingface(model: Any, path: str) -> str:
199
+ try:
200
+ import transformers # noqa: F401
201
+ except ImportError:
202
+ raise FrameworkNotInstalledError("huggingface")
203
+
204
+ save_dir = os.path.join(path, "hf_model")
205
+ model.save_pretrained(save_dir)
206
+ return save_dir
207
+
208
+
209
+ def _load_huggingface(path: str) -> Any:
210
+ try:
211
+ from transformers import AutoModel
212
+ except ImportError:
213
+ raise FrameworkNotInstalledError("huggingface")
214
+
215
+ load_dir = path if os.path.isdir(path) and os.path.exists(os.path.join(path, "config.json")) else os.path.join(path, "hf_model")
216
+ return AutoModel.from_pretrained(load_dir)
217
+
218
+
219
+ # ==================== Pickle (fallback) ====================
220
+
221
+ def _save_pickle(model: Any, path: str) -> str:
222
+ filepath = os.path.join(path, "model.pkl")
223
+ with open(filepath, "wb") as f:
224
+ pickle.dump(model, f)
225
+ return filepath
226
+
227
+
228
+ def _load_pickle(path: str) -> Any:
229
+ filepath = path if os.path.isfile(path) else os.path.join(path, "model.pkl")
230
+ with open(filepath, "rb") as f:
231
+ return pickle.load(f)
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.4
2
+ Name: podstack
3
+ Version: 1.2.0
4
+ Summary: Official Python SDK for Podstack GPU Notebook Platform
5
+ Author-email: Podstack <support@podstack.io>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://podstack.io
8
+ Project-URL: Documentation, https://docs.podstack.io
9
+ Project-URL: Repository, https://github.com/podstack/podstack-python
10
+ Project-URL: Issues, https://github.com/podstack/podstack-python/issues
11
+ Keywords: gpu,notebook,machine-learning,deep-learning,cloud,jupyter
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: httpx>=0.24.0
28
+ Requires-Dist: requests>=2.28.0
29
+ Provides-Extra: torch
30
+ Requires-Dist: torch; extra == "torch"
31
+ Provides-Extra: tensorflow
32
+ Requires-Dist: tensorflow; extra == "tensorflow"
33
+ Provides-Extra: sklearn
34
+ Requires-Dist: scikit-learn; extra == "sklearn"
35
+ Provides-Extra: huggingface
36
+ Requires-Dist: transformers; extra == "huggingface"
37
+ Requires-Dist: safetensors; extra == "huggingface"
38
+ Provides-Extra: all
39
+ Requires-Dist: torch; extra == "all"
40
+ Requires-Dist: tensorflow; extra == "all"
41
+ Requires-Dist: scikit-learn; extra == "all"
42
+ Requires-Dist: transformers; extra == "all"
43
+ Requires-Dist: safetensors; extra == "all"
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
46
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
47
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
48
+ Requires-Dist: black>=23.0.0; extra == "dev"
49
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
50
+ Requires-Dist: ruff>=0.0.270; extra == "dev"
51
+ Dynamic: license-file
52
+
53
+ # Podstack Python SDK
54
+
55
+ Official Python SDK for the Podstack GPU Notebook Platform. Launch GPU notebooks in under 1 second and execute ML workloads with ease.
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install podstack
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```python
66
+ import asyncio
67
+ from podstack import Client
68
+
69
+ async def main():
70
+ async with Client(api_key="your-api-key") as client:
71
+ # Create a GPU notebook
72
+ notebook = await client.notebooks.create(
73
+ name="my-experiment",
74
+ gpu_type="A100",
75
+ environment="pytorch"
76
+ )
77
+
78
+ # Execute code
79
+ result = await notebook.execute("""
80
+ import torch
81
+ print(f"GPU: {torch.cuda.get_device_name(0)}")
82
+ print(f"Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
83
+ """)
84
+
85
+ print(result.output)
86
+
87
+ # Save a version
88
+ version = await notebook.save(message="Initial experiment")
89
+
90
+ # Stop when done
91
+ await notebook.stop()
92
+
93
+ asyncio.run(main())
94
+ ```
95
+
96
+ ## Sync Usage
97
+
98
+ For simple scripts, use the sync wrappers:
99
+
100
+ ```python
101
+ from podstack import Client
102
+
103
+ client = Client(api_key="your-api-key")
104
+
105
+ # Create notebook
106
+ notebook = client.sync_create_notebook(name="quick-test", gpu_type="A10")
107
+
108
+ # Run code
109
+ result = client.sync_run("print('Hello GPU!')", gpu_type="A10")
110
+ print(result.output)
111
+ ```
112
+
113
+ ## Features
114
+
115
+ ### Notebooks
116
+
117
+ ```python
118
+ # Create with options
119
+ notebook = await client.notebooks.create(
120
+ name="training-run",
121
+ gpu_type="A100",
122
+ environment="pytorch",
123
+ project_id="proj_xxx",
124
+ idle_timeout_minutes=60,
125
+ auto_shutdown_enabled=True,
126
+ metadata={"experiment": "v2"}
127
+ )
128
+
129
+ # List notebooks
130
+ notebooks = await client.notebooks.list(status="running")
131
+
132
+ # Execute code
133
+ result = await notebook.execute("import torch; print(torch.cuda.device_count())")
134
+
135
+ # Access JupyterLab
136
+ print(f"JupyterLab: {notebook.jupyter_url}")
137
+
138
+ # Stop/Start
139
+ await notebook.stop()
140
+ await notebook.start()
141
+ ```
142
+
143
+ ### Serverless Executions
144
+
145
+ Run code without managing notebooks:
146
+
147
+ ```python
148
+ # Quick execution
149
+ result = await client.executions.run(
150
+ code="print('Hello!')",
151
+ gpu_type="A10",
152
+ environment="pytorch"
153
+ )
154
+
155
+ # Non-blocking execution
156
+ execution = await client.executions.create(
157
+ code=long_running_code,
158
+ gpu_type="H100",
159
+ timeout_seconds=3600
160
+ )
161
+
162
+ # Check status later
163
+ await execution.refresh()
164
+ if execution.is_complete:
165
+ print(execution.output)
166
+ ```
167
+
168
+ ### Notebook Versioning
169
+
170
+ Git-like versioning for notebooks:
171
+
172
+ ```python
173
+ # Save a version
174
+ version = await notebook.save(message="Added training loop")
175
+
176
+ # List versions
177
+ versions = await notebook.list_versions()
178
+
179
+ # Restore a version
180
+ await notebook.restore_version(version.id)
181
+
182
+ # Create a branch
183
+ await notebook.create_branch("experiment-v2", from_version_id=version.id)
184
+ ```
185
+
186
+ ### Projects
187
+
188
+ Organize notebooks into projects:
189
+
190
+ ```python
191
+ # Create project
192
+ project = await client.create_project(
193
+ name="ML Research",
194
+ description="Transformer experiments"
195
+ )
196
+
197
+ # Create notebook in project
198
+ notebook = await client.notebooks.create(
199
+ name="attention-study",
200
+ gpu_type="A100",
201
+ project_id=project.id
202
+ )
203
+
204
+ # List project notebooks
205
+ notebooks = await client.notebooks.list(project_id=project.id)
206
+ ```
207
+
208
+ ### Billing & Usage
209
+
210
+ ```python
211
+ # Check balance
212
+ balance = await client.get_wallet_balance()
213
+ print(f"Balance: ₹{balance.balance:.2f}")
214
+
215
+ # Get usage
216
+ usage = await client.get_usage(
217
+ start_date="2024-01-01",
218
+ end_date="2024-01-31",
219
+ group_by="day"
220
+ )
221
+ print(f"Total cost: ₹{usage.total_cost:.2f}")
222
+ ```
223
+
224
+ ### GPU Types
225
+
226
+ ```python
227
+ # List available GPUs
228
+ gpus = await client.list_gpus()
229
+ for gpu in gpus:
230
+ print(f"{gpu.type}: {gpu.memory_gb}GB, ₹{gpu.price_per_hour_paise/100:.2f}/hr")
231
+ ```
232
+
233
+ Available GPU types:
234
+ - `T4` - 16GB, budget-friendly
235
+ - `L4` - 24GB, inference optimized
236
+ - `A10` - 24GB, balanced
237
+ - `A100_40GB` - 40GB, training
238
+ - `A100_80GB` - 80GB, large models
239
+ - `H100` - 80GB, fastest
240
+
241
+ ### Webhooks
242
+
243
+ ```python
244
+ # Create webhook
245
+ webhook = await client.create_webhook(
246
+ url="https://your-server.com/webhook",
247
+ events=["notebook.started", "execution.completed"]
248
+ )
249
+
250
+ # List webhooks
251
+ webhooks = await client.list_webhooks()
252
+ ```
253
+
254
+ ## Error Handling
255
+
256
+ ```python
257
+ from podstack import (
258
+ Client,
259
+ PodstackError,
260
+ AuthenticationError,
261
+ GPUNotAvailableError,
262
+ RateLimitError,
263
+ ExecutionTimeoutError
264
+ )
265
+
266
+ try:
267
+ async with Client(api_key="invalid") as client:
268
+ await client.notebooks.create(name="test", gpu_type="A100")
269
+ except AuthenticationError:
270
+ print("Invalid API key")
271
+ except GPUNotAvailableError as e:
272
+ print(f"GPU {e.gpu_type} not available, try: {e.available_types}")
273
+ except RateLimitError as e:
274
+ print(f"Rate limited, retry after {e.retry_after}s")
275
+ except ExecutionTimeoutError as e:
276
+ print(f"Execution {e.execution_id} timed out")
277
+ except PodstackError as e:
278
+ print(f"Error [{e.code}]: {e.message}")
279
+ ```
280
+
281
+ ## Configuration
282
+
283
+ ```python
284
+ # Environment variables
285
+ # PODSTACK_API_KEY=psk_live_xxxxx
286
+ # PODSTACK_BASE_URL=https://api.podstack.io/v1
287
+
288
+ # Or pass directly
289
+ client = Client(
290
+ api_key="psk_live_xxxxx",
291
+ base_url="https://api.podstack.io/v1",
292
+ timeout=60.0,
293
+ max_retries=5
294
+ )
295
+ ```
296
+
297
+ ## License
298
+
299
+ MIT License - see LICENSE for details.
@@ -0,0 +1,27 @@
1
+ podstack/__init__.py,sha256=WLwTzeSi8QFkhGjFeXS85vg9KnDlyOxefYhwRfd9mZE,5026
2
+ podstack/annotations.py,sha256=7O68bjF-djeNNZbJxG0-gWNxuTTx8CL-Sqqp5Mpo6sw,23308
3
+ podstack/client.py,sha256=8_Zl7TX19XGvM_BDXwI-U10GgxLqYvvCYERUA526nB0,10838
4
+ podstack/exceptions.py,sha256=02e-9BjYlO0t2wwCJ9XFWqhX-VbaH31gfmUpoqr2KH8,4288
5
+ podstack/execution.py,sha256=CHtRw_xWQzYgZJOfGL40CRtduZQnppkmFNw0NmTKsu8,8948
6
+ podstack/gpu_runner.py,sha256=BDvdrVNyTh7oeOqpvUuq2xSj53xD4TU-bdo-h88JO_o,41047
7
+ podstack/models.py,sha256=qMD5kIyJ30eysZhgcVR4XgQzOFina1ZtFJ8qwr8ThxE,7552
8
+ podstack/notebook.py,sha256=Q2ZrdZ1wYaAKyrRLcCZW3EPV2K8V76_OxDpzLeFItv4,12425
9
+ podstack/registry/__init__.py,sha256=HfzDP6rIPSvEfgoBckPjAj7D8TYHb_NHmZwJnnKOUuY,9989
10
+ podstack/registry/client.py,sha256=Q1vI4scH1CgeQZb_lYcaGEuMzdFn1p8P6thuqMyLd7c,31537
11
+ podstack/registry/exceptions.py,sha256=NyRufm3nA_8wypIMqo6k_oo2re0L5cddIZWsAxftYgw,3408
12
+ podstack/registry/experiment.py,sha256=V_n1Er37jghXamUq9wtZg7KMpdY2XvuCEH7BklwLhXo,7375
13
+ podstack/registry/model.py,sha256=JT7mZf2kKiguzp-S0e5fC1UlLjvGe7_-QrH074yCAkI,9673
14
+ podstack/registry/model_utils.py,sha256=XD5ckSXUd-sVLAAFlSGx66l8X_2WJO31D_sRQQKaG1I,6872
15
+ podstack-1.2.0.dist-info/licenses/LICENSE,sha256=NlFavTiYs5FYzcXNNBZFBob8ih5Ktj79h1oyCAHjkD8,1070
16
+ podstack_gpu/__init__.py,sha256=uIRZ2MGniy2G-UoJzKLxdCC-3d3ef01h5osrCWPLC-k,3674
17
+ podstack_gpu/app.py,sha256=eMTIOBPyv01JCwngtflZhE_OuYn2i9Ox7gmCQ7HA-pI,21357
18
+ podstack_gpu/exceptions.py,sha256=oOWM0cdShby5ZQnlGP2xTxl8lgU9rdS4WKBw5OKvs38,757
19
+ podstack_gpu/image.py,sha256=4D8apxNM66PoBpzdk488B6zb03heWRgwR0g9o3f5vyM,10387
20
+ podstack_gpu/runner.py,sha256=jk78MMi0DCHyuDeTuBQvQwTW93GzQiUH0sTxG0691WI,25565
21
+ podstack_gpu/secret.py,sha256=kKFbfVrPETmkbhpJwAZPeIQ-btCFd-XX9gEtkMyoSjM,5331
22
+ podstack_gpu/utils.py,sha256=vAa0S_zUHOs4cjs3Qsbk-o5Sji9VA7mfYGNjI8JikKc,6639
23
+ podstack_gpu/volume.py,sha256=N_t8qlW94l7rh8Uq2uvJKEvhGobzEY7Bcw5emdyLauQ,5533
24
+ podstack-1.2.0.dist-info/METADATA,sha256=nNthxAu_tLzIEUQzwQn5NlH89QZhVLPzKhZw2z6oeSg,7334
25
+ podstack-1.2.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
26
+ podstack-1.2.0.dist-info/top_level.txt,sha256=Mj_82W07f_sLiodgYSGpa86Lke95K7RP0uE5aNE4qzY,22
27
+ podstack-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Podstack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ podstack
2
+ podstack_gpu