speedy-utils 1.1.26__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.
Files changed (54) hide show
  1. llm_utils/__init__.py +16 -4
  2. llm_utils/chat_format/__init__.py +10 -10
  3. llm_utils/chat_format/display.py +33 -21
  4. llm_utils/chat_format/transform.py +17 -19
  5. llm_utils/chat_format/utils.py +6 -4
  6. llm_utils/group_messages.py +17 -14
  7. llm_utils/lm/__init__.py +6 -5
  8. llm_utils/lm/async_lm/__init__.py +1 -0
  9. llm_utils/lm/async_lm/_utils.py +10 -9
  10. llm_utils/lm/async_lm/async_llm_task.py +141 -137
  11. llm_utils/lm/async_lm/async_lm.py +48 -42
  12. llm_utils/lm/async_lm/async_lm_base.py +59 -60
  13. llm_utils/lm/async_lm/lm_specific.py +4 -3
  14. llm_utils/lm/base_prompt_builder.py +93 -70
  15. llm_utils/lm/llm.py +126 -108
  16. llm_utils/lm/llm_signature.py +4 -2
  17. llm_utils/lm/lm_base.py +72 -73
  18. llm_utils/lm/mixins.py +102 -62
  19. llm_utils/lm/openai_memoize.py +124 -87
  20. llm_utils/lm/signature.py +105 -92
  21. llm_utils/lm/utils.py +42 -23
  22. llm_utils/scripts/vllm_load_balancer.py +23 -30
  23. llm_utils/scripts/vllm_serve.py +8 -7
  24. llm_utils/vector_cache/__init__.py +9 -3
  25. llm_utils/vector_cache/cli.py +1 -1
  26. llm_utils/vector_cache/core.py +59 -63
  27. llm_utils/vector_cache/types.py +7 -5
  28. llm_utils/vector_cache/utils.py +12 -8
  29. speedy_utils/__imports.py +244 -0
  30. speedy_utils/__init__.py +90 -194
  31. speedy_utils/all.py +125 -227
  32. speedy_utils/common/clock.py +37 -42
  33. speedy_utils/common/function_decorator.py +6 -12
  34. speedy_utils/common/logger.py +43 -52
  35. speedy_utils/common/notebook_utils.py +13 -21
  36. speedy_utils/common/patcher.py +21 -17
  37. speedy_utils/common/report_manager.py +42 -44
  38. speedy_utils/common/utils_cache.py +152 -169
  39. speedy_utils/common/utils_io.py +137 -103
  40. speedy_utils/common/utils_misc.py +15 -21
  41. speedy_utils/common/utils_print.py +22 -28
  42. speedy_utils/multi_worker/process.py +66 -79
  43. speedy_utils/multi_worker/thread.py +78 -155
  44. speedy_utils/scripts/mpython.py +38 -36
  45. speedy_utils/scripts/openapi_client_codegen.py +10 -10
  46. {speedy_utils-1.1.26.dist-info → speedy_utils-1.1.28.dist-info}/METADATA +1 -1
  47. speedy_utils-1.1.28.dist-info/RECORD +57 -0
  48. vision_utils/README.md +202 -0
  49. vision_utils/__init__.py +5 -0
  50. vision_utils/io_utils.py +470 -0
  51. vision_utils/plot.py +345 -0
  52. speedy_utils-1.1.26.dist-info/RECORD +0 -52
  53. {speedy_utils-1.1.26.dist-info → speedy_utils-1.1.28.dist-info}/WHEEL +0 -0
  54. {speedy_utils-1.1.26.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
- import datetime
3
- import os
4
- import pickle
5
- import threading
6
- import time
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[:] = [p for p in SPEEDY_RUNNING_PROCESSES if p.is_running()]
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
- if not _HAS_RAY or not ray.is_initialized():
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 time.time() - child.create_time() < 5: # Created within last 5 seconds
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, "__name__", "func")
81
+ func_name = getattr(func, '__name__', 'func')
99
82
  now = datetime.datetime.now()
100
- stamp = now.strftime("%m%d_%Hh%Mm%Ss")
101
- run_id = f"{func_name}_{stamp}_{uuid.uuid4().hex[:6]}"
102
- path = Path(".cache") / run_id
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"{uuid.uuid4().hex}.pkl"
115
- with open(p, "wb") as fh:
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 RAY_WORKER != workers:
113
+ if not ray.is_initialized() or workers != RAY_WORKER:
131
114
  if ray.is_initialized() and pbar:
132
- pbar.set_postfix_str(f"Restarting Ray {workers} workers")
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"ray.init {workers} took {took:.2f}s")
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["seq", "ray", "mp", "threadpool", "safe"] = "mp",
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 = "ray" if _HAS_RAY else "mp"
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=f"multi_process [{backend}]", disable=not progress
180
+ total=total, desc=desc , disable=not progress
197
181
  ) as pbar:
198
182
  # ---- sequential backend ----
199
- if backend == "seq":
200
- pbar.set_postfix_str("backend=seq")
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 == "ray":
209
- if not _HAS_RAY:
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 == "mp":
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 == "threadpool":
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 == "safe":
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 _track_executor_threads, _prune_dead_threads
250
- with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
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(max_workers=workers) as executor:
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[:]: # Copy to avoid modification during iteration
255
+ for process in SPEEDY_RUNNING_PROCESSES[
256
+ :
257
+ ]: # Copy to avoid modification during iteration
274
258
  try:
275
- print(f"🔪 Killing tracked process {process.pid} ({process.name()})")
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"⚠️ Could not kill process {process.pid}: {e}")
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"🔪 Killing child process {child.pid} ({child.name()})")
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, kill_all_thread, _prune_dead_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"🔪 Killed {killed_threads} tracked threads")
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"⚠️ Thread {t.name} is still running (cannot be force-killed).")
305
-
306
- print(f"✅ Cleaned up {killed_processes} tracked processes and child processes (kernel untouched).")
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
- "SPEEDY_RUNNING_PROCESSES",
313
- "multi_process",
314
- "cleanup_phantom_workers",
300
+ 'SPEEDY_RUNNING_PROCESSES',
301
+ 'multi_process',
302
+ 'cleanup_phantom_workers',
315
303
  ]
316
-