Skip to content

Validation dataloader so that train_size gets used - #39

Merged
jp-dark merged 8 commits into
single-cell-data:mainfrom
maarten-devries:val_dataloader
Sep 19, 2025
Merged

Validation dataloader so that train_size gets used#39
jp-dark merged 8 commits into
single-cell-data:mainfrom
maarten-devries:val_dataloader

Conversation

@maarten-devries

@maarten-devries maarten-devries commented Aug 20, 2025

Copy link
Copy Markdown
Contributor

Currently, SCVIDataModule lacks a val_dataloader.

This means scVI is unable to use the train_size: 0.9 parameter which splits the data into train and validation and allows you to log the validation loss.

Currently, we are only able to log the train loss which is not ideal.

@maarten-devries
maarten-devries marked this pull request as draft August 20, 2025 14:25
@aaronwolen

Copy link
Copy Markdown
Member

@maarten-devries, thank you for the contribution and flagging this issue! We'll review and test whenever you're ready.

@maarten-devries
maarten-devries marked this pull request as ready for review August 20, 2025 16:16
@maarten-devries

Copy link
Copy Markdown
Contributor Author

@aaronwolen ready for review!

@jp-dark
jp-dark requested a review from ktsitsi August 20, 2025 18:17

@ktsitsi ktsitsi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to fix the linter issues?

Comment thread src/tiledbsoma_ml/scvi.py Outdated
Comment on lines +156 to +176

def val_dataloader(self) -> DataLoader | None:
if self.val_query_ids is not None and self.x_locator is not None:
# Filter out query and layer_name from dataset_kwargs since we're using x_locator and query_ids
filtered_kwargs = {k: v for k, v in self.dataset_kwargs.items()
if k not in ('query', 'layer_name')}

# Create dataset with validation query_ids and x_locator
val_dataset = ExperimentDataset(
x_locator=self.x_locator,
query_ids=self.val_query_ids,
obs_column_names=self.batch_column_names, # type: ignore[arg-type]
*self.dataset_args,
**filtered_kwargs, # type: ignore[misc]
)
return experiment_dataloader(
val_dataset,
**self.dataloader_kwargs,
)
else:
return None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the function is duplicate of the train_dataloader would it make sense to abstract the functionality under a common function dataloader e.g. with a selector for train, valquery ids? An enum maybe? In the future I would imagine a similar dataloader functionality for test data would also be relevant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be nice, but keep in mind scVI does expect a function train_dataloader and val_dataloader to exist. Those could indeed call the shared helper function. It wouldn't save many lines of code though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed this now

@maarten-devries

maarten-devries commented Aug 21, 2025

Copy link
Copy Markdown
Contributor Author

@ktsitsi I did test the code above and was happy that it was working. Your suggestions around linters and shared abstractions are good but I can't address them right now.

I ended up just using from scvi.dataloaders import TileDBDataModule instead.

This has the validation dataloader implemented already and makes life easier.

It probably doesn't make sense to support both TileDBDataModule and SCVIDataModule in the long term as it's potentially confusing.

@maarten-devries

maarten-devries commented Aug 22, 2025

Copy link
Copy Markdown
Contributor Author

@ktsitsi

Actually, one thing I'm noticing in favor of SCVIDataModule is that it's faster. So I'd prefer to use it after all!

I'm seeing a significant speedup for one epoch in a quick test (both are using shuffle=True):

GPUs   Memory   Batch Size   Data Module     Time/Epoch
-----  -------  -----------  --------------  -----------
8      80 GB    20,480       TileDBDataModule ~3:30
8      80 GB    20,480       SCVIDataModule  ~2:30

@ktsitsi

ktsitsi commented Sep 2, 2025

Copy link
Copy Markdown
Collaborator

@maarten-devries

Thank you so much for your changes and your contributions. Approving the PR and I am prioritizing an investigation why you noticed this performance difference between the two.

@jp-dark
jp-dark merged commit 3193230 into single-cell-data:main Sep 19, 2025
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants