geneflowdecoder 0.1.0__tar.gz → 0.1.2__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: geneflowdecoder
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Gene Transformer + Conditional Rectified Flow decoder from cell-state z to log1p expression X.
5
5
  Author: GeneFlowDecoder Contributors
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "geneflowdecoder"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Gene Transformer + Conditional Rectified Flow decoder from cell-state z to log1p expression X."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -28,31 +28,121 @@ try:
28
28
  import torch.nn as nn
29
29
  import torch.nn.functional as F
30
30
  _no_grad = torch.no_grad
31
+ _TORCH_AVAILABLE = True
31
32
  except Exception: # pragma: no cover - soft dep for import-only environments
32
- torch = None # type: ignore
33
- nn = object # type: ignore
34
- F = object # type: ignore
35
-
36
- def _no_grad(fn): # identity decorator when torch is absent
37
- return fn
38
-
33
+ import types
34
+
35
+ torch = types.ModuleType("torch")
36
+ nn = types.ModuleType("torch.nn")
37
+ F = types.ModuleType("torch.nn.functional")
38
+ class _NoGrad:
39
+ """无 torch 时充当 ``@torch.no_grad()`` 与 ``with torch.no_grad():`` 的透明桩。"""
40
+
41
+ def __call__(self, fn=None):
42
+ return fn if fn is not None else self
43
+
44
+ def __enter__(self):
45
+ return self
46
+
47
+ def __exit__(self, *args):
48
+ pass
49
+
50
+ _no_grad = _NoGrad()
51
+ _TORCH_AVAILABLE = False
52
+
53
+ class _Device(str):
54
+ """``torch.device(device)`` 的桩:保留字符串语义与 ``.type`` 属性。"""
55
+
56
+ def __new__(cls, dev):
57
+ dev = str(dev)
58
+ if dev.startswith("cuda"):
59
+ dev = "cuda"
60
+ return super().__new__(cls, dev)
61
+
62
+ @property
63
+ def type(self):
64
+ return str(self)
65
+
66
+
67
+ # 让 ``class Foo(nn.Module)`` 与 ``Tensor`` 注解在无 torch 时仍能解析。
68
+ # 运行时真正调用前向/构造模型会立即报清晰的错误,而不是在 import 期崩。
69
+ class _ModuleStub:
70
+ def __init_subclass__(cls, **kwargs):
71
+ pass
72
+
73
+ nn.Module = _ModuleStub # type: ignore[attr-defined]
74
+
75
+ # 补齐 GeneFlowDecoder 类体里会用到的 torch 属性,保持 import 期可解析
76
+ torch.Tensor = object # type: ignore[attr-defined]
77
+ torch.no_grad = _no_grad # 即 _NoGrad() 实例 # type: ignore[attr-defined]
78
+ torch.device = _Device # type: ignore[attr-defined]
79
+ torch.cuda = types.ModuleType("torch.cuda")
80
+ torch.cuda.is_available = lambda: False
81
+ torch.utils = types.ModuleType("torch.utils")
82
+ torch.utils.data = types.ModuleType("torch.utils.data")
83
+ torch.utils.data.TensorDataset = object
84
+ torch.utils.data.DataLoader = object
39
85
  Tensor = object # placeholder for type annotations only
86
+ else:
87
+ try:
88
+ from torch import Tensor as _TorchTensor # noqa: F401
89
+ Tensor = _TorchTensor # type: ignore
90
+ except Exception:
91
+ Tensor = object # placeholder for type annotations only
40
92
 
41
- try:
42
- from torch import Tensor as _TorchTensor # noqa: F401
43
- Tensor = _TorchTensor # type: ignore
44
- except Exception:
45
- pass
93
+ Array = Union[np.ndarray, Tensor] # type: ignore[valid-type]
46
94
 
47
- Array = Union[np.ndarray, torch.Tensor]
48
95
 
96
+ def _as_tensor(x: Array, device: "torch.device") -> "torch.Tensor":
97
+ """把 numpy / CPU / GPU 输入规范化成 torch 张量。
49
98
 
50
- def _as_tensor(x: Array, device: torch.device) -> torch.Tensor:
99
+ 关键:输入**已经**在 GPU 上时,保留其原设备,不再 ``.to(device)``。
100
+ 否则 cuda 输入会被拷回 CPU,DataLoader 的 pin_memory 又会对它报错:
101
+ RuntimeError: cannot pin 'torch.cuda.FloatTensor'
102
+ """
103
+ if not _TORCH_AVAILABLE:
104
+ raise RuntimeError(
105
+ "GeneFlowDecoder requires PyTorch, but it could not be imported. "
106
+ "Install it with: pip install 'torch>=2.1'"
107
+ )
51
108
  if isinstance(x, np.ndarray):
52
- x = torch.from_numpy(x)
109
+ return torch.as_tensor(x, device=device) # CPU numpy -> CPU tensor
110
+ if x.device.type != "cpu":
111
+ return x # 已在 GPU:原样返回
53
112
  return x.to(device, non_blocking=True)
54
113
 
55
114
 
115
+ def _is_cuda_array(x: Array) -> bool:
116
+ """判断输入是否已经落在 GPU 上。
117
+
118
+ numpy 数组一定在 CPU;torch 张量则可能已在 cuda。DataLoader 的
119
+ ``pin_memory`` 只接受 CPU tensor,输入已经是 cuda 时必须降级为 False,
120
+ 否则会在 ``pin_memory()`` 处抛出
121
+ ``RuntimeError: cannot pin 'torch.cuda.FloatTensor'``。
122
+
123
+ 注意:无 torch 时 ``torch.Tensor`` 只是一个占位桩(object),此时 numpy
124
+ 数组不该被当成 Tensor,故先用 ``not isinstance(x, (np.ndarray, ...))``
125
+ 兜底,保证纯 numpy 输入永远返回 False(即开启 pin_memory)。
126
+ """
127
+ if isinstance(x, np.ndarray):
128
+ return False
129
+ try:
130
+ return isinstance(x, torch.Tensor) and x.device.type != "cpu"
131
+ except Exception:
132
+ return False
133
+
134
+
135
+ def _want_pin_memory(X: Array, z: Array) -> bool:
136
+ """输入全在 CPU 时开启 pin_memory 加速;任一在 GPU 则关闭。
137
+
138
+ numpy 输入一律视为 CPU 数据,走加速路径;只有在 torch 可用且输入
139
+ device 为 cuda 时才关闭。
140
+ """
141
+ if isinstance(X, np.ndarray) and isinstance(z, np.ndarray):
142
+ return True
143
+ return not (_is_cuda_array(X) or _is_cuda_array(z))
144
+
145
+
56
146
  # ----------------------------------------------------------------------
57
147
  # 1. Conditional modulation & transformer blocks
58
148
  # ----------------------------------------------------------------------
@@ -244,6 +334,11 @@ class GeneFlowDecoder(nn.Module):
244
334
  device: str = "auto",
245
335
  ):
246
336
  super().__init__()
337
+ if not _TORCH_AVAILABLE:
338
+ raise RuntimeError(
339
+ "GeneFlowDecoder requires PyTorch, but it could not be imported. "
340
+ "Install it with: pip install 'torch>=2.1'"
341
+ )
247
342
  if device == "auto":
248
343
  device = "cuda" if torch.cuda.is_available() else "cpu"
249
344
  self.device = torch.device(device)
@@ -398,13 +493,20 @@ class GeneFlowDecoder(nn.Module):
398
493
  X = _as_tensor(X, self.device)
399
494
  z = self._maybe_detach(_as_tensor(z, self.device), snapshot=True)
400
495
 
496
+ # pin_memory 要求 CPU tensor;若 X/z 已在 GPU(常见于 scVI / mudata
497
+ # 把 .X 或 obsm 留在 cuda 的情况),强制关闭,避免
498
+ # RuntimeError: cannot pin 'torch.cuda.FloatTensor'
499
+ # 注:_as_tensor 对 cuda 输入原样返回,因此这里 X/z 仍在 GPU。
500
+ pin_memory = _want_pin_memory(X, z)
501
+ assert X.device.type == "cpu" or z.device.type == "cpu" or not pin_memory
502
+
401
503
  ds = torch.utils.data.TensorDataset(X, z)
402
504
  dl = torch.utils.data.DataLoader(
403
505
  ds,
404
506
  batch_size=int(batch_size),
405
507
  shuffle=True,
406
508
  num_workers=int(num_workers),
407
- pin_memory=True,
509
+ pin_memory=pin_memory,
408
510
  )
409
511
 
410
512
  has_val = False
@@ -1,2 +1,2 @@
1
1
  # -*- coding: utf-8 -*-
2
- __version__ = "0.1.0"
2
+ __version__ = "0.1.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geneflowdecoder
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Gene Transformer + Conditional Rectified Flow decoder from cell-state z to log1p expression X.
5
5
  Author: GeneFlowDecoder Contributors
6
6
  License: MIT
File without changes