#5086·deepchem

ScScore.forward() applies dropout during eval() — F.dropout() missing training=self.training

Author: Shivansh-thecoderCreated Aug 9, 2026Updated Sep 9, 2026

Bug

I identified that ScScore.forward() in deepchem/models/torch_models/scscore.py calls torch.nn.functional.dropout() without passing training=self.training, at both dropout call sites:

python
if self.dropout > 0.0:
    x = F.dropout(x, p=self.dropout)

ScScore is a torch.nn.Module. F.dropout() is the functional dropout API, which defaults training=True regardless of the calling module's actual mode. Unlike an nn.Dropout submodule (which automatically reads the parent module's self.training state), a bare F.dropout() call ignores self.training unless it is passed explicitly. As a result, calling model.eval() did not disable these dropout calls, so ScScore (and ScScoreModel) could produce different outputs on identical input during evaluation/inference.

I implemented a fix in my local checkout and verified it with a passing regression test, reported here for maintainer review.

To Reproduce

I first reproduced the underlying PyTorch mechanism with a minimal standalone nn.Module using the same call pattern as ScScore.forward(): with F.dropout(x, p=self.dropout) and model.eval(), five repeated forward passes on identical input gave All identical: False. Changing the call to F.dropout(x, p=self.dropout, training=self.training) and repeating gave All identical: True.

I applied the same fix directly to ScScore.forward(), changing both call sites from F.dropout(x, p=self.dropout) to F.dropout(x, p=self.dropout, training=self.training).

I added a regression test, test_scscore_dropout_respects_eval_mode, to deepchem/models/torch_models/tests/test_scscore.py. It constructs ScScore directly (dropout=0.5) and checks: in eval() mode, two forward passes on identical input are bit-identical (torch.equal); in train() mode with two different seeds, outputs differ.

python
model.eval()
eval_out1 = model(x)
eval_out2 = model(x)
assert torch.equal(eval_out1, eval_out2)

model.train()
torch.manual_seed(1)
train_out1 = model(x)
torch.manual_seed(2)
train_out2 = model(x)
assert not torch.equal(train_out1, train_out2)

I ran:

python -m pytest deepchem/models/torch_models/tests/test_scscore.py -m torch -v

Result: 3 passed in 0.30s. All three tests in the file passed: test_restore_scscore, test_loaded_pretrained_scscore, and the new test_scscore_dropout_respects_eval_mode, confirming the fix works and does not break the existing ScScore save/reload and pretrained-loading tests.

Expected behavior

After calling model.eval() (or via TorchModel.predict(), which calls self.model.eval() internally), dropout should be disabled, so repeated forward passes on identical input should be deterministic. During model.train(), dropout should remain active, so that regularization during training is unaffected by this fix. Both conditions are now verified by the passing regression test.

Environment

- OS: Windows
- Python: 3.11.14
- DeepChem: 2.8.1.dev
- PyTorch: 2.2.1+cpu
- NumPy: 1.26.4
- CPU-only environment

Additional context

What I changed: In deepchem/models/torch_models/scscore.py, I passed training=self.training to both F.dropout() calls in ScScore.forward() — the call after the input layer, and the call inside the hidden-layer loop. No other logic in the file was changed.

Regression test added: In deepchem/models/torch_models/tests/test_scscore.py, I added test_scscore_dropout_respects_eval_mode, which verifies (1) eval() mode produces bit-identical output across repeated forward passes on the same input, and (2) train() mode with different seeds produces different output, confirming dropout is disabled only during evaluation. All three tests in the file pass together.

Other checks I ran: flake8 and yapf --diff against both modified files, using the repository's own setup.cfg/.style.yapf configuration — both passed with zero issues.

Related issue: #3115 ("Dropout layers are not activated when predicting uncertainty in some models") is related but distinct — it concerns MC-dropout uncertainty estimation for GraphConv/DAG models, where dropout is expected to stay active during repeated predict_uncertainty() calls but is turned off by standard eval() behavior. This issue is the opposite case: ScScore has no uncertainty-estimation feature, and dropout should be disabled during ordinary evaluation, which is what my change addresses.

Additional location worth reviewing: I also identified a similar raw F.dropout() call (without training=self.training) in the PyTorch AtomicConv implementation in deepchem/models/torch_models/layers.py. I did not modify AtomicConv and did not run any reproduction or test against it as part of this fix — I'm flagging it only as a location maintainers may want to review separately for the same issue.