use std::fs::File;
use std::path::PathBuf;
use ec_gpu_gen::EcError;
use ec_gpu_gen::fft::FftKernel;
use ec_gpu_gen::rust_gpu_tools::{Device, UniqueId};
use ff::Field;
use fs2::FileExt;
use group::prime::PrimeCurveAffine;
use log::{debug, info, warn};
use crate::gpu::error::{GpuError, GpuResult};
use crate::gpu::{CpuGpuMultiexpKernel, GpuName};
const GPU_LOCK_NAME: &str = "bellman.gpu.lock";
const PRIORITY_LOCK_NAME: &str = "bellman.priority.lock";
fn tmp_path(filename: &str, id: Option<UniqueId>) -> PathBuf {
let temp_file = match id {
Some(id) => format!("{}.{}", filename, id),
None => filename.to_string(),
};
std::env::temp_dir().join(temp_file)
}
#[derive(Debug)]
struct LockInfo<'a> {
file: File,
path: PathBuf,
device: Option<&'a Device>,
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
pub struct GPULock<'a>(Vec<LockInfo<'a>>);
impl<'a> GPULock<'a> {
pub fn lock() -> Self {
if let Ok(val) = std::env::var("BELLPERSON_GPUS_PER_LOCK") {
match val.parse::<usize>() {
Ok(val) if val > 0 => {
let devices = Device::all();
info!(
"BELLPERSON_GPUS_PER_LOCK == {}, try lock {}/{} gpus",
val,
val,
devices.len(),
);
let mut locks = Vec::new();
for (index, device) in devices.iter().enumerate() {
let uid = device.unique_id();
let path = tmp_path(GPU_LOCK_NAME, Some(uid));
debug!("Acquiring GPU lock {}/{} at {:?} ...", index, val, &path);
let file = File::create(&path).unwrap_or_else(|_| {
panic!("Cannot create GPU {:?} lock file at {:?}", uid, &path)
});
if file.try_lock_exclusive().is_err() {
continue;
}
debug!("GPU lock acquired at {:?}", path);
locks.push(LockInfo {
file,
path,
device: Some(device),
});
if locks.len() >= val {
break;
}
}
return GPULock(locks);
}
Ok(0) => {
info!("BELLPERSON_GPUS_PER_LOCK == 0, no lock acquired");
return GPULock(Vec::new());
}
Ok(val) => warn!(
"BELLPERSON_GPUS_PER_LOCK has invalid value {}, using all gpus",
val,
),
Err(_) => warn!("BELLPERSON_GPUS_PER_LOCK parsing failed, using all gpus"),
};
}
info!("BELLPERSON_GPUS_PER_LOCK fallback to single lock mode");
let path = tmp_path(GPU_LOCK_NAME, None);
debug!("Acquiring GPU lock at {:?} ...", &path);
let file = File::create(&path).unwrap_or_else(|_| {
panic!("Cannot create GPU lock file at {:?}", &path);
});
file.lock_exclusive().unwrap();
debug!("GPU lock acquired at {:?}", path);
GPULock(vec![LockInfo {
file,
path,
device: None,
}])
}
fn devices(&self) -> Vec<&'a Device> {
if self.0.is_empty() || (self.0.len() == 1 && self.0[0].device.is_none()) {
Device::all()
} else {
self.0
.iter()
.filter_map(|&LockInfo { device, .. }| device)
.collect()
}
}
}
impl Drop for GPULock<'_> {
fn drop(&mut self) {
for lock_info in &self.0 {
lock_info.file.unlock().unwrap();
debug!("GPU lock released at {:?}", lock_info.path);
}
}
}
#[derive(Debug)]
pub(crate) struct PriorityLock(File);
impl PriorityLock {
#[allow(dead_code)]
pub fn lock() -> PriorityLock {
let priority_lock_file = tmp_path(PRIORITY_LOCK_NAME, None);
debug!("Acquiring priority lock at {:?} ...", &priority_lock_file);
let f = File::create(&priority_lock_file).unwrap_or_else(|_| {
panic!(
"Cannot create priority lock file at {:?}",
&priority_lock_file
)
});
f.lock_exclusive().unwrap();
debug!("Priority lock acquired!");
PriorityLock(f)
}
fn wait(priority: bool) {
if !priority
&& let Err(err) = File::create(tmp_path(PRIORITY_LOCK_NAME, None))
.unwrap()
.lock_exclusive()
{
warn!("failed to create priority log: {:?}", err);
}
}
fn is_taken() -> bool {
if let Err(err) = File::create(tmp_path(PRIORITY_LOCK_NAME, None))
.unwrap()
.try_lock_shared()
{
if matches!(err, std::fs::TryLockError::WouldBlock) {
return true;
}
warn!("failed to check lock: {:?}", err);
}
false
}
}
impl Drop for PriorityLock {
fn drop(&mut self) {
self.0.unlock().unwrap();
debug!("Priority lock released!");
}
}
fn create_fft_kernel<'a, F>(priority: bool) -> Option<(FftKernel<'a, F>, GPULock<'a>)>
where
F: Field + GpuName,
{
let lock = GPULock::lock();
let programs = lock
.devices()
.iter()
.map(|device| ec_gpu_gen::program!(device))
.collect::<Result<_, _>>()
.ok()?;
let kernel = if priority {
FftKernel::create(programs)
} else {
FftKernel::create_with_abort(programs, &PriorityLock::is_taken)
};
match kernel {
Ok(k) => {
info!("GPU FFT kernel instantiated!");
Some((k, lock))
}
Err(e) => {
warn!("Cannot instantiate GPU FFT kernel! Error: {}", e);
None
}
}
}
fn create_multiexp_kernel<'a, G>(
priority: bool,
) -> Option<(CpuGpuMultiexpKernel<'a, G>, GPULock<'a>)>
where
G: PrimeCurveAffine + GpuName,
{
let lock = GPULock::lock();
let devices = lock.devices();
let kernel = if priority {
CpuGpuMultiexpKernel::create(&devices)
} else {
CpuGpuMultiexpKernel::create_with_abort(&devices, &PriorityLock::is_taken)
};
match kernel {
Ok(k) => {
info!("GPU Multiexp kernel instantiated!");
Some((k, lock))
}
Err(e) => {
warn!("Cannot instantiate GPU Multiexp kernel! Error: {}", e);
None
}
}
}
macro_rules! locked_kernel {
($kernel:ty, $func:ident, $name:expr, pub struct $class:ident<$lifetime:lifetime, $generic:ident>
where $(
$bound:ty: $boundvalue:tt $(+ $morebounds:tt )*,
)+
) => {
pub struct $class<$lifetime, $generic>
where $(
$bound : $boundvalue $(+ $morebounds)*,
)+
{
priority: bool,
kernel_and_lock: Option<($kernel, GPULock<'a>)>,
}
impl<'a, $generic> $class<$lifetime, $generic>
where $(
$bound: $boundvalue $(+ $morebounds)*,
)+
{
pub fn new(priority: bool) -> Self {
Self {
priority,
kernel_and_lock: None,
}
}
fn init(&mut self) {
if self.kernel_and_lock.is_none() {
PriorityLock::wait(self.priority);
info!("GPU is available for {}!", $name);
if let Some((kernel, lock)) = $func(self.priority) {
self.kernel_and_lock = Some((kernel, lock));
}
}
}
fn free(&mut self) {
if let Some(_) = self.kernel_and_lock.take() {
warn!(
"GPU acquired by a high priority process! Freeing up {} kernels...",
$name
);
}
}
pub fn with<Fun, R>(&mut self, mut f: Fun) -> GpuResult<R>
where
Fun: FnMut(&mut $kernel) -> GpuResult<R>,
{
if let Ok(bellman_no_gpu) = std::env::var("BELLMAN_NO_GPU") {
if bellman_no_gpu != "0" {
return Err(GpuError::GpuDisabled);
}
}
loop {
self.init();
if let Some((ref mut k, ref _gpu_lock)) = self.kernel_and_lock {
match f(k) {
Err(GpuError::EcGpu(EcError::Aborted)) => {
self.free();
}
Err(e) => {
warn!("GPU {} failed! Falling back to CPU... Error: {}", $name, e);
return Err(e);
}
Ok(v) => return Ok(v),
}
} else {
return Err(GpuError::KernelUninitialized);
}
}
}
}
};
}
locked_kernel!(
FftKernel<'a, F>,
create_fft_kernel,
"FFT",
pub struct LockedFftKernel<'a, F> where F: Field + GpuName,
);
locked_kernel!(
CpuGpuMultiexpKernel<'a, G>,
create_multiexp_kernel,
"Multiexp",
pub struct LockedMultiexpKernel<'a, G>
where
G: PrimeCurveAffine + GpuName,
);