use crate::candle::data::loader_util::*;
use candle_core::{Device, Tensor};
use crate::matrix::traits::{CandleDataLoaderOps, ConvertMatOps};
pub struct MinibatchData {
pub input: Tensor,
pub input_null: Option<Tensor>,
pub output: Option<Tensor>,
pub output_null: Option<Tensor>,
}
pub trait DataLoader {
fn minibatch_shuffled(
&self,
batch_idx: usize,
target_device: &Device,
) -> anyhow::Result<MinibatchData>;
fn minibatch_ordered(
&self,
lb: usize,
ub: usize,
target_device: &Device,
) -> anyhow::Result<MinibatchData>;
fn num_data(&self) -> usize;
fn num_minibatch(&self) -> usize;
fn shuffle_minibatch(&mut self, batch_size: usize) -> anyhow::Result<()>;
}
pub struct InMemoryData {
input_data: Vec<Tensor>,
input_null_data: Option<Vec<Tensor>>,
output_data: Option<Vec<Tensor>>,
output_null_data: Option<Vec<Tensor>>,
shuffled_input_data: Option<Vec<Tensor>>,
shuffled_input_null_data: Option<Vec<Tensor>>,
shuffled_output_data: Option<Vec<Tensor>>,
shuffled_output_null_data: Option<Vec<Tensor>>,
device_input: Option<Tensor>,
device_input_null: Option<Tensor>,
device_output: Option<Tensor>,
device_output_null: Option<Tensor>,
cached_minibatches: Vec<MinibatchData>,
minibatches: Minibatches,
}
pub struct InMemoryArgs<'a, D>
where
D: CandleDataLoaderOps,
{
pub input: &'a D,
pub input_null: Option<&'a D>,
pub output: Option<&'a D>,
pub output_null: Option<&'a D>,
}
impl InMemoryData {
pub fn from<D>(args: InMemoryArgs<D>) -> anyhow::Result<Self>
where
D: CandleDataLoaderOps,
{
let input = args.input;
let input_null = args.input_null;
let output = args.output;
let output_null = args.output_null;
let input_data = input.rows_to_tensor_vec();
let input_null_data = input_null.map(|x| x.rows_to_tensor_vec());
let output_data = output.map(|x| x.rows_to_tensor_vec());
let output_null_data = output_null.map(|x| x.rows_to_tensor_vec());
let rows = (0..input_data.len()).collect();
Ok(InMemoryData {
input_data,
input_null_data,
output_data,
output_null_data,
shuffled_input_data: None,
shuffled_input_null_data: None,
shuffled_output_data: None,
shuffled_output_null_data: None,
device_input: None,
device_input_null: None,
device_output: None,
device_output_null: None,
cached_minibatches: vec![],
minibatches: Minibatches {
samples: rows,
chunks: vec![],
},
})
}
pub fn from_device<D>(args: InMemoryArgs<D>, target_device: &Device) -> anyhow::Result<Self>
where
D: CandleDataLoaderOps + ConvertMatOps,
{
let device_input = upload_to_device(args.input, target_device)?;
let n = device_input.dim(0)?;
let device_input_null = args
.input_null
.map(|x| upload_to_device(x, target_device))
.transpose()?;
let device_output = args
.output
.map(|x| upload_to_device(x, target_device))
.transpose()?;
let device_output_null = args
.output_null
.map(|x| upload_to_device(x, target_device))
.transpose()?;
let rows = (0..n).collect();
Ok(InMemoryData {
input_data: vec![],
input_null_data: None,
output_data: None,
output_null_data: None,
shuffled_input_data: None,
shuffled_input_null_data: None,
shuffled_output_data: None,
shuffled_output_null_data: None,
device_input: Some(device_input),
device_input_null,
device_output,
device_output_null,
cached_minibatches: vec![],
minibatches: Minibatches {
samples: rows,
chunks: vec![],
},
})
}
pub fn shuffle_minibatch_on_device(&mut self, batch_size: usize) -> anyhow::Result<()> {
let device_input = self.device_input.as_ref().ok_or_else(|| {
anyhow::anyhow!("call from_device before shuffle_minibatch_on_device")
})?;
let dev = device_input.device();
let n = self.minibatches.samples.len();
if n == 0 {
self.cached_minibatches.clear();
self.minibatches.chunks.clear();
return Ok(());
}
if batch_size == 0 {
return Err(anyhow::anyhow!("batch_size must be > 0"));
}
let nbatch = n.div_ceil(batch_size);
let ntot = nbatch * batch_size;
let idx: Vec<u32> = bootstrap_indices(n, ntot);
let idx_tensor = Tensor::from_vec(idx, ntot, dev)?;
let shuffled_input = device_input.index_select(&idx_tensor, 0)?;
let shuffled_input_null = self
.device_input_null
.as_ref()
.map(|t| t.index_select(&idx_tensor, 0))
.transpose()?;
let shuffled_output = self
.device_output
.as_ref()
.map(|t| t.index_select(&idx_tensor, 0))
.transpose()?;
let shuffled_output_null = self
.device_output_null
.as_ref()
.map(|t| t.index_select(&idx_tensor, 0))
.transpose()?;
let mut chunks: Vec<Vec<usize>> = Vec::with_capacity(nbatch);
let mut cached: Vec<MinibatchData> = Vec::with_capacity(nbatch);
for b in 0..nbatch {
let start = b * batch_size;
let input = shuffled_input.narrow(0, start, batch_size)?;
let input_null = shuffled_input_null
.as_ref()
.map(|t| t.narrow(0, start, batch_size))
.transpose()?;
let output = shuffled_output
.as_ref()
.map(|t| t.narrow(0, start, batch_size))
.transpose()?;
let output_null = shuffled_output_null
.as_ref()
.map(|t| t.narrow(0, start, batch_size))
.transpose()?;
cached.push(MinibatchData {
input,
input_null,
output,
output_null,
});
chunks.push(Vec::new());
}
self.minibatches.chunks = chunks;
self.cached_minibatches = cached;
Ok(())
}
pub fn minibatch_cached(&self, batch_idx: usize) -> &MinibatchData {
&self.cached_minibatches[batch_idx]
}
}
impl DataLoader for InMemoryData {
fn minibatch_ordered(
&self,
lb: usize,
ub: usize,
target_device: &Device,
) -> anyhow::Result<MinibatchData> {
if let Some(input) = take_lb_ub(lb, ub, target_device, Some(&self.input_data))? {
let output = take_lb_ub(lb, ub, target_device, self.output_data.as_ref())?;
let output_null = take_lb_ub(lb, ub, target_device, self.output_null_data.as_ref())?;
let input_null = take_lb_ub(lb, ub, target_device, self.input_null_data.as_ref())?;
Ok(MinibatchData {
input,
input_null,
output,
output_null,
})
} else {
Err(anyhow::anyhow!("no input data"))
}
}
fn minibatch_shuffled(
&self,
batch_idx: usize,
target_device: &Device,
) -> anyhow::Result<MinibatchData> {
if let Some(input) =
take_shuffled(batch_idx, target_device, self.shuffled_input_data.as_ref())?
{
let output =
take_shuffled(batch_idx, target_device, self.shuffled_output_data.as_ref())?;
let output_null = take_shuffled(
batch_idx,
target_device,
self.shuffled_output_null_data.as_ref(),
)?;
let input_null = take_shuffled(
batch_idx,
target_device,
self.shuffled_input_null_data.as_ref(),
)?;
Ok(MinibatchData {
input,
input_null,
output,
output_null,
})
} else {
Err(anyhow::anyhow!("need to shuffle data"))
}
}
fn num_data(&self) -> usize {
self.minibatches.samples.len()
}
fn num_minibatch(&self) -> usize {
self.minibatches.chunks.len()
}
fn shuffle_minibatch(&mut self, batch_size: usize) -> anyhow::Result<()> {
self.minibatches.shuffle_minibatch(batch_size);
self.shuffled_input_data = Some(vec![]);
if self.input_null_data.is_some() {
self.shuffled_input_null_data = Some(vec![]);
}
if self.output_data.is_some() {
self.shuffled_output_data = Some(vec![]);
}
if self.output_null_data.is_some() {
self.shuffled_output_null_data = Some(vec![]);
}
for batch_idx in 0..self.num_minibatch() {
if let Some(samples) = self.minibatches.chunks.get(batch_idx) {
copy_shuffled(
samples,
Some(&self.input_data),
self.shuffled_input_data.as_mut(),
)?;
copy_shuffled(
samples,
self.input_null_data.as_ref(),
self.shuffled_input_null_data.as_mut(),
)?;
copy_shuffled(
samples,
self.output_data.as_ref(),
self.shuffled_output_data.as_mut(),
)?;
copy_shuffled(
samples,
self.output_null_data.as_ref(),
self.shuffled_output_null_data.as_mut(),
)?;
} else {
return Err(anyhow::anyhow!(
"invalid index = {} vs. total # = {}",
batch_idx,
self.num_minibatch()
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::DMatrix;
fn small_mat(n: usize, d: usize) -> DMatrix<f32> {
DMatrix::<f32>::from_fn(n, d, |i, j| (i * d + j) as f32)
}
#[test]
fn from_device_uploads_full_tensor() -> anyhow::Result<()> {
let dev = Device::Cpu;
let input = small_mat(7, 3);
let mut loader = InMemoryData::from_device(
InMemoryArgs::<DMatrix<f32>> {
input: &input,
input_null: None,
output: None,
output_null: None,
},
&dev,
)?;
loader.shuffle_minibatch_on_device(4)?;
assert_eq!(loader.num_minibatch(), 2);
for b in 0..loader.num_minibatch() {
let mb = loader.minibatch_cached(b);
assert_eq!(mb.input.dims(), &[4, 3]);
}
Ok(())
}
}