shared-tensor 0.2.12__tar.gz → 0.2.15__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.15
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>
@@ -30,7 +30,6 @@ Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: cloudpickle>=3.0.0
32
32
  Requires-Dist: numpy<2
33
- Requires-Dist: torch>=2.1
34
33
  Provides-Extra: dev
35
34
  Requires-Dist: pytest>=6.0; extra == "dev"
36
35
  Requires-Dist: pytest-cov>=2.0; extra == "dev"
@@ -78,9 +77,10 @@ Not supported:
78
77
 
79
78
  ## Install
80
79
 
81
- Use Python `3.9+` and a CUDA-enabled PyTorch build.
80
+ Use Python `3.9+`. Install a compatible PyTorch build first, then install `shared-tensor`.
82
81
 
83
82
  ```bash
83
+ pip install torch
84
84
  pip install shared-tensor
85
85
  ```
86
86
 
@@ -92,6 +92,8 @@ conda activate shared-tensor-dev
92
92
  pip install -e ".[dev,test]"
93
93
  ```
94
94
 
95
+ If you want to share Hugging Face `transformers` models, install both `torch` and `transformers` in the server and client environments. `shared-tensor` no longer installs `torch` for you.
96
+
95
97
  ## Docs
96
98
 
97
99
  Read the examples first, then the design notes:
@@ -150,6 +152,34 @@ executes endpoint functions reopens CUDA objects via torch IPC
150
152
  manages cache and refcounts releases managed handles explicitly
151
153
  ```
152
154
 
155
+ ## Example: Transformers Models
156
+
157
+ `shared_tensor` also supports CUDA `transformers.PreTrainedModel` instances.
158
+
159
+ See:
160
+ - `examples/transformers_two_proc_demo.py`: minimal same-code two-process demo using `AutoModel`-style loading
161
+ - `examples/transformers_mutation_check.py`: proves client-side in-place parameter mutation is visible on the server
162
+ - `examples/transformers_ipc_benchmark.py`: measures reopen latency and client GPU memory delta
163
+
164
+ Usage:
165
+
166
+ ```bash
167
+ TRANSFORMERS_MODEL_ROOT=/path/to/model-or-hf-cache/models--bert-base-uncased \
168
+ SHARED_TENSOR_ENABLED=1 SHARED_TENSOR_ROLE=server \
169
+ python examples/transformers_two_proc_demo.py
170
+
171
+ TRANSFORMERS_MODEL_ROOT=/path/to/model-or-hf-cache/models--bert-base-uncased \
172
+ SHARED_TENSOR_ENABLED=1 \
173
+ python examples/transformers_two_proc_demo.py
174
+ ```
175
+
176
+ Notes:
177
+ - `TRANSFORMERS_MODEL_ROOT` may point either to a resolved local model directory or to a Hugging Face cache root like `models--...`; the example resolves the newest snapshot automatically
178
+ - `TRANSFORMERS_AUTO_CLASS` defaults to `AutoModel` and can be overridden to another `Auto*` class that exposes `from_pretrained`
179
+ - for custom `transformers` code paths, the library stages the required module source files before reopening the shared module on the client
180
+ - transport remains same-host same-GPU torch CUDA IPC; the client should not allocate a second full model copy just to reconstruct parameters
181
+ - in a fresh client Python process, the first reopen may still look slow because `transformers` import/module resolution is often much slower than the shared-tensor IPC restore path itself; a second reopen in the same process should be much faster
182
+
153
183
  ## Lifetime And Failure Contract
154
184
 
155
185
  `shared_tensor` follows native PyTorch CUDA IPC semantics. It does not virtualize or harden producer lifetime.
@@ -346,6 +376,8 @@ Use call-level invalidation when you want to evict one cache key.
346
376
  Use endpoint-level invalidation when you want to drop all cached variants for the endpoint.
347
377
  Invalidation removes cache lookup entries; it does not guarantee that already-issued client handles remain valid after producer death.
348
378
 
379
+ For cached `transformers` model endpoints, keep `cache=True` unless you explicitly want every request to rebuild and re-share the model.
380
+
349
381
 
350
382
  ## Handle Health Checks
351
383
 
@@ -371,6 +403,22 @@ info = provider.get_runtime_info()
371
403
  # execution_mode, server_socket_path, server_running, server_ready, server_info...
