extern crate nncombinator;
extern crate rand;
use nncombinator::arr::{Arr, Arr2, IntoConverter, SerializedVec};
use nncombinator::cuda::{CudaTensor1dPtr, CudaTensor1dPtrView, CudaTensor2dPtr, CudaVec, CudaVecView, ReadMemory, WriteMemory, Kernel, AsCudaMutPtr, AsCudaView};
use nncombinator::cuda::allocator::{DeviceAlloc, MemoryPoolAllocator};
use nncombinator::cuda::kernel::device::{ForwardLinearBatch, ForwardLinearBatchArgs, BackwardLinearBatch, BackwardLinearBatchArgs, LinearGradientBatch, LinearGradientBatchArgs, ReduceLinearBatchArgs, ReduceLinearBatch};
use nncombinator::device::{DeviceCpu, DeviceReduce};
use rand::Rng;
use nncombinator::device::linear::DeviceLinear;
use crate::common::SHARED_MEMORY_POOL;
use crate::common::gen_inputs;
use crate::common::approx_eq_slice;
use crate::common::upload_inputs_to_device;
use std::convert::TryFrom;
use nncombinator::layer::BatchSize;
const NI: usize = 500;
const NO: usize = 600;
const BATCH: usize = 400;
type A = MemoryPoolAllocator<DeviceAlloc>;
#[test]
fn test_kernel_forward_linear_batch_matches_cpu() {
let device = DeviceCpu::<f32>::new().unwrap();
let (bias,units,inputs) = gen_inputs();
let cpu_out = device.batch_forward_linear(&bias,&units,&inputs).unwrap();
let alloc: &A = &SHARED_MEMORY_POOL;
let (d_bias,d_units,d_inputs) = upload_inputs_to_device::<A>(alloc,&bias,&units,&inputs);
let d_output = CudaVec::<f32,CudaTensor1dPtr<f32,A,NO>,A>::new(BATCH,alloc).unwrap();
let d_inputs = &d_inputs;
let conv = d_inputs.as_cuda_view().into_converter();
let input_view = CudaVecView::<f32,CudaTensor1dPtrView<f32,NI>>::try_from(conv).unwrap();
let mut args = ForwardLinearBatchArgs::<'_,f32,A,NI,NO>::new(&input_view,&d_units,&d_bias,d_output,BATCH);
let mut kernel = ForwardLinearBatch::<'_,f32,A,NI,NO>::new();
kernel.launch(&mut args).unwrap();
let gpu_out = args.output.read_to_vec_with_size(BATCH * NO).unwrap();
let mut cpu_flat: Vec<f32> = Vec::with_capacity(BATCH * NO);
for b in cpu_out.iter() {
for &v in b.iter() {
cpu_flat.push(v);
}
}
approx_eq_slice(&gpu_out,&cpu_flat,15e-3);
}
#[test]
fn test_kernel_backward_linear_batch_matches_cpu() {
let device = DeviceCpu::<f32>::new().unwrap();
let mut rng = rand::thread_rng();
let mut units = Arr2::<f32,NI,NO>::new();
for i in 0..NI { for j in 0..NO { units[(i,j)] = rng.gen::<f32>(); } }
let mut loss_host: Vec<Arr<f32,NO>> = Vec::with_capacity(BATCH);
for _ in 0..BATCH {
let mut v = Arr::<f32,NO>::new();
for x in v.iter_mut() { *x = rng.gen::<f32>(); }
loss_host.push(v);
}
let loss = SerializedVec::<f32,Arr<f32,NO>>::from(loss_host);
let cpu_out = device.batch_backward_linear(&units,&loss).unwrap();
let alloc: &A = &SHARED_MEMORY_POOL;
let mut flat_units: Vec<f32> = Vec::with_capacity(NI * NO);
for i in 0..NI { for j in 0..NO { flat_units.push(units[(i,j)]); } }
let mut d_units = CudaTensor2dPtr::<f32,A,NI,NO>::new(alloc).unwrap();
d_units.memcpy(flat_units.as_ptr(),flat_units.len()).unwrap();
let mut flat_loss: Vec<f32> = Vec::with_capacity(BATCH * NO);
for b in loss.iter() {
for &v in b.iter() { flat_loss.push(v); }
}
let mut d_loss = CudaVec::<f32,CudaTensor1dPtr<f32,A,NO>,A>::new(BATCH,alloc).unwrap();
{ let mut p = d_loss.as_cuda_mut_ptr(); p.memcpy(flat_loss.as_ptr(),flat_loss.len()).unwrap(); }
let d_output = CudaVec::<f32,CudaTensor1dPtr<f32,A,NI>,A>::new(BATCH,alloc).unwrap();
let loss_view = CudaVecView::<f32,CudaTensor1dPtrView<f32,NO>>::try_from(&d_loss).unwrap();
let mut args = BackwardLinearBatchArgs::<'_,f32,A,NI,NO>::new(&loss_view,&d_units,d_output,BATCH);
let mut kernel = BackwardLinearBatch::<'_,f32,A,NI,NO>::new();
kernel.launch(&mut args).unwrap();
let gpu_out = args.output.read_to_vec_with_size(BATCH * NI).unwrap();
let mut cpu_flat: Vec<f32> = Vec::with_capacity(BATCH * NI);
for b in cpu_out.iter() {
for &v in b.iter() {
cpu_flat.push(v);
}
}
approx_eq_slice(&gpu_out,&cpu_flat,15e-3);
}
#[test]
fn test_kernel_linear_gradient_batch_matches_cpu() {
let device = DeviceCpu::<f32>::new().unwrap();
let mut rng = rand::thread_rng();
let mut inputs_host: Vec<Arr<f32,NI>> = Vec::with_capacity(BATCH);
for _ in 0..BATCH {
let mut v = Arr::<f32,NI>::new();
for x in v.iter_mut() { *x = rng.gen::<f32>(); }
inputs_host.push(v);
}
let inputs = SerializedVec::<f32,Arr<f32,NI>>::from(inputs_host);
let mut loss_host: Vec<Arr<f32,NO>> = Vec::with_capacity(BATCH);
for _ in 0..BATCH {
let mut v = Arr::<f32,NO>::new();
for x in v.iter_mut() { *x = rng.gen::<f32>(); }
loss_host.push(v);
}
let loss = SerializedVec::<f32,Arr<f32,NO>>::from(loss_host);
let cpu_grad = device.batch_backward_weight_gradient(&inputs,&loss).unwrap();
let alloc: &A = &SHARED_MEMORY_POOL;
let mut flat_inputs: Vec<f32> = Vec::with_capacity(BATCH * NI);
for b in inputs.iter() {
for &v in b.iter() { flat_inputs.push(v); }
}
let mut d_inputs = CudaVec::<f32,CudaTensor1dPtr<f32,A,NI>,A>::new(BATCH,alloc).unwrap();
{ let mut p = d_inputs.as_cuda_mut_ptr(); p.memcpy(flat_inputs.as_ptr(),flat_inputs.len()).unwrap(); }
let mut flat_loss: Vec<f32> = Vec::with_capacity(BATCH * NO);
for b in loss.iter() {
for &v in b.iter() { flat_loss.push(v); }
}
let mut d_loss = CudaVec::<f32,CudaTensor1dPtr<f32,A,NO>,A>::new(BATCH,alloc).unwrap();
{ let mut p = d_loss.as_cuda_mut_ptr(); p.memcpy(flat_loss.as_ptr(),flat_loss.len()).unwrap(); }
let d_output = CudaTensor2dPtr::<f32,A,NI,NO>::with_initializer(alloc,|| 0.0f32).unwrap();
let d_inputs = &d_inputs;
let input_view_conv = d_inputs.as_cuda_view().into_converter();
let input_view = CudaVecView::<f32,CudaTensor1dPtrView<f32,NI>>::try_from(input_view_conv).unwrap();
let loss_view = CudaVecView::<f32,CudaTensor1dPtrView<f32,NO>>::try_from(&d_loss).unwrap();
let mut args = LinearGradientBatchArgs::<'_,f32,A,NI,NO>::new(&loss_view,&input_view,d_output,BATCH);
let mut kernel = LinearGradientBatch::<'_,f32,A,NI,NO>::new();
kernel.launch(&mut args).unwrap();
let gpu_out = args.output.read_to_vec_with_size(NI * NO).unwrap();
let mut cpu_flat: Vec<f32> = Vec::with_capacity(BATCH * NO);
for i in cpu_grad.iter() {
for &v in i.iter() { cpu_flat.push(v); }
}
approx_eq_slice(&gpu_out,&cpu_flat,15e-3);
}
#[test]
fn test_kernel_reduce_linear_batch_matches_cpu() {
let device = DeviceCpu::<f32>::new().unwrap();
let mut rng = rand::thread_rng();
let input = (0..1200).map(|_| {
let mut v = Arr::<f32,1000>::new();
for v in v.iter_mut() {
*v = rng.gen::<f32>();
}
v
}).collect::<Vec<_>>();
let input = SerializedVec::<f32,Arr<f32,1000>>::from(input);
let cpu_out:Arr<f32,1000> = device.reduce(&input).unwrap();
let input = &input;
let allocator:&A = &SHARED_MEMORY_POOL;
let mut input_ptr = CudaVec::<f32,CudaTensor1dPtr<f32,A,1000>,A>::new(input.size(),allocator).unwrap();
input_ptr.memcpy(input.as_ptr(),input.size() * 1000).unwrap();
let input_ptr = &input_ptr;
let input_ptr = input_ptr.as_cuda_view();
let output_ptr = CudaTensor1dPtr::<f32,A,1000>::new(allocator).unwrap();
let mut args = ReduceLinearBatchArgs::new(&input_ptr,output_ptr,1000,input.size());
let mut kernel = ReduceLinearBatch::<f32,A,1000>::new();
kernel.launch(&mut args).unwrap();
let gpu_out = args.output.read_to_vec().unwrap();
let mut cpu_flat: Vec<f32> = Vec::with_capacity(1200 * 1000);
for &i in cpu_out.iter() {
cpu_flat.push(i);
}
approx_eq_slice(&gpu_out,&cpu_flat,2e-4);
}