use candle_core::{Device, Tensor, Result};
use memmap2::Mmap;
use std::fs::File;
use std::path::Path;
use std::sync::mpsc::{self, SyncSender, Receiver};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
#[allow(dead_code)]
pub trait DataLoader {
fn next_batch(&mut self) -> Result<(Tensor, Tensor)>;
fn dataset_size(&self) -> usize;
}
pub struct LitDataLoader {
data_source: DataSource,
batch_size: usize,
seq_len: usize,
cursor: usize,
device: Device,
shard_start: usize,
shard_end: usize,
}
enum DataSource {
InMemory(Vec<u32>),
MemoryMapped {
_mmap: Mmap, tokens: &'static [u32], len: usize,
},
}
impl LitDataLoader {
pub fn new(tokens: Vec<u32>, batch_size: usize, seq_len: usize, device: Device) -> Self {
let len = tokens.len();
let data_source = DataSource::InMemory(tokens);
Self {
data_source,
batch_size,
seq_len,
cursor: 0,
device,
shard_start: 0,
shard_end: len,
}
}
pub fn from_file<P: AsRef<Path>>(
path: P,
batch_size: usize,
seq_len: usize,
device: Device,
) -> Result<Self> {
let file = File::open(path.as_ref())
.map_err(|e| candle_core::Error::Msg(format!("Failed to open data file: {}", e)))?;
let mmap = unsafe {
Mmap::map(&file)
.map_err(|e| candle_core::Error::Msg(format!("Failed to mmap file: {}", e)))?
};
if mmap.len() % std::mem::size_of::<u32>() != 0 {
return Err(candle_core::Error::Msg(
"File size is not a multiple of u32 size".to_string()
));
}
let len = mmap.len() / std::mem::size_of::<u32>();
let tokens: &'static [u32] = unsafe {
std::slice::from_raw_parts(
mmap.as_ptr() as *const u32,
len
)
};
let data_source = DataSource::MemoryMapped {
_mmap: mmap,
tokens,
len,
};
Ok(Self {
data_source,
batch_size,
seq_len,
cursor: 0,
device,
shard_start: 0,
shard_end: len,
})
}
#[allow(dead_code)]
pub fn with_sharding(mut self, rank: usize, world_size: usize) -> Self {
let total_size = self.dataset_size();
let shard_size = total_size / world_size;
self.shard_start = rank * shard_size;
self.shard_end = if rank == world_size - 1 {
total_size } else {
(rank + 1) * shard_size
};
self.cursor = self.shard_start;
self
}
pub fn dataset_size(&self) -> usize {
match &self.data_source {
DataSource::InMemory(tokens) => tokens.len(),
DataSource::MemoryMapped { len, .. } => *len,
}
}
fn get_tokens(&self, start: usize, count: usize) -> &[u32] {
match &self.data_source {
DataSource::InMemory(tokens) => &tokens[start..start + count],
DataSource::MemoryMapped { tokens, .. } => &tokens[start..start + count],
}
}
pub fn next_batch(&mut self) -> Result<(Tensor, Tensor)> {
let n_tokens_needed = self.batch_size * (self.seq_len + 1);
if self.cursor + n_tokens_needed > self.shard_end {
self.cursor = self.shard_start;
}
let current_cursor = self.cursor;
self.cursor += self.batch_size * self.seq_len;
let slice = self.get_tokens(current_cursor, n_tokens_needed);
let raw_tensor = Tensor::from_slice(slice, (self.batch_size, self.seq_len + 1), &self.device)?;
let inputs = raw_tensor.narrow(1, 0, self.seq_len)?;
let targets = raw_tensor.narrow(1, 1, self.seq_len)?;
Ok((inputs, targets))
}
#[allow(dead_code)]
pub fn shard_size(&self) -> usize {
self.shard_end - self.shard_start
}
}
impl DataLoader for LitDataLoader {
fn next_batch(&mut self) -> Result<(Tensor, Tensor)> {
LitDataLoader::next_batch(self)
}
fn dataset_size(&self) -> usize {
LitDataLoader::dataset_size(self)
}
}
pub fn create_dummy_data(vocab_size: usize, total_tokens: usize) -> Vec<u32> {
let mut data = Vec::with_capacity(total_tokens);
for _ in 0..total_tokens {
data.push((rand::random::<usize>() % vocab_size) as u32);
}
data
}
#[allow(dead_code)] pub fn write_tokens_to_file<P: AsRef<Path>>(tokens: &[u32], path: P) -> std::io::Result<()> {
use std::io::Write;
let mut file = File::create(path)?;
let bytes = unsafe {
std::slice::from_raw_parts(
tokens.as_ptr() as *const u8,
tokens.len() * std::mem::size_of::<u32>()
)
};
file.write_all(bytes)?;
file.sync_all()?;
Ok(())
}
struct PrefetchedBatch {
inputs: Vec<u32>,
targets: Vec<u32>,
batch_size: usize,
seq_len: usize,
}
#[allow(dead_code)]
pub struct PrefetchDataLoader {
receiver: Receiver<PrefetchedBatch>,
prefetch_thread: Option<JoinHandle<()>>,
stop_signal: Arc<AtomicBool>,
device: Device,
batch_size: usize,
seq_len: usize,
dataset_size: usize,
}
impl PrefetchDataLoader {
pub fn new(base_loader: LitDataLoader, prefetch_count: usize) -> Result<Self> {
let device = base_loader.device.clone();
let batch_size = base_loader.batch_size;
let seq_len = base_loader.seq_len;
let dataset_size = base_loader.dataset_size();
let (sender, receiver) = mpsc::sync_channel(prefetch_count);
let stop_signal = Arc::new(AtomicBool::new(false));
let stop_signal_clone = Arc::clone(&stop_signal);
let prefetch_thread = thread::spawn(move || {
Self::prefetch_worker(base_loader, sender, stop_signal_clone);
});
Ok(Self {
receiver,
prefetch_thread: Some(prefetch_thread),
stop_signal,
device,
batch_size,
seq_len,
dataset_size,
})
}
fn prefetch_worker(
mut loader: LitDataLoader,
sender: SyncSender<PrefetchedBatch>,
stop_signal: Arc<AtomicBool>,
) {
loop {
if stop_signal.load(Ordering::Relaxed) {
break;
}
let n_tokens_needed = loader.batch_size * (loader.seq_len + 1);
if loader.cursor + n_tokens_needed > loader.shard_end {
loader.cursor = loader.shard_start;
}
let current_cursor = loader.cursor;
loader.cursor += loader.batch_size * loader.seq_len;
let slice = loader.get_tokens(current_cursor, n_tokens_needed);
let mut inputs = Vec::with_capacity(loader.batch_size * loader.seq_len);
let mut targets = Vec::with_capacity(loader.batch_size * loader.seq_len);
for b in 0..loader.batch_size {
let batch_start = b * (loader.seq_len + 1);
for s in 0..loader.seq_len {
inputs.push(slice[batch_start + s]);
targets.push(slice[batch_start + s + 1]);
}
}
let batch = PrefetchedBatch {
inputs,
targets,
batch_size: loader.batch_size,
seq_len: loader.seq_len,
};
if sender.send(batch).is_err() {
break; }
}
}
pub fn next_batch(&mut self) -> Result<(Tensor, Tensor)> {
let batch = self.receiver.recv()
.map_err(|_| candle_core::Error::Msg("Prefetch thread stopped".to_string()))?;
let inputs = Tensor::from_slice(
&batch.inputs,
(batch.batch_size, batch.seq_len),
&self.device,
)?;
let targets = Tensor::from_slice(
&batch.targets,
(batch.batch_size, batch.seq_len),
&self.device,
)?;
Ok((inputs, targets))
}
pub fn shutdown(&mut self) {
self.stop_signal.store(true, Ordering::Relaxed);
if let Some(handle) = self.prefetch_thread.take() {
let _ = handle.join();
}
}
#[allow(dead_code)]
pub fn batch_size(&self) -> usize {
self.batch_size
}
#[allow(dead_code)]
pub fn seq_len(&self) -> usize {
self.seq_len
}
#[allow(dead_code)]
pub fn dataset_size(&self) -> usize {
self.dataset_size
}
}
impl DataLoader for PrefetchDataLoader {
fn next_batch(&mut self) -> Result<(Tensor, Tensor)> {
PrefetchDataLoader::next_batch(self)
}
fn dataset_size(&self) -> usize {
self.dataset_size
}
}
impl Drop for PrefetchDataLoader {
fn drop(&mut self) {
self.shutdown();
}
}
#[allow(dead_code)]
pub fn create_prefetch_loader<P: AsRef<Path>>(
path: P,
batch_size: usize,
seq_len: usize,
device: Device,
prefetch_count: usize,
) -> Result<PrefetchDataLoader> {
let base_loader = LitDataLoader::from_file(path, batch_size, seq_len, device)?;
PrefetchDataLoader::new(base_loader, prefetch_count)
}