shared-tensor 0.2.13__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.13
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.13"
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.13"
22
+ __version__ = "0.2.15"
@@ -34,6 +34,7 @@ except ImportError: # pragma: no cover - exercised when transformers is not ins
34
34
 
35
35
  TORCH_MODULE: Any | None = _torch
36
36
  TRANSFORMERS_PRETRAINED_MODEL: Any | None = _transformers_pretrained_model
37
+ _TRANSFORMERS_CLASS_CACHE: dict[tuple[str, str], Any] = {}
37
38
 
38
39
 
39
40
  CONTROL_ENCODING = "control_pickle"
@@ -177,9 +178,20 @@ def _serialize_transformers_model(module: Any) -> bytes:
177
178
  if config is None:
178
179
  raise SharedTensorSerializationError("transformers model is missing a config")
179
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))
180
183
  payload = {
181
184
  "module_sources": module_sources,
182
- "module_bytes": _torch_serialize(module),
185
+ "config_bytes": pickle.dumps(config, protocol=pickle.HIGHEST_PROTOCOL),
186
+ "model_module": type(module).__module__,
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)),
183
195
  }
184
196
  return pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)
185
197
 
@@ -307,17 +319,128 @@ def _import_transformers_module(module_name: str, module_sources: dict[str, dict
307
319
  return importlib.import_module(module_name)
308
320
 
309
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:
342
+ raise SharedTensorSerializationError(
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"
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
414
+
415
+
310
416
  def _deserialize_transformers_model(payload: bytes) -> Any:
311
417
  encoded = pickle.loads(payload)
312
418
  module_sources = cast(dict[str, dict[str, str]], encoded.get("module_sources", {}))
313
419
  for module_name in module_sources:
314
420
  _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)
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)
319
442
  eval_fn = getattr(model, "eval", None)
320
- if callable(eval_fn):
443
+ if callable(eval_fn) and not training:
321
444
  eval_fn()
322
445
  _validate_module_device(model)
323
446
  return model
File without changes
File without changes