shared-tensor 0.2.12__tar.gz → 0.2.13__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shared-tensor
3
- Version: 0.2.12
3
+ Version: 0.2.13
4
4
  Summary: Native PyTorch CUDA IPC over Unix Domain Socket for same-host process separation
5
5
  Author-email: Athena Team <contact@world-sim-dev.org>
6
6
  Maintainer-email: Athena Team <contact@world-sim-dev.org>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "shared-tensor"
7
- version = "0.2.12"
7
+ version = "0.2.13"
8
8
  description = "Native PyTorch CUDA IPC over Unix Domain Socket for same-host process separation"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -19,4 +19,4 @@ __all__ = [
19
19
  "TaskStatus",
20
20
  ]
21
21
 
22
- __version__ = "0.2.12"
22
+ __version__ = "0.2.13"
@@ -24,7 +24,7 @@ class AsyncSharedTensorClient:
24
24
  poll_interval: float = 1.0,
25
25
  *,
26
26
  device_index: int | None = None,
27
- timeout: float = 30.0,
27
+ timeout: float = 600.0,
28
28
  ) -> None:
29
29
  self.poll_interval = poll_interval
30
30
  self.timeout = timeout
@@ -3,7 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ import errno
6
7
  import socket
8
+ import time
7
9
  from dataclasses import dataclass
8
10
  from typing import Any, cast
9
11
 
@@ -32,6 +34,9 @@ from shared_tensor.utils import (
32
34
 
33
35
  logger = logging.getLogger(__name__)
34
36
 
37
+ CONNECT_RETRY_TIMEOUT_SECONDS = 60.0
38
+ CONNECT_RETRY_INTERVAL_SECONDS = 0.5
39
+
35
40
 
36
41
  @dataclass(slots=True)
37
42
  class _ClientReleaser(ReleaseHandle):
@@ -53,7 +58,7 @@ class SharedTensorClient:
53
58
  base_path: str = "/tmp/shared-tensor",
54
59
  *,
55
60
  device_index: int | None = None,
56
- timeout: float = 30.0,
61
+ timeout: float = 600.0,
57
62
  verbose_debug: bool = False,
58
63
  ) -> None:
59
64
  self.base_path = base_path
@@ -117,12 +122,35 @@ class SharedTensorClient:
117
122
  method = request.get("method", "<unknown>")
118
123
  if self.verbose_debug:
119
124
  logger.debug("Client sending request", extra={"method": method, "socket_path": self.socket_path})
125
+ started_at = time.monotonic()
120
126
  try:
121
- with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
122
- sock.settimeout(self.timeout)
123
- sock.connect(self.socket_path)
124
- send_message(sock, request)
125
- response = recv_message(sock)
127
+ while True:
128
+ try:
129
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
130
+ sock.settimeout(self.timeout)
131
+ sock.connect(self.socket_path)
132
+ send_message(sock, request)
133
+ response = recv_message(sock)
134
+ break
135
+ except OSError as exc:
136
+ retryable = exc.errno in {
137
+ errno.ENOENT,
138
+ errno.ECONNREFUSED,
139
+ }
140
+ elapsed = time.monotonic() - started_at
141
+ if not retryable or elapsed >= CONNECT_RETRY_TIMEOUT_SECONDS:
142
+ raise
143
+ if self.verbose_debug:
144
+ logger.info(
145
+ "Client retrying request connection",
146
+ extra={
147
+ "method": method,
148
+ "socket_path": self.socket_path,
149
+ "elapsed": round(elapsed, 3),
150
+ "error": str(exc),
151
+ },
152
+ )
153
+ time.sleep(CONNECT_RETRY_INTERVAL_SECONDS)
126
154
  except TimeoutError as exc:
127
155
  if self.verbose_debug:
128
156
  logger.warning("Client request timed out", extra={"method": method, "socket_path": self.socket_path})
@@ -91,7 +91,7 @@ class SharedTensorProvider:
91
91
  *,
92
92
  enabled: bool | None = None,
93
93
  device_index: int | None = None,
94
- timeout: float = 30.0,
94
+ timeout: float = 600.0,
95
95
  execution_mode: str = "auto",
96
96
  server_startup_timeout: float = 30.0,
97
97
  verbose_debug: bool = False,
@@ -9,6 +9,8 @@ import io
9
9
  import multiprocessing.reduction as mp_reduction
10
10
  import os
11
11
  import pickle
12
+ import sys
13
+ import tempfile
12
14
  from pathlib import Path
13
15
  from collections.abc import Callable
14
16
  from typing import Any, cast
@@ -174,16 +176,10 @@ def _serialize_transformers_model(module: Any) -> bytes:
174
176
  config = getattr(module, "config", None)
175
177
  if config is None:
176
178
  raise SharedTensorSerializationError("transformers model is missing a config")
177
- if not hasattr(config, "to_dict"):
178
- raise SharedTensorSerializationError("transformers model config must provide to_dict()")
179
+ module_sources = _collect_transformers_module_sources(type(module).__module__, type(config).__module__)
179
180
  payload = {
180
- "model_module": type(module).__module__,
181
- "model_name": type(module).__name__,
182
- "config_module": type(config).__module__,
183
- "config_name": type(config).__name__,
184
- "config_dict": config.to_dict(),
185
- "training": bool(module.training),
186
- "state_bytes": _torch_serialize(module.state_dict()),
181
+ "module_sources": module_sources,
182
+ "module_bytes": _torch_serialize(module),
187
183
  }
188
184
  return pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)
189
185
 
