Skip to content

Commit

Permalink
POL5598 SLURM-friendliness for CLI (PolusAI#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
friskluft authored Jan 16, 2025
1 parent e0366b4 commit f76043c
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 93 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ set(SOURCE
src/nyx/cli_fpimage_options.cpp
src/nyx/cli_gabor_options.cpp
src/nyx/cli_glcm_options.cpp
src/nyx/cli_gpu_options.cpp
src/nyx/cli_nested_roi_options.cpp
src/nyx/common_stats.cpp
src/nyx/dirs_and_files.cpp
Expand Down
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,23 +358,25 @@ Assuming you [built the Nyxus binary](#building-from-source) as outlined below,
--intDir | Directory of intensity image collection | path
--outDir | Output directory | path
--segDir | Directory of labeled image collection | path
--coarseGrayDepth | (optional) Custom number of greyscale level bins used in texture features. Default: '--coarseGrayDepth=256' | integer
--glcmAngles | (optional) Enabled direction angles of the GLCM feature. Superset of values: 0, 45, 90, and 135. Default: '--glcmAngles=0,45,90,135' | list of integer constants
--intSegMapDir | (optional) Data collection of the ad-hoc intensity-to-mask file mapping. Must be used in combination with parameter '--intSegMapFile' | path
--intSegMapFile | (optional) Name of the text file containing an ad-hoc intensity-to-mask file mapping. The files are assumed to reside in corresponding intensity and label collections. Must be used in combination with parameter '--intSegMapDir' | string
--pixelDistance | (optional) Number of pixels to treat ROIs within specified distance as neighbors. Default value: '--pixelDistance=5' | integer
--pixelsPerCentimeter | (optional) Number of pixels in centimeter used by unit length-related features. Default value: 0 | real
--ramLimit | (optional) Amount of memory not to exceed by Nyxus, in megabytes. Default value: 50\% of available memory. Example: '--ramLimit=2000' to use 2,000 megabytes | integer
--reduceThreads | (optional) Number of CPU threads used on the feature calculation step. Default: '--reduceThreads=1' | integer
--skiproi | (optional) Skip ROIs having specified labels. Example: '--skiproi=image1.tif:2,3,4;image2.tif:45,56' | string
--tempDir | (optional) Directory used by temporary out-of-RAM objects. Default value: system temporary directory | path
--hsig | (optional) Channel signature Example: "--hsig=_c" to match images whose file names have channel info starting substring '_c' like in 'p0_y1_r1_c1.ome.tiff' | string
--hpar | (optional) Channel number that should be used as a provider of parent segments. Example: '--hpar=1' | integer
--hchi | (optional) Channel number that should be used as a provider of child segments. Example: '--hchi=0' | integer
--hag | (optional) Name of a method how to aggregate features of segments recognized as children of same parent segment. Valid options are 'SUM', 'MEAN', 'MIN', 'MAX', 'WMA' (weighted mean average), and 'NONE' (no aggregation, instead, same parent child segments will be laid out horizontally) | string
--fpimgdr | (optional) Desired dynamic range of voxels of a floating point TIFF image. Example: --fpimgdr=240 makes intensities be read in range 0-240. Default value: 10e4 | unsigned integer
--fpimgmin | (optional) Minimum intensity of voxels of a floating point TIFF image. Default value: 0.0 | real
--fpimgdr | (optional) Maximum intensity of voxels of a floating point TIFF image. Default value: 1.0 | real
--useGpu | ${\color{red}\textsf{(optional)}}$ Calculate compute-expensive features on an NVIDIA GPU device specified by parameter --gpuDeviceID. Default: '--useGpu=false'. Example: --useGpu=true | boolean
--gpuDeviceID | ${\color{red}\textsf{(optional)}}$ ID of a GPU device to be used when '--useGpu=true'. Default: '--gpuDeviceID=0'. Example 1 (single GPU device): '--useGpu=true --gpuDeviceID=2' to strictly use device 2. Example 2 (multiple GPU devices, usually in SLURM scenarios): '--useGpu=true --gpuDeviceID=0,1,3' to use the GPU device having maximum free RAM of devices 0, 1, and 3. | integer or list of integers
--coarseGrayDepth | ${\color{red}\textsf{(optional)}}$ Custom number of greyscale level bins used in texture features. Default: '--coarseGrayDepth=256' | integer
--glcmAngles | ${\color{red}\textsf{(optional)}}$ Enabled direction angles of the GLCM feature. Superset of values: 0, 45, 90, and 135. Default: '--glcmAngles=0,45,90,135' | list of integers
--intSegMapDir | ${\color{red}\textsf{(optional)}}$ Data collection of the ad-hoc intensity-to-mask file mapping. Must be used in combination with parameter '--intSegMapFile' | path
--intSegMapFile | ${\color{red}\textsf{(optional)}}$ Name of the text file containing an ad-hoc intensity-to-mask file mapping. The files are assumed to reside in corresponding intensity and label collections. Must be used in combination with parameter '--intSegMapDir' | string
--pixelDistance | ${\color{red}\textsf{(optional)}}$ Number of pixels to treat ROIs within specified distance as neighbors. Default value: '--pixelDistance=5' | integer
--pixelsPerCentimeter | ${\color{red}\textsf{(optional)}}$ Number of pixels in centimeter used by unit length-related features. Default value: 0 | real
--ramLimit | ${\color{red}\textsf{(optional)}}$ Amount of memory not to exceed by Nyxus, in megabytes. Default value: 50\% of available memory. Example: '--ramLimit=2000' to use 2,000 megabytes | integer
--reduceThreads | ${\color{red}\textsf{(optional)}}$ Number of CPU threads used on the feature calculation step. Default: '--reduceThreads=1' | integer
--skiproi | ${\color{red}\textsf{(optional)}}$ Skip ROIs having specified labels. Example: '--skiproi=image1.tif:2,3,4;image2.tif:45,56' | string
--tempDir | ${\color{red}\textsf{(optional)}}$ Directory used by temporary out-of-RAM objects. Default value: system temporary directory | path
--hsig | ${\color{red}\textsf{(optional)}}$ Channel signature Example: "--hsig=_c" to match images whose file names have channel info starting substring '_c' like in 'p0_y1_r1_c1.ome.tiff' | string
--hpar | ${\color{red}\textsf{(optional)}}$ Channel number that should be used as a provider of parent segments. Example: '--hpar=1' | integer
--hchi | ${\color{red}\textsf{(optional)}}$ Channel number that should be used as a provider of child segments. Example: '--hchi=0' | integer
--hag | ${\color{red}\textsf{(optional)}}$ Name of a method how to aggregate features of segments recognized as children of same parent segment. Valid options are 'SUM', 'MEAN', 'MIN', 'MAX', 'WMA' (weighted mean average), and 'NONE' (no aggregation, instead, same parent child segments will be laid out horizontally) | string
--fpimgdr | ${\color{red}\textsf{(optional)}}$ Desired dynamic range of voxels of a floating point TIFF image. Example: --fpimgdr=240 makes intensities be read in range 0-240. Default value: 10e4 | unsigned integer
--fpimgmin | ${\color{red}\textsf{(optional)}}$ Minimum intensity of voxels of a floating point TIFF image. Default value: 0.0 | real
--fpimgdr | ${\color{red}\textsf{(optional)}}$ Maximum intensity of voxels of a floating point TIFF image. Default value: 1.0 | real

---

Expand Down Expand Up @@ -595,7 +597,7 @@ These packages also have underlying dependencies and at times, these dependency

By default, Nyxus can be built with a minimal set of dependecies (Tiff support and Python interface). To build Nyxus with all the supported IO options mentioned above, pass `-DALLEXTRAS=ON` in the `cmake` command.

### __GPU Support__
### __Adding GPU Support__
Nyxus also can be build with NVIDIA GPU support. To do so, a `CUDA` Development toolkit compatible with the host `C++` compiler need to be present in the system. For building with GPU support, pass `-DUSEGPU=ON` flag in the `cmake` command.

### __Inside Conda__
Expand Down
119 changes: 119 additions & 0 deletions src/nyx/cli_gpu_options.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "cli_gpu_options.h"
#include "helpers/helpers.h"

#ifdef USE_GPU
namespace NyxusGpu
{
bool get_best_device(
// in
const std::vector<int>& devIds,
// out
int& best_id,
std::string& lastCuErmsg);
}
#endif

bool GpuOptions::empty()
{
return raw_use_gpu.empty();
}

void GpuOptions::set_using_gpu(bool use)
{
using_gpu_ = use;
}

bool GpuOptions::get_using_gpu()
{
return using_gpu_;
}

bool GpuOptions::set_single_device_id(int id)
{
if (get_using_gpu())
{
this->best_device_id_ = id;
return true;
}
else
return false;
}

int GpuOptions::get_single_device_id()
{
return best_device_id_;
}

bool GpuOptions::parse_input (std::string & ermsg)
{
#ifdef USE_GPU

auto u = Nyxus::toupper (this->raw_use_gpu);
if (u.length() == 0)
{
set_using_gpu (false);
}
else
{
auto t = Nyxus::toupper("true"),
f = Nyxus::toupper("false");
if (u != t && u != f)
{
ermsg = "valid values are " + t + " or " + f;
return false;
}
set_using_gpu (u == t);

// process user's GPU device choice
if (get_using_gpu())
{
std::vector<int> devIds;

if (! this->raw_requested_device_ids.empty())
{
// user input -> vector of IDs
std::vector<std::string> S;
Nyxus::parse_delimited_string (this->raw_requested_device_ids, ",", S);
// examine those IDs
for (const auto& s : S)
{
if (!s.empty())
{
// string -> int
int id;
if (sscanf (s.c_str(), "%d", &id) != 1 || id < 0)
{
ermsg = s + ": expecting a non-negative integer";
return false;
}
devIds.push_back (id);
}
}
}
else
devIds.push_back (0); // user did not requested a specific ID, so default it to 0

// given a set of suggested devices, choose the least memory-busy one
int best_id = -1;
std::string lastCuErmsg;
if (! NyxusGpu::get_best_device(devIds, best_id, lastCuErmsg))
{
ermsg = "cannot use any of GPU devices in " + Nyxus::virguler<int> (devIds) + " due to " + lastCuErmsg;
return false;
}

// we found at least one workable
this->best_device_id_ = best_id;
}
}

return true;

#else

ermsg = "To have GPU options available, use a Nyxus version with GPU support enabled";
set_using_gpu (false);
return false;

#endif
}
30 changes: 30 additions & 0 deletions src/nyx/cli_gpu_options.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <string>
#include <vector>

class GpuOptions
{
public:
// parses "raw_use_gpu" and "raw_requested_device_ids"
bool parse_input (std::string & ermsg);

// true if the parameters have never been specified via "raw_*"
bool empty();

// accessor of the "using" status
void set_using_gpu(bool use);
bool get_using_gpu();

// accessor of active device ID
bool set_single_device_id(int id);
int get_single_device_id();

// exposed to command line processor
std::string raw_use_gpu;
std::string raw_requested_device_ids;

private:
bool using_gpu_ = false;
int best_device_id_ = -1;
};
85 changes: 34 additions & 51 deletions src/nyx/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include "helpers/timing.h"
#include "version.h"

#ifdef USE_GPU
std::vector<std::map<std::string, std::string>> get_gpu_properties();
#endif

namespace Nyxus
{
bool existsOnFilesystem(const std::string&);
Expand Down Expand Up @@ -172,7 +176,7 @@ void Environment::show_cmdline_help()
<< "\tnyxus --help\tDisplay help info\n";

#ifdef USE_GPU
std::cout << " [" << USEGPU << "=<true or false>" << " [" << GPUDEVICEID << "=<valid GPU device ID>] ]\n";
std::cout << " [" << USEGPU << "=<true or false>" << " [" << GPUDEVICEID << "=<comma separated GPU device ID>] ]\n";
#endif
}

Expand All @@ -197,9 +201,10 @@ void Environment::show_summary(const std::string& head, const std::string& tail)
<< "\tverbosity level\t" << verbosity_level << "\n";

#ifdef USE_GPU
std::cout << "\tusing GPU\t" << (using_gpu() ? "yes" : "no") << "\n";
if (using_gpu())
std::cout << "\tGPU device ID \t" << get_gpu_device_choice() << "\n";
std::cout << "\tusing GPU\t" << (gpuOptions.get_using_gpu() ? "yes" : "no") << "\n";
if (gpuOptions.get_using_gpu())
std::cout << "\trequested GPU device IDs: " << (gpuOptions.raw_requested_device_ids.empty() ? "(blank)" : gpuOptions.raw_requested_device_ids) << "\n"
"\tbest GPU device ID: " << gpuOptions.get_single_device_id() << "\n";
#endif

// Features
Expand Down Expand Up @@ -388,8 +393,8 @@ bool Environment::parse_cmdline(int argc, char** argv)
#endif

#ifdef USE_GPU
|| find_string_argument(i, USEGPU, rawUseGpu)
|| find_string_argument(i, GPUDEVICEID, rawGpuDeviceID)
|| find_string_argument(i, USEGPU, gpuOptions.raw_use_gpu)
|| find_string_argument(i, GPUDEVICEID, gpuOptions.raw_requested_device_ids)
#endif
))
unrecognizedArgs.push_back(*i);
Expand Down Expand Up @@ -700,7 +705,7 @@ bool Environment::parse_cmdline(int argc, char** argv)
}