372
404
  ```
373
405
 
406
+ ## Client Retry And Timeout Defaults
407
+
408
+ The client now retries initial connection setup for up to `60s` when the server socket is not ready yet, covering the common server-startup race where the client starts slightly earlier.
409
+
410
+ Default request timeout is now `600s` for:
411
+ - `SharedTensorClient`
412
+ - `AsyncSharedTensorClient`
413
+ - `SharedTensorProvider`
414
+
415
+ You can still override these per instance:
416
+
417
+ ```python
418
+ client = SharedTensorClient(timeout=120.0)
419
+ provider = SharedTensorProvider(timeout=120.0)
420
+ ```
421
+
374
422
  ## Testing
375
423
 
376
424
  Default suite:
@@ -25,9 +25,10 @@ Not supported:
25
25
 
26
26
  ## Install
27
27
 
28
- Use Python `3.9+` and a CUDA-enabled PyTorch build.
28
+ Use Python `3.9+`. Install a compatible PyTorch build first, then install `shared-tensor`.
29
29
 
30
30
  ```bash
31
+ pip install torch
31
32
  pip install shared-tensor
32
33
  ```
33
34
 
@@ -39,6 +40,8 @@ conda activate shared-tensor-dev
39
40
  pip install -e ".[dev,test]"
40
41
  ```
41
42
 
43
+ If you want to share Hugging Face `transformers` models, install both `torch` and `transformers` in the server and client environments. `shared-tensor` no longer installs `torch` for you.
44
+
42
45
  ## Docs
43
46
 
44
47
  Read the examples first, then the design notes:
@@ -97,6 +100,34 @@ executes endpoint functions reopens CUDA objects via torch IPC
97
100
  manages cache and refcounts releases managed handles explicitly
98
101
  ```
99
102
 
103
+ ## Example: Transformers Models
104
+
105
+ `shared_tensor` also supports CUDA `transformers.PreTrainedModel` instances.
106
+
107
+ See:
108
+ - `examples/transformers_two_proc_demo.py`: minimal same-code two-process demo using `AutoModel`-style loading
109
+ - `examples/transformers_mutation_check.py`: proves client-side in-place parameter mutation is visible on the server
110
+ - `examples/transformers_ipc_benchmark.py`: measures reopen latency and client GPU memory delta
111
+
112
+ Usage:
113
+
114
+ ```bash
115
+ TRANSFORMERS_MODEL_ROOT=/path/to/model-or-hf-cache/models--bert-base-uncased \
116
+ SHARED_TENSOR_ENABLED=1 SHARED_TENSOR_ROLE=server \
117
+ python examples/transformers_two_proc_demo.py
118
+
119
+ TRANSFORMERS_MODEL_ROOT=/path/to/model-or-hf-cache/models--bert-base-uncased \
120
+ SHARED_TENSOR_ENABLED=1 \
121
+ python examples/transformers_two_proc_demo.py
122
+ ```
123
+
124
+ Notes:
125
+ - `TRANSFORMERS_MODEL_ROOT` may point either to a resolved local model directory or to a Hugging Face cache root like `models--...`; the example resolves the newest snapshot automatically
126
+ - `TRANSFORMERS_AUTO_CLASS` defaults to `AutoModel` and can be overridden to another `Auto*` class that exposes `from_pretrained`
127
+ - for custom `transformers` code paths, the library stages the required module source files before reopening the shared module on the client
128
+ - transport remains same-host same-GPU torch CUDA IPC; the client should not allocate a second full model copy just to reconstruct parameters
129
+ - in a fresh client Python process, the first reopen may still look slow because `transformers` import/module resolution is often much slower than the shared-tensor IPC restore path itself; a second reopen in the same process should be much faster
130
+
100
131
  ## Lifetime And Failure Contract
101
132
 
102
133
  `shared_tensor` follows native PyTorch CUDA IPC semantics. It does not virtualize or harden producer lifetime.
@@ -293,6 +324,8 @@ Use call-level invalidation when you want to evict one cache key.
293
324
  Use endpoint-level invalidation when you want to drop all cached variants for the endpoint.
294
325
  Invalidation removes cache lookup entries; it does not guarantee that already-issued client handles remain valid after producer death.
295
326
 
327
+ For cached `transformers` model endpoints, keep `cache=True` unless you explicitly want every request to rebuild and re-share the model.
328
+
296
329
 
297
330
  ## Handle Health Checks
298
331
 
@@ -318,6 +351,22 @@ info = provider.get_runtime_info()
318
351
  # execution_mode, server_socket_path, server_running, server_ready, server_info...
319
352
  ```