@@ -240,37 +236,89 @@ def serialize_call_payloads(
240
236
  raise SharedTensorSerializationError(f"Failed to serialize call payloads: {exc}") from exc
241
237
 
242
238
 
243
- def _load_module_state_dict(module: Any, state_dict: dict[str, Any]) -> None:
244
- load_state_dict = module.load_state_dict
245
- if "assign" in inspect.signature(load_state_dict).parameters:
246
- incompatible = load_state_dict(state_dict, strict=False, assign=True)
247
- else: # pragma: no cover - compatibility fallback
248
- incompatible = load_state_dict(state_dict, strict=False)
249
- missing = list(getattr(incompatible, "missing_keys", []))
250
- unexpected = list(getattr(incompatible, "unexpected_keys", []))
251
- if missing or unexpected:
252
- raise SharedTensorSerializationError(
253
- f"Failed to reconstruct module state: missing={missing[:10]} unexpected={unexpected[:10]}"
254
- )
239
+ def _bundle_module_files(module_name: str) -> dict[str, str] | None:
240
+ module = sys.modules.get(module_name)
241
+ if module is None:
242
+ module = importlib.import_module(module_name)
243
+ module_file = getattr(module, "__file__", None)
244
+ if not module_file:
245
+ return None
246
+ module_path = Path(module_file).resolve()
247
+ if module_path.suffix != ".py":
248
+ return None
249
+ transformers_root = None
250
+ if TRANSFORMERS_PRETRAINED_MODEL is not None:
251
+ transformers_file = getattr(sys.modules.get("transformers"), "__file__", None)
252
+ if transformers_file is not None:
253
+ transformers_root = Path(transformers_file).resolve().parent
254
+ if transformers_root is not None:
255
+ try:
256
+ module_path.relative_to(transformers_root)
257
+ if not module_name.startswith("transformers_modules."):
258
+ return None
259
+ except ValueError:
260
+ pass
261
+ files = {module_path.name: module_path.read_text(encoding="utf-8")}
262
+ get_relative_import_files = getattr(importlib.import_module("transformers.dynamic_module_utils"), "get_relative_import_files", None)
263
+ if callable(get_relative_import_files):
264
+ for needed in get_relative_import_files(str(module_path)):
265
+ needed_path = Path(needed).resolve()
266
+ files[needed_path.name] = needed_path.read_text(encoding="utf-8")
267
+ return files
268
+
269
+
270
+ def _collect_transformers_module_sources(*module_names: str) -> dict[str, dict[str, str]]:
271
+ bundles: dict[str, dict[str, str]] = {}
272
+ for module_name in module_names:
273
+ bundle = _bundle_module_files(module_name)
274
+ if bundle:
275
+ bundles[module_name] = bundle
276
+ return bundles
277
+
278
+
279
+ def _stage_transformers_module_sources(module_sources: dict[str, dict[str, str]]) -> None:
280
+ if not module_sources:
281
+ return
282
+ staging_root = Path(tempfile.gettempdir()) / "shared-tensor-transformers-modules"
283
+ staging_root.mkdir(parents=True, exist_ok=True)
284
+ root_str = str(staging_root)
285
+ if root_str not in sys.path:
286
+ sys.path.insert(0, root_str)
287
+ for module_name, files in module_sources.items():
288
+ parts = module_name.split(".")
289
+ package_dir = staging_root.joinpath(*parts[:-1])
290
+ package_dir.mkdir(parents=True, exist_ok=True)
291
+ current = staging_root
292
+ for part in parts[:-1]:
293
+ current = current / part
294
+ init_file = current / "__init__.py"
295
+ if not init_file.exists():
296
+ init_file.write_text("", encoding="utf-8")
297
+ for filename, content in files.items():
298
+ (package_dir / filename).write_text(content, encoding="utf-8")
299
+ importlib.invalidate_caches()
300
+
301
+
302
+ def _import_transformers_module(module_name: str, module_sources: dict[str, dict[str, str]]) -> Any:
303
+ try:
304
+ return importlib.import_module(module_name)
305
+ except ModuleNotFoundError:
306
+ _stage_transformers_module_sources(module_sources)
307
+ return importlib.import_module(module_name)
255
308
 
256
309
 
257
310
  def _deserialize_transformers_model(payload: bytes) -> Any:
258
311
  encoded = pickle.loads(payload)
259
- model_module = importlib.import_module(encoded["model_module"])
260
- model_cls = getattr(model_module, encoded["model_name"])
261
- config_module = importlib.import_module(encoded["config_module"])
262
- config_cls = getattr(config_module, encoded["config_name"])
263
- if hasattr(config_cls, "from_dict"):
264
- config = config_cls.from_dict(encoded["config_dict"])
265
- else: # pragma: no cover - defensive
266
- config = config_cls(**encoded["config_dict"])
267
- state_dict = pickle.loads(encoded["state_bytes"])
268
- model = model_cls(config)
269
- first_tensor = next(iter(state_dict.values()), None)
270
- if TORCH_MODULE is not None and isinstance(first_tensor, TORCH_MODULE.Tensor):
271
- model = model.to(first_tensor.device)
272
- _load_module_state_dict(model, state_dict)
273
- model.train(bool(encoded["training"]))
312
+ module_sources = cast(dict[str, dict[str, str]], encoded.get("module_sources", {}))
313
+ for module_name in module_sources:
314
+ _import_transformers_module(module_name, module_sources)
315
+ module_bytes = encoded.get("module_bytes")
316
+ if not isinstance(module_bytes, (bytes, bytearray)):
317
+ raise SharedTensorSerializationError("transformers module payload is invalid")
318
+ model = pickle.loads(module_bytes)
319
+ eval_fn = getattr(model, "eval", None)
320
+ if callable(eval_fn):
321
+ eval_fn()
274
322
  _validate_module_device(model)
275
323
  return model
276
324
 
File without changes
File without changes
File without changes