Algorithms are given save_solution function.

This commit is contained in:
FiveMovesAhead 2025-09-30 15:27:53 +01:00
parent fb8f746a1b
commit 270f0eaa85
10 changed files with 263 additions and 218 deletions

1
Cargo.lock generated
View File

@ -2046,6 +2046,7 @@ dependencies = [
name = "tig-binary"
version = "0.1.0"
dependencies = [
"anyhow",
"cudarc",
"tig-algorithms",
"tig-challenges",

View File

@ -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 }

View File

@ -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<Option<Solution>, 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<CudaModule>,
stream: Arc<CudaStream>,
prop: &cudaDeviceProp,
) -> Result<Option<Solution>, 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"))
})
}

View File

@ -31,6 +31,14 @@ pub struct Solution {
pub sub_solutions: Vec<SubSolution>,
}
impl Solution {
pub fn new() -> Self {
Self {
sub_solutions: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SubSolution {
pub partition: Vec<u32>,

View File

@ -33,6 +33,14 @@ pub struct Solution {
pub sub_solutions: Vec<SubSolution>,
}
impl Solution {
pub fn new() -> Self {
Self {
sub_solutions: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SubSolution {
pub items: Vec<usize>,

View File

@ -55,6 +55,20 @@ pub struct Solution {
pub bn_running_vars: Vec<Vec<f32>>,
}
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 {

View File

@ -42,6 +42,14 @@ pub struct Solution {
pub variables: Vec<bool>,
}
impl Solution {
pub fn new() -> Self {
Self {
variables: Vec::new(),
}
}
}
impl TryFrom<Map<String, Value>> for Solution {
type Error = serde_json::Error;

View File

@ -33,6 +33,14 @@ pub struct Solution {
pub indexes: Vec<usize>,
}
impl Solution {
pub fn new() -> Self {
Self {
indexes: Vec::new(),
}
}
}
impl TryFrom<Map<String, Value>> for Solution {
type Error = serde_json::Error;

View File

@ -34,6 +34,14 @@ pub struct Solution {
pub sub_solutions: Vec<SubSolution>,
}
impl Solution {
pub fn new() -> Self {
Self {
sub_solutions: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SubSolution {
pub routes: Vec<Vec<usize>>,

View File

@ -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::<PathBuf>("ptx").cloned(),
*matches.get_one::<u64>("fuel").unwrap(),
matches.get_one::<PathBuf>("output").cloned(),
matches.get_one::<bool>("compress").unwrap().clone(),
matches.get_one::<usize>("gpu").cloned(),
) {
eprintln!("Runtime Error: {}", e);
@ -81,8 +76,7 @@ pub fn compute_solution(
library_path: PathBuf,
ptx_path: Option<PathBuf>,
max_fuel: u64,
output_file: Option<PathBuf>,
compress: bool,
output_folder: Option<PathBuf>,
gpu_device: Option<usize>,
) -> 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<String>,
) = '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::<fn(&$c::Challenge) -> Result<Option<$c::Solution>, 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::<fn(&$c::Challenge, &dyn Fn(&$c::Solution) -> 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::<fn(
&$c::Challenge,
Arc<CudaModule>,
Arc<CudaStream>,
&cudaDeviceProp,
) -> Result<Option<$c::Solution>, 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::<fn(
&$c::Challenge,
save_solution: &dyn Fn(&$c::Solution) -> anyhow::Result<()>,
Arc<CudaModule>,
Arc<CudaStream>,
&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 {