diff --git a/Cargo.lock b/Cargo.lock index edc36c9..c491346 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2046,6 +2046,7 @@ dependencies = [ name = "tig-binary" version = "0.1.0" dependencies = [ + "anyhow", "cudarc", "tig-algorithms", "tig-challenges", diff --git a/tig-binary/Cargo.toml b/tig-binary/Cargo.toml index 809ec48..99d9e7a 100644 --- a/tig-binary/Cargo.toml +++ b/tig-binary/Cargo.toml @@ -11,6 +11,7 @@ edition.workspace = true crate-type = ["cdylib", "staticlib", "rlib"] [dependencies] +anyhow = "1.0.81" cudarc = { git = "https://github.com/tig-foundation/cudarc.git", branch = "runtime-fuel/cudnn-cublas", features = [ "cuda-version-from-build-system", ], optional = true } diff --git a/tig-binary/src/entry_point_template.rs b/tig-binary/src/entry_point_template.rs index 772a747..901b17b 100644 --- a/tig-binary/src/entry_point_template.rs +++ b/tig-binary/src/entry_point_template.rs @@ -1,4 +1,5 @@ -use std::panic::catch_unwind; +use anyhow::{anyhow, Result}; +use std::panic::{catch_unwind, AssertUnwindSafe}; use tig_algorithms::{CHALLENGE}::{ALGORITHM}; use tig_challenges::{CHALLENGE}::*; @@ -13,28 +14,32 @@ use std::sync::Arc; #[cfg(not(feature = "cuda"))] #[unsafe(no_mangle)] -pub extern "C" fn entry_point(challenge: &Challenge) -> Result, String> +pub fn entry_point( + challenge: &Challenge, + save_solution: &dyn Fn(&Solution) -> Result<()> +) -> Result<()> { - return catch_unwind(|| { - {ALGORITHM}::solve_challenge(challenge).map_err(|e| e.to_string()) - }).unwrap_or_else(|_| { - Err("Panic occurred calling solve_challenge".to_string()) - }); + catch_unwind(AssertUnwindSafe(|| { + {ALGORITHM}::solve_challenge(challenge, save_solution) + })).unwrap_or_else(|_| { + Err(anyhow!("Panic occurred calling solve_challenge")) + }) } #[cfg(feature = "cuda")] #[unsafe(no_mangle)] -pub extern "C" fn entry_point( +pub fn entry_point( challenge: &Challenge, + save_solution: &dyn Fn(&Solution) -> Result<()>, module: Arc, stream: Arc, prop: &cudaDeviceProp, ) -> Result, String> { - return catch_unwind(|| { - {ALGORITHM}::solve_challenge(challenge, module, stream, prop).map_err(|e| e.to_string()) - }).unwrap_or_else(|_| { - Err("Panic occurred calling solve_challenge".to_string()) - }); + catch_unwind(AssertUnwindSafe(|| { + {ALGORITHM}::solve_challenge(challenge, module, stream, prop) + })).unwrap_or_else(|_| { + Err(anyhow!("Panic occurred calling solve_challenge")) + }) } \ No newline at end of file diff --git a/tig-challenges/src/hypergraph.rs b/tig-challenges/src/hypergraph.rs index dfa7c89..4e90b3d 100644 --- a/tig-challenges/src/hypergraph.rs +++ b/tig-challenges/src/hypergraph.rs @@ -31,6 +31,14 @@ pub struct Solution { pub sub_solutions: Vec, } +impl Solution { + pub fn new() -> Self { + Self { + sub_solutions: Vec::new(), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SubSolution { pub partition: Vec, diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index 1b4e4b1..2b7fc0d 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -33,6 +33,14 @@ pub struct Solution { pub sub_solutions: Vec, } +impl Solution { + pub fn new() -> Self { + Self { + sub_solutions: Vec::new(), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SubSolution { pub items: Vec, diff --git a/tig-challenges/src/neuralnet_optimizer.rs b/tig-challenges/src/neuralnet_optimizer.rs index 68a3375..4f566a3 100644 --- a/tig-challenges/src/neuralnet_optimizer.rs +++ b/tig-challenges/src/neuralnet_optimizer.rs @@ -55,6 +55,20 @@ pub struct Solution { pub bn_running_vars: Vec>, } +impl Solution { + pub fn new() -> Self { + Self { + weights: Vec::new(), + biases: Vec::new(), + epochs_used: 0, + bn_weights: Vec::new(), + bn_biases: Vec::new(), + bn_running_means: Vec::new(), + bn_running_vars: Vec::new(), + } + } +} + // Helper struct for (de)serialization #[derive(Serialize, Deserialize)] struct SolutionData { diff --git a/tig-challenges/src/satisfiability.rs b/tig-challenges/src/satisfiability.rs index 234f709..797e80a 100644 --- a/tig-challenges/src/satisfiability.rs +++ b/tig-challenges/src/satisfiability.rs @@ -42,6 +42,14 @@ pub struct Solution { pub variables: Vec, } +impl Solution { + pub fn new() -> Self { + Self { + variables: Vec::new(), + } + } +} + impl TryFrom> for Solution { type Error = serde_json::Error; diff --git a/tig-challenges/src/vector_search.rs b/tig-challenges/src/vector_search.rs index a1e1707..5e92429 100644 --- a/tig-challenges/src/vector_search.rs +++ b/tig-challenges/src/vector_search.rs @@ -33,6 +33,14 @@ pub struct Solution { pub indexes: Vec, } +impl Solution { + pub fn new() -> Self { + Self { + indexes: Vec::new(), + } + } +} + impl TryFrom> for Solution { type Error = serde_json::Error; diff --git a/tig-challenges/src/vehicle_routing.rs b/tig-challenges/src/vehicle_routing.rs index 812eb4c..1c74d4f 100644 --- a/tig-challenges/src/vehicle_routing.rs +++ b/tig-challenges/src/vehicle_routing.rs @@ -34,6 +34,14 @@ pub struct Solution { pub sub_solutions: Vec, } +impl Solution { + pub fn new() -> Self { + Self { + sub_solutions: Vec::new(), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SubSolution { pub routes: Vec>, diff --git a/tig-runtime/src/main.rs b/tig-runtime/src/main.rs index 5ab4c6a..df34a5c 100644 --- a/tig-runtime/src/main.rs +++ b/tig-runtime/src/main.rs @@ -1,10 +1,10 @@ use anyhow::{anyhow, Result}; -use clap::{arg, ArgAction, Command}; +use clap::{arg, Command}; use libloading::Library; use std::{fs, panic, path::PathBuf}; use tig_challenges::*; -use tig_structs::core::{BenchmarkSettings, CPUArchitecture, OutputData, Solution}; -use tig_utils::{compress_obj, dejsonify, jsonify}; +use tig_structs::core::{BenchmarkSettings, CPUArchitecture, OutputData}; +use tig_utils::{dejsonify, jsonify}; #[cfg(feature = "cuda")] use { cudarc::{ @@ -42,13 +42,9 @@ fn cli() -> Command { .value_parser(clap::value_parser!(u64)), ) .arg( - arg!(--output [OUTPUT_FILE] "If set, the output data will be saved to this file path (default json)") + arg!(--output [OUTPUT_FOLDER] "If set, the output data will be saved to this folder (default current directory)") .value_parser(clap::value_parser!(PathBuf)), ) - .arg( - arg!(--compress [COMPRESS] "If output file is set, the output data will be compressed as zlib") - .action(ArgAction::SetTrue) - ) .arg( arg!(--gpu [GPU] "Which GPU device to use") .value_parser(clap::value_parser!(usize)), @@ -66,7 +62,6 @@ fn main() { matches.get_one::("ptx").cloned(), *matches.get_one::("fuel").unwrap(), matches.get_one::("output").cloned(), - matches.get_one::("compress").unwrap().clone(), matches.get_one::("gpu").cloned(), ) { eprintln!("Runtime Error: {}", e); @@ -81,8 +76,7 @@ pub fn compute_solution( library_path: PathBuf, ptx_path: Option, max_fuel: u64, - output_file: Option, - compress: bool, + output_folder: Option, gpu_device: Option, ) -> Result<()> { let settings = load_settings(&settings); @@ -94,117 +88,127 @@ pub fn compute_solution( let runtime_signature_ptr = unsafe { *library.get::<*mut u64>(b"__runtime_signature")? }; unsafe { *runtime_signature_ptr = u64::from_be_bytes(seed[0..8].try_into().unwrap()) }; - let (fuel_consumed, runtime_signature, solution, invalid_reason): ( - u64, - u64, - Solution, - Option, - ) = 'out_of_fuel: { - macro_rules! dispatch_challenge { - ($c:ident, cpu) => {{ - // library function may exit 87 if it runs out of fuel - let solve_challenge_fn = unsafe { - library.get:: Result, String>>( + let output_file = match output_folder { + Some(folder) => { + fs::create_dir_all(&folder)?; + folder.join(format!("{}.json", nonce)) + } + None => format!("{}.json", nonce).into(), + }; + + macro_rules! dispatch_challenge { + ($c:ident, cpu) => {{ + // library function may exit 87 if it runs out of fuel + let solve_challenge_fn = unsafe { + library + .get:: Result<()>) -> Result<()>>( b"entry_point", )? - }; + }; - let challenge = - $c::Challenge::generate_instance(&seed, &settings.difficulty.into())?; - - let result = solve_challenge_fn(&challenge).map_err(|e| anyhow!("{}", e))?; - let fuel_consumed = - max_fuel - unsafe { **library.get::<*const u64>(b"__fuel_remaining")? }; - if fuel_consumed > max_fuel { - break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None); - } + let challenge = $c::Challenge::generate_instance(&seed, &settings.difficulty.into())?; + let save_solution_fn = |solution: &$c::Solution| -> Result<()> { + let fuel_consumed = (max_fuel + - unsafe { **library.get::<*const u64>(b"__fuel_remaining")? }) + .min(max_fuel + 1); let runtime_signature = unsafe { **library.get::<*const u64>(b"__runtime_signature")? }; - let (solution, invalid_reason) = match result { - Some(s) => match challenge.verify_solution(&s) { - Ok(_) => ( - serde_json::to_value(&s) - .unwrap() - .as_object() - .unwrap() - .to_owned(), - None, - ), - Err(e) => (Solution::new(), Some(e.to_string())), + + let solution = match challenge.verify_solution(&solution) { + Ok(_) => match serde_json::to_value(&solution).unwrap() { + serde_json::Value::String(s) => { + let mut map = serde_json::Map::new(); + map.insert("base64".to_string(), serde_json::Value::String(s)); + map + } + serde_json::Value::Object(map) => map, + _ => return Err(anyhow!("Expected String or Object from to_value")), }, - None => (Solution::new(), None), + Err(e) => { + eprintln!("Invalid solution: {}", e); + serde_json::Map::new() + } }; - (fuel_consumed, runtime_signature, solution, invalid_reason) - }}; - - ($c:ident, gpu) => {{ - if ptx_path.is_none() { - panic!("PTX file is required for GPU challenges."); - } - let ptx_path = ptx_path.unwrap(); - // library function may exit 87 if it runs out of fuel - let solve_challenge_fn = unsafe { - library.get::, - Arc, - &cudaDeviceProp, - ) -> Result, String>>(b"entry_point")? + let output_data = OutputData { + nonce, + runtime_signature, + fuel_consumed, + solution, + #[cfg(target_arch = "x86_64")] + cpu_arch: CPUArchitecture::AMD64, + #[cfg(target_arch = "aarch64")] + cpu_arch: CPUArchitecture::ARM64, }; + fs::write(&output_file, jsonify(&output_data))?; + Ok(()) + }; + let result = solve_challenge_fn(&challenge, &save_solution_fn); + if !output_file.exists() { + save_solution_fn(&$c::Solution::new())?; + } + result + }}; - let gpu_fuel_scale = 20; // scale fuel to loosely align with CPU - let ptx_content = std::fs::read_to_string(&ptx_path) - .map_err(|e| anyhow!("Failed to read PTX file: {}", e))?; - let max_fuel_hex = format!("0x{:016x}", max_fuel * gpu_fuel_scale); - let modified_ptx = ptx_content.replace("0xdeadbeefdeadbeef", &max_fuel_hex); + ($c:ident, gpu) => {{ + if ptx_path.is_none() { + panic!("PTX file is required for GPU challenges."); + } + let ptx_path = ptx_path.unwrap(); + // library function may exit 87 if it runs out of fuel + let solve_challenge_fn = unsafe { + library.get:: anyhow::Result<()>, + Arc, + Arc, + &cudaDeviceProp, + ) -> Result<()>>(b"entry_point")? + }; - let num_gpus = CudaContext::device_count()?; - if num_gpus == 0 { - panic!("No CUDA devices found"); - } - let gpu_device = gpu_device.unwrap_or((nonce % num_gpus as u64) as usize); - let ptx = Ptx::from_src(modified_ptx); - let ctx = CudaContext::new(gpu_device)?; - ctx.set_blocking_synchronize()?; - let module = ctx.load_module(ptx)?; - let stream = ctx.fuel_check_stream(); - let prop = get_device_prop(gpu_device as i32)?; + let gpu_fuel_scale = 20; // scale fuel to loosely align with CPU + let ptx_content = std::fs::read_to_string(&ptx_path) + .map_err(|e| anyhow!("Failed to read PTX file: {}", e))?; + let max_fuel_hex = format!("0x{:016x}", max_fuel * gpu_fuel_scale); + let modified_ptx = ptx_content.replace("0xdeadbeefdeadbeef", &max_fuel_hex); - let challenge = $c::Challenge::generate_instance( - &seed, - &settings.difficulty.into(), - module.clone(), - stream.clone(), - &prop, - )?; + let num_gpus = CudaContext::device_count()?; + if num_gpus == 0 { + panic!("No CUDA devices found"); + } + let gpu_device = gpu_device.unwrap_or((nonce % num_gpus as u64) as usize); + let ptx = Ptx::from_src(modified_ptx); + let ctx = CudaContext::new(gpu_device)?; + ctx.set_blocking_synchronize()?; + let module = ctx.load_module(ptx)?; + let stream = ctx.fuel_check_stream(); + let prop = get_device_prop(gpu_device as i32)?; - // ctx.enable_memory_tracking(1024 * 1024); + let challenge = $c::Challenge::generate_instance( + &seed, + &settings.difficulty.into(), + module.clone(), + stream.clone(), + &prop, + )?; - let initialize_kernel = module.load_function("initialize_kernel")?; + let initialize_kernel = module.load_function("initialize_kernel")?; - let cfg = LaunchConfig { - grid_dim: (1, 1, 1), - block_dim: (1, 1, 1), - shared_mem_bytes: 0, - }; + let cfg = LaunchConfig { + grid_dim: (1, 1, 1), + block_dim: (1, 1, 1), + shared_mem_bytes: 0, + }; - unsafe { - stream - .launch_builder(&initialize_kernel) - .arg(&(u64::from_be_bytes(seed[8..16].try_into().unwrap()))) - .launch(cfg)?; - } + unsafe { + stream + .launch_builder(&initialize_kernel) + .arg(&(u64::from_be_bytes(seed[8..16].try_into().unwrap()))) + .launch(cfg)?; + } - let result = solve_challenge_fn(&challenge, module.clone(), stream.clone(), &prop); - if result - .as_ref() - .is_err_and(|e| e.contains("ran out of fuel")) - { - break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None); - } - let result = result.map_err(|e| anyhow!("{}", e))?; + let save_solution_fn = |solution: &$c::Solution| -> Result<()> { stream.synchronize()?; ctx.synchronize()?; @@ -229,123 +233,103 @@ pub fn compute_solution( .launch(cfg)?; } - if stream.memcpy_dtov(&error_stat)?[0] != 0 { - break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None); - } let gpu_fuel_consumed = stream.memcpy_dtov(&fuel_usage)?[0] / gpu_fuel_scale; let cpu_fuel_consumed = max_fuel - unsafe { **library.get::<*const u64>(b"__fuel_remaining")? }; - let fuel_consumed = gpu_fuel_consumed + cpu_fuel_consumed; - - if fuel_consumed > max_fuel { - break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None); - } + let fuel_consumed = (gpu_fuel_consumed + cpu_fuel_consumed).min(max_fuel + 1); let gpu_runtime_signature = stream.memcpy_dtov(&signature)?[0]; let cpu_runtime_signature = unsafe { **library.get::<*const u64>(b"__runtime_signature")? }; let runtime_signature = gpu_runtime_signature ^ cpu_runtime_signature; - let (solution, invalid_reason) = match result { - Some(s) => { - match challenge.verify_solution(&s, module.clone(), stream.clone(), &prop) { - Ok(_) => ( - match serde_json::to_value(&s).unwrap() { - serde_json::Value::String(s) => { - let mut map = serde_json::Map::new(); - map.insert( - "base64".to_string(), - serde_json::Value::String(s), - ); - map - } - serde_json::Value::Object(map) => map, - _ => panic!("Expected String or Object from to_value"), - }, - None, - ), - Err(e) => (Solution::new(), Some(e.to_string())), + let solution = match challenge.verify_solution( + &solution, + module.clone(), + stream.clone(), + &prop, + ) { + Ok(_) => match serde_json::to_value(&solution).unwrap() { + serde_json::Value::String(s) => { + let mut map = serde_json::Map::new(); + map.insert("base64".to_string(), serde_json::Value::String(s)); + map } + serde_json::Value::Object(map) => map, + _ => return Err(anyhow!("Expected String or Object from to_value")), + }, + Err(e) => { + eprintln!("Invalid solution: {}", e); + serde_json::Map::new() } - None => (Solution::new(), None), }; - (fuel_consumed, runtime_signature, solution, invalid_reason) - }}; - } - - match settings.challenge_id.as_str() { - "c001" => { - #[cfg(not(feature = "c001"))] - panic!("tig-runtime was not compiled with '--features c001'"); - #[cfg(feature = "c001")] - dispatch_challenge!(c001, cpu) + let output_data = OutputData { + nonce, + runtime_signature, + fuel_consumed, + solution, + #[cfg(target_arch = "x86_64")] + cpu_arch: CPUArchitecture::AMD64, + #[cfg(target_arch = "aarch64")] + cpu_arch: CPUArchitecture::ARM64, + }; + fs::write(&output_file, jsonify(&output_data))?; + Ok(()) + }; + let result = solve_challenge_fn( + &challenge, + &save_solution_fn, + module.clone(), + stream.clone(), + &prop, + ); + if !output_file.exists() { + save_solution_fn(&$c::Solution::new())?; } - "c002" => { - #[cfg(not(feature = "c002"))] - panic!("tig-runtime was not compiled with '--features c002'"); - #[cfg(feature = "c002")] - dispatch_challenge!(c002, cpu) - } - "c003" => { - #[cfg(not(feature = "c003"))] - panic!("tig-runtime was not compiled with '--features c003'"); - #[cfg(feature = "c003")] - dispatch_challenge!(c003, cpu) - } - "c004" => { - #[cfg(not(feature = "c004"))] - panic!("tig-runtime was not compiled with '--features c004'"); - #[cfg(feature = "c004")] - dispatch_challenge!(c004, gpu) - } - "c005" => { - #[cfg(not(feature = "c005"))] - panic!("tig-runtime was not compiled with '--features c005'"); - #[cfg(feature = "c005")] - dispatch_challenge!(c005, gpu) - } - "c006" => { - #[cfg(not(feature = "c006"))] - panic!("tig-runtime was not compiled with '--features c006'"); - #[cfg(feature = "c006")] - dispatch_challenge!(c006, gpu) - } - _ => panic!("Unsupported challenge"), - } - }; - - let output_data = OutputData { - nonce, - runtime_signature, - fuel_consumed, - solution, - #[cfg(target_arch = "x86_64")] - cpu_arch: CPUArchitecture::AMD64, - #[cfg(target_arch = "aarch64")] - cpu_arch: CPUArchitecture::ARM64, - }; - if let Some(path) = output_file { - if compress { - fs::write(&path, compress_obj(&output_data))?; - } else { - fs::write(&path, jsonify(&output_data))?; - } - println!("output_data written to: {:?}", path); - } else { - println!("{}", jsonify(&output_data)); + result + }}; } - if fuel_consumed > max_fuel { - eprintln!("Ran out of fuel"); - std::process::exit(87); - } else if let Some(msg) = invalid_reason { - eprintln!("Invalid solution: {}", msg); - std::process::exit(86); - } else if output_data.solution.len() == 0 { - eprintln!("No solution found"); - std::process::exit(85); + + match settings.challenge_id.as_str() { + "c001" => { + #[cfg(not(feature = "c001"))] + panic!("tig-runtime was not compiled with '--features c001'"); + #[cfg(feature = "c001")] + dispatch_challenge!(c001, cpu) + } + "c002" => { + #[cfg(not(feature = "c002"))] + panic!("tig-runtime was not compiled with '--features c002'"); + #[cfg(feature = "c002")] + dispatch_challenge!(c002, cpu) + } + "c003" => { + #[cfg(not(feature = "c003"))] + panic!("tig-runtime was not compiled with '--features c003'"); + #[cfg(feature = "c003")] + dispatch_challenge!(c003, cpu) + } + "c004" => { + #[cfg(not(feature = "c004"))] + panic!("tig-runtime was not compiled with '--features c004'"); + #[cfg(feature = "c004")] + dispatch_challenge!(c004, gpu) + } + "c005" => { + #[cfg(not(feature = "c005"))] + panic!("tig-runtime was not compiled with '--features c005'"); + #[cfg(feature = "c005")] + dispatch_challenge!(c005, gpu) + } + "c006" => { + #[cfg(not(feature = "c006"))] + panic!("tig-runtime was not compiled with '--features c006'"); + #[cfg(feature = "c006")] + dispatch_challenge!(c006, gpu) + } + _ => panic!("Unsupported challenge"), } - Ok(()) } fn load_settings(settings: &str) -> BenchmarkSettings {