diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index ebcdfe9..ff4443e 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -46,8 +46,8 @@ jobs:
         uses: Jimver/cuda-toolkit@v0.2.5
         with:
           cuda: '11.5.1'
-          method: 'network'
-          sub-packages: '["nvcc", "cudart"]'
+          #method: 'local'
+          #sub-packages: '["nvcc", "cudart"]'
 
       - name: Build on Linux GNU
         if: matrix.os == 'ubuntu-18.04'
diff --git a/Cargo.lock b/Cargo.lock
index 959dd7e..edf58b7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -648,6 +648,25 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
+[[package]]
+name = "include_dir"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "482a2e29200b7eed25d7fdbd14423326760b7f6658d21a4cf12d55a50713c69f"
+dependencies = [
+ "include_dir_macros",
+]
+
+[[package]]
+name = "include_dir_macros"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e074c19deab2501407c91ba1860fa3d6820bfde307db6d8cb851b55a10be89b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
 [[package]]
 name = "indexmap"
 version = "1.7.0"
@@ -745,6 +764,7 @@ version = "0.1.0"
 dependencies = [
  "clap",
  "env_logger",
+ "include_dir",
  "kaspa-miner",
  "log",
  "opencl3",
diff --git a/plugins/cuda/src/lib.rs b/plugins/cuda/src/lib.rs
index 23e4cea..c7c2d2e 100644
--- a/plugins/cuda/src/lib.rs
+++ b/plugins/cuda/src/lib.rs
@@ -49,81 +49,82 @@ impl Plugin for CudaPlugin {
     }
 
     //noinspection RsTypeCheck
-    fn process_option(&mut self, matches: &ArgMatches) -> Result<(), kaspa_miner::Error> {
+    fn process_option(&mut self, matches: &ArgMatches) -> Result<usize, kaspa_miner::Error> {
         let opts: CudaOpt = CudaOpt::from_arg_matches(matches)?;
 
         self._enabled = !opts.cuda_disable;
+        if self._enabled {
+            let gpus: Vec<u16> = match &opts.cuda_device {
+                Some(devices) => devices.clone(),
+                None => {
+                    let gpu_count = Device::num_devices().unwrap() as u16;
+                    (0..gpu_count).collect()
+                }
+            };
+
+            // if any of cuda_lock_core_clocks / cuda_lock_mem_clocks / cuda_power_limit is valid, init nvml and try to apply
+            if opts.cuda_lock_core_clocks.is_some()
+                || opts.cuda_lock_mem_clocks.is_some()
+                || opts.cuda_power_limits.is_some()
+            {
+                for i in 0..gpus.len() {
+                    let lock_mem_clock: Option<u32> = match &opts.cuda_lock_mem_clocks {
+                        Some(mem_clocks) if i < mem_clocks.len() => Some(mem_clocks[i]),
+                        Some(mem_clocks) if !mem_clocks.is_empty() => Some(*mem_clocks.last().unwrap()),
+                        _ => None,
+                    };
 
-        let gpus: Vec<u16> = match &opts.cuda_device {
-            Some(devices) => devices.clone(),
-            None => {
-                let gpu_count = Device::num_devices().unwrap() as u16;
-                (0..gpu_count).collect()
-            }
-        };
-
-        // if any of cuda_lock_core_clocks / cuda_lock_mem_clocks / cuda_power_limit is valid, init nvml and try to apply
-        if opts.cuda_lock_core_clocks.is_some()
-            || opts.cuda_lock_mem_clocks.is_some()
-            || opts.cuda_power_limits.is_some()
-        {
-            for i in 0..gpus.len() {
-                let lock_mem_clock: Option<u32> = match &opts.cuda_lock_mem_clocks {
-                    Some(mem_clocks) if i < mem_clocks.len() => Some(mem_clocks[i]),
-                    Some(mem_clocks) if !mem_clocks.is_empty() => Some(*mem_clocks.last().unwrap()),
-                    _ => None,
-                };
-
-                let lock_core_clock: Option<u32> = match &opts.cuda_lock_core_clocks {
-                    Some(core_clocks) if i < core_clocks.len() => Some(core_clocks[i]),
-                    Some(core_clocks) if !core_clocks.is_empty() => Some(*core_clocks.last().unwrap()),
-                    _ => None,
-                };
-
-                let power_limit: Option<u32> = match &opts.cuda_power_limits {
-                    Some(power_limits) if i < power_limits.len() => Some(power_limits[i]),
-                    Some(power_limits) if !power_limits.is_empty() => Some(*power_limits.last().unwrap()),
-                    _ => None,
-                };
-
-                let mut nvml_device: NvmlDevice = self.nvml_instance.device_by_index(gpus[i] as u32)?;
-
-                if let Some(lmc) = lock_mem_clock {
-                    match nvml_device.set_mem_locked_clocks(lmc, lmc) {
-                        Err(e) => error!("set mem locked clocks {:?}", e),
-                        _ => info!("GPU #{} #{} lock mem clock at {} Mhz", i, &nvml_device.name()?, &lmc),
+                    let lock_core_clock: Option<u32> = match &opts.cuda_lock_core_clocks {
+                        Some(core_clocks) if i < core_clocks.len() => Some(core_clocks[i]),
+                        Some(core_clocks) if !core_clocks.is_empty() => Some(*core_clocks.last().unwrap()),
+                        _ => None,
                     };
-                }
 
-                if let Some(lcc) = lock_core_clock {
-                    match nvml_device.set_gpu_locked_clocks(lcc, lcc) {
-                        Err(e) => error!("set gpu locked clocks {:?}", e),
-                        _ => info!("GPU #{} #{} lock core clock at {} Mhz", i, &nvml_device.name()?, &lcc),
+                    let power_limit: Option<u32> = match &opts.cuda_power_limits {
+                        Some(power_limits) if i < power_limits.len() => Some(power_limits[i]),
+                        Some(power_limits) if !power_limits.is_empty() => Some(*power_limits.last().unwrap()),
+                        _ => None,
                     };
-                };
 
-                if let Some(pl) = power_limit {
-                    match nvml_device.set_power_management_limit(pl * 1000) {
-                        Err(e) => error!("set power limit {:?}", e),
-                        _ => info!("GPU #{} #{} power limit at {} W", i, &nvml_device.name()?, &pl),
+                    let mut nvml_device: NvmlDevice = self.nvml_instance.device_by_index(gpus[i] as u32)?;
+
+                    if let Some(lmc) = lock_mem_clock {
+                        match nvml_device.set_mem_locked_clocks(lmc, lmc) {
+                            Err(e) => error!("set mem locked clocks {:?}", e),
+                            _ => info!("GPU #{} #{} lock mem clock at {} Mhz", i, &nvml_device.name()?, &lmc),
+                        };
+                    }
+
+                    if let Some(lcc) = lock_core_clock {
+                        match nvml_device.set_gpu_locked_clocks(lcc, lcc) {
+                            Err(e) => error!("set gpu locked clocks {:?}", e),
+                            _ => info!("GPU #{} #{} lock core clock at {} Mhz", i, &nvml_device.name()?, &lcc),
+                        };
                     };
-                };
+
+                    if let Some(pl) = power_limit {
+                        match nvml_device.set_power_management_limit(pl * 1000) {
+                            Err(e) => error!("set power limit {:?}", e),
+                            _ => info!("GPU #{} #{} power limit at {} W", i, &nvml_device.name()?, &pl),
+                        };
+                    };
+                }
             }
-        }
 
-        self.specs = (0..gpus.len())
-            .map(|i| CudaWorkerSpec {
-                device_id: gpus[i] as u32,
-                workload: match &opts.cuda_workload {
-                    Some(workload) if i < workload.len() => workload[i],
-                    Some(workload) if !workload.is_empty() => *workload.last().unwrap(),
-                    _ => DEFAULT_WORKLOAD_SCALE,
-                },
-                is_absolute: opts.cuda_workload_absolute,
-                blocking_sync: !opts.cuda_no_blocking_sync,
-            })
-            .collect();
-        Ok(())
+            self.specs = (0..gpus.len())
+                .map(|i| CudaWorkerSpec {
+                    device_id: gpus[i] as u32,
+                    workload: match &opts.cuda_workload {
+                        Some(workload) if i < workload.len() => workload[i],
+                        Some(workload) if !workload.is_empty() => *workload.last().unwrap(),
+                        _ => DEFAULT_WORKLOAD_SCALE,
+                    },
+                    is_absolute: opts.cuda_workload_absolute,
+                    blocking_sync: !opts.cuda_no_blocking_sync,
+                })
+                .collect();
+        }
+        Ok(self.specs.len())
     }
 }
 
diff --git a/plugins/opencl/Cargo.toml b/plugins/opencl/Cargo.toml
index 74bbf22..bc1bd5a 100644
--- a/plugins/opencl/Cargo.toml
+++ b/plugins/opencl/Cargo.toml
@@ -11,6 +11,7 @@ env_logger = "0.9"
 opencl3 = {version = "0.6", features = ["CL_VERSION_2_1", "CL_VERSION_2_2", "CL_VERSION_3_0"]}
 log = "0.4"
 rand = "0.8"
+include_dir = "0.7"
 
 [lib]
 crate-type = ["cdylib"]
diff --git a/plugins/opencl/src/lib.rs b/plugins/opencl/src/lib.rs
index 5396bda..ed94d7c 100644
--- a/plugins/opencl/src/lib.rs
+++ b/plugins/opencl/src/lib.rs
@@ -45,7 +45,7 @@ impl Plugin for OpenCLPlugin {
     }
 
     //noinspection RsTypeCheck
-    fn process_option(&mut self, matches: &ArgMatches) -> Result<(), kaspa_miner::Error> {
+    fn process_option(&mut self, matches: &ArgMatches) -> Result<usize, kaspa_miner::Error> {
         let opts: OpenCLOpt = OpenCLOpt::from_arg_matches(matches)?;
 
         self._enabled = opts.opencl_enable;
@@ -77,38 +77,39 @@ impl Plugin for OpenCLPlugin {
             }
             None => &platforms[0],
         };
-        info!(
-            "Chose to mine on {}: {}.",
-            &_platform.vendor().unwrap_or_else(|_| "Unk".into()),
-            &_platform.name().unwrap_or_else(|_| "Unk".into())
-        );
-
-        let device_ids = _platform.get_devices(CL_DEVICE_TYPE_ALL).unwrap();
-        let gpus = match opts.opencl_device {
-            Some(dev) => {
-                self._enabled = true;
-                dev.iter().map(|d| device_ids[*d as usize]).collect::<Vec<cl_device_id>>()
-            }
-            None => device_ids,
-        };
-
-        self.specs = (0..gpus.len())
-            .map(|i| OpenCLWorkerSpec {
-                _platform: *_platform,
-                device_id: Device::new(gpus[i]),
-                workload: match &opts.opencl_workload {
-                    Some(workload) if i < workload.len() => workload[i],
-                    Some(workload) if !workload.is_empty() => *workload.last().unwrap(),
-                    _ => DEFAULT_WORKLOAD_SCALE,
-                },
-                is_absolute: opts.opencl_workload_absolute,
-                experimental_amd: opts.experimental_amd,
-                use_amd_binary: !opts.opencl_no_amd_binary,
-                random: opts.nonce_gen,
-            })
-            .collect();
-
-        Ok(())
+        if self._enabled {
+            info!(
+                "Chose to mine on {}: {}.",
+                &_platform.vendor().unwrap_or_else(|_| "Unk".into()),
+                &_platform.name().unwrap_or_else(|_| "Unk".into())
+            );
+
+            let device_ids = _platform.get_devices(CL_DEVICE_TYPE_ALL).unwrap();
+            let gpus = match opts.opencl_device {
+                Some(dev) => {
+                    self._enabled = true;
+                    dev.iter().map(|d| device_ids[*d as usize]).collect::<Vec<cl_device_id>>()
+                }
+                None => device_ids,
+            };
+
+            self.specs = (0..gpus.len())
+                .map(|i| OpenCLWorkerSpec {
+                    _platform: *_platform,
+                    device_id: Device::new(gpus[i]),
+                    workload: match &opts.opencl_workload {
+                        Some(workload) if i < workload.len() => workload[i],
+                        Some(workload) if !workload.is_empty() => *workload.last().unwrap(),
+                        _ => DEFAULT_WORKLOAD_SCALE,
+                    },
+                    is_absolute: opts.opencl_workload_absolute,
+                    experimental_amd: opts.experimental_amd,
+                    use_amd_binary: !opts.opencl_no_amd_binary,
+                    random: opts.nonce_gen,
+                })
+                .collect();
+        }
+        Ok(self.specs.len())
     }
 }
 
diff --git a/plugins/opencl/src/worker.rs b/plugins/opencl/src/worker.rs
index da2a7e9..87a4174 100644
--- a/plugins/opencl/src/worker.rs
+++ b/plugins/opencl/src/worker.rs
@@ -1,8 +1,9 @@
 use crate::cli::NonceGenEnum;
 use crate::Error;
+use include_dir::{include_dir, Dir};
 use kaspa_miner::xoshiro256starstar::Xoshiro256StarStar;
 use kaspa_miner::Worker;
-use log::info;
+use log::{info, warn};
 use opencl3::command_queue::{CommandQueue, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE};
 use opencl3::context::Context;
 use opencl3::device::Device;
@@ -18,6 +19,7 @@ use std::ffi::c_void;
 use std::ptr;
 use std::sync::Arc;
 
+static BINARY_DIR: Dir = include_dir!("./plugins/opencl/resources/bin/");
 static PROGRAM_SOURCE: &str = include_str!("../resources/kaspa-opencl.cl");
 
 pub struct OpenCLGPUWorker {
@@ -197,70 +199,21 @@ impl OpenCLGPUWorker {
 
         let program = match use_binary {
             true => {
-                let device_name = device.name().unwrap_or_else(|_| "Unknown".into()).to_lowercase();
+                let mut device_name = device.name().unwrap_or_else(|_| "Unknown".into()).to_lowercase();
+                if device_name.contains(':') {
+                    device_name = device_name.split_once(':').expect("We checked for `:`").0.to_string();
+                }
                 info!("{}: Looking for binary for {}", name, device_name);
-                match device_name.as_str() {
-                    /*"ellesmere" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/ellesmere_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|e| {
-                        panic!("{}::Program::create_and_build_from_binary failed: {}", name, String::from(e))
-                    }),*/
-                    "gfx906" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx906_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx908" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx908_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx1010" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx1010_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx1011" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx1011_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx1012" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx1012_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx1030" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx1030_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx1031" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx1031_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|_| panic!("{}::Program::create_and_build_from_binary failed", name)),
-                    "gfx1032" => Program::create_and_build_from_binary(
-                        &context,
-                        &[include_bytes!("../resources/bin/gfx1032_kaspa-opencl.bin")],
-                        "",
-                    )
-                    .unwrap_or_else(|e| panic!("{}::Program::create_and_build_from_binary failed: {}", name, e)),
-                    other => {
-                        panic!(
-                            "{}: Found device {} without prebuilt binary. Trying to run without --opencl-amd-binary.",
-                            name, other
-                        );
+                match BINARY_DIR.get_file(format!("{}_kaspa-opencl.bin", device_name)) {
+                    Some(binary) => {
+                        Program::create_and_build_from_binary(&context, &[binary.contents()], "").unwrap_or_else(|e|{
+                            warn!("{}::Program::create_and_build_from_source failed: {}. Reverting to compiling from source", name, e);
+                            from_source(&context, &device, options).unwrap_or_else(|e| panic!("{}::Program::create_and_build_from_binary failed: {}", name, e))
+                        })
+                    },
+                    None => {
+                        warn!("Binary file not found for {}. Reverting to compiling from source.", device_name);
+                        from_source(&context, &device, options).unwrap_or_else(|e| panic!("{}::Program::create_and_build_from_binary failed: {}", name, e))
                     }
                 }
             }
diff --git a/src/cli.rs b/src/cli.rs
index 2cf1cbb..6a0b47a 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -70,8 +70,12 @@ impl Opt {
         }
 
         if !self.kaspad_address.contains("://") {
-            let port = self.port();
-            self.kaspad_address = format!("grpc://{}:{}", self.kaspad_address, port);
+            let port_str = self.port().to_string();
+            let (kaspad, port) = match self.kaspad_address.contains(':') {
+                true => self.kaspad_address.split_once(':').expect("We checked for `:`"),
+                false => (self.kaspad_address.as_str(), port_str.as_str()),
+            };
+            self.kaspad_address = format!("grpc://{}:{}", kaspad, port);
         }
         log::info!("kaspad address: {}", self.kaspad_address);
 
diff --git a/src/lib.rs b/src/lib.rs
index 5b049a8..556c9c7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -65,13 +65,17 @@ impl PluginManager {
         Ok(specs)
     }
 
-    pub fn process_options(&mut self, matchs: &ArgMatches) -> Result<(), Error> {
+    /**
+    Process the options for a plugin, and reports how many workers are available
+    */
+    pub fn process_options(&mut self, matchs: &ArgMatches) -> Result<usize, Error> {
+        let mut count = 0usize;
         self.plugins.iter_mut().for_each(|plugin| {
-            plugin
+            count += plugin
                 .process_option(matchs)
                 .unwrap_or_else(|_| panic!("Could not process option for plugin {}", plugin.name()))
         });
-        Ok(())
+        Ok(count)
     }
 
     pub fn has_specs(&self) -> bool {
@@ -83,7 +87,7 @@ pub trait Plugin: Any + Send + Sync {
     fn name(&self) -> &'static str;
     fn enabled(&self) -> bool;
     fn get_worker_specs(&self) -> Vec<Box<dyn WorkerSpec>>;
-    fn process_option(&mut self, matchs: &ArgMatches) -> Result<(), Error>;
+    fn process_option(&mut self, matchs: &ArgMatches) -> Result<usize, Error>;
 }
 
 pub trait WorkerSpec: Any + Send + Sync {
@@ -116,7 +120,7 @@ pub fn load_plugins<'help>(
     for path in paths {
         app = unsafe {
             factory.load_single_plugin(app, path.as_str()).unwrap_or_else(|(app, e)| {
-                eprintln!("Failed loading plugin {}: {}", path, e);
+                eprintln!("WARNING: Failed loading plugin {} (ignore if you do not intend to use): {}", path, e);
                 app
             })
         };
diff --git a/src/main.rs b/src/main.rs
index aec3195..8e40553 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -117,11 +117,16 @@ async fn main() -> Result<(), Error> {
 
     let matches = app.get_matches();
 
-    plugin_manager.process_options(&matches)?;
+    let worker_count = plugin_manager.process_options(&matches)?;
     let mut opt: Opt = Opt::from_arg_matches(&matches)?;
     opt.process()?;
     env_logger::builder().filter_level(opt.log_level()).parse_default_env().init();
     info!("Found plugins: {:?}", plugins);
+    info!("Plugins found {} workers", worker_count);
+    if worker_count == 0 && opt.num_threads.unwrap_or(0) == 0 {
+        error!("No workers specified");
+        return Err("No workers specified".into());
+    }
 
     let block_template_ctr = Arc::new(AtomicU16::new((thread_rng().next_u64() % 10_000u64) as u16));
     if opt.devfund_percent > 0 {