Skip to content

Commit 2e3117e

Browse files
authored
fix: miscalculation of num_steps when using num_epoch and lmdb (deepmodeling#5488)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * More accurate batch-count reporting for mixed and grouped batching modes. * Dataset index now reflects dataset-level batch totals (sampler-driven) instead of reader-only estimates. * Distributed sampler now reports per-rank batch counts using a precomputed global total. * Training step resolution updated to use actual dataloader batch counts. * **Tests** * Added tests validating dataset total/batch index behavior and distributed sampler length caching. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2362236 commit 2e3117e

4 files changed

Lines changed: 104 additions & 11 deletions

File tree

deepmd/dpmodel/utils/lmdb_data.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -751,11 +751,17 @@ def set_noise(self, noise_settings: dict[str, Any]) -> None:
751751
@property
752752
def index(self) -> list[int]:
753753
"""Number of batches per system (single system)."""
754-
return [max(1, self.nframes // self.batch_size)]
754+
return [self.total_batch]
755755

756756
@property
757757
def total_batch(self) -> int:
758-
return self.index[0]
758+
if self.mixed_batch:
759+
return math.ceil(self.nframes / self.batch_size) if self.nframes else 0
760+
total = 0
761+
for nloc, indices in self._nloc_groups.items():
762+
bs = self.get_batch_size_for_nloc(nloc)
763+
total += (len(indices) + bs - 1) // bs
764+
return total
759765

760766
@property
761767
def batch_sizes(self) -> list[int]:
@@ -1269,6 +1275,13 @@ def __init__(
12691275
self._seed = seed if seed is not None else 0
12701276
self._epoch = 0
12711277
self._block_targets = block_targets
1278+
self._total_batches = len(
1279+
SameNlocBatchSampler(
1280+
self._reader,
1281+
shuffle=False,
1282+
block_targets=self._block_targets,
1283+
)
1284+
)
12721285

12731286
def set_epoch(self, epoch: int) -> None:
12741287
"""Set epoch for deterministic cross-rank shuffling.
@@ -1304,11 +1317,11 @@ def _partition_batches(self, all_batches: list[list[int]]) -> list[list[int]]:
13041317

13051318
def __len__(self) -> int:
13061319
"""Number of batches for this rank."""
1307-
total = 0
1308-
for nloc, indices in self._reader.nloc_groups.items():
1309-
bs = self._reader.get_batch_size_for_nloc(nloc)
1310-
total += (len(indices) + bs - 1) // bs
1311-
return math.ceil(total / self._world_size)
1320+
return max(
1321+
0,
1322+
(self._total_batches + self._world_size - 1 - self._rank)
1323+
// self._world_size,
1324+
)
13121325

13131326
@property
13141327
def rank(self) -> int:

deepmd/pt/train/training.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ def get_lr(lr_params: dict[str, Any]) -> BaseLR:
649649
if self.num_epoch <= 0:
650650
raise ValueError("training.num_epoch must be positive.")
651651
if isinstance(training_data, LmdbDataset):
652-
total_numb_batch = training_data.total_batch
652+
total_numb_batch = len(self.training_dataloader)
653653
else:
654654
sampler_weights = to_numpy_array(
655655
self.training_dataloader.sampler.weights
@@ -678,7 +678,7 @@ def get_lr(lr_params: dict[str, Any]) -> BaseLR:
678678
)
679679
for model_key in self.model_keys:
680680
if isinstance(training_data[model_key], LmdbDataset):
681-
per_task_total.append(training_data[model_key].total_batch)
681+
per_task_total.append(len(self.training_dataloader[model_key]))
682682
else:
683683
sampler_weights = to_numpy_array(
684684
self.training_dataloader[model_key].sampler.weights

deepmd/pt/utils/lmdb_dataset.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,14 @@ def set_noise(self, noise_settings: dict[str, Any]) -> None:
311311

312312
@property
313313
def index(self) -> list[int]:
314-
return self._reader.index
314+
"""Number of batches per logical LMDB dataset."""
315+
if not self._block_targets:
316+
return self._reader.index
317+
return [self.total_batch]
315318

316319
@property
317320
def total_batch(self) -> int:
318-
return self._reader.total_batch
321+
return len(self._batch_sampler)
319322

320323
@property
321324
def batch_sizes(self) -> list[int]:

source/tests/pt/test_lmdb_dataloader.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import pytest
1313
import torch
1414

15+
from deepmd.dpmodel.utils import (
16+
lmdb_data,
17+
)
1518
from deepmd.dpmodel.utils.lmdb_data import (
1619
DistributedSameNlocBatchSampler,
1720
LmdbDataReader,
@@ -624,6 +627,80 @@ def test_dataset_auto_prob_iteration(self, auto_prob_lmdb):
624627
count = sum(len(batch) for batch in ds._batch_sampler)
625628
assert count > 300 # expanded
626629

630+
def test_total_batch_matches_auto_prob_sampler(self, auto_prob_lmdb):
631+
ds = LmdbDataset(
632+
auto_prob_lmdb,
633+
type_map=["O", "H"],
634+
batch_size=4,
635+
auto_prob_style="prob_sys_size;0:1:0.5;1:3:0.5",
636+
)
637+
assert ds.total_batch == len(ds._batch_sampler)
638+
assert ds.index == [ds.total_batch]
639+
assert ds.index != ds._reader.index
640+
641+
def test_distributed_len_includes_auto_prob_expansion(self, auto_prob_lmdb):
642+
import math
643+
644+
ds = LmdbDataset(
645+
auto_prob_lmdb,
646+
type_map=["O", "H"],
647+
batch_size=4,
648+
auto_prob_style="prob_sys_size;0:1:0.5;1:3:0.5",
649+
)
650+
global_batches = len(ds._batch_sampler)
651+
dist_sampler_rank0 = DistributedSameNlocBatchSampler(
652+
ds._reader,
653+
rank=0,
654+
world_size=2,
655+
shuffle=False,
656+
block_targets=ds._block_targets,
657+
)
658+
dist_sampler_rank1 = DistributedSameNlocBatchSampler(
659+
ds._reader,
660+
rank=1,
661+
world_size=2,
662+
shuffle=False,
663+
block_targets=ds._block_targets,
664+
)
665+
assert len(dist_sampler_rank0) == math.ceil(global_batches / 2)
666+
assert len(dist_sampler_rank1) == global_batches // 2
667+
assert len(dist_sampler_rank0) == len(list(dist_sampler_rank0))
668+
assert len(dist_sampler_rank1) == len(list(dist_sampler_rank1))
669+
670+
def test_distributed_len_reuses_cached_total(self, auto_prob_lmdb, monkeypatch):
671+
calls = 0
672+
real_sampler = lmdb_data.SameNlocBatchSampler
673+
674+
class CountingSameNlocBatchSampler(real_sampler):
675+
def __init__(self, *args, **kwargs):
676+
nonlocal calls
677+
calls += 1
678+
super().__init__(*args, **kwargs)
679+
680+
monkeypatch.setattr(
681+
lmdb_data,
682+
"SameNlocBatchSampler",
683+
CountingSameNlocBatchSampler,
684+
)
685+
ds = LmdbDataset(
686+
auto_prob_lmdb,
687+
type_map=["O", "H"],
688+
batch_size=4,
689+
auto_prob_style="prob_sys_size;0:1:0.5;1:3:0.5",
690+
)
691+
dist_sampler = DistributedSameNlocBatchSampler(
692+
ds._reader,
693+
rank=1,
694+
world_size=2,
695+
shuffle=False,
696+
block_targets=ds._block_targets,
697+
)
698+
699+
assert calls == 1
700+
expected_len = len(ds._batch_sampler) // 2
701+
assert len(dist_sampler) == expected_len
702+
assert calls == 1
703+
627704

628705
class TestMergeLmdbSystemIds:
629706
"""Test merge_lmdb propagates frame_system_ids."""

0 commit comments

Comments
 (0)