diff --git a/src/opencl/Compare.md b/src/opencl/Compare.md new file mode 100644 index 000000000..6df361e57 --- /dev/null +++ b/src/opencl/Compare.md @@ -0,0 +1,372 @@ +# Сравнение библиотек для создания механизма конфигурирования +Вся информация взята из репозиториев соответствующих проектов. Дата обращения: 21.04.26. + +## Критерии сравнения + +| Критерий | Метрика | Единицы измерения | +|:---|:---|:---| +| **Зависимости** | наличие | +/- | +| **Источники** | CLI (argv) / env / файлы | +/-, форматы | +| **Стандарт C++** | минимальная версия | C++хх | +| **Поддерживает стандарт иерархии** | Linux/Windows/MacOS | +/- | +| **Способ распространения** | header-only / статическая / динамическая | — | +| **ОС** | Linux / Windows / macOS | +/-, компиляторы | +| **Активность** | среднее число коммитов | коммитов/мес (за последний мес) | +| **Issues** | open, closed/open | n, коэффициент | +| **Сообщество** | GitHub Stars | n | +| **Проходит ли CI** | CI | +/- | + +> ? - не указано в README. + + +## Сравнение парсеров командной строки (CLI) + +| Критерий | CLI11 | cxxopts | +|:---|:---:|:---:| +| Зависимости | - | - | +| **Источники** | | | +|  CLI (argv) | + | + | +|  env | + | - | +|  Файлы | - | - | +| Стандарт C++ | ≥ C++11 | ≥ C++11 | +| **Поддерживает стандарт иерархии** | | | +|  Linux | + | + | +|  Windows | + | + | +|  MacOS | + | + | +| Способ распространения | header-only | header-only +| **ОС** | | | +|  Linux | + (GCC 4.8+, Clang 3.5+) | + (GCC ≥ 4.9, Clang ≥ 3.1) | +|  Windows | + (MSVC ≥ 2015) | + (MSVC ≥ 2015) | +|  macOS | + (AppleClang 7+) | + (Clang ≥ 3.1 с libc++) | +| **Сообщество** | | | +|  Активность (коммитов/мес) | 4 | 1 | +|  Issues всего | 506 | 307 | +|  Issues closed/open | 9 | 6 | +|  Stars | 4.3k | 4.7k | +| Проходит ли CI | + | + | + | + + +## Парсеры файлов + +| Критерий | nlohmann/json | yaml-cpp | toml++ | toml11 | config-cxx | taocpp/config +|:---|:---:|:---:|:---:|:---:|:---:|:---:| +| Зависимости | - | - | - | - | - | - | +| **Источники** | +|  CLI (argv) | - | - | - | - | - | - | +|  env | - | - | - | - | + | + | +|  Файлы | JSON | YAML | TOML | TOML | JSON, YAML, XML | JSON, JAXN | +| Стандарт C++ | ≥ C++11 | ≥ C++11 | ≥ C++17 | ≥ C++11 | ≥ C++20 | ≥ C++17 | +| **Поддерживает стандарт иерархии** | +|  Linux | + | + | + | + | + | + | +|  Windows | + | + | + | + | + | + | +|  MacOS | + | + | + | + | + | + | +| Способ распространения | header-only | статическая/динамическая¹ | header-only/статическая/динамическая¹ | header-only/статическая/динамическая¹ | header-only | header-only | +| **ОС** | | | +|  Linux | + (GCC 4.8–14.2, Clang 3.4–21.0) | + (GCC, Clang) | + (Clang 8+, GCC 8+) | + (GCC, Clang) | + (GCC 13+, Clang 16+) | + +|  Windows | + (MSVC 2015–2022) | + (MSVC) | + (MSVC VS2019+) | + (MSVC, MinGW) | + (MSVC 143+ (VS 2022)) | + +|  macOS | + (AppleClang 9.1–16.0) | + (Xcode, AppleClang) | + (AppleClang) | + (AppleClang) | + (AppleClang 16+) | + +| **Сообщество** | | | +|  Активность (коммитов/мес) | 2 | 9 | 2 | 4 | 2 | 13 | +|  Issues всего | 3271 | 905 | 193 | 194 | 24 | 5 | +|  Issues closed/open | 68 | 3 | 8 | 5 | 0 open | 0 open | +|  Stars | 49.4k | 15k | 2k | 1.3k | 31 | 194 | +| Проходит ли CI | + | + | + | - | + | ? | + +> ¹ Опционально. + +## Сравнение универсальных инструментов + +| Критерий | Boost.Program_options +|:---|:---:| +| Зависимости | +¹ | +| **Источники** | | | +|  CLI (argv) | + | + | +|  env | + | + | +|  Файлы | INI | +| Стандарт C++ | ≥ C++3 | +| **Поддерживает стандарт иерархии** | | | +|  Linux | + | +|  Windows | + | +|  MacOS | + | +| Способ распространения | статическая/динамическая² | +| **ОС** | | | +|  Linux | + (GCC 5+, Clang 3.6+) | +|  Windows | + (MSVC 2015 (vc140)+) | +|  macOS | + (AppleClang) | +| **Сообщество** | | | +|  Активность (коммитов/мес) | 12 | +|  Issues всего | 400 | +|  Issues closed/open | 1 | +|  Stars | 9.4k | +| Проходит ли CI | + | + | + +> ¹ Список зависимостей: +> 1. Boost.Any +> 2. Boost.Bind +> 3. Boost.Config +> 4. Boost.Core +> 5. Boost.Detail +> 6. Boost.Function +> 7. Boost.Iterator +> 8. Boost.Lexical Cast +> 9. Boost.Smart Ptr +> 10. Boost.ThrowException +> 11. Boost.Tokenizer +> 12. Boost.Type Traits + +> ² Опционально. + + +## Итог: +По результатам сравнения, вероятно, лучшим вариантом будет использование `CLI11` и `nlohmann/json`, так как они, в отличие от `Boost.program_options`, не требуют зависимостей и являются header-only. + + +# Механизм конфигурирования библиотеки SPLA + +## МАНУАЛ: + +## НАЗВАНИЕ +spla - это фреймворк для математических вычислений с использованием GPU/ускорения + +## СИНОПСИС + +```cpp +#include + + +int main(int argc, char* argv[]) { + + spla_configure(argc, argv); + + return 0; +} +``` +Функция `spla_configure(argc, argv)` необходима для обработки аргументов командной строки, переменных окружения и конфигурационных файлов. + +(будет написана) + +## ОПИСАНИЕ +**spla** - это библиотека с открытым исходным кодом, предоставляющая примитивы разреженной линейной алгебры (матрицы, векторы, скаляры) для математических вычислений с ускорением на GPU. Библиотека поддерживает широкий спектр операций, включая умножение матрицы на вектор, решение систем линейных уравнений и другие алгоритмы разреженной алгебры. + +Для большинства пользователей важно, на каком ускорителе будет запускаться их проект, поэтому необходима возможность явного указания графического ускорителя, OpenCL платформы, количества очередей команд и других параметров, влияющих на производительность и удобство пользователя. + +Библиотека предоставляет механизмы конфигурирования, показанные в порядке убывания приоритета. + +1. Аргументы командной строки 1. +2. Переменные окружения 2. +3. Пользовательский конфигурационный TOML/INI файл 3. +4. Системный конфигурационный TOML/INI файл 3. + +Системный конфигурационный файл имеет наименьший приоритет. \ +Его настройки переопределяются пользовательским конфигурационным файлом, переменными окружения и аргументами командной строки. + +Пользовательский конфигурационный файл имеет приоритет над системным +конфигурационным файлом. \ +Его настройки переопределяются переменными +окружения и аргументами командной строки. + +Переменные окружения имеют приоритет над системным и пользовательским конфигурационными файлами, но уступают аргументам командной строки. + +Аргументы командной строки имеют наивысший приоритет и переопределяют любые настройки, определенные предыдущими способами. + +Если источника конфигурации с меньшим приоритетом не существует, библиотека игнорирует его и переходит к следующему источнику конфигурации. + +Библиотека требует настройки всех обязательных параметров. Без этого будет генерироваться исключение `IncompleteConfigError`.\ +Чтобы исключение не возникало, пользователь должен явно настроить все параметры любым [способом](#configuration_methods). + +> 1 Описание аргументов командной строки приведены в разделе [ПАРАМЕТРЫ КОМАНДНОЙ СТРОКИ](#options). \ +2 Описание переменнных окружения приведены в разделе [ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ](#env_variables). \ +3 Шаблон конфигурационного файла с описанием приведен [здесь](#config). + +## ПАРАМЕТРЫ КОМАНДНОЙ СТРОКИ +```bash +-sh +--spla-help +``` +Выводит список всех доступных флагов spla с кратким описанием. + +```bash +-sv +--spla-version +``` +Показывает номер версии spla. + +```bash +-ss <путь> +--spla-sconf <путь> +``` +Путь к системному конфигурационному файлу. + +По умолчанию ищет файл в стандартном для данной ОС месте.\ +`/etc/spla/spla.toml` на Linux. \ +`/Library/Preferences/spla/spla.toml` на macOS. \ +`%ProgramData%\spla\spla.toml` на Windows. + +```bash +-su <путь> +--spla-uconf <путь> +``` +Путь к пользовательскому конфигурационному файлу. + +По умолчанию ищет файл в стандартном для данной ОС месте. \ +`~/.config/spla/spla.toml` на Linux. \ +`~/Library/Application Support/spla/spla.toml` на macOS. \ +`%APPDATA%\spla\spla.toml` на Windows. + +```bash +-sp <индекс> +--spla-platform <индекс> +``` +Индекс OpenCL платформы. + +```bash +-sd <индекс> +--spla-device <индекс> +``` +Индекс OpenCL-устройства выбранной платформы (GPU/CPU). + +```bash +-sq <число> +--spla-queues <число> +``` +Количество очередей команд. + +Увеличение числа очередей может повысить производительность +за счет параллельного выполнения независимых задач, но слишком большое значение может снизить производительность из-за накладных расходов. \ +Рекомендуется начинать с 1 и экспериментально подбирать оптимальное значение. + +```bash +-sV <уровень> +--spla-verbosity <уровень> +``` +Уровень детализации отладочной информации, выводимой библиотекой. + +| Уровень | Значение | Описание | +|:-------:|:---------|:---------| +| **0** | OFF | Вывод отключён. Сообщения не выводятся. | +| **1** | ERROR | Только критические ошибки. | +| **2** | WARNING | Ошибки и предупреждения. | +| **3** | INFO | Основные этапы работы библиотеки. | + +Что выводится на каждом уровне: + +1. ERROR — `Error`, `NoAcceleration`, `PlatformNotFound`, `DeviceNotFound`, `InvalidState`, `InvalidArgument`, `CompilationError` + +2. WARNING — добавляет к ERROR: `NoValue`, `NotImplemented` + +3. INFO — добавляет к WARNING: `Ok` - информация об инициализации, выборе платформы/устройства, загрузке конфигурации + +```bash +-sp +--spla-profiling +``` +Профилирование очередей команд OpenCL. + +Bключение профилирования добавляет накладные расходы на каждую +операцию и может замедлить выполнение. + +```bash +-sa <тип> +--spla-allocator <тип> +``` +Тип аллокатора памяти для OpenCL-устройства. \ +Ожидает: lineral | general. + +Выбор аллокатора влияет на производительность операций с памятью. +Линейный аллокатор даёт прирост скорости за счёт последовательного выделения, +но может привести к фрагментации памяти при длительной работе. +Общий аллокатор более надёжен, но медленнее. + +```bash +-sS <размер> +--spla-allocator-size <размер> +``` +Размер линейного аллокатора в байтах. + +Увеличение размера позволяет выделять больше памяти за один раз, но увеличивает потребление ресурсов. \ +Этот параметр необходим только при использовании линейного аллокатора `--spla-allocator linear`. +При использовании общего аллокатора можно не указывать. + +Пример: +```bash +./my_program --spla-uconf ~/my_configs/spla.toml --spla-platform 1 -sd 0 --spla-verbosity 3 -sp true --spla-allocator linear --spla-allocator-size 1048576 +``` + +## ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ +Для всех параметров командной строки (кроме --spla-help, --spla-version) существуют аналоги в виде переменных окружения. + +`SPLA_SYSTEM_CONFIG_PATH` \ +Путь к системному конфигурационному файлу. Аналог параметра --spla-sconf. + +`SPLA_USER_CONFIG_PATH` \ +Путь к пользовательскому конфигурационному файлу. Аналог параметра --spla-uconf. + +`SPLA_OPENCL_PLATFORM` \ +Индекс OpenCL платформы. Аналог параметра --spla-platform. + +`SPLA_OPENCL_DEVICE`\ +Индекс OpenCL-устройства на выбранной платформе. +Аналог параметра --spla-device. + +`SPLA_QUEUES`\ +Количество очередей команд OpenCL. +Аналог параметра --spla-queues. + +`SPLA_VERBOSITY`\ +Уровень детализации отладочной информации. +Аналог параметра --spla-verbosity.\ +Принимает значения от 0 до 3. + +`SPLA_PROFILING`\ +Включает профилирование очередей команд OpenCL. +Аналог параметра --spla-profiling. + +`SPLA_ALLOCATOR`\ +Тип аллокатора памяти для OpenCL-устройства. +Аналог параметра --spla-allocator.\ +Принимает значения: linear, general. + +`SPLA_ALLOCATOR_SIZE`\ +Размер линейного аллокатора в байтах. +Аналог параметра --spla-allocator-size.\ +Имеет смысл только при использовании линейного аллокатора. + + +## КОНФИГУРАЦИОННЫЕ ФАЙЛЫ +Для всех аргументов командной строки (кроме --spla-help, --spla-version, --spla-sconf, --spla-uconf) и всех переменных окружения существуют аналоги в виде параметров конфигурационного файла. + +Формат файла - TOML или INI. + +Типы значений. +1. platform = int +2. device = int +3. queues = int +4. verbosity = int +5. profiling = true | false +6. allocator = linear | general +7. allocator_size = int + +Примеры конфигурационных файлов.\ +Этот формат справедлив как для пользовательского, так и для системного конфигурационного файла. + +Формат TOML. +```toml +platform = 1 +device = 0 +queues = 4 +verbosity = 2 +profiling = false +allocator = "linear" +allocator_size = 1048576 +``` + +Формата INI. +```ini +platform = 1 +device = 0 +queues = 4 +verbosity = 2 +profiling = true +allocator = linear +allocator_size = 1048576 +``` \ No newline at end of file diff --git a/src/opencl/cl_accelerator.cpp b/src/opencl/cl_accelerator.cpp index 6a7a0517a..0060d1d3e 100644 --- a/src/opencl/cl_accelerator.cpp +++ b/src/opencl/cl_accelerator.cpp @@ -39,40 +39,81 @@ namespace spla { CLAccelerator::CLAccelerator() = default; CLAccelerator::~CLAccelerator() = default; - Status CLAccelerator::init() { - m_description = "no platform or device"; + Status CLAccelerator::init(const Config& cfg) { + + ConfigStatus config_status; + config_status = validate(cfg); + if (config_status != ConfigStatus::Ok) { + std::exit(1); + } - const char* spla_opencl_platform = std::getenv(SPLA_OPENCL_PLATFORM); - const char* spla_opencl_device = std::getenv(SPLA_OPENCL_DEVICE); - int platform_index = (spla_opencl_platform ? std::atoi(spla_opencl_platform) : 0); - int device_index = (spla_opencl_device ? std::atoi(spla_opencl_device) : 0); + std::cout << "Configuration parametrs:" << std::endl; + std::cout << "OpenCL platform index: " << cfg.platform.value() << std::endl; + std::cout << "OpenCL device index: " << cfg.device.value() << std::endl; + std::cout << "Queues number: " << cfg.queues.value() << std::endl; + std::cout << "Profiling: " << cfg.profiling.value() << std::endl; + std::cout << "Allocator: " << cfg.allocator.value() << std::endl; + if (cfg.allocator == "linear") std::cout << "Linear allocator size: " << cfg.allocator_size.value() << std::endl; + std::cout << "Wave size: " << cfg.wave_size.value() << std::endl; + std::cout << "Default wgs: " << cfg.default_wgs.value() << std::endl; + std::cout << "Num of mem banks: " << cfg.num_of_mem_banks.value() << std::endl; + std::cout << "Verbosity: " << cfg.verbosity.value() << std::endl; + + m_description = "no platform or device"; - if (set_platform(platform_index) != Status::Ok) + if (set_platform(cfg.platform.value()) != Status::Ok) return Status::PlatformNotFound; - if (set_device(device_index) != Status::Ok) + if (set_device(cfg.device.value()) != Status::Ok) return Status::DeviceNotFound; - if (set_queues_count(1) != Status::Ok) + if (set_profiling(cfg.profiling.value()) != Status::Ok) + return Status::Error; + + if (set_queues_count(cfg.queues.value()) != Status::Ok) + return Status::Error; + + if (cfg.allocator.value() == "linear") { + if (set_linear_allocator(cfg.allocator_size.value()) != Status::Ok) + return Status::Error; + } else { + if (set_general_allocator() != Status::Ok) + return Status::Error; + } + + if (set_default_wgs(cfg.default_wgs.value()) != Status::Ok) + return Status::Error; + + if (set_wave_size(cfg.wave_size.value()) != Status::Ok) + return Status::Error; + + if (set_num_of_mem_banks(cfg.num_of_mem_banks.value()) != Status::Ok) return Status::Error; m_cache = std::make_unique(); - // Output handy info LOG_MSG(Status::Ok, "Initialize accelerator: " << get_description()); return Status::Ok; } + + Status CLAccelerator::set_platform(int index) { std::vector available_platforms; cl::Platform::get(&available_platforms); if (available_platforms.empty()) { - LOG_MSG(Status::PlatformNotFound, "no platform to select for OpenCL acceleration, check your system runtime"); + LOG_MSG(Status::PlatformNotFound, "no platform to select for OpenCL acceleration"); return Status::PlatformNotFound; } - if (available_platforms.size() <= index) { - LOG_MSG(Status::InvalidArgument, "index out of list of available platforms"); + + if (index < 0) { + LOG_MSG(Status::InvalidArgument, "platform index must be >= 0 (got " << index << ")"); + return Status::InvalidArgument; + } + + if (available_platforms.size() <= static_cast(index)) { + LOG_MSG(Status::InvalidArgument, "platform index out of range (got " << index << ", max " << available_platforms.size() - 1 << ")"); return Status::InvalidArgument; } @@ -81,22 +122,33 @@ namespace spla { m_alloc_linear.reset(); m_alloc_tmp = nullptr; m_device = cl::Device(); - m_platform = available_platforms[index]; + + m_platform = available_platforms[index]; LOG_MSG(Status::Ok, "select OpenCL platform " << m_platform.getInfo()); return Status::Ok; } + + Status CLAccelerator::set_device(int index) { + m_vendor_code.clear(); + std::vector available_devices; m_platform.getDevices(CL_DEVICE_TYPE_GPU, &available_devices); if (available_devices.empty()) { - LOG_MSG(Status::DeviceNotFound, "no device in selected platform, check your OpenCL runtime"); + LOG_MSG(Status::DeviceNotFound, "no device to select for OpenCL acceleration"); return Status::DeviceNotFound; } - if (available_devices.size() <= index) { - LOG_MSG(Status::DeviceNotFound, "index out of list of available devices"); - return Status::DeviceNotFound; + + if (index < 0) { + LOG_MSG(Status::InvalidArgument, "device index must be >= 0 (got " << index << ")"); + return Status::InvalidArgument; + } + + if (available_devices.size() <= static_cast(index)) { + LOG_MSG(Status::InvalidArgument, "platform index out of range (got " << index << ", max " << available_devices.size() - 1 << ")"); + return Status::InvalidArgument; } m_device = available_devices[index]; @@ -120,8 +172,6 @@ namespace spla { m_vendor_name.find("INTEL") != std::string::npos || m_vendor_id == 32902) { m_vendor_code = VENDOR_CODE_INTEL; - m_default_wgs = 64; - m_wave_size = 8; m_is_intel = true; } if (m_vendor_name.find("Nvidia") != std::string::npos || @@ -129,8 +179,6 @@ namespace spla { m_vendor_name.find("NVIDIA") != std::string::npos || m_vendor_id == 4318) { m_vendor_code = VENDOR_CODE_NVIDIA; - m_default_wgs = 64; - m_wave_size = 32; m_is_nvidia = true; } if (m_vendor_name.find("Amd") != std::string::npos || @@ -140,30 +188,19 @@ namespace spla { m_vendor_name.find("advanced micro devices") != std::string::npos || m_vendor_name.find("ADVANCED MICRO DEVICES") != std::string::npos) { m_vendor_code = VENDOR_CODE_AMD; - m_default_wgs = 64; - m_wave_size = 64; m_is_amd = true; - - // Likely, it is an integrated amd device - if (m_max_wgs <= 256 || m_max_cu == 1) m_wave_size = 16; } if (m_vendor_name.find("Imagination Technologies") != std::string::npos || m_vendor_name.find("IMG") != std::string::npos || m_vendor_name.find("img") != std::string::npos || m_vendor_id == 0x1010) { m_vendor_code = VENDOR_CODE_IMG; - m_default_wgs = 32; - m_wave_size = 32; m_is_img = true; } - if (m_vendor_code.empty()) { LOG_MSG(Status::Error, "failed to match one of the pre-defined vendors"); - m_default_wgs = 64; - m_wave_size = 8; } - std::stringstream desc; desc << "OpenCL Acc " << m_platform.getInfo() << " device: " << m_device.getInfo() @@ -175,19 +212,31 @@ namespace spla { m_description = desc.str(); LOG_MSG(Status::Ok, m_description); + return Status::Ok; + } + Status CLAccelerator::set_profiling(bool enabled) { + m_profiling_enabled = enabled; + LOG_MSG(Status::Ok, "set profiling " << (enabled ? "enabled" : "disabled")); return Status::Ok; } + + Status CLAccelerator::set_queues_count(int count) { + if (count <= 0) { + LOG_MSG(Status::InvalidArgument, "queues count must be > 0 (got " << count << ")"); + return Status::InvalidArgument; + } + m_context = cl::Context(m_device); m_queues.clear(); m_queues.reserve(count); for (int i = 0; i < count; i++) { cl_command_queue_properties properties = 0; -#ifndef SPLA_RELEASE - properties = CL_QUEUE_PROFILING_ENABLE; -#endif + if (m_profiling_enabled) { + properties |= CL_QUEUE_PROFILING_ENABLE; + } cl::CommandQueue queue(m_context, properties); m_queues.emplace_back(std::move(queue)); } @@ -196,14 +245,64 @@ namespace spla { m_alloc_general = std::make_unique(); m_alloc_tmp = m_alloc_general.get(); - if (!is_nvidia()) { - m_alloc_linear = std::make_unique(CLAllocLinear::DEFAULT_SIZE, m_addr_align); - m_alloc_tmp = m_alloc_linear.get(); + LOG_MSG(Status::Ok, "configure " << count << " queues for computations" + << " (profiling: " << (m_profiling_enabled ? "ON" : "OFF") << ")"); + return Status::Ok; + } + + + Status CLAccelerator::set_linear_allocator(size_t size) { + if (size == 0) { + LOG_MSG(Status::InvalidArgument, "allocator_size must be > 0 for linear allocator (got " << size << ")"); + return Status::InvalidArgument; } + m_alloc_linear = std::make_unique(size, m_addr_align); + m_alloc_tmp = m_alloc_linear.get(); + LOG_MSG(Status::Ok, "set linear allocator (size: " << size << " bytes)"); + return Status::Ok; + } - LOG_MSG(Status::Ok, "configure " << count << " queues for computations"); + + Status CLAccelerator::set_general_allocator() { + m_alloc_tmp = m_alloc_general.get(); + LOG_MSG(Status::Ok, "set general allocator"); return Status::Ok; } + + + Status CLAccelerator::set_default_wgs(int wgs) { + if (wgs <= 0) { + LOG_MSG(Status::InvalidArgument, "default_wgs must be > 0 (got " << wgs << ")"); + return Status::InvalidArgument; + } + m_default_wgs = wgs; + LOG_MSG(Status::Ok, "set default work group size: " << wgs); + return Status::Ok; + } + + + Status CLAccelerator::set_wave_size(int size) { + if (size <= 0) { + LOG_MSG(Status::InvalidArgument, "wave_size must be > 0 (got " << size << ")"); + return Status::InvalidArgument; + } + m_wave_size = size; + LOG_MSG(Status::Ok, "set wave size: " << size); + return Status::Ok; + } + + + Status CLAccelerator::set_num_of_mem_banks(int banks) { + if (banks <= 0) { + LOG_MSG(Status::InvalidArgument, "num_of_mem_banks must be > 0 (got " << banks << ")"); + return Status::InvalidArgument; + } + m_num_of_mem_banks = banks; + LOG_MSG(Status::Ok, "set num of mem banks: " << banks); + return Status::Ok; + } + + const std::string& CLAccelerator::get_name() { return m_name; } @@ -214,4 +313,4 @@ namespace spla { return m_suffix; } -}// namespace spla +}// namespace spla \ No newline at end of file diff --git a/src/opencl/cl_accelerator.hpp b/src/opencl/cl_accelerator.hpp index 52a0497e1..00aed7200 100644 --- a/src/opencl/cl_accelerator.hpp +++ b/src/opencl/cl_accelerator.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -70,13 +71,16 @@ namespace spla { CLAccelerator(); ~CLAccelerator() override; - Status init() override; - Status set_platform(int index) override; - Status set_device(int index) override; - Status set_queues_count(int count) override; - const std::string& get_name() override; - const std::string& get_description() override; - const std::string& get_suffix() override; + Status init(const Config& cfg) override; + Status set_platform(int index) override; + Status set_device(int index) override; + Status set_profiling(bool enabled); + Status set_queues_count(int count) override; + Status set_linear_allocator(size_t size); + Status set_general_allocator(); + Status set_default_wgs(int wgs); + Status set_wave_size(int size); + Status set_num_of_mem_banks(int banks); cl::Platform& get_platform() { return m_platform; } cl::Device& get_device() { return m_device; } @@ -87,13 +91,18 @@ namespace spla { class CLAllocGeneral* get_alloc_general() { return m_alloc_general.get(); } class CLAlloc* get_alloc_tmp() { return m_alloc_tmp; } - [[nodiscard]] const std::string& get_vendor_name() const { return m_vendor_name; } + const std::string& get_name() override; + const std::string& get_description() override; + const std::string& get_suffix() override; + [[nodiscard]] const std::string& get_vendor_code() const { return m_vendor_code; } + [[nodiscard]] const std::string& get_vendor_name() const { return m_vendor_name; } [[nodiscard]] uint get_vendor_id() const { return m_vendor_id; } [[nodiscard]] uint get_max_cu() const { return m_max_cu; } [[nodiscard]] uint get_max_wgs() const { return m_max_wgs; } [[nodiscard]] uint get_max_local_mem() const { return m_max_local_mem; } [[nodiscard]] uint get_addr_align() const { return m_addr_align; } + [[nodiscard]] bool get_profiling_enabled() const { return m_profiling_enabled; } [[nodiscard]] uint get_default_wgs() const { return m_default_wgs; } [[nodiscard]] uint get_wave_size() const { return m_wave_size; } [[nodiscard]] uint get_num_of_mem_banks() const { return m_num_of_mem_banks; } @@ -106,6 +115,7 @@ namespace spla { cl::Platform m_platform; cl::Device m_device; cl::Context m_context; + ankerl::svector m_queues; std::unique_ptr m_cache; std::unique_ptr m_counter_pool; std::unique_ptr m_alloc_linear; @@ -115,22 +125,22 @@ namespace spla { std::string m_name = "OpenCL"; std::string m_description; std::string m_suffix = "__cl"; - std::string m_vendor_name; + std::string m_vendor_code; - uint m_vendor_id = 0; - uint m_max_cu = 0; - uint m_max_wgs = 0; - uint m_max_local_mem = 0; - uint m_addr_align = 128; - uint m_default_wgs = 64; - uint m_wave_size = 32; - uint m_num_of_mem_banks = 32; - bool m_is_nvidia = false; - bool m_is_amd = false; - bool m_is_intel = false; - bool m_is_img = false; - - ankerl::svector m_queues; + std::string m_vendor_name; + uint m_vendor_id = 0; + uint m_max_cu = 0; + uint m_max_wgs = 0; + uint m_max_local_mem = 0; + uint m_addr_align = 0; + bool m_profiling_enabled = false;// + uint m_default_wgs = 0; // + uint m_wave_size = 0; // + uint m_num_of_mem_banks = 0; // + bool m_is_nvidia = false; + bool m_is_amd = false; + bool m_is_intel = false; + bool m_is_img = false; }; /** diff --git a/src/opencl/cl_configure.cpp b/src/opencl/cl_configure.cpp new file mode 100644 index 000000000..1e3591bef --- /dev/null +++ b/src/opencl/cl_configure.cpp @@ -0,0 +1,462 @@ +#include "cl_configure.hpp" +#include "cl_accelerator.hpp" +#include "opencl.hpp" + +namespace spla { + + Config config_user_and_system; + Config config_cli_and_env; + Config config_final; + + void Config::merge(const Config& source) { + if (source.system_config_path.has_value()) system_config_path = source.system_config_path; + if (source.user_config_path.has_value()) user_config_path = source.user_config_path; + if (source.platform.has_value()) platform = source.platform; + if (source.device.has_value()) device = source.device; + if (source.queues.has_value()) queues = source.queues; + if (source.profiling.has_value()) profiling = source.profiling; + if (source.allocator.has_value()) allocator = source.allocator; + if (source.allocator_size.has_value()) allocator_size = source.allocator_size; + if (source.default_wgs.has_value()) default_wgs = source.default_wgs; + if (source.wave_size.has_value()) wave_size = source.wave_size; + if (source.verbosity.has_value()) verbosity = source.verbosity; + } + + void Config::reset() { + *this = Config{}; + } + + std::string get_home_directory() { +#ifdef _WIN32 + const char* home = std::getenv("USERPROFILE"); + if (home) return std::string(home); + + const char* drive = std::getenv("HOMEDRIVE"); + const char* path = std::getenv("HOMEPATH"); + if (drive && path) return std::string(drive) + std::string(path); + return ""; + +#elif defined(__APPLE__) || defined(__linux__) || defined(__unix__) + const char* home = std::getenv("HOME"); + if (home) return std::string(home); + + struct passwd* pw = getpwuid(getuid()); + if (pw) return std::string(pw->pw_dir); + return ""; + +#else + #error "Unsupported operating system" +#endif + } + + std::string get_default_system_config_path() { +#ifdef _WIN32 + const char* program_data = std::getenv("PROGRAMDATA"); + if (program_data) { + return std::string(program_data) + "\\spla\\"; + } + return "C:\\ProgramData\\spla\\"; + +#elif defined(__APPLE__) + return "/Library/Application Support/spla/"; + +#elif defined(__linux__) || defined(__unix__) + return "/etc/spla/"; + +#else + #error "Unsupported operating system" +#endif + } + + std::string get_default_user_config_path() { + std::string home = get_home_directory(); + if (home.empty()) { + return ""; + } + +#ifdef _WIN32 + const char* app_data = std::getenv("APPDATA"); + if (app_data) return std::string(app_data) + "\\spla\\"; + return home + "\\AppData\\Roaming\\spla\\"; + +#elif defined(__APPLE__) + return home + "/Library/Application Support/spla/"; + +#elif defined(__linux__) || defined(__unix__) + return home + "/.config/spla/"; + +#else + #error "Unsupported operating system" +#endif + } + + std::string find_first_json_file(const std::string& directory) { + if (directory.empty() || !std::filesystem::exists(directory)) { + return ""; + } + + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.is_regular_file()) { + std::string path = entry.path().string(); + if (path.size() >= 5 && + path.compare(path.size() - 5, 5, ".json") == 0) { + return path; + } + } + } + return ""; + } + + ConfigStatus load_from_file(const std::string& path, Config& cfg) { + try { + std::ifstream file(path); + if (!file.is_open()) { + std::cerr << "Failed to open file: " << path << std::endl; + return ConfigStatus::OpenFileError; + } + + nlohmann::json config_data = nlohmann::json::parse(file); + + if (config_data.contains("platform")) { + cfg.platform = config_data["platform"].get(); + } + if (config_data.contains("device")) { + cfg.device = config_data["device"].get(); + } + if (config_data.contains("queues")) { + cfg.queues = config_data["queues"].get(); + } + if (config_data.contains("profiling")) { + cfg.profiling = config_data["profiling"].get(); + } + if (config_data.contains("allocator")) { + cfg.allocator = config_data["allocator"].get(); + } + if (config_data.contains("allocator_size")) { + cfg.allocator_size = config_data["allocator_size"].get(); + } + if (config_data.contains("default_wgs")) { + cfg.default_wgs = config_data["default_wgs"].get(); + } + if (config_data.contains("wave_size")) { + cfg.wave_size = config_data["wave_size"].get(); + } + if (config_data.contains("num_of_mem_banks")) { + cfg.num_of_mem_banks = config_data["num_of_mem_banks"].get(); + } + if (config_data.contains("verbosity")) { + cfg.verbosity = config_data["verbosity"].get(); + } + + return ConfigStatus::Ok; + + } catch (const nlohmann::json::exception& e) { + std::cerr << "Failed to parse config files" << std::endl; + std::cerr << "Error parsing JSON file '" << path << "': " << e.what() << std::endl; + return ConfigStatus::UserOrSystemConfParseError; + } + } + + ConfigStatus parse_system_and_user_conf(const Config& cli_env_config, Config& file_config) { + std::string sys_dir = cli_env_config.system_config_path.value_or(get_default_system_config_path()); + std::string user_dir = cli_env_config.user_config_path.value_or(get_default_user_config_path()); + + ConfigStatus status = ConfigStatus::Ok; + + if (sys_dir != "") { + std::string sys_file = find_first_json_file(sys_dir); + if (sys_file != "") { + std::cout << "Loading system config: " << sys_file << std::endl; + status = load_from_file(sys_file, file_config); + if (status == ConfigStatus::UserOrSystemConfParseError) return status; + } else + std::cout << "No system config found in: " << sys_dir << std::endl; + } else + std::cout << "System config directory is not set. Skipping." << std::endl; + + if (user_dir != "") { + std::string user_file = find_first_json_file(user_dir); + if (user_file != "") { + std::cout << "Loading user config: " << user_file << std::endl; + status = load_from_file(user_file, file_config); + if (status == ConfigStatus::UserOrSystemConfParseError) return status; + } else + std::cout << "No user config found in: " << user_dir << std::endl; + } else + std::cout << "User config directory is not set. Skipping." << std::endl; + + if (status == ConfigStatus::OpenFileError) return status; + + return ConfigStatus::Ok; + } + + std::string get_spla_version() { + return "SPLA version: 0.0.0"; + } + + ConfigStatus parse_cli_and_env(int argc, char** argv, Config& cfg) { + CLI::App app{"SPLA configuration"}; + + app.add_flag("-sh,--spla-help", cfg.help, "Show help and exit"); + app.add_flag("-sv,--spla-version", cfg.version, "Show version and exit"); + + app.add_option("-ss,--spla-sconf", cfg.system_config_path, + "Path to system configuration file\n" + "Config key: (not used in config file)") + ->envname("SPLA_SYSTEM_CONFIG_PATH"); + + app.add_option("-su,--spla-uconf", cfg.user_config_path, + "Path to user configuration file\n" + "Config key: (not used in config file)") + ->envname("SPLA_USER_CONFIG_PATH"); + + app.add_option("-sp,--spla-platform", cfg.platform, + "OpenCL platform index\n" + "Config key: platform") + ->envname("SPLA_OPENCL_PLATFORM"); + + app.add_option("-sd,--spla-device", cfg.device, + "OpenCL device index\n" + "Config key: device") + ->envname("SPLA_OPENCL_DEVICE"); + + app.add_option("-sq,--spla-queues", cfg.queues, + "Number of command queues\n" + "Config key: queues") + ->envname("SPLA_QUEUES"); + + app.add_flag("-pr,--spla-profiling", cfg.profiling, + "Enable profiling of command queues\n" + "Config key: profiling\n") + ->envname("SPLA_PROFILING"); + + app.add_option("-sa,--spla-allocator", cfg.allocator, + "Allocator type: linear or general\n" + "Config key: allocator") + ->envname("SPLA_ALLOCATOR"); + + app.add_option("-as,--spla-allocator-size", cfg.allocator_size, + "Linear allocator size in bytes\n" + "Required for 'linear' allocator. Ignored for 'general'.\n" + "Config key: allocator_size") + ->envname("SPLA_ALLOCATOR_SIZE"); + + app.add_option("-dw,--spla-default-wgs", cfg.default_wgs, + "Default work group size\n" + "Config key: default_wgs") + ->envname("SPLA_DEFAULT_WGS"); + + app.add_option("ws,--spla-wave-size", cfg.wave_size, + "Wave size for device\n" + "Config key: wave_size") + ->envname("SPLA_WAVE_SIZE"); + + app.add_option("--spla-mem-banks", config_cli_and_env.num_of_mem_banks, + "Number of memory banks (for optimization)\n" + "Config key: num_of_mem_banks") + ->envname("SPLA_MEM_BANKS"); + + app.add_option("-sV,--spla-verbosity", cfg.verbosity, + "Verbosity level:\n" + " 0: No output\n" + " 1: Errors only\n" + " 2: Errors + warnings\n" + " 3: All messages (info, warnings, errors)") + ->envname("SPLA_VERBOSITY"); + + try { + app.parse(argc, argv); + } catch (const CLI::ParseError& e) { + std::cerr << "Failed to parse CLI/ENV" << std::endl; + app.exit(e); + return ConfigStatus::CliOrEnvParseError; + } + + if (cfg.help) { + std::cout << app.help() << std::endl; + return ConfigStatus::HelpRequested; + } + + if (cfg.version) { + std::cout << get_spla_version() << std::endl; + return ConfigStatus::VersionRequested; + } + + return ConfigStatus::Ok; + } + + + ConfigStatus check_platform_and_device(int platform_index, int device_index) { + + if (platform_index < 0) { + std::cerr << "Error: platform must be >= 0 (got " << platform_index << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + std::vector platforms; + cl::Platform::get(&platforms); + + if (platforms.empty()) { + std::cerr << "Error: no platform to select for OpenCL acceleration" << std::endl; + return ConfigStatus::PlatformNotFound; + } + + if (static_cast(platform_index) >= platforms.size()) { + std::cerr << "Error: platform index out of range (got " << platform_index << ", max " << platforms.size() - 1 << ")" << std::endl; + return ConfigStatus::PlatformNotFound; + } + + + if (device_index < 0) { + std::cerr << "Error: device must be >= 0 (got " << device_index << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + std::vector devices; + platforms[platform_index].getDevices(CL_DEVICE_TYPE_ALL, &devices); + + if (devices.empty()) { + std::cerr << "Error: no device to select for OpenCL acceleration" << std::endl; + return ConfigStatus::DeviceNotFound; + } + + if (static_cast(device_index) >= devices.size()) { + std::cerr << "Error: device index out of range (got " << device_index << ", max " << devices.size() - 1 << ")" << std::endl; + return ConfigStatus::DeviceNotFound; + } + + return ConfigStatus::Ok; + } + + + ConfigStatus validate(const Config& cfg) { + + if (!cfg.platform.has_value()) { + std::cerr << "Error: platform is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.device.has_value()) { + std::cerr << "Error: device is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.queues.has_value()) { + std::cerr << "Error: queues is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.profiling.has_value()) { + std::cerr << "Error: profiling is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.allocator.has_value()) { + std::cerr << "Error: allocator is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (cfg.allocator.value() == "linear" && !cfg.allocator_size.has_value()) { + std::cerr << "Error: allocator_size is required for linear allocator" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.default_wgs.has_value()) { + std::cerr << "Error: default_wgs is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.wave_size.has_value()) { + std::cerr << "Error: wave_size is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.num_of_mem_banks.has_value()) { + std::cerr << "Error: num_of_mem_banks is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + if (!cfg.verbosity.has_value()) { + std::cerr << "Error: verbosity is required" << std::endl; + return ConfigStatus::MissedParametrs; + } + + + ConfigStatus status; + + status = check_platform_and_device(*cfg.platform, *cfg.device); + if (status != ConfigStatus::Ok) { + return status; + } + + + if (*cfg.queues <= 0) { + std::cerr << "Error: queues must be > 0 (got " << *cfg.queues << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + + if (*cfg.allocator != "linear" && *cfg.allocator != "general") { + std::cerr << "Error: allocator must be 'linear' or 'general' (got '" << *cfg.allocator << "')" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + if (*cfg.allocator == "linear") { + if (*cfg.allocator_size <= 0) { + std::cerr << "Error: allocator_size must be > 0 for linear allocator (got " << *cfg.allocator_size << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + } + + + if (*cfg.default_wgs <= 0) { + std::cerr << "Error: default_wgs must be > 0 (got " << *cfg.default_wgs << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + if (*cfg.wave_size <= 0) { + std::cerr << "Error: wave_size must be > 0 (got " << *cfg.wave_size << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + if (*cfg.num_of_mem_banks <= 0) { + std::cerr << "Error: num_of_mem_banks must be > 0 (got " << *cfg.num_of_mem_banks << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + + if (*cfg.verbosity < 0 || *cfg.verbosity > 3) { + std::cerr << "Error: verbosity must be between 0 and 3 (got " << *cfg.verbosity << ")" << std::endl; + return ConfigStatus::InvalidConfigParams; + } + + return ConfigStatus::Ok; + } + + + ConfigStatus configure(int argc, char** argv) { + config_user_and_system.reset(); + config_cli_and_env.reset(); + config_final.reset(); + + ConfigStatus config_status; + + config_status = parse_cli_and_env(argc, argv, config_cli_and_env); + if (config_status != ConfigStatus::CliOrEnvParseError) return config_status; + else { + std::exit(1); + } + + config_status = parse_system_and_user_conf(config_cli_and_env, config_user_and_system); + if (config_status != ConfigStatus::UserOrSystemConfParseError) return config_status; + else { + std::exit(1); + } + + config_final = config_user_and_system; + config_final.merge(config_cli_and_env); + + auto* acc = get_acc_cl(); + if (!acc) { + std::exit(1); + } + + Status status = acc->init(config_final); + if (status != Status::Ok) { + std::exit(1); + } + + return ConfigStatus::Ok; + } +}// namespace spla diff --git a/src/opencl/cl_configure.hpp b/src/opencl/cl_configure.hpp new file mode 100644 index 000000000..8364a3a34 --- /dev/null +++ b/src/opencl/cl_configure.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include "CLI/CLI.hpp" +#include + +#ifdef _WIN32 +#elif defined(__APPLE__) || defined(__linux__) || defined(__unix__) + #include + #include +#endif + +namespace spla { + + struct Config { + std::optional help; + std::optional version; + std::optional system_config_path; + std::optional user_config_path; + std::optional platform; + std::optional device; + std::optional queues; + std::optional profiling; + std::optional allocator; + std::optional allocator_size; + std::optional default_wgs; + std::optional wave_size; + std::optional num_of_mem_banks; + std::optional verbosity; + + void merge(const Config& source); + void reset(); + }; + + enum ConfigStatus { + Ok, + HelpRequested, + VersionRequested, + + CliOrEnvParseError, + UserOrSystemConfParseError, + OpenFileError, + + MissedParametrs, + PlatformNotFound, + DeviceNotFound, + InvalidConfigParams + }; + + extern Config config_user_and_system; + extern Config config_cli_and_env; + extern Config config_final; + + std::string get_home_directory(); + std::string get_default_system_config_path(); + std::string get_default_user_config_path(); + std::string find_first_json_file(const std::string& directory); + ConfigStatus load_from_file(const std::string& path, Config& cfg); + + ConfigStatus parse_system_and_user_conf(const Config& cli_env_config, Config& file_config); + + std::string get_spla_version(); + ConfigStatus parse_cli_and_env(int argc, char** argv, Config& cfg); + + ConfigStatus check_platform_and_device(int platform_index, int device_index); + ConfigStatus validate(const Config& cfg); + + ConfigStatus configure(int argc, char** argv); + +}// namespace spla \ No newline at end of file