Using Custom Samplers
SFTTrainer feeds the training loop from a
StatefulDataLoader,
and the sampler decides the order in which examples are visited. The sampling
position is written into each checkpoint (data.pt), so a resume_from run
continues from the exact next example of the in-progress epoch. This page covers
the built-in samplers and how to plug in your own.
Built-in samplers
Select a sampler with the sampler flag:
sampler=random(default) -- reshuffles every epoch usingseed. The in-progress epoch resumes bit-exactly; later epochs are re-shuffled into a valid (but not byte-identical) order, matching the RL trainer. With multipletrain_datasetsthis switches to weighted per-source mixing (see Mixing Multiple Datasets).sampler=sequential-- iterates the dataset in order (StatefulSequentialSampler).sampler=custom-- loads your own stateful sampler fromsampler_class_path.
dataloader_num_workers (and dataloader_persistent_workers) control the
underlying dataloader worker processes; 0 loads in the main process.
Writing a custom sampler
A custom sampler is a torch.utils.data.Sampler subclass that is
checkpointable: beyond __iter__ / __len__, it implements state_dict /
load_state_dict so the trainer can save and restore its position.
SFTTrainer instantiates it as ClassName(tokenized, **sampler_kwargs), where
tokenized is the (concatenated) training dataset:
import torch
class MySampler(torch.utils.data.Sampler[int]):
def __init__(self, data_source, num_samples=None, seed=0):
self.n = len(data_source)
self.num_samples = num_samples or self.n
self.position = 0
# ... build a deterministic index plan from `seed` ...
def __iter__(self):
while self.position < self.num_samples:
idx = self._plan[self.position]
self.position += 1
yield idx
self.position = 0 # reset for the next epoch
def __len__(self):
return self.num_samples
def state_dict(self):
return {"position": self.position}
def load_state_dict(self, state):
self.position = state["position"]Wire it up with:
bash examples/train/sft/run_sft_megatron.sh \
sampler=custom \
sampler_class_path=my_package.my_module.MySampler \
'sampler_kwargs={num_samples: 40, seed: 42}'sampler_class_path is imported inside a Ray task, which does not inherit
the driver's PYTHONPATH. Use a dotted path that resolves from the worker's
sys.path (which includes the repo root when you launch from it), e.g.
examples.train.sft.curriculum_sampler.CurriculumLearningSampler, and run from
the repo root. No __init__.py is needed thanks to namespace packages.
Curriculum learning example
curriculum_sampler.py
is a reference custom sampler (CurriculumLearningSampler) that walks through
difficulty-ordered subsets, progressively unlocking harder data. Order the
dataset easy→hard and give the per-stage lengths. Set
num_samples = num_steps * batch_size so the whole schedule is covered in a
single pass -- this keeps the curriculum state intact across epoch boundaries
and makes resume bit-exact across the entire run.
bash examples/train/sft/run_sft_megatron.sh \
sampler=custom \
sampler_class_path=examples.train.sft.curriculum_sampler.CurriculumLearningSampler \
'sampler_kwargs={lengths: [34, 33, 33], num_samples: 40, seed: 42}'Here lengths must sum to the dataset size (100 for the script's train[:100]
split), and num_samples should be num_steps * batch_size to cover the whole
schedule.
Custom samplers over multiple datasets
A custom sampler still controls the mixture however it likes (e.g. mixing
weights that change as training progresses). With multiple train_datasets, the
trainer injects the tokenized per-dataset lengths into sampler_kwargs
(user-supplied lengths win), so the constructor must accept a lengths
kwarg. Explicit train_dataset_weights are rejected outside sampler=random --
pass your ratios through sampler_kwargs instead:
bash examples/train/sft/run_sft_megatron.sh \
train_datasets="['allenai/tulu-3-sft-mixture','yahma/alpaca-cleaned']" \
train_dataset_splits="['train[:80]','train[:20]']" \
sampler=custom \
sampler_class_path=examples.train.sft.curriculum_sampler.CurriculumLearningSampler \
'sampler_kwargs={num_samples: 40, seed: 42}'Here the curriculum sampler treats the concatenated datasets as its
difficulty-ordered stages via the injected lengths.
API reference
See SFTConfig in the API reference
for the full documentation of the sampler and dataloader fields: sampler,
sampler_class_path, sampler_kwargs, dataloader_num_workers,
dataloader_persistent_workers, and seed.
For native SFT samplers in SkyRL, see SFT Samplers in the API reference