Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15edf4a806 | ||
|
|
a47db3d1a2 | ||
|
|
8359892406 | ||
|
|
d99be29a64 | ||
|
|
7296c003bb | ||
|
|
d37f1dc0e5 | ||
|
|
4d53fff54b | ||
|
|
18e8539fa3 | ||
|
|
4ab6914331 |
22
.github/workflows/docker-image.yml
vendored
22
.github/workflows/docker-image.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
description: "Optional: Specify a full version (e.g., v0.3.0 or v0.3.1-beta.1) to build. If empty, uses the latest release tag. MUST start with 'v'."
|
||||
required: false
|
||||
default: ""
|
||||
use_main_dockerfile:
|
||||
description: "Optional: Use the main Dockerfile instead of the tag version-specific Dockerfile. If empty, uses the tag version-specific Dockerfile."
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -96,19 +101,8 @@ jobs:
|
||||
with:
|
||||
ref: ${{ env.TARGET_VERSION_WITH_V }}
|
||||
|
||||
- name: Check if Dockerfile exists in tag
|
||||
id: check_dockerfile
|
||||
run: |
|
||||
if [ ! -f Dockerfile ]; then
|
||||
echo "::warning::Dockerfile not found in tag ${{ env.TARGET_VERSION_WITH_V }}, falling back to main branch Dockerfile"
|
||||
echo "use_main=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Dockerfile found in tag"
|
||||
echo "use_main=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Checkout main branch for Dockerfile (if needed)
|
||||
if: steps.check_dockerfile.outputs.use_main == 'true'
|
||||
if: github.event.inputs.use_main_dockerfile == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
@@ -119,13 +113,13 @@ jobs:
|
||||
path: dockerfile-source
|
||||
|
||||
- name: Copy Dockerfile from main (if needed)
|
||||
if: steps.check_dockerfile.outputs.use_main == 'true'
|
||||
if: github.event.inputs.use_main_dockerfile == 'true'
|
||||
run: |
|
||||
cp dockerfile-source/Dockerfile ./
|
||||
[ -f dockerfile-source/.dockerignore ] && cp dockerfile-source/.dockerignore ./ || true
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -571,7 +571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "engine-cpu"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"hex",
|
||||
@@ -582,7 +582,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "engine-gpu"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"criterion",
|
||||
@@ -599,6 +599,8 @@ dependencies = [
|
||||
"qp-poseidon-core",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"regex",
|
||||
"tokio",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
@@ -1468,7 +1470,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "metrics"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
@@ -1496,7 +1498,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miner-cli"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"engine-cpu",
|
||||
@@ -1513,7 +1515,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miner-service"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam-channel",
|
||||
@@ -1534,7 +1536,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miner-telemetry"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -2018,7 +2020,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pow-core"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"primitive-types 0.13.1",
|
||||
|
||||
@@ -15,7 +15,7 @@ resolver = "2"
|
||||
edition = "2021"
|
||||
authors = ["Quantus Network"]
|
||||
description = "Quantus External Miner Workspace"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1"
|
||||
|
||||
@@ -18,7 +18,6 @@ WORKDIR /build
|
||||
# Copy workspace files
|
||||
COPY Cargo.toml Cargo.lock rust-toolchain taplo.toml ./
|
||||
COPY crates ./crates
|
||||
COPY tests ./tests
|
||||
|
||||
# Build the miner-cli in release mode
|
||||
RUN cargo build --release -p miner-cli --locked
|
||||
|
||||
@@ -51,6 +51,10 @@ pub enum EngineStatus {
|
||||
Cancelled {
|
||||
hash_count: u64,
|
||||
},
|
||||
/// GPU device lost or unresponsive. Worker should exit permanently.
|
||||
DeviceLost {
|
||||
hash_count: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Cancellation checker passed to search_range.
|
||||
|
||||
@@ -27,17 +27,20 @@ qp-poseidon-constants = "1.1"
|
||||
qp-plonky2-field = { version = "1.1.1" }
|
||||
plonky2 = { package = "qp-plonky2", version = "1.1.3" }
|
||||
|
||||
wgpu = { version = "27.0.1" } # GPU compute library
|
||||
futures = "0.3" # For async executor
|
||||
bytemuck = "1.16" # For buffer mapping
|
||||
wgpu = { version = "27.0.1" } # GPU compute library
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "time"] } # Async runtime with timeout support
|
||||
futures = "0.3" # For async oneshot channels in test modules
|
||||
bytemuck = "1.16" # For buffer mapping
|
||||
rand = { workspace = true, features = ["std", "std_rng"] }
|
||||
rand_chacha = "0.9" # For deterministic random test generation
|
||||
rand_chacha = "0.9" # For deterministic random test generation
|
||||
regex = "1" # For GPU name pattern matching
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { workspace = true }
|
||||
criterion = "0.5"
|
||||
rand = { workspace = true, features = ["std", "std_rng"] }
|
||||
env_logger.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "time"] } # Add macros for #[tokio::main]
|
||||
|
||||
[[bench]]
|
||||
name = "gpu_engine_bench"
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::atomic::AtomicBool;
|
||||
|
||||
fn bench_cpu_vs_gpu_small(c: &mut Criterion) {
|
||||
let cpu_engine = FastCpuEngine::new(10_000);
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let cancel_check = AtomicBoolCancelCheck(&cancel_flag);
|
||||
|
||||
@@ -59,7 +59,7 @@ fn bench_cpu_vs_gpu_small(c: &mut Criterion) {
|
||||
|
||||
fn bench_cpu_vs_gpu_medium(c: &mut Criterion) {
|
||||
let cpu_engine = FastCpuEngine::new(10_000);
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let cancel_check = AtomicBoolCancelCheck(&cancel_flag);
|
||||
|
||||
@@ -110,7 +110,7 @@ fn bench_cpu_vs_gpu_medium(c: &mut Criterion) {
|
||||
|
||||
fn bench_cpu_vs_gpu_large(c: &mut Criterion) {
|
||||
let cpu_engine = FastCpuEngine::new(10_000);
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let cancel_check = AtomicBoolCancelCheck(&cancel_flag);
|
||||
|
||||
@@ -161,7 +161,7 @@ fn bench_cpu_vs_gpu_large(c: &mut Criterion) {
|
||||
|
||||
fn bench_solution_finding(c: &mut Criterion) {
|
||||
let cpu_engine = FastCpuEngine::new(10_000);
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let cancel_check = AtomicBoolCancelCheck(&cancel_flag);
|
||||
|
||||
@@ -212,7 +212,7 @@ fn bench_solution_finding(c: &mut Criterion) {
|
||||
|
||||
fn bench_throughput_per_second(c: &mut Criterion) {
|
||||
let cpu_engine = FastCpuEngine::new(10_000);
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let cancel_check = AtomicBoolCancelCheck(&cancel_flag);
|
||||
|
||||
@@ -262,7 +262,7 @@ fn bench_throughput_per_second(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn bench_gpu_batch_efficiency(c: &mut Criterion) {
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let cancel_check = AtomicBoolCancelCheck(&cancel_flag);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ fn main() {
|
||||
|
||||
// 3. Verify with GPU engine
|
||||
log::info!("Initializing GPU engine...");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0).expect("Failed to init GPU");
|
||||
let gpu_engine = GpuEngine::try_new(10_000_000, 0, false).expect("Failed to init GPU");
|
||||
|
||||
// Search a small range around the valid nonce
|
||||
let gpu_range = Range {
|
||||
@@ -56,6 +56,9 @@ fn main() {
|
||||
EngineStatus::Cancelled { .. } => {
|
||||
log::error!("FAILURE: GPU search cancelled!");
|
||||
}
|
||||
EngineStatus::DeviceLost { .. } => {
|
||||
log::error!("FAILURE: GPU device lost!");
|
||||
}
|
||||
EngineStatus::Running { .. } => {
|
||||
log::error!("FAILURE: GPU returned Running status!");
|
||||
}
|
||||
|
||||
723
crates/engine-gpu/src/gpu_tiers.rs
Normal file
723
crates/engine-gpu/src/gpu_tiers.rs
Normal file
@@ -0,0 +1,723 @@
|
||||
//! GPU tier detection using regex patterns
|
||||
//!
|
||||
//! This module provides a table-driven approach to GPU detection,
|
||||
//! using regex patterns with word boundaries to avoid substring matching issues.
|
||||
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// GPU tier configuration
|
||||
struct GpuTier {
|
||||
/// Regex pattern to match GPU name (case-insensitive)
|
||||
pattern: &'static str,
|
||||
/// Human-readable tier name
|
||||
name: &'static str,
|
||||
/// Divisor for max_workgroups (higher = more conservative)
|
||||
workgroup_divisor: u32,
|
||||
/// Minimum workgroups to use
|
||||
min_workgroups: u32,
|
||||
}
|
||||
|
||||
/// Compiled GPU tier with regex
|
||||
struct CompiledGpuTier {
|
||||
regex: Regex,
|
||||
name: &'static str,
|
||||
workgroup_divisor: u32,
|
||||
min_workgroups: u32,
|
||||
}
|
||||
|
||||
impl CompiledGpuTier {
|
||||
fn from_tier(tier: &GpuTier) -> Self {
|
||||
Self {
|
||||
regex: Regex::new(&format!("(?i){}", tier.pattern)).expect("Invalid GPU tier regex"),
|
||||
name: tier.name,
|
||||
workgroup_divisor: tier.workgroup_divisor,
|
||||
min_workgroups: tier.min_workgroups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU tiers are checked in order - first match wins
|
||||
// Use word boundaries (\b) to avoid substring issues like "550" matching "5500"
|
||||
|
||||
const NVIDIA_TIERS: &[GpuTier] = &[
|
||||
// Blackwell (RTX 50 series)
|
||||
GpuTier {
|
||||
pattern: r"\b50[89]0\b",
|
||||
name: "NVIDIA RTX 50 Flagship (Blackwell)",
|
||||
workgroup_divisor: 6,
|
||||
min_workgroups: 5120,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b50[67]0\b|rtx 50",
|
||||
name: "NVIDIA RTX 50 (Blackwell)",
|
||||
workgroup_divisor: 7,
|
||||
min_workgroups: 4608,
|
||||
},
|
||||
// Ada Lovelace (RTX 40 series)
|
||||
GpuTier {
|
||||
pattern: r"\b40[89]0\b",
|
||||
name: "NVIDIA RTX 40 Flagship (Ada)",
|
||||
workgroup_divisor: 8,
|
||||
min_workgroups: 4096,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b40[67]0\b|rtx 40",
|
||||
name: "NVIDIA RTX 40 (Ada)",
|
||||
workgroup_divisor: 10,
|
||||
min_workgroups: 3072,
|
||||
},
|
||||
// Ampere/Turing (RTX 30/20 series)
|
||||
GpuTier {
|
||||
pattern: r"\b30[5-9]0\b|\b20[6-8]0\b|rtx 30|rtx 20",
|
||||
name: "NVIDIA RTX 30/20 (Ampere/Turing)",
|
||||
workgroup_divisor: 12,
|
||||
min_workgroups: 2048,
|
||||
},
|
||||
// Turing/Pascal (GTX 16/10 series)
|
||||
GpuTier {
|
||||
pattern: r"\b16[56]0\b|\b10[3-8]0\b|gtx 16|gtx 10",
|
||||
name: "NVIDIA GTX 16/10 (Turing/Pascal)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1024,
|
||||
},
|
||||
// Maxwell (GTX 900 series)
|
||||
GpuTier {
|
||||
pattern: r"\b9[5-8]0\b|gtx 9",
|
||||
name: "NVIDIA GTX 900 (Maxwell)",
|
||||
workgroup_divisor: 18,
|
||||
min_workgroups: 768,
|
||||
},
|
||||
// Kepler/Maxwell (GTX 700 series)
|
||||
GpuTier {
|
||||
pattern: r"\b7[5-8]0\b|gtx 7",
|
||||
name: "NVIDIA GTX 700 (Kepler/Maxwell)",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
// Legacy GTX
|
||||
GpuTier {
|
||||
pattern: r"gtx [456]",
|
||||
name: "NVIDIA GTX Legacy (Fermi/Kepler)",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 384,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\bgtx\b",
|
||||
name: "NVIDIA GTX (Unknown)",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
// Mobile
|
||||
GpuTier {
|
||||
pattern: r"\bmx[1-5]\d0|geforce mx",
|
||||
name: "NVIDIA MX (Mobile)",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 384,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"geforce gt\b|\bgt \d{3}\b",
|
||||
name: "NVIDIA GT (Entry-Level)",
|
||||
workgroup_divisor: 28,
|
||||
min_workgroups: 256,
|
||||
},
|
||||
// Professional
|
||||
GpuTier {
|
||||
pattern: r"quadro|rtx a\d|tesla|\ba100\b|\bh100\b|\bl4\b",
|
||||
name: "NVIDIA Quadro/Professional",
|
||||
workgroup_divisor: 10,
|
||||
min_workgroups: 2560,
|
||||
},
|
||||
];
|
||||
|
||||
const AMD_TIERS: &[GpuTier] = &[
|
||||
// RDNA 4 (RX 9000 series)
|
||||
GpuTier {
|
||||
pattern: r"rx 9|\b90[78]0\b",
|
||||
name: "AMD RX 9000 (RDNA 4)",
|
||||
workgroup_divisor: 8,
|
||||
min_workgroups: 4096,
|
||||
},
|
||||
// RDNA 3 Discrete
|
||||
GpuTier {
|
||||
pattern: r"\b7900\b",
|
||||
name: "AMD RX 7900 (RDNA 3 Flagship)",
|
||||
workgroup_divisor: 9,
|
||||
min_workgroups: 3584,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"rx 7|\b7[6-8]00\b",
|
||||
name: "AMD RX 7000 (RDNA 3)",
|
||||
workgroup_divisor: 10,
|
||||
min_workgroups: 3072,
|
||||
},
|
||||
// RDNA 3 APUs - check before discrete to avoid substring match
|
||||
GpuTier {
|
||||
pattern: r"\b780m\b|radeon 780m",
|
||||
name: "AMD Radeon 780M (RDNA 3 APU)",
|
||||
workgroup_divisor: 12,
|
||||
min_workgroups: 2048,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b7[46]0m\b|radeon 7[46]0m",
|
||||
name: "AMD Radeon 7x0M (RDNA 3 APU)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1024,
|
||||
},
|
||||
// RDNA 2 Discrete
|
||||
GpuTier {
|
||||
pattern: r"\b6[89][05]0\b",
|
||||
name: "AMD RX 6900/6800 (RDNA 2 Flagship)",
|
||||
workgroup_divisor: 12,
|
||||
min_workgroups: 2560,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b6[67][05]0\b",
|
||||
name: "AMD RX 6700/6600 (RDNA 2)",
|
||||
workgroup_divisor: 14,
|
||||
min_workgroups: 2048,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b6[45]00\b",
|
||||
name: "AMD RX 6500/6400 (RDNA 2 Entry)",
|
||||
workgroup_divisor: 22,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"rx 6\d{3}",
|
||||
name: "AMD RX 6000 (RDNA 2)",
|
||||
workgroup_divisor: 14,
|
||||
min_workgroups: 2048,
|
||||
},
|
||||
// RDNA 2 APUs - check before discrete
|
||||
GpuTier {
|
||||
pattern: r"\b680m\b|radeon 680m",
|
||||
name: "AMD Radeon 680M (RDNA 2 APU)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1536,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b6[16]0m\b|radeon 6[16]0m",
|
||||
name: "AMD Radeon 6x0M (RDNA 2 APU)",
|
||||
workgroup_divisor: 22,
|
||||
min_workgroups: 768,
|
||||
},
|
||||
// RDNA 1 (RX 5000 series) - 4-digit patterns to avoid matching Polaris 3-digit
|
||||
GpuTier {
|
||||
pattern: r"\b5700\b",
|
||||
name: "AMD RX 5700 (RDNA 1)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1536,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\b5[56]00\b|rx 5\d{3}",
|
||||
name: "AMD RX 5000 (RDNA 1)",
|
||||
workgroup_divisor: 18,
|
||||
min_workgroups: 1024,
|
||||
},
|
||||
// Polaris (RX 400/500 series) - 3-digit models with boundaries
|
||||
GpuTier {
|
||||
pattern: r"rx [45]\d0\b|\b[45][6-9]0\b|\b590\b|rx 5.0x",
|
||||
name: "AMD RX 500/400 (Polaris)",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 768,
|
||||
},
|
||||
// Vega
|
||||
GpuTier {
|
||||
pattern: r"radeon vii\b",
|
||||
name: "AMD Radeon VII (Vega 20)",
|
||||
workgroup_divisor: 12,
|
||||
min_workgroups: 2048,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"vega\s*64",
|
||||
name: "AMD Vega 64 (Discrete)",
|
||||
workgroup_divisor: 14,
|
||||
min_workgroups: 1536,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"vega\s*56",
|
||||
name: "AMD Vega 56 (Discrete)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1280,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"vega",
|
||||
name: "AMD Vega (APU)",
|
||||
workgroup_divisor: 28,
|
||||
min_workgroups: 384,
|
||||
},
|
||||
// GCN
|
||||
GpuTier {
|
||||
pattern: r"fury|nano",
|
||||
name: "AMD R9 Fury/Nano (Fiji)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1280,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"r9.*(3[89]0|2[89]0)|\b[23][89]0x?\b",
|
||||
name: "AMD R9 (GCN)",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 768,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"r7.*(3[67]0|2[67]0)|\b[23][67]0x?\b",
|
||||
name: "AMD R7 (GCN)",
|
||||
workgroup_divisor: 22,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
// Professional
|
||||
GpuTier {
|
||||
pattern: r"radeon pro|instinct mi|mi[123]\d0|firepro|w[567]\d{3}",
|
||||
name: "AMD Radeon Pro/Instinct",
|
||||
workgroup_divisor: 10,
|
||||
min_workgroups: 2560,
|
||||
},
|
||||
// OEM/APU fallbacks
|
||||
GpuTier {
|
||||
pattern: r"radeon\s*\(tm\)\s*[67]\d{2}\b|radeon [67]\d{2}\b",
|
||||
name: "AMD Radeon OEM (Polaris Rebrand)",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"radeon\s*(\(tm\)\s*)?graphics",
|
||||
name: "AMD Radeon Graphics (APU)",
|
||||
workgroup_divisor: 26,
|
||||
min_workgroups: 384,
|
||||
},
|
||||
];
|
||||
|
||||
const INTEL_TIERS: &[GpuTier] = &[
|
||||
// Battlemage (Arc B-Series)
|
||||
GpuTier {
|
||||
pattern: r"arc b|\bb5[78]0\b",
|
||||
name: "Intel Arc B-Series (Battlemage)",
|
||||
workgroup_divisor: 10,
|
||||
min_workgroups: 2560,
|
||||
},
|
||||
// Alchemist Mobile - check BEFORE desktop (a770m contains a770)
|
||||
GpuTier {
|
||||
pattern: r"\ba7[37]0m\b",
|
||||
name: "Intel Arc A7 Mobile (Alchemist)",
|
||||
workgroup_divisor: 14,
|
||||
min_workgroups: 1536,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\ba5[57]0m\b",
|
||||
name: "Intel Arc A5 Mobile (Alchemist)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1024,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\ba3[57]0m\b",
|
||||
name: "Intel Arc A3 Mobile (Alchemist)",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
// Alchemist Desktop
|
||||
GpuTier {
|
||||
pattern: r"\ba7[57]0\b",
|
||||
name: "Intel Arc A7 (Alchemist)",
|
||||
workgroup_divisor: 12,
|
||||
min_workgroups: 2048,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\ba580\b",
|
||||
name: "Intel Arc A5 (Alchemist)",
|
||||
workgroup_divisor: 14,
|
||||
min_workgroups: 1536,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\ba3[18]0\b",
|
||||
name: "Intel Arc A3 (Alchemist)",
|
||||
workgroup_divisor: 18,
|
||||
min_workgroups: 768,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"arc a|\barc\b",
|
||||
name: "Intel Arc (Unknown)",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1024,
|
||||
},
|
||||
// Integrated
|
||||
GpuTier {
|
||||
pattern: r"iris xe max",
|
||||
name: "Intel Iris Xe Max (Discrete)",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"iris xe",
|
||||
name: "Intel Iris Xe (Integrated)",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 384,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"iris pro",
|
||||
name: "Intel Iris Pro (Integrated)",
|
||||
workgroup_divisor: 26,
|
||||
min_workgroups: 256,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"iris plus|iris\b",
|
||||
name: "Intel Iris Plus (Integrated)",
|
||||
workgroup_divisor: 26,
|
||||
min_workgroups: 320,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"uhd.*(7\d{2}|graphics 7)",
|
||||
name: "Intel UHD 700 (Integrated)",
|
||||
workgroup_divisor: 26,
|
||||
min_workgroups: 320,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"uhd.*(6\d{2}|graphics 6)",
|
||||
name: "Intel UHD 600 (Integrated)",
|
||||
workgroup_divisor: 28,
|
||||
min_workgroups: 256,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"uhd",
|
||||
name: "Intel UHD Graphics (Integrated)",
|
||||
workgroup_divisor: 28,
|
||||
min_workgroups: 256,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"hd graphics|hd [456]\d{2}",
|
||||
name: "Intel HD Graphics (Integrated)",
|
||||
workgroup_divisor: 30,
|
||||
min_workgroups: 192,
|
||||
},
|
||||
];
|
||||
|
||||
const QUALCOMM_TIERS: &[GpuTier] = &[
|
||||
// Snapdragon X (Adreno X1)
|
||||
GpuTier {
|
||||
pattern: r"x elite|x plus|adreno x|x1-[89]",
|
||||
name: "Qualcomm Adreno X1 (Snapdragon X)",
|
||||
workgroup_divisor: 14,
|
||||
min_workgroups: 1536,
|
||||
},
|
||||
// Adreno 700 series (730, 740, 750, etc.)
|
||||
GpuTier {
|
||||
pattern: r"adreno\s*7[0-9]{2}\b|\b7[345]0\b",
|
||||
name: "Qualcomm Adreno 700 Series",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 1024,
|
||||
},
|
||||
// Adreno 600 series (610, 612, 615, 618, 619, 620, 630, 640, 650, 660, etc.)
|
||||
GpuTier {
|
||||
pattern: r"adreno\s*6[0-9]{2}\b|\b6[1-6][0-9]\b",
|
||||
name: "Qualcomm Adreno 600 Series",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 512,
|
||||
},
|
||||
// Adreno 500 series (505, 506, 508, 509, 510, 512, 530, 540)
|
||||
GpuTier {
|
||||
pattern: r"adreno\s*5[0-4][0-9]\b|\b5[0-4][0-9]\b",
|
||||
name: "Qualcomm Adreno 500 Series",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 384,
|
||||
},
|
||||
];
|
||||
|
||||
const APPLE_TIERS: &[GpuTier] = &[
|
||||
// M4 series
|
||||
GpuTier {
|
||||
pattern: r"m4 ultra",
|
||||
name: "Apple M4 Ultra",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 1600,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m4 max",
|
||||
name: "Apple M4 Max",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 800,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m4 pro",
|
||||
name: "Apple M4 Pro",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 400,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\bm4\b",
|
||||
name: "Apple M4",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 200,
|
||||
},
|
||||
// M3 series
|
||||
GpuTier {
|
||||
pattern: r"m3 ultra",
|
||||
name: "Apple M3 Ultra",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 1520,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m3 max",
|
||||
name: "Apple M3 Max",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 800,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m3 pro",
|
||||
name: "Apple M3 Pro",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 360,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\bm3\b",
|
||||
name: "Apple M3",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 200,
|
||||
},
|
||||
// M2 series
|
||||
GpuTier {
|
||||
pattern: r"m2 ultra",
|
||||
name: "Apple M2 Ultra",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 1520,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m2 max",
|
||||
name: "Apple M2 Max",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 760,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m2 pro",
|
||||
name: "Apple M2 Pro",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 380,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\bm2\b",
|
||||
name: "Apple M2",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 200,
|
||||
},
|
||||
// M1 series
|
||||
GpuTier {
|
||||
pattern: r"m1 ultra",
|
||||
name: "Apple M1 Ultra",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 1280,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m1 max",
|
||||
name: "Apple M1 Max",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 640,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"m1 pro",
|
||||
name: "Apple M1 Pro",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 320,
|
||||
},
|
||||
GpuTier {
|
||||
pattern: r"\bm1\b",
|
||||
name: "Apple M1",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 160,
|
||||
},
|
||||
];
|
||||
|
||||
/// Compiled GPU tier tables (lazily initialized)
|
||||
struct GpuTierTables {
|
||||
nvidia: Vec<CompiledGpuTier>,
|
||||
amd: Vec<CompiledGpuTier>,
|
||||
intel: Vec<CompiledGpuTier>,
|
||||
qualcomm: Vec<CompiledGpuTier>,
|
||||
apple: Vec<CompiledGpuTier>,
|
||||
}
|
||||
|
||||
static GPU_TIERS: LazyLock<GpuTierTables> = LazyLock::new(|| GpuTierTables {
|
||||
nvidia: NVIDIA_TIERS
|
||||
.iter()
|
||||
.map(CompiledGpuTier::from_tier)
|
||||
.collect(),
|
||||
amd: AMD_TIERS.iter().map(CompiledGpuTier::from_tier).collect(),
|
||||
intel: INTEL_TIERS.iter().map(CompiledGpuTier::from_tier).collect(),
|
||||
qualcomm: QUALCOMM_TIERS
|
||||
.iter()
|
||||
.map(CompiledGpuTier::from_tier)
|
||||
.collect(),
|
||||
apple: APPLE_TIERS.iter().map(CompiledGpuTier::from_tier).collect(),
|
||||
});
|
||||
|
||||
/// Result of GPU tier matching
|
||||
pub struct GpuTierMatch {
|
||||
pub name: &'static str,
|
||||
pub workgroup_divisor: u32,
|
||||
pub min_workgroups: u32,
|
||||
pub is_fallback: bool,
|
||||
}
|
||||
|
||||
/// Find matching tier from a list of compiled tiers
|
||||
fn find_matching_tier(name: &str, tiers: &[CompiledGpuTier]) -> Option<GpuTierMatch> {
|
||||
tiers
|
||||
.iter()
|
||||
.find(|tier| tier.regex.is_match(name))
|
||||
.map(|tier| GpuTierMatch {
|
||||
name: tier.name,
|
||||
workgroup_divisor: tier.workgroup_divisor,
|
||||
min_workgroups: tier.min_workgroups,
|
||||
is_fallback: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Detect GPU tier based on adapter info
|
||||
///
|
||||
/// Returns (tier_name, workgroup_divisor, min_workgroups, is_fallback)
|
||||
pub fn detect_gpu_tier(vendor_name: &str, vendor_id: u32, is_metal_backend: bool) -> GpuTierMatch {
|
||||
let name_lower = vendor_name.to_lowercase();
|
||||
|
||||
// Determine vendor and find matching tier
|
||||
if name_lower.contains("nvidia") || vendor_id == 0x10DE {
|
||||
find_matching_tier(&name_lower, &GPU_TIERS.nvidia).unwrap_or(GpuTierMatch {
|
||||
name: "NVIDIA Unknown",
|
||||
workgroup_divisor: 20,
|
||||
min_workgroups: 512,
|
||||
is_fallback: true,
|
||||
})
|
||||
} else if name_lower.contains("amd") || name_lower.contains("radeon") || vendor_id == 0x1002 {
|
||||
find_matching_tier(&name_lower, &GPU_TIERS.amd).unwrap_or(GpuTierMatch {
|
||||
name: "AMD Unknown",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 512,
|
||||
is_fallback: true,
|
||||
})
|
||||
} else if name_lower.contains("intel") || vendor_id == 0x8086 {
|
||||
find_matching_tier(&name_lower, &GPU_TIERS.intel).unwrap_or(GpuTierMatch {
|
||||
name: "Intel Unknown",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 256,
|
||||
is_fallback: true,
|
||||
})
|
||||
} else if name_lower.contains("qualcomm")
|
||||
|| name_lower.contains("adreno")
|
||||
|| vendor_id == 0x5143
|
||||
{
|
||||
find_matching_tier(&name_lower, &GPU_TIERS.qualcomm).unwrap_or(GpuTierMatch {
|
||||
name: "Qualcomm Adreno (Unknown)",
|
||||
workgroup_divisor: 24,
|
||||
min_workgroups: 384,
|
||||
is_fallback: true,
|
||||
})
|
||||
} else if is_metal_backend {
|
||||
// Apple Silicon - detected by Metal backend
|
||||
find_matching_tier(&name_lower, &GPU_TIERS.apple).unwrap_or(GpuTierMatch {
|
||||
name: "Apple Silicon Unknown",
|
||||
workgroup_divisor: 4,
|
||||
min_workgroups: 160,
|
||||
is_fallback: true,
|
||||
})
|
||||
} else {
|
||||
GpuTierMatch {
|
||||
name: "Unknown GPU",
|
||||
workgroup_divisor: 16,
|
||||
min_workgroups: 512,
|
||||
is_fallback: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_nvidia_detection() {
|
||||
// RTX 40 series
|
||||
let tier = detect_gpu_tier("NVIDIA GeForce RTX 4090", 0x10DE, false);
|
||||
assert_eq!(tier.name, "NVIDIA RTX 40 Flagship (Ada)");
|
||||
assert!(!tier.is_fallback);
|
||||
|
||||
let tier = detect_gpu_tier("NVIDIA GeForce RTX 4070 Ti", 0x10DE, false);
|
||||
assert_eq!(tier.name, "NVIDIA RTX 40 (Ada)");
|
||||
|
||||
// RTX 30 series
|
||||
let tier = detect_gpu_tier("NVIDIA GeForce RTX 3080", 0x10DE, false);
|
||||
assert_eq!(tier.name, "NVIDIA RTX 30/20 (Ampere/Turing)");
|
||||
|
||||
// GTX 10 series
|
||||
let tier = detect_gpu_tier("NVIDIA GeForce GTX 1080 Ti", 0x10DE, false);
|
||||
assert_eq!(tier.name, "NVIDIA GTX 16/10 (Turing/Pascal)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_amd_rdna_vs_polaris() {
|
||||
// RDNA 1 - should NOT match Polaris
|
||||
let tier = detect_gpu_tier("AMD Radeon RX 5500 XT", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD RX 5000 (RDNA 1)");
|
||||
assert!(!tier.is_fallback);
|
||||
|
||||
let tier = detect_gpu_tier("AMD Radeon RX 5600 XT", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD RX 5000 (RDNA 1)");
|
||||
|
||||
let tier = detect_gpu_tier("AMD Radeon RX 5700 XT", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD RX 5700 (RDNA 1)");
|
||||
|
||||
// Polaris - should match Polaris
|
||||
let tier = detect_gpu_tier("AMD Radeon RX 580", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD RX 500/400 (Polaris)");
|
||||
|
||||
let tier = detect_gpu_tier("AMD Radeon RX 560X", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD RX 500/400 (Polaris)");
|
||||
|
||||
let tier = detect_gpu_tier("AMD Radeon RX 550", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD RX 500/400 (Polaris)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_amd_vega_apu() {
|
||||
let tier = detect_gpu_tier("AMD Radeon(TM) Vega 8 Graphics", 0x1002, false);
|
||||
assert_eq!(tier.name, "AMD Vega (APU)");
|
||||
assert!(!tier.is_fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel_arc_mobile_vs_desktop() {
|
||||
// Mobile should match mobile tier
|
||||
let tier = detect_gpu_tier("Intel Arc A770M", 0x8086, false);
|
||||
assert_eq!(tier.name, "Intel Arc A7 Mobile (Alchemist)");
|
||||
assert!(!tier.is_fallback);
|
||||
|
||||
// Desktop should match desktop tier
|
||||
let tier = detect_gpu_tier("Intel Arc A770", 0x8086, false);
|
||||
assert_eq!(tier.name, "Intel Arc A7 (Alchemist)");
|
||||
assert!(!tier.is_fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apple_silicon() {
|
||||
let tier = detect_gpu_tier("Apple M1 Pro", 0, true);
|
||||
assert_eq!(tier.name, "Apple M1 Pro");
|
||||
assert!(!tier.is_fallback);
|
||||
|
||||
let tier = detect_gpu_tier("Apple M3 Max", 0, true);
|
||||
assert_eq!(tier.name, "Apple M3 Max");
|
||||
assert!(!tier.is_fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualcomm_adreno_series() {
|
||||
// Adreno 627 should match 600 series, NOT 700 series
|
||||
let tier = detect_gpu_tier("Qualcomm Adreno 627", 0x5143, false);
|
||||
assert_eq!(tier.name, "Qualcomm Adreno 600 Series");
|
||||
assert!(!tier.is_fallback);
|
||||
|
||||
// Adreno 730 should match 700 series
|
||||
let tier = detect_gpu_tier("Qualcomm Adreno 730", 0x5143, false);
|
||||
assert_eq!(tier.name, "Qualcomm Adreno 700 Series");
|
||||
assert!(!tier.is_fallback);
|
||||
|
||||
// Adreno 540 should match 500 series
|
||||
let tier = detect_gpu_tier("Qualcomm Adreno 540", 0x5143, false);
|
||||
assert_eq!(tier.name, "Qualcomm Adreno 500 Series");
|
||||
assert!(!tier.is_fallback);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod gpu_tiers;
|
||||
|
||||
pub mod end_to_end_tests;
|
||||
pub mod tests;
|
||||
|
||||
use engine_cpu::{CancelCheck, Candidate, EngineStatus, FoundOrigin, MinerEngine, Range};
|
||||
use futures::executor::block_on;
|
||||
use pow_core::{format_hashrate, format_u512, JobContext};
|
||||
use primitive_types::U512;
|
||||
use std::cell::RefCell;
|
||||
@@ -43,6 +47,9 @@ pub struct GpuEngine {
|
||||
thread_local! {
|
||||
static ASSIGNED_GPU_DEVICE: RefCell<Option<usize>> = const { RefCell::new(None) };
|
||||
static WORKER_RESOURCES: RefCell<Option<GpuResources>> = const { RefCell::new(None) };
|
||||
/// Set to true when this worker's GPU device is lost/unresponsive.
|
||||
/// Once set, the worker will immediately return Cancelled on any search attempt.
|
||||
static DEVICE_LOST: RefCell<bool> = const { RefCell::new(false) };
|
||||
}
|
||||
|
||||
impl GpuContext {
|
||||
@@ -138,22 +145,149 @@ impl GpuContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rank backends for mining: native compute APIs first.
|
||||
fn backend_rank(backend: wgpu::Backend) -> u8 {
|
||||
match backend {
|
||||
wgpu::Backend::Vulkan | wgpu::Backend::Metal => 0,
|
||||
wgpu::Backend::Dx12 => 1,
|
||||
_ => 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// Select which adapters to mine on, returning indices into `infos` ordered
|
||||
/// discrete-first.
|
||||
///
|
||||
/// wgpu can enumerate the same physical GPU once per backend (Vulkan + DX12 on
|
||||
/// Windows) plus CPU-emulated fallbacks such as "Microsoft Basic Render Driver".
|
||||
/// Mining on every entry causes VRAM contention and OOM (issue #61), so CPU
|
||||
/// adapters are dropped and only the best-ranked backend present is kept.
|
||||
/// Within a single backend each physical GPU appears exactly once, so rigs with
|
||||
/// multiple identical cards keep every card.
|
||||
///
|
||||
/// When discrete GPUs are present, integrated GPUs (APUs) are skipped by default
|
||||
/// to avoid resource contention and driver instability from mining on both
|
||||
/// simultaneously. Set `allow_integrated` to true to override this behavior.
|
||||
///
|
||||
/// Note: This function no longer filters integrated GPUs - that decision is made
|
||||
/// after initialization, so we can fall back to integrated if discrete fails.
|
||||
fn select_adapters(infos: &[wgpu::AdapterInfo]) -> Vec<usize> {
|
||||
let usable: Vec<usize> = (0..infos.len())
|
||||
.filter(|&i| {
|
||||
if infos[i].device_type == wgpu::DeviceType::Cpu {
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"Skipping CPU-emulated adapter: {} ({:?})",
|
||||
infos[i].name,
|
||||
infos[i].backend
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
})
|
||||
.collect();
|
||||
|
||||
let Some(best) = usable.iter().map(|&i| backend_rank(infos[i].backend)).min() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut selected: Vec<usize> = usable
|
||||
.into_iter()
|
||||
.filter(|&i| {
|
||||
if backend_rank(infos[i].backend) != best {
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"Skipping adapter on lower-priority backend: {} ({:?})",
|
||||
infos[i].name,
|
||||
infos[i].backend
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort discrete GPUs first, but keep all adapters for now
|
||||
// (integrated filtering happens after init, based on what actually succeeded)
|
||||
selected.sort_by_key(|&i| match infos[i].device_type {
|
||||
wgpu::DeviceType::DiscreteGpu => 0,
|
||||
wgpu::DeviceType::IntegratedGpu => 1,
|
||||
_ => 2,
|
||||
});
|
||||
selected
|
||||
}
|
||||
|
||||
/// Filter initialized GPUs based on device types.
|
||||
/// Returns indices of GPUs to keep.
|
||||
///
|
||||
/// Rules:
|
||||
/// - If any discrete GPU initialized successfully and `allow_integrated` is false,
|
||||
/// drop all integrated GPUs
|
||||
/// - Otherwise keep all GPUs
|
||||
///
|
||||
/// This is extracted as a pure function for testability.
|
||||
fn filter_initialized_gpus(
|
||||
device_types: &[wgpu::DeviceType],
|
||||
allow_integrated: bool,
|
||||
) -> Vec<usize> {
|
||||
let has_discrete = device_types.contains(&wgpu::DeviceType::DiscreteGpu);
|
||||
|
||||
if has_discrete && !allow_integrated {
|
||||
// Keep only discrete GPUs
|
||||
device_types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &dt)| dt != wgpu::DeviceType::IntegratedGpu)
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
} else {
|
||||
// Keep all
|
||||
(0..device_types.len()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuEngine {
|
||||
/// Try to initialize the GPU engine with the given batch size and throttle (ms between batches).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `batch_size` - Number of nonces per batch
|
||||
/// * `throttle_ms` - Delay between batches in milliseconds (0 = no throttle)
|
||||
/// * `allow_integrated` - If true, use integrated GPUs even when discrete GPUs are available
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if:
|
||||
/// - `batch_size` is zero (no work would be performed)
|
||||
/// - No suitable GPU adapters are found
|
||||
pub fn try_new(batch_size: u32, throttle_ms: u64) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
/// - No usable GPU adapters are found
|
||||
pub fn try_new(
|
||||
batch_size: u32,
|
||||
throttle_ms: u64,
|
||||
allow_integrated: bool,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
if batch_size == 0 {
|
||||
return Err("batch_size must be non-zero".into());
|
||||
}
|
||||
block_on(Self::init(batch_size, throttle_ms))
|
||||
|
||||
// Handle both cases: called from within a tokio runtime or from outside
|
||||
match tokio::runtime::Handle::try_current() {
|
||||
Ok(handle) => {
|
||||
// We're inside a tokio runtime - use block_in_place to allow blocking
|
||||
tokio::task::block_in_place(|| {
|
||||
handle.block_on(Self::init(batch_size, throttle_ms, allow_integrated))
|
||||
})
|
||||
}
|
||||
Err(_) => {
|
||||
// No runtime exists - create a temporary one
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(Self::init(batch_size, throttle_ms, allow_integrated))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn init(batch_size: u32, throttle_ms: u64) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
async fn init(
|
||||
batch_size: u32,
|
||||
throttle_ms: u64,
|
||||
allow_integrated: bool,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
log::info!(target: "gpu_engine", "Initializing WGPU...");
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::PRIMARY,
|
||||
@@ -161,36 +295,80 @@ impl GpuEngine {
|
||||
});
|
||||
|
||||
let adapters = instance.enumerate_adapters(wgpu::Backends::PRIMARY);
|
||||
let infos: Vec<wgpu::AdapterInfo> = adapters.iter().map(|a| a.get_info()).collect();
|
||||
|
||||
// Collect adapters to a vector to check count and iterate with index
|
||||
let adapters: Vec<_> = adapters.into_iter().collect();
|
||||
|
||||
if adapters.is_empty() {
|
||||
log::error!(target: "gpu_engine", "No suitable GPU adapters found.");
|
||||
return Err("No suitable GPU adapters found".into());
|
||||
let selected = select_adapters(&infos);
|
||||
if selected.is_empty() {
|
||||
log::error!(
|
||||
target: "gpu_engine",
|
||||
"No usable GPU adapters found ({} enumerated). Use --gpu-devices 0 to disable GPU mining.",
|
||||
infos.len()
|
||||
);
|
||||
return Err("No usable GPU adapters found".into());
|
||||
}
|
||||
|
||||
let mut contexts = Vec::new();
|
||||
let mut adapter_infos = Vec::new();
|
||||
for (i, adapter) in adapters.into_iter().enumerate() {
|
||||
let info = adapter.get_info();
|
||||
log::debug!(target: "gpu_engine", "Adapter {} raw info: {:?}", i, info);
|
||||
adapter_infos.push(info.clone());
|
||||
let mut adapters: Vec<Option<wgpu::Adapter>> = adapters.into_iter().map(Some).collect();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
label: Some("Mining Device"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
memory_hints: Default::default(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
// Track successfully initialized contexts with their device type
|
||||
struct InitializedGpu {
|
||||
context: Arc<GpuContext>,
|
||||
device_type: wgpu::DeviceType,
|
||||
name: String,
|
||||
}
|
||||
let mut initialized: Vec<InitializedGpu> = Vec::new();
|
||||
|
||||
// Timeout for initializing each adapter (30 seconds should be plenty)
|
||||
let init_timeout = std::time::Duration::from_secs(30);
|
||||
|
||||
for (i, idx) in selected.into_iter().enumerate() {
|
||||
let adapter = adapters[idx].take().expect("adapter selected exactly once");
|
||||
let info = &infos[idx];
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"Initializing GPU device {i}: {} ({:?}, {:?})",
|
||||
info.name,
|
||||
info.device_type,
|
||||
info.backend
|
||||
);
|
||||
log::debug!(target: "gpu_engine", "Adapter {i} raw info: {info:?}");
|
||||
|
||||
// Try to initialize this adapter with a proper timeout.
|
||||
// If the driver hangs, we'll skip this adapter after the timeout.
|
||||
let device_future = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
label: Some("Mining Device"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
memory_hints: Default::default(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let device_result = match tokio::time::timeout(init_timeout, device_future).await {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
log::warn!(
|
||||
target: "gpu_engine",
|
||||
"GPU device {i} ({}) timed out after {}s during initialization, skipping",
|
||||
info.name, init_timeout.as_secs()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let (device, queue) = match device_result {
|
||||
Ok(dq) => dq,
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
target: "gpu_engine",
|
||||
"Failed to initialize GPU device {i} ({}): {}. Skipping.",
|
||||
info.name, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Log device limits at debug level
|
||||
let limits = device.limits();
|
||||
log::debug!(target: "gpu_engine", "Adapter {} limits: max_workgroups={}, max_workgroup_size={}x{}x{}, max_buffer={}",
|
||||
i,
|
||||
log::debug!(target: "gpu_engine", "GPU device {i} limits: max_workgroups={}, max_workgroup_size={}x{}x{}, max_buffer={}",
|
||||
limits.max_compute_workgroups_per_dimension,
|
||||
limits.max_compute_workgroup_size_x,
|
||||
limits.max_compute_workgroup_size_y,
|
||||
@@ -198,6 +376,9 @@ impl GpuEngine {
|
||||
limits.max_buffer_size
|
||||
);
|
||||
|
||||
// Shader and pipeline creation are synchronous - can't timeout, but usually fast
|
||||
let pipeline_start = std::time::Instant::now();
|
||||
|
||||
let shader_source = include_str!("mining.wgsl");
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Mining Shader"),
|
||||
@@ -213,19 +394,54 @@ impl GpuEngine {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
log::debug!(target: "gpu_engine", "Pipeline initialized for adapter {}", i);
|
||||
let pipeline_elapsed = pipeline_start.elapsed();
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"GPU device {i} ({}) initialized successfully (pipeline compiled in {:.1}s)",
|
||||
info.name, pipeline_elapsed.as_secs_f64()
|
||||
);
|
||||
|
||||
// Calculate vendor-specific configuration once during initialization
|
||||
let optimal_workgroups = get_vendor_specific_dispatch(&info, &device);
|
||||
let optimal_workgroups = get_vendor_specific_dispatch(info, &device);
|
||||
|
||||
contexts.push(Arc::new(GpuContext {
|
||||
device,
|
||||
queue,
|
||||
pipeline,
|
||||
optimal_workgroups,
|
||||
}));
|
||||
initialized.push(InitializedGpu {
|
||||
context: Arc::new(GpuContext {
|
||||
device,
|
||||
queue,
|
||||
pipeline,
|
||||
optimal_workgroups,
|
||||
}),
|
||||
device_type: info.device_type,
|
||||
name: info.name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if initialized.is_empty() {
|
||||
log::error!(target: "gpu_engine", "No GPU adapters could be initialized successfully.");
|
||||
return Err("No GPU adapters could be initialized".into());
|
||||
}
|
||||
|
||||
// Filter integrated GPUs if discrete GPUs successfully initialized
|
||||
let device_types: Vec<_> = initialized.iter().map(|g| g.device_type).collect();
|
||||
let keep_indices = filter_initialized_gpus(&device_types, allow_integrated);
|
||||
|
||||
let contexts: Vec<Arc<GpuContext>> = initialized
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, g)| {
|
||||
if keep_indices.contains(&i) {
|
||||
Some(g.context)
|
||||
} else {
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"Dropping integrated GPU (discrete GPU initialized successfully, use --allow-integrated to override): {}",
|
||||
g.name
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"GPU engine initialized with {} devices (batch size: {} nonces, throttle: {}ms)",
|
||||
@@ -280,6 +496,13 @@ impl MinerEngine for GpuEngine {
|
||||
return EngineStatus::Exhausted { hash_count: 0 };
|
||||
}
|
||||
|
||||
// Check if this worker's GPU device was previously lost
|
||||
let device_is_lost = DEVICE_LOST.with(|lost| *lost.borrow());
|
||||
if device_is_lost {
|
||||
// Device was lost in a previous call - signal worker should exit
|
||||
return EngineStatus::DeviceLost { hash_count: 0 };
|
||||
}
|
||||
|
||||
// Empty or inverted range: nothing to do.
|
||||
if range.start > range.end {
|
||||
return EngineStatus::Exhausted { hash_count: 0 };
|
||||
@@ -430,6 +653,22 @@ impl MinerEngine for GpuEngine {
|
||||
BatchResult::NotFound { hash_count } => {
|
||||
total_hashes += hash_count;
|
||||
}
|
||||
BatchResult::DeviceLost => {
|
||||
// GPU device is lost/unresponsive - mark as permanently dead
|
||||
// and clear resources to prevent "buffer already mapped" panics
|
||||
DEVICE_LOST.with(|lost| *lost.borrow_mut() = true);
|
||||
WORKER_RESOURCES.with(|res| *res.borrow_mut() = None);
|
||||
|
||||
log::error!(
|
||||
target: "gpu_engine",
|
||||
"GPU {} device lost or unresponsive - stopping worker. \
|
||||
This GPU will not process further batches.",
|
||||
device_index
|
||||
);
|
||||
return EngineStatus::DeviceLost {
|
||||
hash_count: total_hashes,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next batch
|
||||
@@ -498,6 +737,8 @@ enum BatchResult {
|
||||
NotFound {
|
||||
hash_count: u64,
|
||||
},
|
||||
/// GPU device is lost or unresponsive - caller should stop using this device
|
||||
DeviceLost,
|
||||
}
|
||||
|
||||
/// Run a single batch of GPU computation
|
||||
@@ -568,25 +809,49 @@ fn run_single_batch(
|
||||
|
||||
// Wait for GPU to complete (blocking)
|
||||
let buffer_slice = resources.staging_buffer.slice(..);
|
||||
let mapped = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let mapped_clone = mapped.clone();
|
||||
// Use atomic to track completion: 0 = pending, 1 = success, 2 = error
|
||||
let map_status = std::sync::Arc::new(std::sync::atomic::AtomicU8::new(0));
|
||||
let map_status_clone = map_status.clone();
|
||||
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
|
||||
if result.is_ok() {
|
||||
mapped_clone.store(true, Ordering::Release);
|
||||
}
|
||||
map_status_clone.store(if result.is_ok() { 1 } else { 2 }, Ordering::Release);
|
||||
});
|
||||
|
||||
// Poll until complete
|
||||
loop {
|
||||
// Poll until complete, error, or timeout (30 seconds max to prevent infinite hang)
|
||||
let poll_start = std::time::Instant::now();
|
||||
let max_poll_duration = std::time::Duration::from_secs(30);
|
||||
let final_status = loop {
|
||||
let _ = gpu_ctx.device.poll(wgpu::PollType::Wait {
|
||||
submission_index: None,
|
||||
timeout: Some(std::time::Duration::from_millis(10)),
|
||||
});
|
||||
|
||||
if mapped.load(Ordering::Acquire) {
|
||||
break;
|
||||
match map_status.load(Ordering::Acquire) {
|
||||
1 => break 1, // Success - buffer is mapped
|
||||
2 => {
|
||||
// Mapping failed - buffer was never successfully mapped, don't unmap
|
||||
log::error!(
|
||||
target: "gpu_engine",
|
||||
"GPU buffer mapping failed - possible device lost or resource error"
|
||||
);
|
||||
return BatchResult::DeviceLost;
|
||||
}
|
||||
_ => {
|
||||
// Still pending - check timeout
|
||||
if poll_start.elapsed() > max_poll_duration {
|
||||
log::error!(
|
||||
target: "gpu_engine",
|
||||
"GPU buffer mapping timed out after {}s - GPU may be unresponsive",
|
||||
max_poll_duration.as_secs()
|
||||
);
|
||||
// Timeout: map_async callback never fired, buffer not mapped, don't unmap
|
||||
return BatchResult::DeviceLost;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Only reach here if final_status == 1 (success), buffer is mapped
|
||||
debug_assert_eq!(final_status, 1);
|
||||
|
||||
// Read results
|
||||
let data = buffer_slice.get_mapped_range();
|
||||
@@ -634,269 +899,22 @@ fn get_vendor_specific_dispatch(adapter_info: &wgpu::AdapterInfo, device: &wgpu:
|
||||
let limits = device.limits();
|
||||
let max_workgroups = limits.max_compute_workgroups_per_dimension.min(65535);
|
||||
|
||||
// Parse vendor from adapter info
|
||||
let vendor_name = adapter_info.name.to_lowercase();
|
||||
let _device_name = adapter_info.device.to_string().to_lowercase();
|
||||
let is_metal = adapter_info.backend == wgpu::Backend::Metal;
|
||||
let tier = gpu_tiers::detect_gpu_tier(&adapter_info.name, adapter_info.vendor, is_metal);
|
||||
|
||||
// Vendor-specific heuristics based on architecture knowledge
|
||||
// Returns (workgroups, tier_name, is_fallback)
|
||||
let (optimal_workgroups, tier, is_fallback) =
|
||||
if vendor_name.contains("nvidia") || adapter_info.vendor == 4318 {
|
||||
// NVIDIA GPUs (vendor ID 0x10DE = 4318)
|
||||
if vendor_name.contains("5090") || vendor_name.contains("5080") {
|
||||
(
|
||||
(max_workgroups / 6).max(5120),
|
||||
"NVIDIA RTX 50 Flagship (Blackwell)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("5070")
|
||||
|| vendor_name.contains("5060")
|
||||
|| vendor_name.contains("rtx 50")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 7).max(4608),
|
||||
"NVIDIA RTX 50 (Blackwell)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("4090") || vendor_name.contains("4080") {
|
||||
(
|
||||
(max_workgroups / 8).max(4096),
|
||||
"NVIDIA RTX 40 Flagship (Ada)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("rtx 40")
|
||||
|| vendor_name.contains("4070")
|
||||
|| vendor_name.contains("4060")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 10).max(3072),
|
||||
"NVIDIA RTX 40 (Ada)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("rtx 30")
|
||||
|| vendor_name.contains("rtx 20")
|
||||
|| vendor_name.contains("3090")
|
||||
|| vendor_name.contains("3080")
|
||||
|| vendor_name.contains("3070")
|
||||
|| vendor_name.contains("2080")
|
||||
|| vendor_name.contains("2070")
|
||||
|| vendor_name.contains("2060")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 12).max(2048),
|
||||
"NVIDIA RTX 30/20 (Ampere/Turing)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("gtx 16")
|
||||
|| vendor_name.contains("gtx 10")
|
||||
|| vendor_name.contains("1660")
|
||||
|| vendor_name.contains("1650")
|
||||
|| vendor_name.contains("1080")
|
||||
|| vendor_name.contains("1070")
|
||||
|| vendor_name.contains("1060")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 16).max(1024),
|
||||
"NVIDIA GTX 16/10 (Turing/Pascal)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("gtx") {
|
||||
((max_workgroups / 18).max(768), "NVIDIA GTX (Legacy)", false)
|
||||
} else if vendor_name.contains("quadro")
|
||||
|| vendor_name.contains("rtx a")
|
||||
|| vendor_name.contains("tesla")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 10).max(2560),
|
||||
"NVIDIA Quadro/Professional",
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
((max_workgroups / 20).max(512), "NVIDIA Unknown", true)
|
||||
}
|
||||
} else if vendor_name.contains("amd")
|
||||
|| vendor_name.contains("radeon")
|
||||
|| adapter_info.vendor == 4098
|
||||
{
|
||||
// AMD GPUs (vendor ID 0x1002 = 4098)
|
||||
if vendor_name.contains("rx 9")
|
||||
|| vendor_name.contains("9070")
|
||||
|| vendor_name.contains("9080")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 8).max(4096),
|
||||
"AMD RX 9000 (RDNA 4)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("7900") {
|
||||
(
|
||||
(max_workgroups / 9).max(3584),
|
||||
"AMD RX 7900 (RDNA 3 Flagship)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("rx 7")
|
||||
|| vendor_name.contains("7800")
|
||||
|| vendor_name.contains("7700")
|
||||
|| vendor_name.contains("7600")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 10).max(3072),
|
||||
"AMD RX 7000 (RDNA 3)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("6900") || vendor_name.contains("6800") {
|
||||
(
|
||||
(max_workgroups / 12).max(2560),
|
||||
"AMD RX 6900/6800 (RDNA 2 Flagship)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("rx 6")
|
||||
|| vendor_name.contains("6700")
|
||||
|| vendor_name.contains("6600")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 14).max(2048),
|
||||
"AMD RX 6000 (RDNA 2)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("5700") {
|
||||
(
|
||||
(max_workgroups / 16).max(1536),
|
||||
"AMD RX 5700 (RDNA 1)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("rx 5")
|
||||
|| vendor_name.contains("5600")
|
||||
|| vendor_name.contains("5500")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 18).max(1024),
|
||||
"AMD RX 5000 (RDNA 1)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("rx 4")
|
||||
|| vendor_name.contains("580")
|
||||
|| vendor_name.contains("570")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 20).max(768),
|
||||
"AMD RX 500/400 (Polaris)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("radeon pro")
|
||||
|| vendor_name.contains("instinct")
|
||||
|| vendor_name.contains("mi")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 10).max(2560),
|
||||
"AMD Radeon Pro/Instinct",
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
((max_workgroups / 24).max(512), "AMD Unknown", true)
|
||||
}
|
||||
} else if vendor_name.contains("intel") || adapter_info.vendor == 32902 {
|
||||
// Intel GPUs (vendor ID 0x8086 = 32902)
|
||||
if vendor_name.contains("arc b")
|
||||
|| vendor_name.contains("b580")
|
||||
|| vendor_name.contains("b570")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 10).max(2560),
|
||||
"Intel Arc B-Series (Battlemage)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("a770") || vendor_name.contains("a750") {
|
||||
(
|
||||
(max_workgroups / 12).max(2048),
|
||||
"Intel Arc A7 (Alchemist)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("a580")
|
||||
|| vendor_name.contains("a380")
|
||||
|| vendor_name.contains("arc a5")
|
||||
|| vendor_name.contains("arc a3")
|
||||
{
|
||||
(
|
||||
(max_workgroups / 16).max(1024),
|
||||
"Intel Arc A5/A3 (Alchemist)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("a310") {
|
||||
((max_workgroups / 20).max(512), "Intel Arc A3 Entry", false)
|
||||
} else if vendor_name.contains("iris xe") || vendor_name.contains("iris plus") {
|
||||
(
|
||||
(max_workgroups / 24).max(384),
|
||||
"Intel Iris Xe/Plus (Integrated)",
|
||||
false,
|
||||
)
|
||||
} else if vendor_name.contains("uhd") || vendor_name.contains("hd graphics") {
|
||||
(
|
||||
(max_workgroups / 28).max(256),
|
||||
"Intel UHD/HD Graphics (Integrated)",
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
((max_workgroups / 24).max(256), "Intel Unknown", true)
|
||||
}
|
||||
} else if adapter_info.backend == wgpu::Backend::Metal {
|
||||
// Apple GPUs (detected by Metal backend)
|
||||
let (gpu_cores, workgroups, tier) = if vendor_name.contains("m4 ultra") {
|
||||
(80, 1600, "Apple M4 Ultra")
|
||||
} else if vendor_name.contains("m4 max") {
|
||||
(40, 800, "Apple M4 Max")
|
||||
} else if vendor_name.contains("m4 pro") {
|
||||
(20, 400, "Apple M4 Pro")
|
||||
} else if vendor_name.contains("m4") {
|
||||
(10, 200, "Apple M4")
|
||||
} else if vendor_name.contains("m3 ultra") {
|
||||
(76, 1520, "Apple M3 Ultra")
|
||||
} else if vendor_name.contains("m3 max") {
|
||||
(40, 800, "Apple M3 Max")
|
||||
} else if vendor_name.contains("m3 pro") {
|
||||
(18, 360, "Apple M3 Pro")
|
||||
} else if vendor_name.contains("m3") {
|
||||
(10, 200, "Apple M3")
|
||||
} else if vendor_name.contains("m2 ultra") {
|
||||
(76, 1520, "Apple M2 Ultra")
|
||||
} else if vendor_name.contains("m2 max") {
|
||||
(38, 760, "Apple M2 Max")
|
||||
} else if vendor_name.contains("m2 pro") {
|
||||
(19, 380, "Apple M2 Pro")
|
||||
} else if vendor_name.contains("m2") {
|
||||
(10, 200, "Apple M2")
|
||||
} else if vendor_name.contains("m1 ultra") {
|
||||
(64, 1280, "Apple M1 Ultra")
|
||||
} else if vendor_name.contains("m1 max") {
|
||||
(32, 640, "Apple M1 Max")
|
||||
} else if vendor_name.contains("m1 pro") {
|
||||
(16, 320, "Apple M1 Pro")
|
||||
} else if vendor_name.contains("m1") {
|
||||
(8, 160, "Apple M1")
|
||||
} else {
|
||||
(8, 160, "Apple Silicon Unknown")
|
||||
};
|
||||
|
||||
let clamped_workgroups = workgroups.min(max_workgroups / 4).max(64);
|
||||
let _ = gpu_cores; // gpu_cores currently unused but kept for potential future tuning
|
||||
let is_fallback = tier == "Apple Silicon Unknown";
|
||||
(clamped_workgroups, tier, is_fallback)
|
||||
} else {
|
||||
// Unknown/Generic GPU - use conservative defaults
|
||||
((max_workgroups / 16).max(512), "Unknown GPU", true)
|
||||
};
|
||||
let optimal_workgroups = (max_workgroups / tier.workgroup_divisor).max(tier.min_workgroups);
|
||||
|
||||
// Log GPU detection result
|
||||
log::info!(
|
||||
target: "gpu_engine",
|
||||
"GPU detected: {} | tier: {} | workgroups: {} (max: {})",
|
||||
adapter_info.name,
|
||||
tier,
|
||||
tier.name,
|
||||
optimal_workgroups,
|
||||
max_workgroups
|
||||
);
|
||||
|
||||
if is_fallback {
|
||||
if tier.is_fallback {
|
||||
log::warn!(
|
||||
target: "gpu_engine",
|
||||
"GPU not recognized, using fallback config. Please report: name='{}', vendor=0x{:04X}, device={}",
|
||||
@@ -909,3 +927,214 @@ fn get_vendor_specific_dispatch(adapter_info: &wgpu::AdapterInfo, device: &wgpu:
|
||||
|
||||
optimal_workgroups
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod adapter_selection_tests {
|
||||
use super::*;
|
||||
|
||||
fn info(
|
||||
name: &str,
|
||||
device_type: wgpu::DeviceType,
|
||||
backend: wgpu::Backend,
|
||||
) -> wgpu::AdapterInfo {
|
||||
wgpu::AdapterInfo {
|
||||
name: name.into(),
|
||||
vendor: 0,
|
||||
device: 0,
|
||||
device_type,
|
||||
driver: String::new(),
|
||||
driver_info: String::new(),
|
||||
backend,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_multi_backend_keeps_one_context_per_physical_gpu() {
|
||||
// The exact enumeration from issue #61
|
||||
let infos = [
|
||||
info(
|
||||
"AMD Radeon(TM) Graphics",
|
||||
wgpu::DeviceType::IntegratedGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
info(
|
||||
"NVIDIA GeForce RTX 3070",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
info(
|
||||
"AMD Radeon(TM) Graphics",
|
||||
wgpu::DeviceType::IntegratedGpu,
|
||||
wgpu::Backend::Dx12,
|
||||
),
|
||||
info(
|
||||
"NVIDIA GeForce RTX 3070",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Dx12,
|
||||
),
|
||||
info(
|
||||
"Microsoft Basic Render Driver",
|
||||
wgpu::DeviceType::Cpu,
|
||||
wgpu::Backend::Dx12,
|
||||
),
|
||||
];
|
||||
// select_adapters returns all non-CPU adapters on best backend, discrete first
|
||||
// Integrated filtering now happens after init in the init() function
|
||||
assert_eq!(select_adapters(&infos), vec![1, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identical_multi_gpu_rig_keeps_every_card() {
|
||||
let infos = [
|
||||
info(
|
||||
"RTX 3090",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
info(
|
||||
"RTX 3090",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
info(
|
||||
"RTX 3090",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
];
|
||||
assert_eq!(select_adapters(&infos), vec![0, 1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dx12_only_machine_returns_all_adapters_sorted() {
|
||||
let infos = [
|
||||
info("iGPU", wgpu::DeviceType::IntegratedGpu, wgpu::Backend::Dx12),
|
||||
info("dGPU", wgpu::DeviceType::DiscreteGpu, wgpu::Backend::Dx12),
|
||||
];
|
||||
// select_adapters returns both, discrete first (integrated filtering is post-init)
|
||||
assert_eq!(select_adapters(&infos), vec![1, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integrated_only_machine_keeps_integrated() {
|
||||
// When no discrete GPU exists, integrated GPUs should be used
|
||||
let infos = [
|
||||
info(
|
||||
"Intel UHD",
|
||||
wgpu::DeviceType::IntegratedGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
info(
|
||||
"AMD Vega 8",
|
||||
wgpu::DeviceType::IntegratedGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
];
|
||||
assert_eq!(select_adapters(&infos), vec![0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn software_only_environment_selects_nothing() {
|
||||
let infos = [info(
|
||||
"llvmpipe",
|
||||
wgpu::DeviceType::Cpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
)];
|
||||
assert!(select_adapters(&infos).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_enumeration_selects_nothing() {
|
||||
assert!(select_adapters(&[]).is_empty());
|
||||
}
|
||||
|
||||
/// Exact scenario from Windows ASUS laptop with RX 560X + Vega 8 APU.
|
||||
/// Both GPUs appear on both Vulkan and Dx12 backends.
|
||||
/// select_adapters returns both Vulkan adapters (discrete first).
|
||||
/// The init() function will later drop the integrated one if discrete succeeds.
|
||||
#[test]
|
||||
fn windows_amd_discrete_plus_apu_selects_both_on_best_backend() {
|
||||
let infos = [
|
||||
info(
|
||||
"Microsoft Basic Render Driver",
|
||||
wgpu::DeviceType::Cpu,
|
||||
wgpu::Backend::Dx12,
|
||||
),
|
||||
info(
|
||||
"AMD Radeon(TM) Vega 8 Graphics",
|
||||
wgpu::DeviceType::IntegratedGpu,
|
||||
wgpu::Backend::Dx12,
|
||||
),
|
||||
info(
|
||||
"Radeon RX 560X",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Dx12,
|
||||
),
|
||||
info(
|
||||
"AMD Radeon(TM) Vega 8 Graphics",
|
||||
wgpu::DeviceType::IntegratedGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
info(
|
||||
"Radeon RX 560X",
|
||||
wgpu::DeviceType::DiscreteGpu,
|
||||
wgpu::Backend::Vulkan,
|
||||
),
|
||||
];
|
||||
// select_adapters returns both Vulkan adapters, discrete first
|
||||
// - Index 0: Skipped (CPU emulated)
|
||||
// - Index 1, 2: Skipped (Dx12 lower priority than Vulkan)
|
||||
// - Index 4: Selected first (discrete, Vulkan)
|
||||
// - Index 3: Selected second (integrated, Vulkan)
|
||||
assert_eq!(select_adapters(&infos), vec![4, 3]);
|
||||
}
|
||||
|
||||
// Tests for filter_initialized_gpus (post-init filtering)
|
||||
|
||||
#[test]
|
||||
fn filter_discrete_present_drops_integrated() {
|
||||
use wgpu::DeviceType::*;
|
||||
// Discrete at index 0, integrated at index 1
|
||||
let types = vec![DiscreteGpu, IntegratedGpu];
|
||||
assert_eq!(filter_initialized_gpus(&types, false), vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_discrete_failed_keeps_integrated() {
|
||||
use wgpu::DeviceType::*;
|
||||
// Only integrated initialized (discrete failed/timed out)
|
||||
let types = vec![IntegratedGpu];
|
||||
assert_eq!(filter_initialized_gpus(&types, false), vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_allow_integrated_keeps_both() {
|
||||
use wgpu::DeviceType::*;
|
||||
let types = vec![DiscreteGpu, IntegratedGpu];
|
||||
// With allow_integrated=true, keep both
|
||||
assert_eq!(filter_initialized_gpus(&types, true), vec![0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_multiple_discrete_keeps_all_discrete() {
|
||||
use wgpu::DeviceType::*;
|
||||
let types = vec![DiscreteGpu, DiscreteGpu, IntegratedGpu];
|
||||
// Drops integrated, keeps both discrete
|
||||
assert_eq!(filter_initialized_gpus(&types, false), vec![0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_only_discrete_keeps_all() {
|
||||
use wgpu::DeviceType::*;
|
||||
let types = vec![DiscreteGpu, DiscreteGpu];
|
||||
assert_eq!(filter_initialized_gpus(&types, false), vec![0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_multiple_integrated_no_discrete_keeps_all() {
|
||||
use wgpu::DeviceType::*;
|
||||
let types = vec![IntegratedGpu, IntegratedGpu];
|
||||
// No discrete, so keep all integrated
|
||||
assert_eq!(filter_initialized_gpus(&types, false), vec![0, 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use futures::executor::block_on;
|
||||
|
||||
mod end_to_end_tests;
|
||||
mod tests;
|
||||
|
||||
fn main() {
|
||||
block_on(run()).unwrap();
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run().await
|
||||
}
|
||||
|
||||
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
@@ -52,6 +52,12 @@ enum Command {
|
||||
)]
|
||||
gpu_throttle_ms: u64,
|
||||
|
||||
/// Allow integrated GPUs (APUs) even when discrete GPUs are available.
|
||||
/// By default, integrated GPUs are skipped when a discrete GPU is present
|
||||
/// to avoid resource contention and driver instability.
|
||||
#[arg(long = "allow-integrated", env = "MINER_ALLOW_INTEGRATED")]
|
||||
allow_integrated: bool,
|
||||
|
||||
/// Enable verbose logging
|
||||
#[arg(short, long, env = "MINER_VERBOSE")]
|
||||
verbose: bool,
|
||||
@@ -79,6 +85,10 @@ enum Command {
|
||||
#[arg(short, long, default_value_t = 10)]
|
||||
duration: u64,
|
||||
|
||||
/// Allow integrated GPUs (APUs) even when discrete GPUs are available
|
||||
#[arg(long = "allow-integrated", env = "MINER_ALLOW_INTEGRATED")]
|
||||
allow_integrated: bool,
|
||||
|
||||
/// Enable verbose logging
|
||||
#[arg(short, long, env = "MINER_VERBOSE")]
|
||||
verbose: bool,
|
||||
@@ -112,6 +122,7 @@ async fn main() {
|
||||
cpu_batch_size,
|
||||
gpu_throttle_ms,
|
||||
metrics_port,
|
||||
allow_integrated,
|
||||
verbose,
|
||||
} => {
|
||||
init_logger(verbose);
|
||||
@@ -135,6 +146,7 @@ async fn main() {
|
||||
gpu_batch_size,
|
||||
cpu_batch_size,
|
||||
gpu_throttle_ms,
|
||||
allow_integrated,
|
||||
};
|
||||
|
||||
if let Err(e) = run(config).await {
|
||||
@@ -149,6 +161,7 @@ async fn main() {
|
||||
gpu_batch_size,
|
||||
cpu_batch_size,
|
||||
duration,
|
||||
allow_integrated,
|
||||
verbose,
|
||||
} => {
|
||||
init_logger(verbose);
|
||||
@@ -158,6 +171,7 @@ async fn main() {
|
||||
gpu_batch_size,
|
||||
cpu_batch_size,
|
||||
duration,
|
||||
allow_integrated,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -166,10 +180,11 @@ async fn main() {
|
||||
|
||||
fn init_logger(verbose: bool) {
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
// Filter out noisy wgpu/naga shader compilation logs
|
||||
let log_level = if verbose {
|
||||
"debug,miner=debug,gpu_engine=debug,engine_cpu=debug"
|
||||
"debug,miner=debug,gpu_engine=debug,engine_cpu=debug,wgpu=warn,wgpu_core=warn,wgpu_hal=warn,naga=warn"
|
||||
} else {
|
||||
"info,miner=info,gpu_engine=info"
|
||||
"info,miner=info,gpu_engine=info,wgpu=error,wgpu_core=error,wgpu_hal=error,naga=error"
|
||||
};
|
||||
std::env::set_var("RUST_LOG", log_level);
|
||||
}
|
||||
@@ -182,18 +197,23 @@ async fn run_benchmark(
|
||||
gpu_batch_size: u32,
|
||||
cpu_batch_size: u64,
|
||||
duration: u64,
|
||||
allow_integrated: bool,
|
||||
) {
|
||||
let effective_cpu_workers = cpu_workers.unwrap_or_else(num_cpus::get);
|
||||
|
||||
// Initialize GPU engine (no throttle for benchmark)
|
||||
let (gpu_engine, effective_gpu_devices) =
|
||||
match miner_service::resolve_gpu_configuration(gpu_devices, gpu_batch_size, 0) {
|
||||
Ok((engine, count)) => (engine, count),
|
||||
Err(e) => {
|
||||
eprintln!("❌ ERROR: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let (gpu_engine, effective_gpu_devices) = match miner_service::resolve_gpu_configuration(
|
||||
gpu_devices,
|
||||
gpu_batch_size,
|
||||
0,
|
||||
allow_integrated,
|
||||
) {
|
||||
Ok((engine, count)) => (engine, count),
|
||||
Err(e) => {
|
||||
eprintln!("❌ ERROR: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let total_workers = effective_cpu_workers + effective_gpu_devices;
|
||||
|
||||
@@ -273,12 +293,18 @@ async fn run_benchmark(
|
||||
match result {
|
||||
engine_cpu::EngineStatus::Found { hash_count, .. }
|
||||
| engine_cpu::EngineStatus::Exhausted { hash_count }
|
||||
| engine_cpu::EngineStatus::Cancelled { hash_count } => {
|
||||
| engine_cpu::EngineStatus::Cancelled { hash_count }
|
||||
| engine_cpu::EngineStatus::DeviceLost { hash_count } => {
|
||||
*hashes.lock().unwrap() += hash_count;
|
||||
}
|
||||
engine_cpu::EngineStatus::Running { .. } => {}
|
||||
}
|
||||
|
||||
// Exit if device is lost
|
||||
if matches!(result, engine_cpu::EngineStatus::DeviceLost { .. }) {
|
||||
break;
|
||||
}
|
||||
|
||||
if start.elapsed() >= Duration::from_secs(duration) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ pub struct ServiceConfig {
|
||||
pub cpu_batch_size: u64,
|
||||
/// GPU throttle delay in milliseconds between batches (0 = no throttle)
|
||||
pub gpu_throttle_ms: u64,
|
||||
/// Allow integrated GPUs even when discrete GPUs are available
|
||||
pub allow_integrated: bool,
|
||||
}
|
||||
|
||||
/// Engine type for tracking metrics per compute type.
|
||||
@@ -206,12 +208,34 @@ impl WorkerPool {
|
||||
|
||||
// Dispatch job to all workers using bounded channels (capacity 16).
|
||||
// Workers drain to get the latest job, so we just need room to queue.
|
||||
let mut disconnected_count = 0;
|
||||
for (i, tx) in self.job_senders.iter().enumerate() {
|
||||
if let Err(e) = tx.try_send(job.clone()) {
|
||||
match e {
|
||||
crossbeam_channel::TrySendError::Disconnected(_) => {
|
||||
// Worker thread has exited (e.g., device lost)
|
||||
disconnected_count += 1;
|
||||
log::debug!("Worker {i} channel disconnected (worker exited)");
|
||||
}
|
||||
crossbeam_channel::TrySendError::Full(_) => {
|
||||
log::warn!(
|
||||
"Failed to send job {new_job_id} to worker {i}: channel full - \
|
||||
worker may be stuck or jobs arriving too fast"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disconnected_count > 0 {
|
||||
let active = self.job_senders.len() - disconnected_count;
|
||||
if active == 0 {
|
||||
log::error!(
|
||||
"Failed to send job {new_job_id} to worker {i}: {e}. \
|
||||
Channel full - worker may be stuck or jobs arriving too fast."
|
||||
"All workers have exited! No workers available to process jobs. \
|
||||
Consider restarting the miner."
|
||||
);
|
||||
} else {
|
||||
log::warn!("{disconnected_count} worker(s) have exited, {active} still active");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +360,7 @@ fn worker_loop(
|
||||
engine_cpu::EngineStatus::Found { .. } => "FOUND",
|
||||
engine_cpu::EngineStatus::Exhausted { .. } => "EXHAUSTED",
|
||||
engine_cpu::EngineStatus::Cancelled { .. } => "CANCELLED",
|
||||
engine_cpu::EngineStatus::DeviceLost { .. } => "DEVICE_LOST",
|
||||
engine_cpu::EngineStatus::Running { .. } => "RUNNING",
|
||||
};
|
||||
log::debug!(
|
||||
@@ -352,6 +377,7 @@ fn worker_loop(
|
||||
engine_cpu::EngineStatus::Found { hash_count, .. } => hash_count,
|
||||
engine_cpu::EngineStatus::Exhausted { hash_count } => hash_count,
|
||||
engine_cpu::EngineStatus::Cancelled { hash_count } => hash_count,
|
||||
engine_cpu::EngineStatus::DeviceLost { hash_count } => hash_count,
|
||||
engine_cpu::EngineStatus::Running { .. } => 0,
|
||||
};
|
||||
log_worker_completion(
|
||||
@@ -380,7 +406,7 @@ fn worker_loop(
|
||||
..
|
||||
} => {
|
||||
log::info!(
|
||||
"{type_str} worker {thread_id} found solution! Nonce: {}, Hash: {} (job {job_id})",
|
||||
"🎯 {type_str} worker {thread_id} found solution! Nonce: {}, Hash: {} (job {job_id})",
|
||||
format_u512(nonce),
|
||||
format_u512(hash),
|
||||
);
|
||||
@@ -400,6 +426,21 @@ fn worker_loop(
|
||||
log_worker_completion(type_str, thread_id, "new block", hash_count, search_elapsed);
|
||||
(None, hash_count)
|
||||
}
|
||||
engine_cpu::EngineStatus::DeviceLost { hash_count } => {
|
||||
log::error!(
|
||||
"{type_str} worker {thread_id} GPU device lost - worker exiting permanently"
|
||||
);
|
||||
// Send final result before exiting
|
||||
let _ = result_tx.try_send(WorkerResult {
|
||||
thread_id,
|
||||
engine_type,
|
||||
job_id,
|
||||
candidate: None,
|
||||
hash_count,
|
||||
completed: true,
|
||||
});
|
||||
break; // Exit the worker loop
|
||||
}
|
||||
engine_cpu::EngineStatus::Running { .. } => {
|
||||
// Should not happen for synchronous search
|
||||
(None, 0)
|
||||
@@ -430,6 +471,7 @@ pub fn resolve_gpu_configuration(
|
||||
requested_devices: Option<usize>,
|
||||
batch_size: u32,
|
||||
throttle_ms: u64,
|
||||
allow_integrated: bool,
|
||||
) -> anyhow::Result<(Option<Arc<dyn MinerEngine>>, usize)> {
|
||||
// Explicit 0 means no GPU
|
||||
if requested_devices == Some(0) {
|
||||
@@ -437,7 +479,7 @@ pub fn resolve_gpu_configuration(
|
||||
}
|
||||
|
||||
// Try to initialize GPU engine
|
||||
let engine = engine_gpu::GpuEngine::try_new(batch_size, throttle_ms);
|
||||
let engine = engine_gpu::GpuEngine::try_new(batch_size, throttle_ms, allow_integrated);
|
||||
let engine = match engine {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
@@ -482,6 +524,7 @@ pub async fn run(config: ServiceConfig) -> anyhow::Result<()> {
|
||||
config.gpu_devices,
|
||||
config.gpu_batch_size,
|
||||
config.gpu_throttle_ms,
|
||||
config.allow_integrated,
|
||||
)?;
|
||||
|
||||
// Resolve CPU workers
|
||||
|
||||
Reference in New Issue
Block a user