320
353
 
354
+ ## Client Retry And Timeout Defaults
355
+
356
+ The client now retries initial connection setup for up to `60s` when the server socket is not ready yet, covering the common server-startup race where the client starts slightly earlier.
357
+
358
+ Default request timeout is now `600s` for:
359
+ - `SharedTensorClient`
360
+ - `AsyncSharedTensorClient`
361
+ - `SharedTensorProvider`
362
+
363
+ You can still override these per instance:
364
+
365
+ ```python
366
+ client = SharedTensorClient(timeout=120.0)
367
+ provider = SharedTensorProvider(timeout=120.0)
368
+ ```
369
+
321
370
  ## Testing
322
371
 
323
372
  Default suite:
@@ -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.15"
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"
@@ -47,7 +47,6 @@ requires-python = ">=3.9"
47
47
  dependencies = [
48
48
  "cloudpickle>=3.0.0",
49
49
  "numpy<2",
50
- "torch>=2.1",
51
50
  ]
52
51
 
53
52
  [project.optional-dependencies]
@@ -19,4 +19,4 @@ __all__ = [
19
19
  "TaskStatus",
20
20
  ]
21
21
 
22
- __version__ = "0.2.12"
22
+ __version__ = "0.2.15"
@@ -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
@@ -32,6 +34,7 @@ except ImportError: # pragma: no cover - exercised when transformers is not ins
32
34
 
33
35
  TORCH_MODULE: Any | None = _torch
34
36
  TRANSFORMERS_PRETRAINED_MODEL: Any | None = _transformers_pretrained_model
37
+ _TRANSFORMERS_CLASS_CACHE: dict[tuple[str, str], Any] = {}
35
38
 
36
39
 
37
40
  CONTROL_ENCODING = "control_pickle"
@@ -174,16 +177,21 @@ def _serialize_transformers_model(module: Any) -> bytes:
174
177
  config = getattr(module, "config", None)
175
178
  if config is None:
176
179
  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()")
180
+ module_sources = _collect_transformers_module_sources(type(module).__module__, type(config).__module__)
181
+ parameters = dict(module.named_parameters(remove_duplicate=False))
182
+ buffers = dict(module.named_buffers(remove_duplicate=False))
179
183
  payload = {
184
+ "module_sources": module_sources,
185
+ "config_bytes": pickle.dumps(config, protocol=pickle.HIGHEST_PROTOCOL),
180
186
  "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()),
187
+ "model_qualname": type(module).__qualname__,
188
+ "state_bytes": _torch_serialize(
189
+ {
190
+ "parameters": parameters,
191
+ "buffers": buffers,
192
+ }
193
+ ),
194
+ "training": bool(getattr(module, "training", False)),
187
195
  }
188
196
  return pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)
189
197
 
