add throughput mode to pim-simulator
This commit is contained in:
@@ -4,7 +4,7 @@ use mimalloc::MiMalloc;
|
||||
static GLOBAL: MiMalloc = MiMalloc;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use clap::Parser;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use glob::glob;
|
||||
use pimcore::binary_to_instruction::binary_to_executor;
|
||||
use pimcore::cpu::crossbar::Crossbar;
|
||||
@@ -14,7 +14,7 @@ use pimcore::tracing::TRACER;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufReader, Write};
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Program to simulate core execution configuration
|
||||
@@ -44,14 +44,40 @@ struct Args {
|
||||
/// Comma separated list of (address,size) for memory output dump
|
||||
#[arg(short, long, value_delimiter = ',', num_args = 1.., value_name = "ADDR,SIZE")]
|
||||
dump: Vec<usize>,
|
||||
|
||||
/// Simulator execution mode
|
||||
#[arg(long, value_enum, default_value_t = ExecutionMode::Latency)]
|
||||
mode: ExecutionMode,
|
||||
|
||||
/// Number of inputs to execute (required in throughput mode)
|
||||
#[arg(long)]
|
||||
batch_size: Option<u32>,
|
||||
|
||||
/// Input binary for one iteration; repeat once per batch entry
|
||||
#[arg(long = "input")]
|
||||
inputs: Vec<PathBuf>,
|
||||
|
||||
/// Optional directory for per-iteration output dumps
|
||||
#[arg(long)]
|
||||
batch_output_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ValueEnum)]
|
||||
enum ExecutionMode {
|
||||
Latency,
|
||||
Throughput,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let config_json = retrive_config(&args)?;
|
||||
let mut core_inputs = retrive_cores(&args)?;
|
||||
let memory = retrive_memory(&args)?;
|
||||
let config_json = retrieve_config(&args)?;
|
||||
let batch_size = batch_size(&args)?;
|
||||
let input_regions = input_regions(&config_json)?;
|
||||
let input_data = retrieve_inputs(&args, batch_size)?;
|
||||
let inputs: Vec<&[u8]> = input_data.iter().map(Vec::as_slice).collect();
|
||||
let mut core_inputs = retrieve_cores(&args)?;
|
||||
let memory = retrieve_memory(&args)?;
|
||||
let global_crossbars = get_crossbars(&config_json, &args).unwrap();
|
||||
let crossbars = map_crossbars_to_cores(&config_json, &args, &global_crossbars);
|
||||
let mut executor = match &mut core_inputs {
|
||||
@@ -67,11 +93,68 @@ fn main() -> Result<()> {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.init(executor.cpu().num_core(), args.output.clone());
|
||||
executor.execute()?;
|
||||
dump_memory(executor, &args)?;
|
||||
let dumps = dump_ranges(&args.dump)?;
|
||||
let batch_outputs = executor.execute_batch(&inputs, &input_regions, &dumps)?;
|
||||
fs::write(
|
||||
&args.output,
|
||||
batch_outputs
|
||||
.last()
|
||||
.context("simulation produced no output")?,
|
||||
)?;
|
||||
if let Some(batch_output_dir) = args.batch_output_dir {
|
||||
write_batch_outputs(batch_output_dir, batch_outputs)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn batch_size(args: &Args) -> Result<u32> {
|
||||
match (&args.mode, args.batch_size) {
|
||||
(ExecutionMode::Latency, None | Some(1)) => Ok(1),
|
||||
(ExecutionMode::Latency, Some(_)) => bail!("latency mode requires batch size 1"),
|
||||
(ExecutionMode::Throughput, Some(0)) => bail!("batch size must be positive"),
|
||||
(ExecutionMode::Throughput, Some(batch_size)) => Ok(batch_size),
|
||||
(ExecutionMode::Throughput, None) => bail!("throughput mode requires --batch-size"),
|
||||
}
|
||||
}
|
||||
|
||||
fn input_regions(config: &Value) -> Result<Vec<(usize, usize)>> {
|
||||
let addresses = config
|
||||
.get("inputs_addresses")
|
||||
.and_then(Value::as_array)
|
||||
.context("config.json has no inputs_addresses array")?;
|
||||
let sizes = config
|
||||
.get("inputs_sizes")
|
||||
.and_then(Value::as_array)
|
||||
.context("config.json has no inputs_sizes array")?;
|
||||
if addresses.len() != sizes.len() {
|
||||
bail!("config.json input address/size count mismatch");
|
||||
}
|
||||
addresses
|
||||
.iter()
|
||||
.zip(sizes)
|
||||
.map(|(address, size)| {
|
||||
Ok((
|
||||
usize::try_from(address.as_u64().context("invalid input address")?)?,
|
||||
usize::try_from(size.as_u64().context("invalid input size")?)?,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn retrieve_inputs(args: &Args, batch_size: u32) -> Result<Vec<Vec<u8>>> {
|
||||
if args.inputs.len() != batch_size as usize {
|
||||
bail!(
|
||||
"batch size {batch_size} requires {} inputs, got {}",
|
||||
batch_size,
|
||||
args.inputs.len()
|
||||
);
|
||||
}
|
||||
args.inputs
|
||||
.iter()
|
||||
.map(|path| fs::read(path).with_context(|| format!("Failed to read input file: {path:?}")))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn map_crossbars_to_cores<'c>(
|
||||
config: &Value,
|
||||
args: &Args,
|
||||
@@ -114,7 +197,7 @@ fn map_crossbars_to_cores<'c>(
|
||||
let path_as_str = real_path.to_str().unwrap();
|
||||
assert!(
|
||||
global_crossbars.contains_key(path_as_str),
|
||||
"symlink point to {:?}\n a not stored crossbar",
|
||||
"symlink points to {:?}\n a crossbar that was not stored",
|
||||
real_path
|
||||
);
|
||||
|
||||
@@ -131,7 +214,7 @@ fn map_crossbars_to_cores<'c>(
|
||||
fn get_crossbars(config: &Value, args: &Args) -> anyhow::Result<HashMap<String, Crossbar>> {
|
||||
let xbar_size = config.get("xbar_size").unwrap().as_array().unwrap();
|
||||
let rows_crossbar = xbar_size[0].as_i64().unwrap() as usize;
|
||||
let column_corssbar = xbar_size[1].as_i64().unwrap() as usize;
|
||||
let column_crossbar = xbar_size[1].as_i64().unwrap() as usize;
|
||||
let mut res = HashMap::new();
|
||||
|
||||
if let Some(folder) = args.folder.as_ref() {
|
||||
@@ -154,7 +237,7 @@ fn get_crossbars(config: &Value, args: &Args) -> anyhow::Result<HashMap<String,
|
||||
let bytes = std::fs::read(weight_file.path()).expect("Failed to read binary file");
|
||||
let stored_row_bytes = bytes.len() / rows_crossbar;
|
||||
let mut crossbar = Crossbar::new(
|
||||
std::cmp::max(column_corssbar * 4, stored_row_bytes),
|
||||
std::cmp::max(column_crossbar * 4, stored_row_bytes),
|
||||
rows_crossbar,
|
||||
CoreMemory::new(),
|
||||
);
|
||||
@@ -174,21 +257,22 @@ fn get_crossbars(config: &Value, args: &Args) -> anyhow::Result<HashMap<String,
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn dump_memory(mut executor: pimcore::Executable, args: &Args) -> Result<()> {
|
||||
let dumps: Vec<(usize, usize)> = args
|
||||
.dump
|
||||
fn dump_ranges(values: &[usize]) -> Result<Vec<(usize, usize)>> {
|
||||
if !values.len().is_multiple_of(2) {
|
||||
bail!("memory dump requires address,size pairs");
|
||||
}
|
||||
Ok(values
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| (chunk[0], chunk[1]))
|
||||
.collect();
|
||||
let mut out_file = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&args.output)
|
||||
.with_context(|| format!("cannot open file {:?} for writing", args.output))?;
|
||||
.collect())
|
||||
}
|
||||
|
||||
for (address, size) in dumps {
|
||||
out_file.write_all(executor.cpu_mut().host().load::<u8>(address, size).unwrap()[0])?;
|
||||
fn write_batch_outputs(output_dir: PathBuf, outputs: Vec<Vec<u8>>) -> Result<()> {
|
||||
fs::create_dir_all(&output_dir)
|
||||
.with_context(|| format!("cannot create batch output directory {output_dir:?}"))?;
|
||||
for (iteration, output) in outputs.into_iter().enumerate() {
|
||||
let path = output_dir.join(format!("output_{iteration:06}.bin"));
|
||||
fs::write(&path, output).with_context(|| format!("cannot write batch output {path:?}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -197,7 +281,7 @@ fn set_memory(executor: &mut pimcore::Executable, memory: Vec<u8>) {
|
||||
executor.cpu_mut().host().execute_store(0, &memory).unwrap();
|
||||
}
|
||||
|
||||
fn retrive_memory(args: &Args) -> Result<Vec<u8>> {
|
||||
fn retrieve_memory(args: &Args) -> Result<Vec<u8>> {
|
||||
let memory_path = if let Some(mem_override) = &args.memory {
|
||||
mem_override.clone()
|
||||
} else if let Some(folder) = &args.folder.as_ref() {
|
||||
@@ -237,7 +321,7 @@ enum CoreInputs {
|
||||
Binary(Vec<Vec<u8>>),
|
||||
}
|
||||
|
||||
fn retrive_cores(args: &Args) -> Result<CoreInputs, anyhow::Error> {
|
||||
fn retrieve_cores(args: &Args) -> Result<CoreInputs, anyhow::Error> {
|
||||
if let Some(cores_override) = &args.cores {
|
||||
let first_extension = cores_override
|
||||
.first()
|
||||
@@ -310,7 +394,7 @@ fn core_sort_key(path: &PathBuf) -> i32 {
|
||||
stem.parse::<i32>().unwrap()
|
||||
}
|
||||
|
||||
fn retrive_config(args: &Args) -> Result<Value, anyhow::Error> {
|
||||
fn retrieve_config(args: &Args) -> Result<Value, anyhow::Error> {
|
||||
let config_path: PathBuf = {
|
||||
let override_path = args.config.as_ref();
|
||||
let folder = args.folder.as_ref();
|
||||
|
||||
@@ -13,6 +13,33 @@ pub mod crossbar;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CPU<'a> {
|
||||
cores: Box<[Core<'a>]>,
|
||||
batch_outputs: Option<BatchOutputs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct BatchOutputs {
|
||||
iteration: usize,
|
||||
ranges: Vec<(usize, usize)>,
|
||||
outputs: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl BatchOutputs {
|
||||
fn record(&mut self, address: usize, bytes: &[u8]) {
|
||||
let output = &mut self.outputs[self.iteration];
|
||||
let store_end = address + bytes.len();
|
||||
let mut output_offset = 0;
|
||||
for &(range_address, range_size) in &self.ranges {
|
||||
let start = address.max(range_address);
|
||||
let end = store_end.min(range_address + range_size);
|
||||
if start < end {
|
||||
let size = end - start;
|
||||
output[output_offset + start - range_address
|
||||
..output_offset + start - range_address + size]
|
||||
.copy_from_slice(&bytes[start - address..start - address + size]);
|
||||
}
|
||||
output_offset += range_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CPU<'a> {
|
||||
@@ -25,9 +52,63 @@ impl<'a> CPU<'a> {
|
||||
}
|
||||
Self {
|
||||
cores: cores.into(),
|
||||
batch_outputs: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_current_iteration(&mut self, iteration: u32) {
|
||||
if let Some(batch_outputs) = &mut self.batch_outputs {
|
||||
batch_outputs.iteration = iteration as usize;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn begin_host_store_recording(
|
||||
&mut self,
|
||||
batch_size: usize,
|
||||
dump_ranges: &[(usize, usize)],
|
||||
) -> Result<()> {
|
||||
let mut initial = Vec::new();
|
||||
for &(address, size) in dump_ranges {
|
||||
initial.extend_from_slice(self.host().load::<u8>(address, size)?[0]);
|
||||
}
|
||||
self.batch_outputs = Some(BatchOutputs {
|
||||
iteration: 0,
|
||||
ranges: dump_ranges.to_vec(),
|
||||
outputs: vec![initial; batch_size],
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn store_to_host(
|
||||
&mut self,
|
||||
core: impl TryToUsize,
|
||||
host_address: impl AddressArg,
|
||||
core_address: impl AddressArg,
|
||||
size: impl TryToUsize,
|
||||
) -> Result<()> {
|
||||
let core = core.try_into().expect("core can not be negative");
|
||||
let host_address = host_address.to_address_usize()?;
|
||||
let core_address = core_address.to_address_usize()?;
|
||||
let size = size.try_into().context("size can not be negative")?;
|
||||
let Self {
|
||||
cores,
|
||||
batch_outputs,
|
||||
} = self;
|
||||
let (host, cores) = cores.split_at_mut(1);
|
||||
let bytes = cores[core - 1].load::<u8>(core_address, size)?[0];
|
||||
host[0].execute_store(host_address, bytes)?;
|
||||
if let Some(batch_outputs) = batch_outputs {
|
||||
batch_outputs.record(host_address, bytes);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn finish_host_store_recording(&mut self) -> Vec<Vec<u8>> {
|
||||
self.batch_outputs
|
||||
.take()
|
||||
.map_or_else(Vec::new, |batch_outputs| batch_outputs.outputs)
|
||||
}
|
||||
|
||||
pub fn host<'b>(&'b mut self) -> &'b mut Core<'a>
|
||||
where
|
||||
'a: 'b,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
cpu::{CPU, crossbar},
|
||||
instruction_set::{
|
||||
Instruction, InstructionData, InstructionStatus, InstructionType, VectorBitWith,
|
||||
Instruction, InstructionData, InstructionStatus, InstructionType, VectorBitWidth,
|
||||
helper::add_all,
|
||||
},
|
||||
memory_manager::{
|
||||
@@ -200,20 +200,20 @@ pub fn isa_simd(functor: InstructionType) -> bool {
|
||||
|
||||
pub fn dispatch_simd(
|
||||
functor: InstructionType,
|
||||
vector_bit_with: VectorBitWith,
|
||||
vector_bit_width: VectorBitWidth,
|
||||
) -> Result<InstructionType> {
|
||||
let VectorBitWith {
|
||||
vector_input_bitwith,
|
||||
vector_output_bitwith,
|
||||
} = vector_bit_with;
|
||||
let VectorBitWidth {
|
||||
vector_input_bitwidth,
|
||||
vector_output_bitwidth,
|
||||
} = vector_bit_width;
|
||||
let res = SIMD
|
||||
.get(&(functor as usize))
|
||||
.context("Request a non present simd")?
|
||||
.get(&(vector_input_bitwith, vector_output_bitwith))
|
||||
.get(&(vector_input_bitwidth, vector_output_bitwidth))
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Function not found for the requested size input:{} output:{}",
|
||||
vector_input_bitwith, vector_output_bitwith
|
||||
vector_input_bitwidth, vector_output_bitwidth
|
||||
)
|
||||
})?;
|
||||
Ok(*res)
|
||||
@@ -819,13 +819,15 @@ pub fn st(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
let (core, rd, r1, _, imm_len, offset_select, offset_value) =
|
||||
data.get_core_rd_r1_r2_immlen_offset();
|
||||
ensure!(core != 0, "ST cannot be used to move from host to host");
|
||||
let (host, core) = cores.host_and_cores(core);
|
||||
let r1_val = core.register(r1);
|
||||
let rd_val = core.register(rd);
|
||||
let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
|
||||
let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
|
||||
let local_memory = core.load::<u8>(r1_val, imm_len)?;
|
||||
host.execute_store(rd_val, local_memory[0]);
|
||||
let (rd_val, r1_val) = {
|
||||
let core = cores.core(core);
|
||||
let r1_val = core.register(r1);
|
||||
let rd_val = core.register(rd);
|
||||
let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
|
||||
let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
|
||||
(rd_val, r1_val)
|
||||
};
|
||||
cores.store_to_host(core, rd_val, r1_val, imm_len)?;
|
||||
TRACER.lock().unwrap().post_st(cores, data);
|
||||
Ok(InstructionStatus::Completed)
|
||||
}
|
||||
@@ -881,7 +883,7 @@ pub fn isa_recv(functor: usize) -> bool {
|
||||
|
||||
#[inline(never)]
|
||||
pub fn recv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
|
||||
Ok(InstructionStatus::Reciving(data))
|
||||
Ok(InstructionStatus::Receiving(data))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
|
||||
@@ -22,7 +22,7 @@ pub enum InstructionStatus {
|
||||
Completed,
|
||||
Waiting(InstructionData),
|
||||
Sending(InstructionData),
|
||||
Reciving(InstructionData),
|
||||
Receiving(InstructionData),
|
||||
Sync(InstructionData),
|
||||
#[default]
|
||||
NotExecuted,
|
||||
@@ -59,21 +59,21 @@ pub type Instructions = Vec<Instruction>;
|
||||
pub type InstructionType = fn(&mut CPU, InstructionData) -> Result<InstructionStatus>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct VectorBitWith {
|
||||
pub vector_input_bitwith: usize,
|
||||
pub vector_output_bitwith: usize,
|
||||
pub struct VectorBitWidth {
|
||||
pub vector_input_bitwidth: usize,
|
||||
pub vector_output_bitwidth: usize,
|
||||
}
|
||||
|
||||
/// Support for the
|
||||
/// setbw ibiw, obiw
|
||||
/// Set the bit-widths of each element for input vectors and output vectors. Related vector instructions
|
||||
/// use the configured bit-widths. Once setbw is caled, all subsequent related vector instructions will
|
||||
/// use the configured bit-widths. Once setbw is called, all subsequent related vector instructions will
|
||||
/// use the configured bit-widths, until a new setbw is called. Once ibiw and obiw are set, ibyw and
|
||||
/// obyw are also set accordingly by the hardware.
|
||||
/// If the hardware does not support variable bit-width, this instruction is invalid and the matrix/vector
|
||||
/// instructions use the fixed bit-width of the hardware.
|
||||
pub struct InstructionsBuilder {
|
||||
vector_bit_with: VectorBitWith,
|
||||
vector_bit_width: VectorBitWidth,
|
||||
instructions: Instructions,
|
||||
}
|
||||
|
||||
@@ -86,9 +86,9 @@ impl Default for InstructionsBuilder {
|
||||
impl InstructionsBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vector_bit_with: VectorBitWith {
|
||||
vector_input_bitwith: 32,
|
||||
vector_output_bitwith: 32,
|
||||
vector_bit_width: VectorBitWidth {
|
||||
vector_input_bitwidth: 32,
|
||||
vector_output_bitwidth: 32,
|
||||
},
|
||||
instructions: Instructions::new(),
|
||||
}
|
||||
@@ -97,9 +97,9 @@ impl InstructionsBuilder {
|
||||
pub fn make_inst(&mut self, functor: InstructionType, data: InstructionData) {
|
||||
if is_setbw(functor) {
|
||||
let (ibiw, obiw) = data.get_ibiw_obiw();
|
||||
self.vector_bit_with.vector_input_bitwith =
|
||||
self.vector_bit_width.vector_input_bitwidth =
|
||||
ibiw.try_into().expect("ibiw can not be negative");
|
||||
self.vector_bit_with.vector_output_bitwith =
|
||||
self.vector_bit_width.vector_output_bitwidth =
|
||||
obiw.try_into().expect("obiw can not be negative");
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ impl InstructionsBuilder {
|
||||
if (isa_simd(functor)) {
|
||||
self.instructions.push(Instruction::new(
|
||||
data,
|
||||
dispatch_simd(functor, self.vector_bit_with).unwrap(),
|
||||
dispatch_simd(functor, self.vector_bit_width).unwrap(),
|
||||
))
|
||||
} else {
|
||||
self.instructions.push(Instruction::new(data, functor))
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{
|
||||
Mutex,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
@@ -25,6 +29,9 @@ pub mod send_recv;
|
||||
pub mod tracing;
|
||||
pub mod utility;
|
||||
|
||||
static GLOBAL_ITERATION: AtomicU32 = AtomicU32::new(0);
|
||||
static EXECUTION_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CoreInstructionsBuilder {
|
||||
core_instructions: Vec<CoreInstructions>,
|
||||
@@ -54,6 +61,7 @@ impl CoreInstructionsBuilder {
|
||||
pub struct CoreInstructions {
|
||||
instructions: Instructions,
|
||||
program_counter: usize,
|
||||
current_iteration: u32,
|
||||
}
|
||||
|
||||
impl CoreInstructions {
|
||||
@@ -61,6 +69,7 @@ impl CoreInstructions {
|
||||
Self {
|
||||
instructions,
|
||||
program_counter,
|
||||
current_iteration: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +77,7 @@ impl CoreInstructions {
|
||||
Self {
|
||||
instructions: Vec::new(),
|
||||
program_counter: 0,
|
||||
current_iteration: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +87,7 @@ impl From<Instructions> for CoreInstructions {
|
||||
CoreInstructions {
|
||||
instructions: value,
|
||||
program_counter: 0,
|
||||
current_iteration: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +141,40 @@ impl<'a> Executable<'a> {
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
self.execute_batch(&[&[]], &[], &[]).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn execute_batch<'b>(
|
||||
&'b mut self,
|
||||
inputs: &[&[u8]],
|
||||
input_regions: &[(usize, usize)],
|
||||
dump_ranges: &[(usize, usize)],
|
||||
) -> Result<Vec<Vec<u8>>>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
validate_inputs(inputs, input_regions)?;
|
||||
self.execute_iterations(inputs, input_regions, dump_ranges)
|
||||
}
|
||||
|
||||
fn execute_iterations<'b>(
|
||||
&'b mut self,
|
||||
inputs: &[&[u8]],
|
||||
input_regions: &[(usize, usize)],
|
||||
dump_ranges: &[(usize, usize)],
|
||||
) -> Result<Vec<Vec<u8>>>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
let _execution_lock = EXECUTION_LOCK.lock().unwrap();
|
||||
let batch_size = u32::try_from(inputs.len().max(1)).context("batch size exceeds u32")?;
|
||||
GLOBAL_ITERATION.store(0, Ordering::SeqCst);
|
||||
if let Some(input) = inputs.first() {
|
||||
store_input(&mut self.cpu, input, input_regions)?;
|
||||
}
|
||||
self.cpu
|
||||
.begin_host_store_recording(batch_size as usize, dump_ranges)?;
|
||||
|
||||
let Self {
|
||||
cpu,
|
||||
core_instructions: cores_instructions,
|
||||
@@ -147,9 +192,24 @@ impl<'a> Executable<'a> {
|
||||
&& let Some(core_instruction) = cores_instructions.get_mut(cpu_index)
|
||||
{
|
||||
core_result = InstructionStatus::NotExecuted;
|
||||
if core_instruction.program_counter == core_instruction.instructions.len() {
|
||||
if core_instruction.instructions.is_empty()
|
||||
|| core_instruction.current_iteration + 1 >= batch_size
|
||||
{
|
||||
break;
|
||||
}
|
||||
core_instruction.current_iteration += 1;
|
||||
core_instruction.program_counter = 0;
|
||||
let iteration = core_instruction.current_iteration;
|
||||
if iteration > GLOBAL_ITERATION.fetch_max(iteration, Ordering::SeqCst) {
|
||||
store_input(cpu, inputs[iteration as usize], input_regions)?;
|
||||
}
|
||||
}
|
||||
cpu.set_current_iteration(core_instruction.current_iteration);
|
||||
let CoreInstructions {
|
||||
instructions,
|
||||
program_counter,
|
||||
..
|
||||
} = core_instruction;
|
||||
core_result = instructions
|
||||
.get(*program_counter)
|
||||
@@ -211,7 +271,7 @@ impl<'a> Executable<'a> {
|
||||
|
||||
#[cfg(feature = "profile_time")]
|
||||
TRACER.lock().unwrap().report();
|
||||
Ok(())
|
||||
Ok(cpu.finish_host_store_recording())
|
||||
}
|
||||
|
||||
pub fn cpu(&self) -> &CPU<'a> {
|
||||
@@ -233,6 +293,29 @@ impl<'a> Executable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_inputs(inputs: &[&[u8]], input_regions: &[(usize, usize)]) -> Result<()> {
|
||||
let input_size = input_regions.iter().try_fold(0usize, |total, (_, size)| {
|
||||
total.checked_add(*size).context("input size overflow")
|
||||
})?;
|
||||
if inputs.is_empty() {
|
||||
bail!("at least one input is required");
|
||||
}
|
||||
if inputs.iter().any(|input| input.len() != input_size) {
|
||||
bail!("each input must contain exactly {input_size} bytes");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn store_input(cpu: &mut CPU, input: &[u8], input_regions: &[(usize, usize)]) -> Result<()> {
|
||||
let mut offset = 0;
|
||||
for &(address, size) in input_regions {
|
||||
cpu.host()
|
||||
.execute_store(address, &input[offset..offset + size])?;
|
||||
offset += size;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn detect_deadlock(cores_instructions: &[CoreInstructions]) -> Option<DeadlockInfo> {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum CoreState {
|
||||
|
||||
@@ -33,10 +33,10 @@ pub struct SendRecv {
|
||||
impl SendRecv {
|
||||
pub fn new(num_core: usize) -> Self {
|
||||
let sending = [Option::None].repeat(num_core);
|
||||
let reciving = [Option::None].repeat(num_core);
|
||||
let receiving = [Option::None].repeat(num_core);
|
||||
Self {
|
||||
sending: sending.into(),
|
||||
receiving: reciving.into(),
|
||||
receiving: receiving.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,18 +73,18 @@ where
|
||||
let data = inst.data;
|
||||
TRACER.lock().unwrap().pre_recv(cpu, data);
|
||||
}
|
||||
let [sender_core, reciver_core] =
|
||||
let [sender_core, receiver_core] =
|
||||
cpu.get_multiple_cores([sender.internal_core, receiver.internal_core]);
|
||||
let memory = sender_core
|
||||
.load::<u8>(sender.address, sender.size)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Sender crash tranfering memroy from {} with size {}",
|
||||
"Sender crashed while transferring memory from {} with size {}",
|
||||
sender.address, sender.size
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
reciver_core.execute_store(receiver.address, memory[0]);
|
||||
receiver_core.execute_store(receiver.address, memory[0]);
|
||||
{
|
||||
let sender = &mut core_instructions[sender.internal_core];
|
||||
let pc = sender.program_counter;
|
||||
@@ -124,19 +124,19 @@ where
|
||||
let receiver: usize = imm_core.try_into().expect("imm_core can not be negative");
|
||||
assert_ne!(receiver, 0, "Host can not use receive");
|
||||
send_recv.sending[sender] = Some(SendRecvInfo::new(sender, receiver, address, imm_len));
|
||||
let transfered = transfer_memory(
|
||||
let transferred = transfer_memory(
|
||||
cpu,
|
||||
core_instructions,
|
||||
send_recv.sending[sender],
|
||||
send_recv.receiving[receiver],
|
||||
);
|
||||
if transfered {
|
||||
if transferred {
|
||||
send_recv.sending[sender] = None;
|
||||
send_recv.receiving[receiver] = None;
|
||||
}
|
||||
(transfered, if transfered { receiver } else { 0 })
|
||||
(transferred, if transferred { receiver } else { 0 })
|
||||
}
|
||||
InstructionStatus::Reciving(instruction_data) => {
|
||||
InstructionStatus::Receiving(instruction_data) => {
|
||||
let (core_idx, imm_core) = instruction_data.get_core_immcore();
|
||||
let rd = instruction_data.rd();
|
||||
let imm_len = instruction_data
|
||||
@@ -153,17 +153,17 @@ where
|
||||
assert_ne!(sender, 0, "Host can not use send");
|
||||
send_recv.receiving[receiver] =
|
||||
Some(SendRecvInfo::new(receiver, sender, address, imm_len));
|
||||
let transfered = transfer_memory(
|
||||
let transferred = transfer_memory(
|
||||
cpu,
|
||||
core_instructions,
|
||||
send_recv.sending[sender],
|
||||
send_recv.receiving[receiver],
|
||||
);
|
||||
if transfered {
|
||||
if transferred {
|
||||
send_recv.sending[sender] = None;
|
||||
send_recv.receiving[receiver] = None;
|
||||
}
|
||||
(transfered, if transfered { sender } else { 0 })
|
||||
(transferred, if transferred { sender } else { 0 })
|
||||
}
|
||||
_ => (false, 0),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
mod common;
|
||||
|
||||
use pimcore::{
|
||||
CoreInstructionsBuilder, Executable,
|
||||
instruction_set::{InstructionsBuilder, instruction_data::InstructionDataBuilder, isa::*},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn restarts_cores_and_loads_each_input() {
|
||||
let cpu = common::empty_cpu(1);
|
||||
let mut cores = CoreInstructionsBuilder::new(1);
|
||||
let mut instructions = InstructionsBuilder::new();
|
||||
let mut data = InstructionDataBuilder::new();
|
||||
data.set_core_indx(1).fix_core_indx();
|
||||
instructions.make_inst(sldi, data.set_rdimm(1, 0).build());
|
||||
instructions.make_inst(sldi, data.set_rdimm(2, 0).build());
|
||||
instructions.make_inst(ld, data.set_rdr1(2, 1).set_imm_len(4).build());
|
||||
instructions.make_inst(sldi, data.set_rdimm(3, 4).build());
|
||||
instructions.make_inst(st, data.set_rdr1(3, 2).set_imm_len(4).build());
|
||||
cores.set_core(1, instructions.build());
|
||||
|
||||
let mut executable = Executable::new(cpu, cores.build());
|
||||
let first = 1.0f32.to_ne_bytes();
|
||||
let second = 2.0f32.to_ne_bytes();
|
||||
assert!(
|
||||
executable
|
||||
.execute_batch(&[&first[..3]], &[(0, 4)], &[])
|
||||
.is_err()
|
||||
);
|
||||
executable
|
||||
.execute_batch(&[&first, &second], &[(0, 4)], &[])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
executable.cpu_mut().host().load::<f32>(4, 4).unwrap()[0],
|
||||
[2.0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records_each_iteration_output() {
|
||||
let cpu = common::empty_cpu(1);
|
||||
let mut cores = CoreInstructionsBuilder::new(1);
|
||||
let mut instructions = InstructionsBuilder::new();
|
||||
let mut data = InstructionDataBuilder::new();
|
||||
data.set_core_indx(1).fix_core_indx();
|
||||
instructions.make_inst(sldi, data.set_rdimm(1, 0).build());
|
||||
instructions.make_inst(sldi, data.set_rdimm(2, 0).build());
|
||||
instructions.make_inst(ld, data.set_rdr1(2, 1).set_imm_len(4).build());
|
||||
instructions.make_inst(sldi, data.set_rdimm(3, 4).build());
|
||||
instructions.make_inst(st, data.set_rdr1(3, 2).set_imm_len(4).build());
|
||||
cores.set_core(1, instructions.build());
|
||||
|
||||
let mut executable = Executable::new(cpu, cores.build());
|
||||
let first = 1.0f32.to_ne_bytes();
|
||||
let second = 2.0f32.to_ne_bytes();
|
||||
let outputs = executable
|
||||
.execute_batch(&[&first, &second], &[(0, 4)], &[(4, 2), (6, 2)])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(outputs.len(), 2);
|
||||
assert_eq!(
|
||||
f32::from_ne_bytes(outputs[0].as_slice().try_into().unwrap()),
|
||||
1.0
|
||||
);
|
||||
assert_eq!(
|
||||
f32::from_ne_bytes(outputs[1].as_slice().try_into().unwrap()),
|
||||
2.0
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user