//==== Parse exclusive-inclusive timing
#ifdef CHECKTIMING
#ifdef CHECKTIMING
if (!rawExclusiveTiming.empty())
{
std::transform(rawExclusiveTiming.begin(), rawExclusiveTiming.end(), rawExclusiveTiming.begin(), ::tolower);
Expand All @@ -709,44 +714,20 @@ bool Environment::parse_cmdline(int argc, char** argv)
else
Stopwatch::set_inclusive(true);
}
#endif
#endif

//==== Using GPU
#ifdef USE_GPU
auto rawUseGpuUC = Nyxus::toupper(rawUseGpu);
if (rawUseGpuUC.length() == 0)
{
set_use_gpu(false);
std::cout << "\n!\n! Not using GPU. To involve GPU, use command line option " << USEGPU << "=true\n!\n\n";
}
else
#ifdef USE_GPU
if (!gpuOptions.empty())
{
auto validUsegpu1 = Nyxus::toupper("true"),
validUsegpu2 = Nyxus::toupper("false");
if (rawUseGpuUC != validUsegpu1 && rawUseGpuUC != validUsegpu2)
std::string ermsg;
if (!gpuOptions.parse_input(ermsg))
{
std::cerr << "Error: valid values of " << USEGPU << " are " << validUsegpu1 << " or " << validUsegpu2 << "\n";
std::cerr << ermsg << "\n";
return false;
}
use_gpu_ = rawUseGpuUC == validUsegpu1;

// Process user's GPU device choice
if (use_gpu_)
{
if (!rawGpuDeviceID.empty())
{
// string -> integer
if (sscanf(rawGpuDeviceID.c_str(), "%d", &gpu_device_id_) != 1 || gpu_device_id_ < 0)
{
std::cerr << "Error: " << GPUDEVICEID << "=" << gpu_device_id_ << ": expecting 0 or positive integer constant\n";
return false;
}
}
else
gpu_device_id_ = 0; // Specific GPU device ID was not requested, defaulting to 0
}
}
#endif
#endif

