Replacing the Data Loader
The DALI pipeline for StreamPETR is created using the pipeline described in the StreamPETR Pipeline example (including the pipeline itself, the custom processing step, and the custom post-processing function). It also uses the NuScenes loading implementation described in Data Loading for NuScenes.
The goal is to replace the original PyTorch DataLoader (used for the training data loading) with the DALI pipeline. Note that we focus on the training data loading, and do not replace the validation data loading.
This page describes the changes needed to the training script to enable the use of the DALI pipeline.
See the Evaluation for the runtime and CPU usage gains from this integration and instructions for comparing it with the PyTorch data loader.
Note
This demo re-uses the StreamPETR pipeline setup already described in the
StreamPETR Pipeline Setup
example, and assumes that the pipeline_setup module is available in the Python path. That example
primarily describes the design and construction of the DALI pipeline. Please make sure that you are
familiar with it before continuing here. This page focuses on the remaining integration work needed to
use the completed pipeline inside the StreamPETR training implementation.
Main Training Script
See also
The main training script can be found at demos/streampetr/tools/train.py in the repository.
Environment and Paths
Before creating the pipeline, the script configures the Python path to include the DALI example module and project roots needed by the demo.
95# ===== Setting up the Python Path (including DALI pipeline) =====
96
97import sys
98sys.path.append("/streampetr")
99#@NOTE: Make the DALI pipeline example code available.
100sys.path.append("/ACCV-Lab/packages/dali_pipeline_framework/examples")
101
102# ===== End of Setting up the Python Path (including DALI pipeline) =====
DALI Pipeline Setup
The major change is the creation of the DALI pipeline instead of a PyTorch Dataset. Both options are supported
in our modified implementation, and can be enabled by setting the use_dali_pipeline flag to True in
the used configuration file.
388 # ===== Get Dataset or DALI Output Iterator =====
389 #@NOTE: The if-branch is for the DALI pipeline, the else-branch is for the original PyTorch Dataset.
390 if cfg.data_loading_config.use_dali_pipeline:
391
392 #@NOTE
393 # Import the StreamPETR pipeline setup from the DALI pipeline example (and notify user if it is not
394 # found).
395 try:
396 from pipeline_setup import stream_petr_pipeline
397 except ImportError:
398 raise ImportError(
399 "Could not import `stream_petr_pipeline` from `pipeline_setup`. When running with the DALI "
400 "pipeline, please make sure that the `pipeline_setup` module from the "
401 "dali_pipeline_framework example (packages/dali_pipeline_framework/examples) is in the python path, as "
402 "this demo re-uses the StreamPETR pipeline setup showcased there."
403 )
404
405 #@NOTE
406 # Use the same dataset root for DALI training and the PyTorch validation dataset. The NuScenes
407 # version is specified separately.
408 nuscenes_base_dir = cfg.data.train.data_root
409 nuscenes_version = cfg.data_loading_config.nuscenes_version
410
411 num_batches_in_epoch = cfg.num_iters_per_epoch
412
413 #@NOTE
414 # Create the DALI pipeline. Note that the returned object is a
415 # `dali_pipeline_framework.DALIStructuredOutputIterator` object, which can be used as a drop-in replacement for
416 # a PyTorch DataLoader. This means that if DALI is used, one needs to skip the creation of a PyTorch
417 # DataLoader from this object, and use it directly instead. This is done in
418 # `projects/mmdet3d_plugin/core/apis/mmdet_train.py`.
419 dali_iterator = stream_petr_pipeline.setup_dali_pipeline_stream_petr_train(
420 nuscenes_base_dir, nuscenes_version, num_batches_in_epoch, config=cfg.dali_preproc_config,
421 batch_size=cfg.data.samples_per_gpu,
422 **get_dali_pipeline_distributed_args(
423 local_rank, rank, world_size,
424 cfg.data_loading_config.get('replicate_dataset_per_gpu', False),
425 ),
426 )
427
428 #@NOTE
429 # Store as the only element in the `datasets` list. In case DALI is not used, the same variable will
430 # contain the actual PyTorch Dataset (also as a single-element list), see else-branch below.
431 datasets = [dali_iterator]
432
433 #@NOTE
434 # The classes can be obtrained from the Dataset object if DALI is not used. Here, we manually
435 # set the classes instead.
436 classes = [
437 'car', 'truck', 'construction_vehicle', 'bus', 'trailer', 'barrier', 'motorcycle', 'bicycle',
438 'pedestrian', 'traffic_cone'
439 ]
440
441 else:
442
443 #@NOTE: This corresponds to the original implementation, where a PyTorch Dataset is created and used.
444
445 datasets = [build_dataset(cfg.data.train)]
446 if len(cfg.workflow) == 2:
447 val_dataset = copy.deepcopy(cfg.data.val)
448 # in case we use a dataset wrapper
449 if 'dataset' in cfg.data.train:
450 val_dataset.pipeline = cfg.data.train.dataset.pipeline
451 else:
452 val_dataset.pipeline = cfg.data.train.pipeline
453 # set test_mode=False here in deep copied config
454 # which do not affect AP/AR calculation later
455 # refer to https://mmdetection3d.readthedocs.io/en/latest/tutorials/customize_runtime.html#customize-workflow # noqa
456 val_dataset.test_mode = False
457 datasets.append(build_dataset(val_dataset))
458
459 #@NOTE
460 # Extract the classes here, to obtain the `classes` variable, equivalent to the one created if using
461 # DALI. This is done to avoid calling `datasets[0].CLASSES` below, which would not work DALI is used.
462 classes = datasets[0].CLASSES
463 # ===== End of Getting Dataset or DALI Output Iterator =====
Checkpoint Configuration
The setting of the checkpoint meta information is slightly adjusted to use the classes variable instead of
dataset[0].CLASSES (as the latter is not available when DALI is used).
466 # ===== Checkpoint Configuration =====
467 #@NOTE
468 # Note that here, we use the previously obtained `classes` variable. In the original implementation,
469 # `dataset[0].CLASSES` is used instead.
470 if cfg.checkpoint_config is not None:
471 # save mmdet version, config file content and class names in
472 # checkpoints as meta data
473 cfg.checkpoint_config.meta = dict(
474 mmdet_version=mmdet_version,
475 mmseg_version=mmseg_version,
476 mmdet3d_version=mmdet3d_version,
477 config=cfg.pretty_text,
478 CLASSES=classes,
479 PALETTE=datasets[0].PALETTE # for segmentors
480 if hasattr(datasets[0], 'PALETTE') else None)
481 # add an attribute for visualization convenience
482 model.CLASSES = classes
483 # ===== End of Checkpoint Configuration =====
Training Entry Point
The training call is unchanged except that the dataset list may contain a DALI iterator. This case needs to be handled correctly in the training function (see the next section for details).
495 # ===== Train the model =====
496 #@NOTE
497 # Call the training function. Note that datasets is passed to the model. As this now may be a DALI
498 # iterator, one needs to make sure that the training function is able to handle this (by using it directly
499 # as a drop-in replacement for a PyTorch DataLoader, instead of creating a PyTorch DataLoader from it).
500 # This is done in `projects/mmdet3d_plugin/core/apis/mmdet_train.py`.
501 custom_train_model(
502 model,
503 datasets,
504 cfg,
505 distributed=distributed,
506 validate=(not args.no_validate),
507 timestamp=timestamp,
508 meta=meta)
509 # ===== End of Train the model =====
Adjustments to the Training Function
In the training script, we call custom_train_model() to train the model, which in turn calls
custom_train_detector() (with the latter defined in projects/mmdet3d_plugin/core/apis/mmdet_train.py).
The implementation of custom_train_detector() needs to be adjusted to handle the case that the passed
dataset is actually a DALIStructuredOutputIterator object. Please see
the code snippet below for details on how this is done.
84 # ===== Using the DALI Iterator Directly (if used) =====
85 #@NOTE
86 # If the passed dataset is a `DALIStructuredOutputIterator` object, use it directly instead of creating a
87 # PyTorch DataLoader from it.
88 #
89 # Note that the `else` branch is the original implementation. This means that if a PyTorch Dataset is
90 # input, the behavior of the original implementation is preserved.
91 if isinstance(dataset[0], DALIStructuredOutputIterator):
92 data_loaders = dataset
93 else:
94 data_loaders = [
95 build_dataloader(
96 ds,
97 cfg.data.samples_per_gpu,
98 cfg.data.workers_per_gpu,
99 # cfg.gpus will be ignored if distributed
100 len(cfg.gpu_ids),
101 dist=distributed,
102 seed=cfg.seed,
103 shuffler_sampler=cfg.data.shuffler_sampler, # dict(type='DistributedGroupSampler'),
104 nonshuffler_sampler=cfg.data.nonshuffler_sampler, # dict(type='DistributedSampler'),
105 runner_type=cfg.runner,
106 # Use unsharded mini-dataset sampling for the evaluation. This does not represent the real training use-case,
107 # but enables the use of the mini-dataset for performance evaluations.
108 replicate_dataset_per_gpu=cfg.get('data_loading_config', {}).get(
109 'replicate_dataset_per_gpu', False),
110 ) for ds in dataset
111 ]
112 # ===== End of Using the DALI Iterator Directly (if used) =====
Important
Note that from this point on, the DALIStructuredOutputIterator
acts as a drop-in replacement for the original PyTorch DataLoader (in case DALI is used), and no changes
deeper in the training code are needed to switch from the original PyTorch DataLoader to the DALI
pipeline.
Note
Mini-dataset replication. The performance configurations set
data_loading_config.replicate_dataset_per_gpu=True for both loaders because
NuScenes mini has too few sequences for the global batch size. This lets each GPU
sample the full dataset. It is an evaluation setting, not a required step when
integrating DALI into another project; the option defaults to False.
In tools/train.py, get_dali_pipeline_distributed_args() passes rank=0 and
world_size=1 to the pipeline setup, while keeping device_id=local_rank.
DDP continues to use the real distributed ranks.
For PyTorch, core/apis/mmdet_train.py forwards the flag to datasets/builder.py,
which passes rank=0 and num_replicas=1 to the training sequence sampler.
Worker initialization still uses the real rank, and validation retains its normal sampler.
See the evaluation’s replication policy
for the workload and augmentation-seed limitations.