beiken1201 commited on
Commit
51dbc66
Β·
verified Β·
1 Parent(s): 3ecef9f

Remove tests directory and refresh README references

Browse files

Remove BioMod/tests/test_training_utils.py as requested. Remove the test command, links and tree entries from both project READMEs while retaining pretraining requirements. Runtime training utilities, task code, model weights, paired configuration and all data files remain unchanged.

BioMod/README.md CHANGED
@@ -60,8 +60,6 @@ BioMod/ # Task code: trai
60
  β”‚ β”œβ”€β”€ README.md
61
  β”‚ β”œβ”€β”€ train.py
62
  β”‚ └── tutorial.ipynb
63
- β”œβ”€β”€ tests/ # CPU tests for shared training utilities
64
- β”‚ └── test_training_utils.py
65
  β”œβ”€β”€ zero_shot_batch/ # Zero-shot integration and evaluation
66
  β”‚ β”œβ”€β”€ tools/ # Optional visualization and evaluation tools
67
  β”‚ β”‚ β”œβ”€β”€ sequencing_depth/ # Low-depth expression generation and evaluation
@@ -113,25 +111,9 @@ jupyter lab tutorial.ipynb
113
  Use `drug_response`, `drug_combination`, `gene_perturbation`, or `zero_shot_batch`
114
  in place of `cell_annotation` to open another task.
115
 
116
- ### Training utility checks
117
 
118
- The pretraining entry point imports `training_utils.py` explicitly. This local
119
- module supplies random seeding, a sequential distributed validation sampler,
120
- cosine learning-rate scheduling with warmup and restarts, scalar metric
121
- reduction, and resumable checkpoint saving. It does not depend on a separate
122
- package named `utils` or on a downstream task's utility module.
123
-
124
- From this source directory, run the CPU utility tests and check entry-point
125
- imports after installing the dependencies:
126
-
127
- ```bash
128
- python -B -m unittest discover -s tests -p test_training_utils.py -v
129
- python -B biomod.py --help
130
- ```
131
-
132
- The utility tests use synthetic tensors and temporary checkpoints; they do not
133
- load the supplied pretrained weights. Passing these checks does not validate a
134
- full training run. The pretraining entry point requires CUDA and an NCCL
135
  process group. Supply `--data_path` explicitly: the pretraining corpus is not
136
  bundled. Configure module inputs with `--biomod_data_dir` or `--biomod_data_path`.
137
  Generated checkpoints default to `tutorial_runs/pretraining/` under the outer
 
60
  β”‚ β”œβ”€β”€ README.md
61
  β”‚ β”œβ”€β”€ train.py
62
  β”‚ └── tutorial.ipynb
 
 
63
  β”œβ”€β”€ zero_shot_batch/ # Zero-shot integration and evaluation
64
  β”‚ β”œβ”€β”€ tools/ # Optional visualization and evaluation tools
65
  β”‚ β”‚ β”œβ”€β”€ sequencing_depth/ # Low-depth expression generation and evaluation
 
111
  Use `drug_response`, `drug_combination`, `gene_perturbation`, or `zero_shot_batch`
112
  in place of `cell_annotation` to open another task.
113
 
114
+ ### Pretraining requirements
115
 
116
+ The pretraining entry point requires CUDA and an NCCL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  process group. Supply `--data_path` explicitly: the pretraining corpus is not
118
  bundled. Configure module inputs with `--biomod_data_dir` or `--biomod_data_path`.
119
  Generated checkpoints default to `tutorial_runs/pretraining/` under the outer
