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.

demos/streampetr/tools/train.py
 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.

demos/streampetr/tools/train.py
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).

demos/streampetr/tools/train.py
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).

demos/streampetr/tools/train.py
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.

demos/streampetr/projects/mmdet3d_plugin/core/apis/mmdet_train.py
 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.