py-adtools 0.3.2__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.
- adtools/__init__.py +1 -0
- adtools/cli.py +61 -0
- adtools/evaluator/__init__.py +2 -0
- adtools/evaluator/auto_server.py +258 -0
- adtools/evaluator/py_evaluator.py +170 -0
- adtools/evaluator/py_evaluator_ray.py +110 -0
- adtools/lm/__init__.py +4 -0
- adtools/lm/lm_base.py +63 -0
- adtools/lm/openai_api.py +118 -0
- adtools/lm/sglang_server.py +423 -0
- adtools/lm/vllm_server.py +452 -0
- adtools/py_code.py +577 -0
- adtools/sandbox/__init__.py +2 -0
- adtools/sandbox/sandbox_executor.py +244 -0
- adtools/sandbox/sandbox_executor_ray.py +194 -0
- adtools/sandbox/utils.py +32 -0
- py_adtools-0.3.2.dist-info/METADATA +567 -0
- py_adtools-0.3.2.dist-info/RECORD +22 -0
- py_adtools-0.3.2.dist-info/WHEEL +5 -0
- py_adtools-0.3.2.dist-info/entry_points.txt +2 -0
- py_adtools-0.3.2.dist-info/licenses/LICENSE +21 -0
- py_adtools-0.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2025 Rui Zhang <rzhang.cs@gmail.com>
|
|
3
|
+
|
|
4
|
+
NOTICE: This code is under MIT license. This code is intended for academic/research purposes only.
|
|
5
|
+
Commercial use of this software or its derivatives requires prior written permission.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import multiprocessing
|
|
9
|
+
import pickle
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from multiprocessing import shared_memory, resource_tracker
|
|
13
|
+
from queue import Empty
|
|
14
|
+
from typing import Any, Dict, List, TypedDict, Optional, Tuple
|
|
15
|
+
import multiprocessing.managers
|
|
16
|
+
import traceback
|
|
17
|
+
|
|
18
|
+
import psutil
|
|
19
|
+
|
|
20
|
+
from adtools.sandbox.utils import _redirect_to_devnull
|
|
21
|
+
|
|
22
|
+
__all__ = ["ExecutionResults", "SandboxExecutor"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExecutionResults(TypedDict):
|
|
26
|
+
result: Any
|
|
27
|
+
evaluate_time: float
|
|
28
|
+
error_msg: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SandboxExecutor:
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
evaluate_worker: Any,
|
|
36
|
+
find_and_kill_children_evaluation_process: bool = False,
|
|
37
|
+
debug_mode: bool = False,
|
|
38
|
+
*,
|
|
39
|
+
join_timeout_seconds: int = 10,
|
|
40
|
+
):
|
|
41
|
+
"""Evaluator interface for evaluating the Python algorithm program. Override this class and implement
|
|
42
|
+
'evaluate_program' method, then invoke 'self.evaluate()' or 'self.secure_evaluate()' for evaluation.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
exec_code: Using 'exec()' to execute the program code and obtain the callable functions and classes,
|
|
46
|
+
which will be passed to 'self.evaluate_program()'. Set this parameter to 'False' if you are going to
|
|
47
|
+
evaluate a Python scripy. Note that if the parameter is set to 'False', the arguments 'callable_...'
|
|
48
|
+
in 'self.evaluate_program()' will no longer be affective.
|
|
49
|
+
find_and_kill_children_evaluation_process: If using 'self.secure_evaluate', kill children processes
|
|
50
|
+
when they are terminated. Note that it is suggested to set to 'False' if the evaluation process
|
|
51
|
+
does not start new processes.
|
|
52
|
+
debug_mode: Debug mode.
|
|
53
|
+
join_timeout_seconds: Timeout in seconds to wait for the process to finish. Kill the process if timeout.
|
|
54
|
+
"""
|
|
55
|
+
self.evaluate_worker = evaluate_worker
|
|
56
|
+
self.debug_mode = debug_mode
|
|
57
|
+
self.find_and_kill_children_evaluation_process = (
|
|
58
|
+
find_and_kill_children_evaluation_process
|
|
59
|
+
)
|
|
60
|
+
self.join_timeout_seconds = join_timeout_seconds
|
|
61
|
+
|
|
62
|
+
def _kill_process_and_its_children(self, process: multiprocessing.Process):
|
|
63
|
+
if self.find_and_kill_children_evaluation_process:
|
|
64
|
+
# Find all children processes
|
|
65
|
+
try:
|
|
66
|
+
parent = psutil.Process(process.pid)
|
|
67
|
+
children_processes = parent.children(recursive=True)
|
|
68
|
+
except psutil.NoSuchProcess:
|
|
69
|
+
children_processes = []
|
|
70
|
+
else:
|
|
71
|
+
children_processes = []
|
|
72
|
+
# Terminate parent process
|
|
73
|
+
process.terminate()
|
|
74
|
+
process.join(timeout=self.join_timeout_seconds)
|
|
75
|
+
if process.is_alive():
|
|
76
|
+
process.kill()
|
|
77
|
+
process.join()
|
|
78
|
+
# Kill all children processes
|
|
79
|
+
for child in children_processes:
|
|
80
|
+
if self.debug_mode:
|
|
81
|
+
print(f"Killing process {process.pid}'s children process {child.pid}")
|
|
82
|
+
child.terminate()
|
|
83
|
+
|
|
84
|
+
def _execute_and_put_res_in_shared_memory(
|
|
85
|
+
self,
|
|
86
|
+
worker_execute_method_name: str,
|
|
87
|
+
method_args: Optional[List | Tuple],
|
|
88
|
+
method_kwargs: Optional[Dict],
|
|
89
|
+
meta_queue: multiprocessing.Queue,
|
|
90
|
+
redirect_to_devnull: bool,
|
|
91
|
+
shm_name_id: str,
|
|
92
|
+
):
|
|
93
|
+
"""Evaluate and store result in shared memory (for large results)."""
|
|
94
|
+
# Redirect STDOUT and STDERR to '/dev/null'
|
|
95
|
+
if redirect_to_devnull:
|
|
96
|
+
_redirect_to_devnull()
|
|
97
|
+
|
|
98
|
+
if hasattr(self.evaluate_worker, worker_execute_method_name): # todo
|
|
99
|
+
method_to_call = getattr(self.evaluate_worker, worker_execute_method_name)
|
|
100
|
+
else:
|
|
101
|
+
raise RuntimeError(
|
|
102
|
+
f"Method named '{worker_execute_method_name}' not found."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Execute and get results
|
|
106
|
+
# noinspection PyBroadException
|
|
107
|
+
try:
|
|
108
|
+
# Execute the target method and get result
|
|
109
|
+
args = method_args or []
|
|
110
|
+
kwargs = method_kwargs or {}
|
|
111
|
+
res = method_to_call(*args, **kwargs)
|
|
112
|
+
|
|
113
|
+
# Dump the results to data
|
|
114
|
+
data = pickle.dumps(res, protocol=pickle.HIGHEST_PROTOCOL)
|
|
115
|
+
# Create shared memory using the ID provided by the parent
|
|
116
|
+
# We must use create=True here as the child is responsible for allocation
|
|
117
|
+
shm = shared_memory.SharedMemory(
|
|
118
|
+
create=True, name=shm_name_id, size=len(data)
|
|
119
|
+
)
|
|
120
|
+
# Unregister the shared memory block from the resource tracker in this child process
|
|
121
|
+
# The shared memory will be managed in the parent process
|
|
122
|
+
# noinspection PyProtectedMember, PyUnresolvedReferences
|
|
123
|
+
resource_tracker.unregister(name=shm._name, rtype="shared_memory")
|
|
124
|
+
|
|
125
|
+
# Write data
|
|
126
|
+
shm.buf[: len(data)] = data
|
|
127
|
+
# We only need to send back the size, as the parent already knows the name.
|
|
128
|
+
# Sending (True, size) to indicate success.
|
|
129
|
+
meta_queue.put((True, len(data)))
|
|
130
|
+
# Child closes its handle
|
|
131
|
+
shm.close()
|
|
132
|
+
except:
|
|
133
|
+
if self.debug_mode:
|
|
134
|
+
traceback.print_exc()
|
|
135
|
+
# Put the exception message to the queue
|
|
136
|
+
# Sending (False, error_message) to indicate failure.
|
|
137
|
+
meta_queue.put((False, str(traceback.format_exc())))
|
|
138
|
+
|
|
139
|
+
def secure_execute(
|
|
140
|
+
self,
|
|
141
|
+
worker_execute_method_name: str,
|
|
142
|
+
method_args: Optional[List | Tuple] = None,
|
|
143
|
+
method_kwargs: Optional[Dict] = None,
|
|
144
|
+
timeout_seconds: int | float = None,
|
|
145
|
+
redirect_to_devnull: bool = False,
|
|
146
|
+
**kwargs,
|
|
147
|
+
) -> ExecutionResults:
|
|
148
|
+
"""Evaluate program in a new process.
|
|
149
|
+
This enables timeout restriction and output redirection.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
worker_execute_method_name: Name of the worker execute method.
|
|
153
|
+
method_args: Arguments of the worker execute method.
|
|
154
|
+
method_kwargs: Keyword arguments of the worker execute method.
|
|
155
|
+
timeout_seconds: return 'None' if the execution time exceeds 'timeout_seconds'.
|
|
156
|
+
redirect_to_devnull: redirect any output to '/dev/null'.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Returns the evaluation results. If the 'get_evaluate_time' is True,
|
|
160
|
+
the return value will be (Results, Time).
|
|
161
|
+
"""
|
|
162
|
+
# Evaluate and get results
|
|
163
|
+
# noinspection PyBroadException
|
|
164
|
+
try:
|
|
165
|
+
# Create a meta queue to get meta information from the evaluation process
|
|
166
|
+
meta_queue = multiprocessing.Queue()
|
|
167
|
+
# Generate a unique name for the shared memory block in the PARENT process.
|
|
168
|
+
# This allows the parent to clean it up even if the child is killed.
|
|
169
|
+
unique_shm_name = f"psm_{uuid.uuid4().hex[:8]}"
|
|
170
|
+
|
|
171
|
+
process = multiprocessing.Process(
|
|
172
|
+
target=self._execute_and_put_res_in_shared_memory,
|
|
173
|
+
args=(
|
|
174
|
+
worker_execute_method_name,
|
|
175
|
+
method_args,
|
|
176
|
+
method_kwargs,
|
|
177
|
+
meta_queue,
|
|
178
|
+
redirect_to_devnull,
|
|
179
|
+
unique_shm_name,
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
evaluate_start_time = time.time()
|
|
183
|
+
process.start()
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Try to get the metadata before timeout
|
|
187
|
+
meta = meta_queue.get(timeout=timeout_seconds)
|
|
188
|
+
# Calculate evaluation time
|
|
189
|
+
eval_time = time.time() - evaluate_start_time
|
|
190
|
+
except Empty:
|
|
191
|
+
if self.debug_mode:
|
|
192
|
+
print(f"DEBUG: evaluation time exceeds {timeout_seconds}s.")
|
|
193
|
+
|
|
194
|
+
# Evaluation timeout happens, we return 'None' as well as the actual evaluate time
|
|
195
|
+
return ExecutionResults(
|
|
196
|
+
result=None,
|
|
197
|
+
evaluate_time=time.time() - evaluate_start_time,
|
|
198
|
+
error_msg="Evaluation timeout.",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# The 'meta' is now (Success_Flag, Data_Size_or_Error_Msg)
|
|
202
|
+
success, payload = meta
|
|
203
|
+
|
|
204
|
+
if not success:
|
|
205
|
+
# Payload is the error message
|
|
206
|
+
error_msg = payload
|
|
207
|
+
result = None
|
|
208
|
+
else:
|
|
209
|
+
error_msg = ""
|
|
210
|
+
# Payload is the size of the data
|
|
211
|
+
size = payload
|
|
212
|
+
# Attach to the existing shared memory by name
|
|
213
|
+
shm = shared_memory.SharedMemory(name=unique_shm_name)
|
|
214
|
+
buf = bytes(shm.buf[:size])
|
|
215
|
+
# Load results from buffer
|
|
216
|
+
result = pickle.loads(buf)
|
|
217
|
+
shm.close()
|
|
218
|
+
|
|
219
|
+
return ExecutionResults(
|
|
220
|
+
result=result, evaluate_time=eval_time, error_msg=error_msg
|
|
221
|
+
)
|
|
222
|
+
except:
|
|
223
|
+
if self.debug_mode:
|
|
224
|
+
print(f"DEBUG: exception in shared evaluate:\n{traceback.format_exc()}")
|
|
225
|
+
|
|
226
|
+
return ExecutionResults(
|
|
227
|
+
result=None,
|
|
228
|
+
evaluate_time=time.time() - evaluate_start_time,
|
|
229
|
+
error_msg=str(traceback.format_exc()),
|
|
230
|
+
)
|
|
231
|
+
finally:
|
|
232
|
+
self._kill_process_and_its_children(process)
|
|
233
|
+
# Critical Cleanup: Ensure the shared memory is unlinked from the OS
|
|
234
|
+
# This runs whether the process finished, timed out, or crashed
|
|
235
|
+
try:
|
|
236
|
+
# Attempt to attach to the shared memory block
|
|
237
|
+
shm_cleanup = shared_memory.SharedMemory(name=unique_shm_name)
|
|
238
|
+
shm_cleanup.close()
|
|
239
|
+
# Unlink (delete) it from the system, and close the shared memory
|
|
240
|
+
shm_cleanup.unlink()
|
|
241
|
+
except FileNotFoundError:
|
|
242
|
+
# This is normal if the child process never reached the creation step
|
|
243
|
+
# (e.g. crashed during calculation before creating SHM)
|
|
244
|
+
pass
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2025 Rui Zhang <rzhang.cs@gmail.com>
|
|
3
|
+
|
|
4
|
+
NOTICE: This code is under MIT license. This code is intended for academic/research purposes only.
|
|
5
|
+
Commercial use of this software or its derivatives requires prior written permission.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import traceback
|
|
13
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from adtools.sandbox.sandbox_executor import SandboxExecutor, ExecutionResults
|
|
16
|
+
from adtools.sandbox.utils import _redirect_to_devnull
|
|
17
|
+
|
|
18
|
+
__all__ = ["SandboxExecutorRay"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SandboxExecutorRay(SandboxExecutor):
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
evaluate_worker: Any,
|
|
26
|
+
init_ray: bool = True,
|
|
27
|
+
debug_mode: bool = False,
|
|
28
|
+
*,
|
|
29
|
+
ray_rotation_max_bytes: int = 50 * 1024 * 1024, # 50 MB
|
|
30
|
+
ray_rotation_backup_count: int = 1,
|
|
31
|
+
):
|
|
32
|
+
"""Evaluator using Ray for secure, isolated execution.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
evaluate_worker: The worker object to be executed.
|
|
36
|
+
init_ray: Whether to initialize ray.
|
|
37
|
+
debug_mode: Enable debug print statements.
|
|
38
|
+
ray_rotation_max_bytes: Max bytes for ray log rotation.
|
|
39
|
+
ray_rotation_backup_count: Backup count for ray log rotation.
|
|
40
|
+
"""
|
|
41
|
+
super().__init__(
|
|
42
|
+
evaluate_worker=evaluate_worker,
|
|
43
|
+
debug_mode=debug_mode,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
import ray
|
|
47
|
+
|
|
48
|
+
if init_ray:
|
|
49
|
+
if ray.is_initialized():
|
|
50
|
+
logging.warning(
|
|
51
|
+
f"Ray is already initialized. "
|
|
52
|
+
f"If you want to disable reinit, "
|
|
53
|
+
f"please set '{self.__class__.__name__}(..., init_ray=False)'."
|
|
54
|
+
)
|
|
55
|
+
# Set environment variable before Ray initialization
|
|
56
|
+
os.environ["RAY_ACCEL_ENV_VAR_OVERRIDE_ON_ZERO"] = "0"
|
|
57
|
+
os.environ["RAY_ROTATION_MAX_BYTES"] = str(ray_rotation_max_bytes)
|
|
58
|
+
os.environ["RAY_ROTATION_BACKUP_COUNT"] = str(ray_rotation_backup_count)
|
|
59
|
+
|
|
60
|
+
# Initialize Ray
|
|
61
|
+
ray.init(
|
|
62
|
+
ignore_reinit_error=True,
|
|
63
|
+
include_dashboard=False,
|
|
64
|
+
logging_level=logging.ERROR,
|
|
65
|
+
log_to_driver=True,
|
|
66
|
+
)
|
|
67
|
+
elif not ray.is_initialized():
|
|
68
|
+
raise RuntimeError(
|
|
69
|
+
f"Ray is not initialized. "
|
|
70
|
+
f"Please set '{self.__class__.__name__}(..., init_ray=True)'."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def secure_execute(
|
|
74
|
+
self,
|
|
75
|
+
worker_execute_method_name: str,
|
|
76
|
+
method_args: Optional[List | Tuple] = None,
|
|
77
|
+
method_kwargs: Optional[Dict] = None,
|
|
78
|
+
timeout_seconds: int | float = None,
|
|
79
|
+
redirect_to_devnull: bool = False,
|
|
80
|
+
*,
|
|
81
|
+
ray_actor_options: dict[str, Any] = None,
|
|
82
|
+
**kwargs,
|
|
83
|
+
) -> ExecutionResults:
|
|
84
|
+
"""Evaluates the program in a separate Ray Actor (process).
|
|
85
|
+
This enables timeout restriction and output redirection.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
worker_execute_method_name: Name of the worker execute method.
|
|
89
|
+
method_args: Arguments of the worker execute method.
|
|
90
|
+
method_kwargs: Keyword arguments of the worker execute method.
|
|
91
|
+
timeout_seconds: return 'None' if the execution time exceeds 'timeout_seconds'.
|
|
92
|
+
redirect_to_devnull: redirect any output to '/dev/null'.
|
|
93
|
+
ray_actor_options: Ray actor options.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Returns the evaluation results. If the 'get_evaluate_time' is True,
|
|
97
|
+
the return value will be (Results, Time).
|
|
98
|
+
"""
|
|
99
|
+
import ray
|
|
100
|
+
from ray.exceptions import GetTimeoutError
|
|
101
|
+
|
|
102
|
+
if ray_actor_options is None:
|
|
103
|
+
ray_actor_options = {}
|
|
104
|
+
else:
|
|
105
|
+
ray_actor_options = ray_actor_options.copy()
|
|
106
|
+
|
|
107
|
+
# Propagate sys.path and PYTHONPATH
|
|
108
|
+
runtime_env = ray_actor_options.get("runtime_env", {})
|
|
109
|
+
env_vars = runtime_env.get("env_vars", {})
|
|
110
|
+
|
|
111
|
+
current_paths = [p for p in sys.path if p and os.path.exists(p)]
|
|
112
|
+
existing_pythonpath = env_vars.get("PYTHONPATH", "")
|
|
113
|
+
if existing_pythonpath:
|
|
114
|
+
current_paths.insert(0, existing_pythonpath)
|
|
115
|
+
|
|
116
|
+
# Deduplicate preserving order
|
|
117
|
+
unique_paths = []
|
|
118
|
+
seen = set()
|
|
119
|
+
for p in current_paths:
|
|
120
|
+
if p not in seen:
|
|
121
|
+
unique_paths.append(p)
|
|
122
|
+
seen.add(p)
|
|
123
|
+
|
|
124
|
+
env_vars["PYTHONPATH"] = os.pathsep.join(unique_paths)
|
|
125
|
+
runtime_env["env_vars"] = env_vars
|
|
126
|
+
ray_actor_options["runtime_env"] = runtime_env
|
|
127
|
+
|
|
128
|
+
# Create Remote Worker Class
|
|
129
|
+
RemoteWorkerClass = ray.remote(max_concurrency=1)(_RayWorker)
|
|
130
|
+
|
|
131
|
+
# Create worker
|
|
132
|
+
worker = RemoteWorkerClass.options(**ray_actor_options).remote(
|
|
133
|
+
self.evaluate_worker
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
start_time = time.time()
|
|
137
|
+
try:
|
|
138
|
+
future = worker.execute.remote(
|
|
139
|
+
worker_execute_method_name,
|
|
140
|
+
method_args,
|
|
141
|
+
method_kwargs,
|
|
142
|
+
redirect_to_devnull,
|
|
143
|
+
)
|
|
144
|
+
result = ray.get(future, timeout=timeout_seconds)
|
|
145
|
+
return ExecutionResults(
|
|
146
|
+
result=result,
|
|
147
|
+
evaluate_time=time.time() - start_time,
|
|
148
|
+
error_msg="",
|
|
149
|
+
)
|
|
150
|
+
except GetTimeoutError:
|
|
151
|
+
if self.debug_mode:
|
|
152
|
+
print(f"DEBUG: Ray evaluation timed out after {timeout_seconds}s.")
|
|
153
|
+
return ExecutionResults(
|
|
154
|
+
result=None,
|
|
155
|
+
evaluate_time=time.time() - start_time,
|
|
156
|
+
error_msg="Evaluation timeout.",
|
|
157
|
+
)
|
|
158
|
+
except Exception:
|
|
159
|
+
if self.debug_mode:
|
|
160
|
+
print(f"DEBUG: Ray evaluation exception:\n{traceback.format_exc()}")
|
|
161
|
+
return ExecutionResults(
|
|
162
|
+
result=None,
|
|
163
|
+
evaluate_time=time.time() - start_time,
|
|
164
|
+
error_msg=str(traceback.format_exc()),
|
|
165
|
+
)
|
|
166
|
+
finally:
|
|
167
|
+
ray.kill(worker, no_restart=True)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class _RayWorker:
|
|
171
|
+
"""A standalone Ray Actor used to execute the evaluation logic in a separate process."""
|
|
172
|
+
|
|
173
|
+
def __init__(self, evaluate_worker: Any):
|
|
174
|
+
self.evaluate_worker = evaluate_worker
|
|
175
|
+
|
|
176
|
+
def execute(
|
|
177
|
+
self,
|
|
178
|
+
worker_execute_method_name: str,
|
|
179
|
+
method_args: Optional[List | Tuple],
|
|
180
|
+
method_kwargs: Optional[Dict],
|
|
181
|
+
redirect_to_devnull: bool,
|
|
182
|
+
) -> Any:
|
|
183
|
+
if redirect_to_devnull:
|
|
184
|
+
_redirect_to_devnull()
|
|
185
|
+
|
|
186
|
+
if hasattr(self.evaluate_worker, worker_execute_method_name):
|
|
187
|
+
method_to_call = getattr(self.evaluate_worker, worker_execute_method_name)
|
|
188
|
+
args = method_args or []
|
|
189
|
+
kwargs = method_kwargs or {}
|
|
190
|
+
return method_to_call(*args, **kwargs)
|
|
191
|
+
else:
|
|
192
|
+
raise RuntimeError(
|
|
193
|
+
f"Method named '{worker_execute_method_name}' not found in worker."
|
|
194
|
+
)
|
adtools/sandbox/utils.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2025 Rui Zhang <rzhang.cs@gmail.com>
|
|
3
|
+
|
|
4
|
+
NOTICE: This code is under MIT license. This code is intended for academic/research purposes only.
|
|
5
|
+
Commercial use of this software or its derivatives requires prior written permission.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import multiprocessing
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import functools
|
|
12
|
+
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _set_mp_start_method(
|
|
17
|
+
multiprocessing_start_method: Literal["default", "auto", "fork", "spawn"],
|
|
18
|
+
):
|
|
19
|
+
if multiprocessing_start_method == "auto":
|
|
20
|
+
# Force macOS and Linux use 'fork' to generate new process
|
|
21
|
+
if sys.platform.startswith("darwin") or sys.platform.startswith("linux"):
|
|
22
|
+
multiprocessing.set_start_method("fork", force=True)
|
|
23
|
+
elif multiprocessing_start_method == "fork":
|
|
24
|
+
multiprocessing.set_start_method("fork", force=True)
|
|
25
|
+
elif multiprocessing_start_method == "spawn":
|
|
26
|
+
multiprocessing.set_start_method("spawn", force=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _redirect_to_devnull():
|
|
30
|
+
with open(os.devnull, "w") as devnull:
|
|
31
|
+
os.dup2(devnull.fileno(), sys.stdout.fileno())
|
|
32
|
+
os.dup2(devnull.fileno(), sys.stderr.fileno())
|