BioMod/tests/test_training_utils.py DELETED
@@ -1,262 +0,0 @@
1
- """CPU-only checks for the utilities used by the pretraining entry point.
2
-
3
- Run from the source directory with:
4
- python -m unittest discover -s tests -p "test_training_utils.py" -v
5
- """
6
-
7
- import copy
8
- import math
9
- import random
10
- import sys
11
- import tempfile
12
- import unittest
13
- from pathlib import Path
14
-
15
- import numpy as np
16
- import torch
17
-
18
- sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
19
-
20
- from training_utils import (
21
- CosineAnnealingWarmupRestarts,
22
- SequentialDistributedSampler,
23
- get_reduced,
24
- save_scaler_ckpt,
25
- seed_all,
26
- )
27
-
28
-
29
- class SeedTests(unittest.TestCase):
30
- def test_seed_reproduces_python_numpy_and_torch_sequences(self):
31
- original_deterministic = torch.backends.cudnn.deterministic
32
- original_benchmark = torch.backends.cudnn.benchmark
33
- try:
34
- seed_all(1234, cuda_deterministic=True)
35
- first = (random.random(), np.random.random(4), torch.rand(4))
36
- seed_all(1234, cuda_deterministic=True)
37
- second = (random.random(), np.random.random(4), torch.rand(4))
38
- self.assertEqual(first[0], second[0])
39
- np.testing.assert_array_equal(first[1], second[1])
40
- torch.testing.assert_close(first[2], second[2], rtol=0, atol=0)
41
- self.assertTrue(torch.backends.cudnn.deterministic)
42
- self.assertFalse(torch.backends.cudnn.benchmark)
43
-
44
- seed_all(4321, cuda_deterministic=False)
45
- self.assertNotEqual(first[0], random.random())
46
- self.assertFalse(torch.backends.cudnn.deterministic)
47
- self.assertTrue(torch.backends.cudnn.benchmark)
48
- finally:
49
- torch.backends.cudnn.deterministic = original_deterministic
50
- torch.backends.cudnn.benchmark = original_benchmark
51
-
52
-
53
- class SequentialSamplerTests(unittest.TestCase):
54
- def test_rank_chunks_recover_input_order_then_last_item_padding(self):
55
- for size, batch_size, world_size in [(11, 2, 3), (2, 4, 3), (12, 2, 3)]:
56
- with self.subTest(size=size, batch_size=batch_size, world_size=world_size):
57
- shards = []
58
- expected_length = math.ceil(size / (batch_size * world_size)) * batch_size
59
- for rank in range(world_size):
60
- sampler = SequentialDistributedSampler(
61
- range(size), batch_size, world_size, rank=rank
62
- )
63
- shard = list(sampler)
64
- self.assertEqual(len(sampler), expected_length)
65
- self.assertEqual(len(shard), expected_length)
66
- self.assertEqual(len(shard) % batch_size, 0)
67
- self.assertEqual(shard, sorted(shard))
68
- shards.extend(shard)
69
- self.assertEqual(shards[:size], list(range(size)))
70
- self.assertEqual(shards[size:], [size - 1] * (len(shards) - size))
71
-
72
- def test_empty_dataset_has_empty_shards(self):
73
- for rank in range(3):
74
- sampler = SequentialDistributedSampler([], 2, 3, rank=rank)
75
- self.assertEqual(len(sampler), 0)
76
- self.assertEqual(list(sampler), [])
77
-
78
- def test_single_process_can_infer_rank_without_process_group(self):
79
- sampler = SequentialDistributedSampler(range(3), 2, 1)
80
- self.assertEqual(list(sampler), [0, 1, 2, 2])
81
-
82
- def test_invalid_batch_world_size_and_rank_are_rejected(self):
83
- for batch_size, world_size, rank in [(0, 1, 0), (-1, 1, 0), (2, 0, 0),
84
- (2, -1, 0), (2, 2, -1), (2, 2, 2)]:
85
- with self.subTest(batch_size=batch_size, world_size=world_size, rank=rank):
86
- with self.assertRaises(ValueError):
87
- SequentialDistributedSampler(range(5), batch_size, world_size, rank=rank)
88
-
89
-
90
- class ReductionTests(unittest.TestCase):
91
- def test_scalar_reduction_without_process_group(self):
92
- self.assertAlmostEqual(get_reduced(2.75, "cpu", 0, 1), 2.75)
93
- self.assertAlmostEqual(get_reduced(3, "cpu", 0, 1), 3.0)
94
-
95
- def test_tensor_input_is_not_modified_or_attached_to_autograd(self):
96
- value = torch.tensor(3.25, requires_grad=True)
97
- original = value.detach().clone()
98
- result = get_reduced(value, "cpu", 0, 1)
99
- self.assertIsInstance(result, float)
100
- self.assertEqual(result, 3.25)
101
- torch.testing.assert_close(value.detach(), original, rtol=0, atol=0)
102
- self.assertTrue(value.requires_grad)
103
- self.assertIsNone(value.grad)
104
-
105
-
106
- class SchedulerTests(unittest.TestCase):
107
- @staticmethod
108
- def make_scheduler(**overrides):
109
- parameter = torch.nn.Parameter(torch.tensor([1.0]))
110
- optimizer = torch.optim.SGD([parameter], lr=0.02, momentum=0.9)
111
- options = dict(first_cycle_steps=6, cycle_mult=2.0, max_lr=1.0,
112
- min_lr=0.0, warmup_steps=2, gamma=0.5)
113
- options.update(overrides)
114
- scheduler = CosineAnnealingWarmupRestarts(optimizer, **options)
115
- return parameter, optimizer, scheduler
116
-
117
- def assert_lr(self, optimizer, scheduler, expected):
118
- for group in optimizer.param_groups:
119
- self.assertAlmostEqual(group["lr"], expected, places=12)
120
- self.assertEqual(scheduler.get_last_lr(), [group["lr"] for group in optimizer.param_groups])
121
-
122
- def test_zero_warmup_starts_at_max_and_decays_before_restart(self):
123
- _, optimizer, scheduler = self.make_scheduler(
124
- first_cycle_steps=4, cycle_mult=1.0, warmup_steps=0
125
- )
126
- expected = [1.0, (1 + math.cos(math.pi / 4)) / 2, 0.5,
127
- (1 + math.cos(3 * math.pi / 4)) / 2, 0.5]
128
- self.assert_lr(optimizer, scheduler, expected[0])
129
- for target in expected[1:]:
130
- optimizer.step()
131
- scheduler.step()
132
- self.assert_lr(optimizer, scheduler, target)
133
-
134
- def test_warmup_and_restart_use_decayed_peak_and_longer_cycle(self):
135
- _, optimizer, scheduler = self.make_scheduler()
136
- expected = [0.0, 0.5, 1.0, (1 + math.cos(math.pi / 4)) / 2,
137
- 0.5, (1 + math.cos(3 * math.pi / 4)) / 2, 0.0, 0.25, 0.5]
138
- self.assert_lr(optimizer, scheduler, expected[0])
139
- for target in expected[1:]:
140
- optimizer.step()
141
- scheduler.step()
142
- self.assert_lr(optimizer, scheduler, target)
143
- scheduler.step(16)
144
- self.assert_lr(optimizer, scheduler, 0.0)
145
- scheduler.step(18)
146
- self.assert_lr(optimizer, scheduler, 0.25)
147
-
148
- def test_explicit_epoch_and_incremental_steps_match_across_cycles(self):
149
- for cycle_mult in (1.0, 1.5, 2.0):
150
- for warmup_steps in (0, 2):
151
- with self.subTest(cycle_mult=cycle_mult, warmup_steps=warmup_steps):
152
- options = dict(first_cycle_steps=5, cycle_mult=cycle_mult,
153
- warmup_steps=warmup_steps)
154
- _, sequential_optimizer, sequential = self.make_scheduler(**options)
155
- _, explicit_optimizer, explicit = self.make_scheduler(**options)
156
- for step in range(41):
157
- if step:
158
- sequential_optimizer.step()
159
- sequential.step()
160
- explicit.step(step)
161
- self.assertAlmostEqual(
162
- sequential_optimizer.param_groups[0]["lr"],
163
- explicit_optimizer.param_groups[0]["lr"], places=12,
164
- msg=f"Schedules differ at step {step}",
165
- )
166
- self.assertEqual(sequential.get_last_lr(), explicit.get_last_lr())
167
-
168
- def test_resume_restores_learning_rates_and_optimizer_trajectory(self):
169
- options = dict(max_lr=0.1, min_lr=0.001, gamma=0.7)
170
- parameter, optimizer, scheduler = self.make_scheduler(**options)
171
-
172
- def update(parameter, optimizer, scheduler):
173
- parameter.grad = torch.ones_like(parameter)
174
- optimizer.step()
175
- optimizer.zero_grad()
176
- scheduler.step()
177
- return optimizer.param_groups[0]["lr"], parameter.detach().clone()
178
-
179
- for _ in range(13):
180
- update(parameter, optimizer, scheduler)
181
- saved_parameter = parameter.detach().clone()
182
- optimizer_state = copy.deepcopy(optimizer.state_dict())
183
- scheduler_state = copy.deepcopy(scheduler.state_dict())
184
- expected = [update(parameter, optimizer, scheduler) for _ in range(12)]
185
-
186
- resumed_parameter, resumed_optimizer, resumed_scheduler = self.make_scheduler(**options)
187
- with torch.no_grad():
188
- resumed_parameter.copy_(saved_parameter)
189
- resumed_optimizer.load_state_dict(optimizer_state)
190
- resumed_scheduler.load_state_dict(scheduler_state)
191
- self.assertEqual(resumed_scheduler.get_last_lr(),
192
- [group["lr"] for group in resumed_optimizer.param_groups])
193
- for expected_lr, expected_parameter in expected:
194
- actual_lr, actual_parameter = update(resumed_parameter, resumed_optimizer, resumed_scheduler)
195
- self.assertAlmostEqual(actual_lr, expected_lr, places=12)
196
- torch.testing.assert_close(actual_parameter, expected_parameter, rtol=0, atol=0)
197
-
198
-
199
- class _CompiledLike(torch.nn.Module):
200
- """Model the wrapper attribute exposed by torch.compile without compiling."""
201
-
202
- def __init__(self, module):
203
- super().__init__()
204
- self._orig_mod = module
205
-
206
-
207
- class _ScalerState:
208
- def state_dict(self):
209
- return {"scale": 1024.0, "_growth_tracker": 7}
210
-
211
-
212
- class CheckpointTests(unittest.TestCase):
213
- def test_checkpoint_schema_paths_and_unwrapped_model_parameters(self):
214
- for use_wrappers in (False, True):
215
- with self.subTest(use_wrappers=use_wrappers), tempfile.TemporaryDirectory() as temp_dir:
216
- model = torch.nn.Linear(2, 1)
217
- wrapped = _CompiledLike(torch.nn.DataParallel(model)) if use_wrappers else model
218
- optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
219
- scheduler = CosineAnnealingWarmupRestarts(
220
- optimizer, first_cycle_steps=4, max_lr=0.1, min_lr=0.001,
221
- warmup_steps=0,
222
- )
223
- optimizer.zero_grad()
224
- model(torch.ones(1, 2)).sum().backward()
225
- optimizer.step()
226
- scheduler.step()
227
- scaler = _ScalerState()
228
- destination = Path(temp_dir) / "nested" / "checkpoints"
229
- saved_path = save_scaler_ckpt(
230
- "epoch3", wrapped, optimizer, scheduler, scaler, 0.125,
231
- "tiny", str(destination),
232
- )
233
- self.assertIsInstance(saved_path, Path)
234
- self.assertEqual(saved_path, destination / "tiny_epoch3.pth")
235
- self.assertTrue(saved_path.is_file())
236
- checkpoint = torch.load(saved_path, map_location="cpu", weights_only=True)
237
- self.assertEqual(set(checkpoint), {
238
- "epoch", "model_state_dict", "optimizer_state_dict",
239
- "scheduler_state_dict", "scaler_state_dict", "losses",
240
- })
241
- self.assertEqual(checkpoint["epoch"], "epoch3")
242
- self.assertEqual(checkpoint["losses"], 0.125)
243
- self.assertEqual(checkpoint["scaler_state_dict"], scaler.state_dict())
244
- self.assertEqual(checkpoint["scheduler_state_dict"], scheduler.state_dict())
245
- self.assertEqual(checkpoint["optimizer_state_dict"]["param_groups"],
246
- optimizer.state_dict()["param_groups"])
247
- self.assertEqual(set(checkpoint["model_state_dict"]), {"weight", "bias"})
248
- for name, expected in model.state_dict().items():
249
- torch.testing.assert_close(checkpoint["model_state_dict"][name], expected, rtol=0, atol=0)
250
- loaded_model = torch.nn.Linear(2, 1)
251
- loaded_model.load_state_dict(checkpoint["model_state_dict"], strict=True)
252
- loaded_optimizer = torch.optim.SGD(loaded_model.parameters(), lr=0.9, momentum=0.9)
253
- loaded_optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
254
- for original_parameter, loaded_parameter in zip(model.parameters(), loaded_model.parameters()):
255
- torch.testing.assert_close(
256
- loaded_optimizer.state[loaded_parameter]["momentum_buffer"],
257
- optimizer.state[original_parameter]["momentum_buffer"], rtol=0, atol=0,
258
- )
259
-
260
-
261
- if __name__ == "__main__":
262
- unittest.main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -203,9 +203,6 @@ The drug-response and drug-combination workflows use validation metrics for
203
  checkpoint selection. Their validation results should not be presented as
204
  performance on an independent final test set.
205
 
206
- For checks of the shared training utilities, see
207
- [Training utility checks](BioMod/README.md#training-utility-checks).
208
-
209
  ## Repository layout
210
 
211
  The tree below includes source files, model dependencies, and the pretrained
@@ -243,8 +240,6 @@ BioMod/ # Project root: s
243
  β”‚ β”‚ β”œβ”€β”€ README.md
244
  β”‚ β”‚ β”œβ”€β”€ train.py
245
  β”‚ β”‚ └── tutorial.ipynb
246
- β”‚ β”œβ”€β”€ tests/ # CPU tests for shared training utilities
247
- β”‚ β”‚ └── test_training_utils.py
248
  β”‚ β”œβ”€β”€ zero_shot_batch/ # Zero-shot integration and evaluation
249
  β”‚ β”‚ β”œβ”€β”€ tools/ # Optional visualization and evaluation tools
250
  β”‚ β”‚ β”‚ β”œβ”€β”€ sequencing_depth/ # Low-depth expression generation and evaluation
 
203
  checkpoint selection. Their validation results should not be presented as
204
  performance on an independent final test set.
205
 
 
 
 
206
  ## Repository layout
207
 
208
  The tree below includes source files, model dependencies, and the pretrained
 
240
  β”‚ β”‚ β”œβ”€β”€ README.md
241
  β”‚ β”‚ β”œβ”€β”€ train.py
242
  β”‚ β”‚ └── tutorial.ipynb
 
 
243
  β”‚ β”œβ”€β”€ zero_shot_batch/ # Zero-shot integration and evaluation
244
  β”‚ β”‚ β”œβ”€β”€ tools/ # Optional visualization and evaluation tools
245
  β”‚ β”‚ β”‚ β”œβ”€β”€ sequencing_depth/ # Low-depth expression generation and evaluation