//==== Parse desired features

Expand Down Expand Up @@ -970,48 +951,50 @@ bool Environment::arrow_is_enabled()

void Environment::set_gpu_device_id (int choice)
{
auto n_gpus = get_gpu_properties().size();
if (n_gpus == 0)
auto prp = get_gpu_properties();
auto n= prp.size();
if (n == 0)
{
std::cerr << "Error: no GPU devices available \n";
return;
}

if (choice > get_gpu_properties().size() - 1)
if (choice >= n)
{
std::cerr << "Warning: GPU choice (" << choice << ") is out of range. Defaulting to device 0 \n";
gpu_device_id_ = 0;
return;
gpuOptions.set_single_device_id (0);
}

gpu_device_id_ = choice;
else
gpuOptions.set_single_device_id (choice);
}

int Environment::get_gpu_device_choice()
{
if (using_gpu())
return gpu_device_id_;
return gpuOptions.get_single_device_id();
else
return -1; // GPU was not requested so return an invalid device ID -1
}

void Environment::set_use_gpu(bool yes)
void Environment::set_using_gpu (bool yes)
{
use_gpu_ = yes;
gpuOptions.set_using_gpu (yes);
}

bool Environment::using_gpu()
{
return use_gpu_;
return gpuOptions.get_using_gpu();
}

std::vector<std::map<std::string, std::string>> Environment::get_gpu_properties() {
std::vector<std::map<std::string, std::string>> Environment::get_gpu_properties()
{
int n_devices;
std::vector<std::map<std::string, std::string>> props;

cudaGetDeviceCount(&n_devices);

for (int i = 0; i < n_devices; ++i) {
for (int i = 0; i < n_devices; ++i)
{
cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, i);

Expand Down
Loading

0 comments on commit f76043c

Please sign in to comment.