support multiple gpus

This commit is contained in:
illuzen
2025-12-13 17:38:24 +08:00
parent 22fecf183e
commit 52318aeaad
3 changed files with 88 additions and 15 deletions
+15
View File
@@ -89,6 +89,9 @@ pub trait MinerEngine: Send + Sync {
/// - Return `Exhausted` if the range is fully searched without a solution.
/// - Return `Cancelled` if `cancel` was observed during the search.
fn search_range(&self, ctx: &JobContext, range: Range, cancel: &AtomicBool) -> EngineStatus;
/// Enable downcasting to concrete engine types.
fn as_any(&self) -> &dyn std::any::Any;
}
/// Baseline CPU engine.
@@ -113,6 +116,10 @@ impl MinerEngine for BaselineCpuEngine {
JobContext::new(header_hash, difficulty)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn search_range(&self, ctx: &JobContext, range: Range, cancel: &AtomicBool) -> EngineStatus {
// Ensure start <= end (inclusive range). If not, treat as exhausted.
if range.start > range.end {
@@ -183,6 +190,10 @@ impl MinerEngine for FastCpuEngine {
JobContext::new(header_hash, difficulty)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn search_range(&self, ctx: &JobContext, range: Range, cancel: &AtomicBool) -> EngineStatus {
use pow_core::{hash_from_nonce, is_valid_hash, step_nonce};
@@ -274,6 +285,10 @@ impl MinerEngine for ChainManipulatorEngine {
ctx
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn search_range(&self, ctx: &JobContext, range: Range, cancel: &AtomicBool) -> EngineStatus {
use pow_core::{hash_from_nonce, is_valid_hash, step_nonce};
+50 -6
View File
@@ -5,8 +5,11 @@ use engine_cpu::{Candidate, EngineStatus, FoundOrigin, MinerEngine, Range};
use futures::executor::block_on;
use pow_core::JobContext;
use primitive_types::U512;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::cell::RefCell;
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc,
};
/// Represents a single GPU device context.
struct GpuContext {
@@ -28,6 +31,12 @@ struct GpuContext {
pub struct GpuEngine {
contexts: Vec<Arc<GpuContext>>,
device_counter: AtomicUsize,
}
// Thread-local storage for consistent GPU device assignment per worker thread
thread_local! {
static ASSIGNED_GPU_DEVICE: RefCell<Option<usize>> = RefCell::new(None);
}
impl Default for GpuEngine {
@@ -252,7 +261,15 @@ impl GpuEngine {
}
}
Ok(Self { contexts })
Ok(Self {
contexts,
device_counter: AtomicUsize::new(0),
})
}
/// Returns the number of GPU devices available
pub fn device_count(&self) -> usize {
self.contexts.len()
}
}
@@ -265,6 +282,10 @@ impl MinerEngine for GpuEngine {
JobContext::new(header_hash, difficulty)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn search_range(&self, ctx: &JobContext, range: Range, cancel: &AtomicBool) -> EngineStatus {
if self.contexts.is_empty() {
log::warn!("No GPUs available for search.");
@@ -276,10 +297,33 @@ impl MinerEngine for GpuEngine {
return EngineStatus::Exhausted { hash_count: 0 };
}
// Use first GPU only for now to simplify and avoid threading overhead
let gpu_ctx = &self.contexts[0];
// Use thread-local assignment for consistent worker-to-GPU mapping
let device_index = ASSIGNED_GPU_DEVICE.with(|assigned| {
let mut assigned_ref = assigned.borrow_mut();
if let Some(index) = *assigned_ref {
// This thread already has a GPU assigned
index
} else {
// First time this thread is calling search_range, assign a GPU device
let index = if self.contexts.len() == 1 {
0
} else {
self.device_counter.fetch_add(1, Ordering::SeqCst) % self.contexts.len()
};
*assigned_ref = Some(index);
log::info!(
"Worker thread assigned to GPU device {} (of {} total devices)",
index,
self.contexts.len()
);
index
}
});
let gpu_ctx = &self.contexts[device_index];
log::debug!(
"Starting GPU search on device 0 for nonce range {}..={} (inclusive)",
"GPU device {} processing range {}..={} (inclusive)",
device_index,
range.start,
range.end
);
+23 -9
View File
@@ -1276,15 +1276,6 @@ pub async fn run(config: ServiceConfig) -> anyhow::Result<()> {
workers = effective_cpus.max(1);
}
// Force workers=1 for GPU engine to prevent concurrent buffer mapping panics
if matches!(config.engine, EngineSelection::Gpu) && workers > 1 {
log::info!(
"GPU engine selected. Forcing workers to 1 (was {}) to avoid buffer contention.",
workers
);
workers = 1;
}
log::info!(
"Using {workers} worker thread(s) for mining (effective logical CPUs available: {effective_cpus})"
);
@@ -1338,6 +1329,29 @@ pub async fn run(config: ServiceConfig) -> anyhow::Result<()> {
EngineSelection::Gpu => "High-performance GPU mining",
}
);
// Adjust worker count for GPU engine based on available devices
if matches!(config.engine, EngineSelection::Gpu) {
#[cfg(feature = "gpu")]
{
if let Some(gpu_engine) = engine.as_any().downcast_ref::<engine_gpu::GpuEngine>() {
let gpu_device_count = gpu_engine.device_count();
if gpu_device_count > 0 {
// Use one worker per GPU device, but don't exceed the configured worker count
workers = workers.min(gpu_device_count);
log::info!(
"GPU engine: {} GPU devices detected, using {} workers",
gpu_device_count,
workers
);
} else {
log::warn!("GPU engine: No GPU devices detected, falling back to 1 worker");
workers = 1;
}
}
}
}
log::info!("⚙️ Service configuration: {config}");
let progress_interval_ms = config.progress_interval_ms.unwrap_or(10000);