@@ -240,37 +248,200 @@ def serialize_call_payloads(
240
248
  raise SharedTensorSerializationError(f"Failed to serialize call payloads: {exc}") from exc
241
249
 
242
250
 
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:
251
+ def _bundle_module_files(module_name: str) -> dict[str, str] | None:
252
+ module = sys.modules.get(module_name)
253
+ if module is None:
254
+ module = importlib.import_module(module_name)
255
+ module_file = getattr(module, "__file__", None)
256
+ if not module_file:
257
+ return None
258
+ module_path = Path(module_file).resolve()
259
+ if module_path.suffix != ".py":
260
+ return None
261
+ transformers_root = None
262
+ if TRANSFORMERS_PRETRAINED_MODEL is not None:
263
+ transformers_file = getattr(sys.modules.get("transformers"), "__file__", None)
264
+ if transformers_file is not None:
265
+ transformers_root = Path(transformers_file).resolve().parent
266
+ if transformers_root is not None:
267
+ try:
268
+ module_path.relative_to(transformers_root)
269
+ if not module_name.startswith("transformers_modules."):
270
+ return None
271
+ except ValueError:
272
+ pass
273
+ files = {module_path.name: module_path.read_text(encoding="utf-8")}
274
+ get_relative_import_files = getattr(importlib.import_module("transformers.dynamic_module_utils"), "get_relative_import_files", None)
275
+ if callable(get_relative_import_files):
276
+ for needed in get_relative_import_files(str(module_path)):
277
+ needed_path = Path(needed).resolve()
278
+ files[needed_path.name] = needed_path.read_text(encoding="utf-8")
279
+ return files
280
+
281
+
282
+ def _collect_transformers_module_sources(*module_names: str) -> dict[str, dict[str, str]]:
283
+ bundles: dict[str, dict[str, str]] = {}
284
+ for module_name in module_names:
285
+ bundle = _bundle_module_files(module_name)
286
+ if bundle:
287
+ bundles[module_name] = bundle
288
+ return bundles
289
+
290
+
291
+ def _stage_transformers_module_sources(module_sources: dict[str, dict[str, str]]) -> None:
292
+ if not module_sources:
293
+ return
294
+ staging_root = Path(tempfile.gettempdir()) / "shared-tensor-transformers-modules"
295
+ staging_root.mkdir(parents=True, exist_ok=True)
296
+ root_str = str(staging_root)
297
+ if root_str not in sys.path:
298
+ sys.path.insert(0, root_str)
299
+ for module_name, files in module_sources.items():
300
+ parts = module_name.split(".")
301
+ package_dir = staging_root.joinpath(*parts[:-1])
302
+ package_dir.mkdir(parents=True, exist_ok=True)
303
+ current = staging_root
304
+ for part in parts[:-1]:
305
+ current = current / part
306
+ init_file = current / "__init__.py"
307
+ if not init_file.exists():
308
+ init_file.write_text("", encoding="utf-8")
309
+ for filename, content in files.items():
310
+ (package_dir / filename).write_text(content, encoding="utf-8")
311
+ importlib.invalidate_caches()
312
+
313
+
314
+ def _import_transformers_module(module_name: str, module_sources: dict[str, dict[str, str]]) -> Any:
315
+ try:
316
+ return importlib.import_module(module_name)
317
+ except ModuleNotFoundError:
318
+ _stage_transformers_module_sources(module_sources)
319
+ return importlib.import_module(module_name)
320
+
321
+
322
+ def _resolve_module_qualname(module_name: str, qualname: str) -> Any:
323
+ cache_key = (module_name, qualname)
324
+ cached = _TRANSFORMERS_CLASS_CACHE.get(cache_key)
325
+ if cached is not None:
326
+ return cached
327
+ module = importlib.import_module(module_name)
328
+ current: Any = module
329
+ for part in qualname.split("."):
330
+ current = getattr(current, part)
331
+ _TRANSFORMERS_CLASS_CACHE[cache_key] = current
332
+ return current
333
+
334
+
335
+ def _build_transformers_meta_shell(model_cls: Any, config: Any) -> Any:
336
+ if TORCH_MODULE is None:
337
+ raise SharedTensorSerializationError("PyTorch is required for transformers deserialization")
338
+ try:
339
+ with TORCH_MODULE.device("meta"):
340
+ model = model_cls(config)
341
+ except Exception as exc:
252
342
  raise SharedTensorSerializationError(
253
- f"Failed to reconstruct module state: missing={missing[:10]} unexpected={unexpected[:10]}"
343
+ f"Failed to construct transformers model shell on meta device: {exc}"
344
+ ) from exc
345
+ return model
346
+
347
+
348
+ def _resolve_parent_module(root: Any, parent_name: str) -> Any:
349
+ if not parent_name:
350
+ return root
351
+ get_submodule = getattr(root, "get_submodule", None)
352
+ if callable(get_submodule):
353
+ return get_submodule(parent_name)
354
+ current = root
355
+ for part in parent_name.split("."):
356
+ current = getattr(current, part)
357
+ return current
358
+
359
+
360
+ def _rebind_named_parameter(module: Any, name: str, value: Any) -> None:
361
+ parent_name, _, local_name = name.rpartition(".")
362
+ parent = _resolve_parent_module(module, parent_name)
363
+ parent._parameters[local_name] = value
364
+
365
+
366
+ def _rebind_named_buffer(module: Any, name: str, value: Any) -> None:
367
+ parent_name, _, local_name = name.rpartition(".")
368
+ parent = _resolve_parent_module(module, parent_name)
369
+ parent._buffers[local_name] = value
370
+
371
+
372
+ def _validate_transformers_state_layout(module: Any, parameters: dict[str, Any], buffers: dict[str, Any]) -> None:
373
+ expected_parameters = dict(module.named_parameters(remove_duplicate=False))
374
+ expected_buffers = dict(module.named_buffers(remove_duplicate=False))
375
+ if set(expected_parameters) != set(parameters):
376
+ raise SharedTensorSerializationError(
377
+ "Transformers parameter layout mismatch between serialized model and client shell"
378
+ )
379
+ if set(expected_buffers) != set(buffers):
380
+ raise SharedTensorSerializationError(
381
+ "Transformers buffer layout mismatch between serialized model and client shell"
254
382
  )
383
+ for name, expected in expected_parameters.items():
384
+ actual = parameters[name]
385
+ if not isinstance(actual, TORCH_MODULE.nn.Parameter):
386
+ raise SharedTensorSerializationError(
387
+ f"Transformers parameter '{name}' payload is not a torch.nn.Parameter"
388
+ )
389
+ if actual.shape != expected.shape or actual.dtype != expected.dtype:
390
+ raise SharedTensorSerializationError(
391
+ f"Transformers parameter '{name}' shape or dtype does not match the client shell"
392
+ )
393
+ for name, expected in expected_buffers.items():
394
+ actual = buffers[name]
395
+ if not isinstance(actual, TORCH_MODULE.Tensor):
396
+ raise SharedTensorSerializationError(
397
+ f"Transformers buffer '{name}' payload is not a torch.Tensor"
398
+ )
399
+ if actual.shape != expected.shape or actual.dtype != expected.dtype:
400
+ raise SharedTensorSerializationError(
401
+ f"Transformers buffer '{name}' shape or dtype does not match the client shell"
402
+ )
403
+
404
+
405
+ def _restore_transformers_state(module: Any, state: dict[str, Any]) -> Any:
406
+ parameters = cast(dict[str, Any], state.get("parameters", {}))
407
+ buffers = cast(dict[str, Any], state.get("buffers", {}))
408
+ _validate_transformers_state_layout(module, parameters, buffers)
409
+ for name, value in parameters.items():
410
+ _rebind_named_parameter(module, name, value)
411
+ for name, value in buffers.items():
412
+ _rebind_named_buffer(module, name, value)
413
+ return module
255
414
 
256
415
 
257
416
  def _deserialize_transformers_model(payload: bytes) -> Any:
258
417
  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"]))
418
+ module_sources = cast(dict[str, dict[str, str]], encoded.get("module_sources", {}))
419
+ for module_name in module_sources:
420
+ _import_transformers_module(module_name, module_sources)
421
+ config_bytes = encoded.get("config_bytes")
422
+ state_bytes = encoded.get("state_bytes")
423
+ model_module = encoded.get("model_module")
424
+ model_qualname = encoded.get("model_qualname")
425
+ if not isinstance(config_bytes, (bytes, bytearray)):
426
+ raise SharedTensorSerializationError("transformers config payload is invalid")
427
+ if not isinstance(state_bytes, (bytes, bytearray)):
428
+ raise SharedTensorSerializationError("transformers state payload is invalid")
429
+ if not isinstance(model_module, str) or not model_module:
430
+ raise SharedTensorSerializationError("transformers model module is invalid")
431
+ if not isinstance(model_qualname, str) or not model_qualname:
432
+ raise SharedTensorSerializationError("transformers model qualname is invalid")
433
+ config = pickle.loads(config_bytes)
434
+ model_cls = _resolve_module_qualname(model_module, model_qualname)
435
+ model = _build_transformers_meta_shell(model_cls, config)
436
+ state = pickle.loads(state_bytes)
437
+ model = _restore_transformers_state(model, cast(dict[str, Any], state))
438
+ train_fn = getattr(model, "train", None)
439
+ training = bool(encoded.get("training", False))
440
+ if callable(train_fn):
441
+ train_fn(training)
442
+ eval_fn = getattr(model, "eval", None)
443
+ if callable(eval_fn) and not training:
444
+ eval_fn()
274
445
  _validate_module_device(model)
275
446
  return model
276
447
 
File without changes
File without changes