speedy-utils 1.1.27__py3-none-any.whl → 1.1.28__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.
- llm_utils/__init__.py +16 -4
- llm_utils/chat_format/__init__.py +10 -10
- llm_utils/chat_format/display.py +33 -21
- llm_utils/chat_format/transform.py +17 -19
- llm_utils/chat_format/utils.py +6 -4
- llm_utils/group_messages.py +17 -14
- llm_utils/lm/__init__.py +6 -5
- llm_utils/lm/async_lm/__init__.py +1 -0
- llm_utils/lm/async_lm/_utils.py +10 -9
- llm_utils/lm/async_lm/async_llm_task.py +141 -137
- llm_utils/lm/async_lm/async_lm.py +48 -42
- llm_utils/lm/async_lm/async_lm_base.py +59 -60
- llm_utils/lm/async_lm/lm_specific.py +4 -3
- llm_utils/lm/base_prompt_builder.py +93 -70
- llm_utils/lm/llm.py +126 -108
- llm_utils/lm/llm_signature.py +4 -2
- llm_utils/lm/lm_base.py +72 -73
- llm_utils/lm/mixins.py +102 -62
- llm_utils/lm/openai_memoize.py +124 -87
- llm_utils/lm/signature.py +105 -92
- llm_utils/lm/utils.py +42 -23
- llm_utils/scripts/vllm_load_balancer.py +23 -30
- llm_utils/scripts/vllm_serve.py +8 -7
- llm_utils/vector_cache/__init__.py +9 -3
- llm_utils/vector_cache/cli.py +1 -1
- llm_utils/vector_cache/core.py +59 -63
- llm_utils/vector_cache/types.py +7 -5
- llm_utils/vector_cache/utils.py +12 -8
- speedy_utils/__imports.py +244 -0
- speedy_utils/__init__.py +90 -194
- speedy_utils/all.py +125 -227
- speedy_utils/common/clock.py +37 -42
- speedy_utils/common/function_decorator.py +6 -12
- speedy_utils/common/logger.py +43 -52
- speedy_utils/common/notebook_utils.py +13 -21
- speedy_utils/common/patcher.py +21 -17
- speedy_utils/common/report_manager.py +42 -44
- speedy_utils/common/utils_cache.py +152 -169
- speedy_utils/common/utils_io.py +137 -103
- speedy_utils/common/utils_misc.py +15 -21
- speedy_utils/common/utils_print.py +22 -28
- speedy_utils/multi_worker/process.py +66 -79
- speedy_utils/multi_worker/thread.py +78 -155
- speedy_utils/scripts/mpython.py +38 -36
- speedy_utils/scripts/openapi_client_codegen.py +10 -10
- {speedy_utils-1.1.27.dist-info → speedy_utils-1.1.28.dist-info}/METADATA +1 -1
- speedy_utils-1.1.28.dist-info/RECORD +57 -0
- vision_utils/README.md +202 -0
- vision_utils/__init__.py +5 -0
- vision_utils/io_utils.py +470 -0
- vision_utils/plot.py +345 -0
- speedy_utils-1.1.27.dist-info/RECORD +0 -52
- {speedy_utils-1.1.27.dist-info → speedy_utils-1.1.28.dist-info}/WHEEL +0 -0
- {speedy_utils-1.1.27.dist-info → speedy_utils-1.1.28.dist-info}/entry_points.txt +0 -0
|
@@ -1,29 +1,9 @@
|
|
|
1
1
|
# ray_multi_process.py
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import uuid
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any, Callable, Iterable
|
|
10
|
-
|
|
11
|
-
import psutil
|
|
12
|
-
from fastcore.parallel import parallel
|
|
13
|
-
from tqdm import tqdm
|
|
14
|
-
|
|
15
|
-
ray: Any
|
|
16
|
-
try:
|
|
17
|
-
import ray as ray # type: ignore
|
|
18
|
-
_HAS_RAY = True
|
|
19
|
-
except Exception: # pragma: no cover
|
|
20
|
-
ray = None # type: ignore
|
|
21
|
-
_HAS_RAY = False
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# ─── global tracking ──────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
# Global tracking for processes and threads
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from ..__imports import *
|
|
5
|
+
|
|
6
|
+
|
|
27
7
|
SPEEDY_RUNNING_PROCESSES: list[psutil.Process] = []
|
|
28
8
|
_SPEEDY_PROCESSES_LOCK = threading.Lock()
|
|
29
9
|
|
|
@@ -31,7 +11,9 @@ _SPEEDY_PROCESSES_LOCK = threading.Lock()
|
|
|
31
11
|
def _prune_dead_processes() -> None:
|
|
32
12
|
"""Remove dead processes from tracking list."""
|
|
33
13
|
with _SPEEDY_PROCESSES_LOCK:
|
|
34
|
-
SPEEDY_RUNNING_PROCESSES[:] = [
|
|
14
|
+
SPEEDY_RUNNING_PROCESSES[:] = [
|
|
15
|
+
p for p in SPEEDY_RUNNING_PROCESSES if p.is_running()
|
|
16
|
+
]
|
|
35
17
|
|
|
36
18
|
|
|
37
19
|
def _track_processes(processes: list[psutil.Process]) -> None:
|
|
@@ -51,8 +33,7 @@ def _track_processes(processes: list[psutil.Process]) -> None:
|
|
|
51
33
|
|
|
52
34
|
def _track_ray_processes() -> None:
|
|
53
35
|
"""Track Ray worker processes when Ray is initialized."""
|
|
54
|
-
|
|
55
|
-
return
|
|
36
|
+
|
|
56
37
|
try:
|
|
57
38
|
# Get Ray worker processes
|
|
58
39
|
current_pid = os.getpid()
|
|
@@ -80,7 +61,9 @@ def _track_multiprocessing_processes() -> None:
|
|
|
80
61
|
for child in parent.children(recursive=False): # Only direct children
|
|
81
62
|
try:
|
|
82
63
|
# Basic heuristic: if it's a recent child process, it might be a worker
|
|
83
|
-
if
|
|
64
|
+
if (
|
|
65
|
+
time.time() - child.create_time() < 5
|
|
66
|
+
): # Created within last 5 seconds
|
|
84
67
|
new_processes.append(child)
|
|
85
68
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
86
69
|
continue
|
|
@@ -95,11 +78,11 @@ def _track_multiprocessing_processes() -> None:
|
|
|
95
78
|
|
|
96
79
|
def _build_cache_dir(func: Callable, items: list[Any]) -> Path:
|
|
97
80
|
"""Build cache dir with function name + timestamp."""
|
|
98
|
-
func_name = getattr(func,
|
|
81
|
+
func_name = getattr(func, '__name__', 'func')
|
|
99
82
|
now = datetime.datetime.now()
|
|
100
|
-
stamp = now.strftime(
|
|
101
|
-
run_id = f
|
|
102
|
-
path = Path(
|
|
83
|
+
stamp = now.strftime('%m%d_%Hh%Mm%Ss')
|
|
84
|
+
run_id = f'{func_name}_{stamp}_{uuid.uuid4().hex[:6]}'
|
|
85
|
+
path = Path('.cache') / run_id
|
|
103
86
|
path.mkdir(parents=True, exist_ok=True)
|
|
104
87
|
return path
|
|
105
88
|
|
|
@@ -111,8 +94,8 @@ def wrap_dump(func: Callable, cache_dir: Path | None):
|
|
|
111
94
|
|
|
112
95
|
def wrapped(x, *args, **kwargs):
|
|
113
96
|
res = func(x, *args, **kwargs)
|
|
114
|
-
p = cache_dir / f
|
|
115
|
-
with open(p,
|
|
97
|
+
p = cache_dir / f'{uuid.uuid4().hex}.pkl'
|
|
98
|
+
with open(p, 'wb') as fh:
|
|
116
99
|
pickle.dump(res, fh)
|
|
117
100
|
return str(p)
|
|
118
101
|
|
|
@@ -127,23 +110,19 @@ RAY_WORKER = None
|
|
|
127
110
|
def ensure_ray(workers: int, pbar: tqdm | None = None):
|
|
128
111
|
"""Initialize or reinitialize Ray with a given worker count, log to bar postfix."""
|
|
129
112
|
global RAY_WORKER
|
|
130
|
-
if not ray.is_initialized() or
|
|
113
|
+
if not ray.is_initialized() or workers != RAY_WORKER:
|
|
131
114
|
if ray.is_initialized() and pbar:
|
|
132
|
-
pbar.set_postfix_str(f
|
|
115
|
+
pbar.set_postfix_str(f'Restarting Ray {workers} workers')
|
|
133
116
|
ray.shutdown()
|
|
134
117
|
t0 = time.time()
|
|
135
118
|
ray.init(num_cpus=workers, ignore_reinit_error=True)
|
|
136
119
|
took = time.time() - t0
|
|
137
120
|
_track_ray_processes() # Track Ray worker processes
|
|
138
121
|
if pbar:
|
|
139
|
-
pbar.set_postfix_str(f
|
|
122
|
+
pbar.set_postfix_str(f'ray.init {workers} took {took:.2f}s')
|
|
140
123
|
RAY_WORKER = workers
|
|
141
124
|
|
|
142
125
|
|
|
143
|
-
# ─── main API ───────────────────────────────────────────────
|
|
144
|
-
from typing import Literal
|
|
145
|
-
|
|
146
|
-
|
|
147
126
|
def multi_process(
|
|
148
127
|
func: Callable[[Any], Any],
|
|
149
128
|
items: Iterable[Any] | None = None,
|
|
@@ -153,7 +132,8 @@ def multi_process(
|
|
|
153
132
|
lazy_output: bool = False,
|
|
154
133
|
progress: bool = True,
|
|
155
134
|
# backend: str = "ray", # "seq", "ray", or "fastcore"
|
|
156
|
-
backend: Literal[
|
|
135
|
+
backend: Literal['seq', 'ray', 'mp', 'threadpool', 'safe'] = 'mp',
|
|
136
|
+
desc: str | None = None,
|
|
157
137
|
**func_kwargs: Any,
|
|
158
138
|
) -> list[Any]:
|
|
159
139
|
"""
|
|
@@ -172,7 +152,7 @@ def multi_process(
|
|
|
172
152
|
|
|
173
153
|
# default backend selection
|
|
174
154
|
if backend is None:
|
|
175
|
-
backend =
|
|
155
|
+
backend = 'ray' if _HAS_RAY else 'mp'
|
|
176
156
|
|
|
177
157
|
# unify items
|
|
178
158
|
# unify items and coerce to concrete list so we can use len() and
|
|
@@ -192,12 +172,16 @@ def multi_process(
|
|
|
192
172
|
f_wrapped = wrap_dump(func, cache_dir)
|
|
193
173
|
|
|
194
174
|
total = len(items)
|
|
175
|
+
if desc:
|
|
176
|
+
desc = desc.strip() + f'[{backend}]'
|
|
177
|
+
else:
|
|
178
|
+
desc = f'Multi-process [{backend}]'
|
|
195
179
|
with tqdm(
|
|
196
|
-
total=total, desc=
|
|
180
|
+
total=total, desc=desc , disable=not progress
|
|
197
181
|
) as pbar:
|
|
198
182
|
# ---- sequential backend ----
|
|
199
|
-
if backend ==
|
|
200
|
-
pbar.set_postfix_str(
|
|
183
|
+
if backend == 'seq':
|
|
184
|
+
pbar.set_postfix_str('backend=seq')
|
|
201
185
|
results = []
|
|
202
186
|
for x in items:
|
|
203
187
|
results.append(f_wrapped(x, **func_kwargs))
|
|
@@ -205,15 +189,8 @@ def multi_process(
|
|
|
205
189
|
return results
|
|
206
190
|
|
|
207
191
|
# ---- ray backend ----
|
|
208
|
-
if backend ==
|
|
209
|
-
|
|
210
|
-
msg = (
|
|
211
|
-
"Ray backend requested but 'ray' is not installed. "
|
|
212
|
-
"Install extra: pip install 'speedy-utils[ray]' or "
|
|
213
|
-
"poetry install -E ray."
|
|
214
|
-
)
|
|
215
|
-
raise RuntimeError(msg)
|
|
216
|
-
pbar.set_postfix_str("backend=ray")
|
|
192
|
+
if backend == 'ray':
|
|
193
|
+
pbar.set_postfix_str('backend=ray')
|
|
217
194
|
ensure_ray(workers, pbar)
|
|
218
195
|
|
|
219
196
|
@ray.remote
|
|
@@ -229,36 +206,41 @@ def multi_process(
|
|
|
229
206
|
return results
|
|
230
207
|
|
|
231
208
|
# ---- fastcore backend ----
|
|
232
|
-
if backend ==
|
|
209
|
+
if backend == 'mp':
|
|
233
210
|
results = parallel(
|
|
234
211
|
f_wrapped, items, n_workers=workers, progress=progress, threadpool=False
|
|
235
212
|
)
|
|
236
213
|
_track_multiprocessing_processes() # Track multiprocessing workers
|
|
237
214
|
_prune_dead_processes() # Clean up dead processes
|
|
238
215
|
return list(results)
|
|
239
|
-
if backend ==
|
|
216
|
+
if backend == 'threadpool':
|
|
240
217
|
results = parallel(
|
|
241
218
|
f_wrapped, items, n_workers=workers, progress=progress, threadpool=True
|
|
242
219
|
)
|
|
243
220
|
return list(results)
|
|
244
|
-
if backend ==
|
|
221
|
+
if backend == 'safe':
|
|
245
222
|
# Completely safe backend for tests - no multiprocessing, no external progress bars
|
|
246
223
|
import concurrent.futures
|
|
224
|
+
|
|
247
225
|
# Import thread tracking from thread module
|
|
248
226
|
try:
|
|
249
|
-
from .thread import
|
|
250
|
-
|
|
227
|
+
from .thread import _prune_dead_threads, _track_executor_threads
|
|
228
|
+
|
|
229
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
230
|
+
max_workers=workers
|
|
231
|
+
) as executor:
|
|
251
232
|
_track_executor_threads(executor) # Track threads
|
|
252
233
|
results = list(executor.map(f_wrapped, items))
|
|
253
234
|
_prune_dead_threads() # Clean up dead threads
|
|
254
235
|
except ImportError:
|
|
255
236
|
# Fallback if thread module not available
|
|
256
|
-
with concurrent.futures.ThreadPoolExecutor(
|
|
237
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
238
|
+
max_workers=workers
|
|
239
|
+
) as executor:
|
|
257
240
|
results = list(executor.map(f_wrapped, items))
|
|
258
241
|
return results
|
|
259
|
-
|
|
260
|
-
raise ValueError(f"Unsupported backend: {backend!r}")
|
|
261
242
|
|
|
243
|
+
raise ValueError(f'Unsupported backend: {backend!r}')
|
|
262
244
|
|
|
263
245
|
|
|
264
246
|
def cleanup_phantom_workers():
|
|
@@ -270,47 +252,52 @@ def cleanup_phantom_workers():
|
|
|
270
252
|
_prune_dead_processes()
|
|
271
253
|
killed_processes = 0
|
|
272
254
|
with _SPEEDY_PROCESSES_LOCK:
|
|
273
|
-
for process in SPEEDY_RUNNING_PROCESSES[
|
|
255
|
+
for process in SPEEDY_RUNNING_PROCESSES[
|
|
256
|
+
:
|
|
257
|
+
]: # Copy to avoid modification during iteration
|
|
274
258
|
try:
|
|
275
|
-
print(f
|
|
259
|
+
print(f'🔪 Killing tracked process {process.pid} ({process.name()})')
|
|
276
260
|
process.kill()
|
|
277
261
|
killed_processes += 1
|
|
278
262
|
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
|
|
279
|
-
print(f
|
|
263
|
+
print(f'⚠️ Could not kill process {process.pid}: {e}')
|
|
280
264
|
SPEEDY_RUNNING_PROCESSES.clear()
|
|
281
|
-
|
|
265
|
+
|
|
282
266
|
# Also kill any remaining child processes (fallback)
|
|
283
267
|
parent = psutil.Process(os.getpid())
|
|
284
268
|
for child in parent.children(recursive=True):
|
|
285
269
|
try:
|
|
286
|
-
print(f
|
|
270
|
+
print(f'🔪 Killing child process {child.pid} ({child.name()})')
|
|
287
271
|
child.kill()
|
|
288
272
|
except psutil.NoSuchProcess:
|
|
289
273
|
pass
|
|
290
|
-
|
|
274
|
+
|
|
291
275
|
# Try to clean up threads using thread module functions if available
|
|
292
276
|
try:
|
|
293
|
-
from .thread import SPEEDY_RUNNING_THREADS,
|
|
277
|
+
from .thread import SPEEDY_RUNNING_THREADS, _prune_dead_threads, kill_all_thread
|
|
278
|
+
|
|
294
279
|
_prune_dead_threads()
|
|
295
280
|
killed_threads = kill_all_thread()
|
|
296
281
|
if killed_threads > 0:
|
|
297
|
-
print(f
|
|
282
|
+
print(f'🔪 Killed {killed_threads} tracked threads')
|
|
298
283
|
except ImportError:
|
|
299
284
|
# Fallback: just report stray threads
|
|
300
285
|
for t in threading.enumerate():
|
|
301
286
|
if t is threading.current_thread():
|
|
302
287
|
continue
|
|
303
288
|
if not t.daemon:
|
|
304
|
-
print(f
|
|
305
|
-
|
|
306
|
-
print(
|
|
289
|
+
print(f'⚠️ Thread {t.name} is still running (cannot be force-killed).')
|
|
290
|
+
|
|
291
|
+
print(
|
|
292
|
+
f'✅ Cleaned up {killed_processes} tracked processes and child processes (kernel untouched).'
|
|
293
|
+
)
|
|
294
|
+
|
|
307
295
|
|
|
308
296
|
# Usage: run this anytime after cancelling a cell
|
|
309
297
|
|
|
310
298
|
|
|
311
299
|
__all__ = [
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
300
|
+
'SPEEDY_RUNNING_PROCESSES',
|
|
301
|
+
'multi_process',
|
|
302
|
+
'cleanup_phantom_workers',
|
|
315
303
|
]
|
|
316
|
-
|