tetra-rp 0.7.0__tar.gz → 0.8.0__tar.gz
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.
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/PKG-INFO +1 -1
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/pyproject.toml +1 -1
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/client.py +25 -16
- tetra_rp-0.8.0/src/tetra_rp/execute_class.py +178 -0
- tetra_rp-0.8.0/src/tetra_rp/protos/remote_execution.py +128 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/stubs/registry.py +14 -5
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp.egg-info/PKG-INFO +1 -1
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp.egg-info/SOURCES.txt +1 -0
- tetra_rp-0.7.0/src/tetra_rp/protos/remote_execution.py +0 -57
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/README.md +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/setup.cfg +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/api/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/api/runpod.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/pool/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/pool/cluster_manager.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/pool/dataclass.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/pool/ex.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/pool/job.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/pool/worker.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/base.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/cloud.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/constants.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/cpu.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/environment.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/gpu.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/live_serverless.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/network_volume.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/resource_manager.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/serverless.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/template.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/resources/utils.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/utils/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/utils/backoff.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/utils/json.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/core/utils/singleton.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/logger.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/protos/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/stubs/__init__.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/stubs/live_serverless.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp/stubs/serverless.py +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp.egg-info/dependency_links.txt +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp.egg-info/requires.txt +0 -0
- {tetra_rp-0.7.0 → tetra_rp-0.8.0}/src/tetra_rp.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tetra_rp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A Python library for distributed inference and serving of machine learning models
|
|
5
5
|
Author-email: Marut Pandya <pandyamarut@gmail.com>, Patrick Rachford <prachford@icloud.com>, Dean Quinanola <dean.quinanola@runpod.io>
|
|
6
6
|
License: MIT
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import logging
|
|
2
3
|
from functools import wraps
|
|
3
|
-
from typing import List
|
|
4
|
-
from .core.resources import ServerlessResource, ResourceManager
|
|
5
|
-
from .stubs import stub_resource
|
|
4
|
+
from typing import List, Optional
|
|
6
5
|
|
|
6
|
+
from .core.resources import ResourceManager, ServerlessResource
|
|
7
|
+
from .execute_class import create_remote_class
|
|
8
|
+
from .stubs import stub_resource
|
|
7
9
|
|
|
8
10
|
log = logging.getLogger(__name__)
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def remote(
|
|
12
14
|
resource_config: ServerlessResource,
|
|
13
|
-
dependencies: List[str] = None,
|
|
14
|
-
system_dependencies: List[str] = None,
|
|
15
|
+
dependencies: Optional[List[str]] = None,
|
|
16
|
+
system_dependencies: Optional[List[str]] = None,
|
|
15
17
|
**extra,
|
|
16
18
|
):
|
|
17
19
|
"""
|
|
@@ -24,8 +26,6 @@ def remote(
|
|
|
24
26
|
to be provisioned or used.
|
|
25
27
|
dependencies (List[str], optional): A list of pip package names to be installed in the remote
|
|
26
28
|
environment before executing the function. Defaults to None.
|
|
27
|
-
mount_volume (NetworkVolume, optional): Configuration for creating and mounting a network volume.
|
|
28
|
-
Should contain 'size', 'datacenter_id', and 'name' keys. Defaults to None.
|
|
29
29
|
extra (dict, optional): Additional parameters for the execution of the resource. Defaults to an empty dict.
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
@@ -45,17 +45,26 @@ def remote(
|
|
|
45
45
|
```
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
-
def decorator(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
resource_config
|
|
48
|
+
def decorator(func_or_class):
|
|
49
|
+
if inspect.isclass(func_or_class):
|
|
50
|
+
# Handle class decoration
|
|
51
|
+
return create_remote_class(
|
|
52
|
+
func_or_class, resource_config, dependencies, system_dependencies, extra
|
|
54
53
|
)
|
|
54
|
+
else:
|
|
55
|
+
# Handle function decoration (unchanged)
|
|
56
|
+
@wraps(func_or_class)
|
|
57
|
+
async def wrapper(*args, **kwargs):
|
|
58
|
+
resource_manager = ResourceManager()
|
|
59
|
+
remote_resource = await resource_manager.get_or_deploy_resource(
|
|
60
|
+
resource_config
|
|
61
|
+
)
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
stub = stub_resource(remote_resource, **extra)
|
|
64
|
+
return await stub(
|
|
65
|
+
func_or_class, dependencies, system_dependencies, *args, **kwargs
|
|
66
|
+
)
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
return wrapper
|
|
60
69
|
|
|
61
70
|
return decorator
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
import textwrap
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import List, Type, Optional
|
|
7
|
+
|
|
8
|
+
import cloudpickle
|
|
9
|
+
|
|
10
|
+
from .core.resources import ResourceManager, ServerlessResource
|
|
11
|
+
from .protos.remote_execution import FunctionRequest
|
|
12
|
+
from .stubs import stub_resource
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_class_code_simple(cls: Type) -> str:
|
|
18
|
+
"""Extract clean class code without decorators and proper indentation"""
|
|
19
|
+
try:
|
|
20
|
+
# Get source code
|
|
21
|
+
source = inspect.getsource(cls)
|
|
22
|
+
|
|
23
|
+
# Split into lines
|
|
24
|
+
lines = source.split("\n")
|
|
25
|
+
|
|
26
|
+
# Find the class definition line (starts with 'class' and contains ':')
|
|
27
|
+
class_start_idx = -1
|
|
28
|
+
for i, line in enumerate(lines):
|
|
29
|
+
stripped = line.strip()
|
|
30
|
+
if stripped.startswith("class ") and ":" in stripped:
|
|
31
|
+
class_start_idx = i
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
if class_start_idx == -1:
|
|
35
|
+
raise ValueError("Could not find class definition")
|
|
36
|
+
|
|
37
|
+
# Take lines from class definition onwards (ignore everything before)
|
|
38
|
+
class_lines = lines[class_start_idx:]
|
|
39
|
+
|
|
40
|
+
# Remove empty lines at the end
|
|
41
|
+
while class_lines and not class_lines[-1].strip():
|
|
42
|
+
class_lines.pop()
|
|
43
|
+
|
|
44
|
+
# Join back and dedent to remove any leading indentation
|
|
45
|
+
class_code = "\n".join(class_lines)
|
|
46
|
+
class_code = textwrap.dedent(class_code)
|
|
47
|
+
|
|
48
|
+
# Validate the code by trying to compile it
|
|
49
|
+
compile(class_code, "<string>", "exec")
|
|
50
|
+
|
|
51
|
+
log.debug(f"Successfully extracted class code for {cls.__name__}")
|
|
52
|
+
return class_code
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
log.warning(f"Could not extract class code for {cls.__name__}: {e}")
|
|
56
|
+
log.warning("Falling back to basic class structure")
|
|
57
|
+
|
|
58
|
+
# Enhanced fallback: try to preserve method signatures
|
|
59
|
+
fallback_methods = []
|
|
60
|
+
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
|
|
61
|
+
try:
|
|
62
|
+
sig = inspect.signature(method)
|
|
63
|
+
fallback_methods.append(f" def {name}{sig}:")
|
|
64
|
+
fallback_methods.append(" pass")
|
|
65
|
+
fallback_methods.append("")
|
|
66
|
+
except (TypeError, ValueError, OSError) as e:
|
|
67
|
+
log.warning(f"Could not extract method signature for {name}: {e}")
|
|
68
|
+
fallback_methods.append(f" def {name}(self, *args, **kwargs):")
|
|
69
|
+
fallback_methods.append(" pass")
|
|
70
|
+
fallback_methods.append("")
|
|
71
|
+
|
|
72
|
+
fallback_code = f"""class {cls.__name__}:
|
|
73
|
+
def __init__(self, *args, **kwargs):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
{chr(10).join(fallback_methods)}"""
|
|
77
|
+
|
|
78
|
+
return fallback_code
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_remote_class(
|
|
82
|
+
cls: Type,
|
|
83
|
+
resource_config: ServerlessResource,
|
|
84
|
+
dependencies: Optional[List[str]],
|
|
85
|
+
system_dependencies: Optional[List[str]],
|
|
86
|
+
extra: dict,
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
Create a remote class wrapper.
|
|
90
|
+
"""
|
|
91
|
+
# Validate inputs
|
|
92
|
+
if not inspect.isclass(cls):
|
|
93
|
+
raise TypeError(f"Expected a class, got {type(cls).__name__}")
|
|
94
|
+
if not hasattr(cls, "__name__"):
|
|
95
|
+
raise ValueError("Class must have a __name__ attribute")
|
|
96
|
+
|
|
97
|
+
class RemoteClassWrapper:
|
|
98
|
+
def __init__(self, *args, **kwargs):
|
|
99
|
+
self._class_type = cls
|
|
100
|
+
self._resource_config = resource_config
|
|
101
|
+
self._dependencies = dependencies or []
|
|
102
|
+
self._system_dependencies = system_dependencies or []
|
|
103
|
+
self._extra = extra
|
|
104
|
+
self._constructor_args = args
|
|
105
|
+
self._constructor_kwargs = kwargs
|
|
106
|
+
self._instance_id = f"{cls.__name__}_{uuid.uuid4().hex[:8]}"
|
|
107
|
+
self._initialized = False
|
|
108
|
+
|
|
109
|
+
self._clean_class_code = extract_class_code_simple(cls)
|
|
110
|
+
|
|
111
|
+
log.debug(f"Created remote class wrapper for {cls.__name__}")
|
|
112
|
+
|
|
113
|
+
async def _ensure_initialized(self):
|
|
114
|
+
"""Ensure the remote instance is created."""
|
|
115
|
+
if self._initialized:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Get remote resource
|
|
119
|
+
resource_manager = ResourceManager()
|
|
120
|
+
remote_resource = await resource_manager.get_or_deploy_resource(
|
|
121
|
+
self._resource_config
|
|
122
|
+
)
|
|
123
|
+
self._stub = stub_resource(remote_resource, **self._extra)
|
|
124
|
+
|
|
125
|
+
# Create the remote instance by calling a method (which will trigger instance creation)
|
|
126
|
+
# We'll do this on first method call
|
|
127
|
+
self._initialized = True
|
|
128
|
+
|
|
129
|
+
def __getattr__(self, name):
|
|
130
|
+
"""Dynamically create method proxies for all class methods."""
|
|
131
|
+
if name.startswith("_"):
|
|
132
|
+
raise AttributeError(
|
|
133
|
+
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
async def method_proxy(*args, **kwargs):
|
|
137
|
+
await self._ensure_initialized()
|
|
138
|
+
|
|
139
|
+
# Create class method request
|
|
140
|
+
|
|
141
|
+
# class_code = inspect.getsource(self._class_type)
|
|
142
|
+
class_code = self._clean_class_code
|
|
143
|
+
|
|
144
|
+
request = FunctionRequest(
|
|
145
|
+
execution_type="class",
|
|
146
|
+
class_name=self._class_type.__name__,
|
|
147
|
+
class_code=class_code,
|
|
148
|
+
method_name=name,
|
|
149
|
+
args=[
|
|
150
|
+
base64.b64encode(cloudpickle.dumps(arg)).decode("utf-8")
|
|
151
|
+
for arg in args
|
|
152
|
+
],
|
|
153
|
+
kwargs={
|
|
154
|
+
k: base64.b64encode(cloudpickle.dumps(v)).decode("utf-8")
|
|
155
|
+
for k, v in kwargs.items()
|
|
156
|
+
},
|
|
157
|
+
constructor_args=[
|
|
158
|
+
base64.b64encode(cloudpickle.dumps(arg)).decode("utf-8")
|
|
159
|
+
for arg in self._constructor_args
|
|
160
|
+
],
|
|
161
|
+
constructor_kwargs={
|
|
162
|
+
k: base64.b64encode(cloudpickle.dumps(v)).decode("utf-8")
|
|
163
|
+
for k, v in self._constructor_kwargs.items()
|
|
164
|
+
},
|
|
165
|
+
dependencies=self._dependencies,
|
|
166
|
+
system_dependencies=self._system_dependencies,
|
|
167
|
+
instance_id=self._instance_id,
|
|
168
|
+
create_new_instance=not hasattr(
|
|
169
|
+
self, "_stub"
|
|
170
|
+
), # Create new only on first call
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Execute via stub
|
|
174
|
+
return await self._stub.execute_class_method(request) # type: ignore
|
|
175
|
+
|
|
176
|
+
return method_proxy
|
|
177
|
+
|
|
178
|
+
return RemoteClassWrapper
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# TODO: generate using betterproto
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FunctionRequest(BaseModel):
|
|
9
|
+
# MADE OPTIONAL - can be None for class-only execution
|
|
10
|
+
function_name: Optional[str] = Field(
|
|
11
|
+
default=None,
|
|
12
|
+
description="Name of the function to execute",
|
|
13
|
+
)
|
|
14
|
+
function_code: Optional[str] = Field(
|
|
15
|
+
default=None,
|
|
16
|
+
description="Source code of the function to execute",
|
|
17
|
+
)
|
|
18
|
+
args: List = Field(
|
|
19
|
+
default_factory=list,
|
|
20
|
+
description="List of base64-encoded cloudpickle-serialized arguments",
|
|
21
|
+
)
|
|
22
|
+
kwargs: Dict = Field(
|
|
23
|
+
default_factory=dict,
|
|
24
|
+
description="Dictionary of base64-encoded cloudpickle-serialized keyword arguments",
|
|
25
|
+
)
|
|
26
|
+
dependencies: Optional[List] = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Optional list of pip packages to install before executing the function",
|
|
29
|
+
)
|
|
30
|
+
system_dependencies: Optional[List] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Optional list of system dependencies to install before executing the function",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# NEW FIELDS FOR CLASS SUPPORT
|
|
36
|
+
execution_type: str = Field(
|
|
37
|
+
default="function", description="Type of execution: 'function' or 'class'"
|
|
38
|
+
)
|
|
39
|
+
class_name: Optional[str] = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Name of the class to instantiate (for class execution)",
|
|
42
|
+
)
|
|
43
|
+
class_code: Optional[str] = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description="Source code of the class to instantiate (for class execution)",
|
|
46
|
+
)
|
|
47
|
+
constructor_args: Optional[List] = Field(
|
|
48
|
+
default_factory=list,
|
|
49
|
+
description="List of base64-encoded cloudpickle-serialized constructor arguments",
|
|
50
|
+
)
|
|
51
|
+
constructor_kwargs: Optional[Dict] = Field(
|
|
52
|
+
default_factory=dict,
|
|
53
|
+
description="Dictionary of base64-encoded cloudpickle-serialized constructor keyword arguments",
|
|
54
|
+
)
|
|
55
|
+
method_name: str = Field(
|
|
56
|
+
default="__call__",
|
|
57
|
+
description="Name of the method to call on the class instance",
|
|
58
|
+
)
|
|
59
|
+
instance_id: Optional[str] = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="Unique identifier for the class instance (for persistence)",
|
|
62
|
+
)
|
|
63
|
+
create_new_instance: bool = Field(
|
|
64
|
+
default=True,
|
|
65
|
+
description="Whether to create a new instance or reuse existing one",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@model_validator(mode="after")
|
|
69
|
+
def validate_execution_requirements(self) -> "FunctionRequest":
|
|
70
|
+
"""Validate that required fields are provided based on execution_type"""
|
|
71
|
+
if self.execution_type == "function":
|
|
72
|
+
if self.function_name is None:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
'function_name is required when execution_type is "function"'
|
|
75
|
+
)
|
|
76
|
+
if self.function_code is None:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
'function_code is required when execution_type is "function"'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
elif self.execution_type == "class":
|
|
82
|
+
if self.class_name is None:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
'class_name is required when execution_type is "class"'
|
|
85
|
+
)
|
|
86
|
+
if self.class_code is None:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
'class_code is required when execution_type is "class"'
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class FunctionResponse(BaseModel):
|
|
95
|
+
# EXISTING FIELDS (unchanged)
|
|
96
|
+
success: bool = Field(
|
|
97
|
+
description="Indicates if the function execution was successful",
|
|
98
|
+
)
|
|
99
|
+
result: Optional[str] = Field(
|
|
100
|
+
default=None,
|
|
101
|
+
description="Base64-encoded cloudpickle-serialized result of the function",
|
|
102
|
+
)
|
|
103
|
+
error: Optional[str] = Field(
|
|
104
|
+
default=None,
|
|
105
|
+
description="Error message if the function execution failed",
|
|
106
|
+
)
|
|
107
|
+
stdout: Optional[str] = Field(
|
|
108
|
+
default=None,
|
|
109
|
+
description="Captured standard output from the function execution",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# NEW FIELDS FOR CLASS SUPPORT
|
|
113
|
+
instance_id: Optional[str] = Field(
|
|
114
|
+
default=None, description="ID of the class instance that was used/created"
|
|
115
|
+
)
|
|
116
|
+
instance_info: Optional[Dict] = Field(
|
|
117
|
+
default=None,
|
|
118
|
+
description="Metadata about the class instance (creation time, call count, etc.)",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class RemoteExecutorStub(ABC):
|
|
123
|
+
"""Abstract base class for remote execution."""
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
async def ExecuteFunction(self, request: FunctionRequest) -> FunctionResponse:
|
|
127
|
+
"""Execute a function on the remote resource."""
|
|
128
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from functools import singledispatch
|
|
3
|
-
|
|
4
|
-
from .serverless import ServerlessEndpointStub
|
|
3
|
+
|
|
5
4
|
from ..core.resources import (
|
|
6
5
|
CpuServerlessEndpoint,
|
|
7
6
|
LiveServerless,
|
|
8
7
|
ServerlessEndpoint,
|
|
9
8
|
)
|
|
10
|
-
|
|
9
|
+
from .live_serverless import LiveServerlessStub
|
|
10
|
+
from .serverless import ServerlessEndpointStub
|
|
11
11
|
|
|
12
12
|
log = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -22,20 +22,29 @@ def stub_resource(resource, **extra):
|
|
|
22
22
|
|
|
23
23
|
@stub_resource.register(LiveServerless)
|
|
24
24
|
def _(resource, **extra):
|
|
25
|
+
stub = LiveServerlessStub(resource)
|
|
26
|
+
|
|
27
|
+
# Function execution
|
|
25
28
|
async def stubbed_resource(
|
|
26
29
|
func, dependencies, system_dependencies, *args, **kwargs
|
|
27
30
|
) -> dict:
|
|
28
31
|
if args == (None,):
|
|
29
|
-
# cleanup: when the function is called with no args
|
|
30
32
|
args = []
|
|
31
33
|
|
|
32
|
-
stub = LiveServerlessStub(resource)
|
|
33
34
|
request = stub.prepare_request(
|
|
34
35
|
func, dependencies, system_dependencies, *args, **kwargs
|
|
35
36
|
)
|
|
36
37
|
response = await stub.ExecuteFunction(request)
|
|
37
38
|
return stub.handle_response(response)
|
|
38
39
|
|
|
40
|
+
# Class method execution
|
|
41
|
+
async def execute_class_method(request):
|
|
42
|
+
response = await stub.ExecuteFunction(request)
|
|
43
|
+
return stub.handle_response(response)
|
|
44
|
+
|
|
45
|
+
# Attach the method to the function
|
|
46
|
+
stubbed_resource.execute_class_method = execute_class_method
|
|
47
|
+
|
|
39
48
|
return stubbed_resource
|
|
40
49
|
|
|
41
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tetra_rp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A Python library for distributed inference and serving of machine learning models
|
|
5
5
|
Author-email: Marut Pandya <pandyamarut@gmail.com>, Patrick Rachford <prachford@icloud.com>, Dean Quinanola <dean.quinanola@runpod.io>
|
|
6
6
|
License: MIT
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# TODO: generate using betterproto
|
|
2
|
-
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import List, Dict, Optional
|
|
5
|
-
from pydantic import BaseModel, Field
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class FunctionRequest(BaseModel):
|
|
9
|
-
function_name: str = Field(
|
|
10
|
-
description="Name of the function to execute",
|
|
11
|
-
)
|
|
12
|
-
function_code: str = Field(
|
|
13
|
-
description="Source code of the function to execute",
|
|
14
|
-
)
|
|
15
|
-
args: List = Field(
|
|
16
|
-
default_factory=list,
|
|
17
|
-
description="List of base64-encoded cloudpickle-serialized arguments",
|
|
18
|
-
)
|
|
19
|
-
kwargs: Dict = Field(
|
|
20
|
-
default_factory=dict,
|
|
21
|
-
description="Dictionary of base64-encoded cloudpickle-serialized keyword arguments",
|
|
22
|
-
)
|
|
23
|
-
dependencies: Optional[List] = Field(
|
|
24
|
-
default=None,
|
|
25
|
-
description="Optional list of pip packages to install before executing the function",
|
|
26
|
-
)
|
|
27
|
-
system_dependencies: Optional[List] = Field(
|
|
28
|
-
default=None,
|
|
29
|
-
description="Optional list of system dependencies to install before executing the function",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class FunctionResponse(BaseModel):
|
|
34
|
-
success: bool = Field(
|
|
35
|
-
description="Indicates if the function execution was successful",
|
|
36
|
-
)
|
|
37
|
-
result: Optional[str] = Field(
|
|
38
|
-
default=None,
|
|
39
|
-
description="Base64-encoded cloudpickle-serialized result of the function",
|
|
40
|
-
)
|
|
41
|
-
error: Optional[str] = Field(
|
|
42
|
-
default=None,
|
|
43
|
-
description="Error message if the function execution failed",
|
|
44
|
-
)
|
|
45
|
-
stdout: Optional[str] = Field(
|
|
46
|
-
default=None,
|
|
47
|
-
description="Captured standard output from the function execution",
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class RemoteExecutorStub(ABC):
|
|
52
|
-
"""Abstract base class for remote execution."""
|
|
53
|
-
|
|
54
|
-
@abstractmethod
|
|
55
|
-
async def ExecuteFunction(self, request: FunctionRequest) -> FunctionResponse:
|
|
56
|
-
"""Execute a function on the remote resource."""
|
|
57
|
-
raise NotImplementedError("Subclasses should implement this method.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|