diff --git a/.github/workflows/self-hosted.yml b/.github/workflows/self-hosted.yml
index b15d036e5..5b6958d31 100644
--- a/.github/workflows/self-hosted.yml
+++ b/.github/workflows/self-hosted.yml
@@ -90,7 +90,7 @@ jobs:
- name: Check for tests results
run: |
- ! grep -qi "FAILED" ${{ env.test-file }}
+ ! grep -qE "\[ FAILED \]" ${{ env.test-file }}
clean:
name: Cleanup workspace
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index 6828c3d40..60c9bdd55 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -106,4 +106,4 @@ jobs:
- name: Check for tests results
run: |
- ! grep -qi "FAILED" ${{ env.test-file }}
+ ! grep -qE "\[ FAILED \]" ${{ env.test-file }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8c8cfa4f0..334095e71 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -178,6 +178,22 @@ endfunction()
add_subdirectory(deps/robin_hood)
add_subdirectory(deps/svector)
+message(STATUS "Add CLI11 as CLI/ENV parser")
+include(FetchContent)
+
+FetchContent_Declare(CLI11
+ GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
+ GIT_TAG v2.4.2
+)
+FetchContent_MakeAvailable(CLI11)
+
+message(STATUS "Add nlohmann/json as JSON parser")
+FetchContent_Declare(nlohmann_json
+ GIT_REPOSITORY https://github.com/nlohmann/json.git
+ GIT_TAG v3.11.3
+)
+FetchContent_MakeAvailable(nlohmann_json)
+
if (SPLA_BUILD_OPENCL)
if (SPLA_TARGET_MACOSX)
message(STATUS "Add standard Apple OpenCL package")
@@ -235,6 +251,8 @@ set(SRC_OPENCL)
if (SPLA_BUILD_OPENCL)
set(SRC_OPENCL
+ src/opencl/cl_configure.cpp
+ src/opencl/cl_configure.hpp
src/opencl/cl_debug.hpp
src/opencl/cl_accelerator.cpp
src/opencl/cl_accelerator.hpp
@@ -371,11 +389,13 @@ target_compile_definitions(spla PRIVATE SPLA_EXPORTS)
target_link_libraries(spla PRIVATE robin_hood)
target_link_libraries(spla PRIVATE svector)
+target_link_libraries(spla PRIVATE CLI11::CLI11)
+target_link_libraries(spla PRIVATE nlohmann_json::nlohmann_json)
if (SPLA_BUILD_OPENCL)
target_link_libraries(spla PUBLIC OpenCL)
target_link_libraries(spla PUBLIC OpenCL::HeadersCpp)
- target_compile_definitions(spla PUBLIC CL_TARGET_OPENCL_VERSION=120)
+ target_compile_definitions(spla PUBLIC CL_TARGET_OPENCL_VERSION=300)
list(APPEND SPLA_DEFINES SPLA_BUILD_OPENCL)
endif ()
diff --git a/deps/opencl-headers/tests/pkgconfig/bare/CMakeLists.txt b/deps/opencl-headers/tests/pkgconfig/bare/CMakeLists.txt
index f3e8466ee..b7a535f91 100644
--- a/deps/opencl-headers/tests/pkgconfig/bare/CMakeLists.txt
+++ b/deps/opencl-headers/tests/pkgconfig/bare/CMakeLists.txt
@@ -19,7 +19,7 @@ target_link_libraries(${PROJECT_NAME}
target_compile_definitions(${PROJECT_NAME}
PRIVATE
- CL_TARGET_OPENCL_VERSION=120
+ CL_TARGET_OPENCL_VERSION=300
)
include(CTest)
diff --git a/deps/opencl-headers/tests/pkgconfig/sdk/CMakeLists.txt b/deps/opencl-headers/tests/pkgconfig/sdk/CMakeLists.txt
index aa36d168b..5b0bb6d37 100644
--- a/deps/opencl-headers/tests/pkgconfig/sdk/CMakeLists.txt
+++ b/deps/opencl-headers/tests/pkgconfig/sdk/CMakeLists.txt
@@ -20,7 +20,7 @@ target_link_libraries(${PROJECT_NAME}
target_compile_definitions(${PROJECT_NAME}
PRIVATE
- CL_TARGET_OPENCL_VERSION=120
+ CL_TARGET_OPENCL_VERSION=300
)
include(CTest)
diff --git a/deps/opencl-icd-loader/test/pkgconfig/bare/CMakeLists.txt b/deps/opencl-icd-loader/test/pkgconfig/bare/CMakeLists.txt
index f58f21e9e..f9120c2bf 100644
--- a/deps/opencl-icd-loader/test/pkgconfig/bare/CMakeLists.txt
+++ b/deps/opencl-icd-loader/test/pkgconfig/bare/CMakeLists.txt
@@ -25,7 +25,7 @@ target_link_libraries(${PROJECT_NAME}
target_compile_definitions(${PROJECT_NAME}
PRIVATE
- CL_TARGET_OPENCL_VERSION=120
+ CL_TARGET_OPENCL_VERSION=300
)
include(CTest)
diff --git a/deps/opencl-icd-loader/test/pkgconfig/pkgconfig/CMakeLists.txt b/deps/opencl-icd-loader/test/pkgconfig/pkgconfig/CMakeLists.txt
index 436fffdb4..221153e79 100644
--- a/deps/opencl-icd-loader/test/pkgconfig/pkgconfig/CMakeLists.txt
+++ b/deps/opencl-icd-loader/test/pkgconfig/pkgconfig/CMakeLists.txt
@@ -39,7 +39,7 @@ target_compile_options(${PROJECT_NAME}
target_compile_definitions(${PROJECT_NAME}
PRIVATE
- CL_TARGET_OPENCL_VERSION=120
+ CL_TARGET_OPENCL_VERSION=300
)
include(CTest)
diff --git a/deps/opencl-icd-loader/test/pkgconfig/sdk/CMakeLists.txt b/deps/opencl-icd-loader/test/pkgconfig/sdk/CMakeLists.txt
index 83a52b70b..1b2021e1f 100644
--- a/deps/opencl-icd-loader/test/pkgconfig/sdk/CMakeLists.txt
+++ b/deps/opencl-icd-loader/test/pkgconfig/sdk/CMakeLists.txt
@@ -24,7 +24,7 @@ target_link_libraries(${PROJECT_NAME}
target_compile_definitions(${PROJECT_NAME}
PRIVATE
- CL_TARGET_OPENCL_VERSION=120
+ CL_TARGET_OPENCL_VERSION=300
)
include(CTest)
diff --git a/include/spla/library.hpp b/include/spla/library.hpp
index a572cb770..88a1c4a08 100644
--- a/include/spla/library.hpp
+++ b/include/spla/library.hpp
@@ -114,6 +114,21 @@ namespace spla {
*/
SPLA_API Status set_message_callback(MessageCallback callback);
+ /**
+ * @brief Set verbosity level for library logger
+ *
+ * Controls which messages are shown:
+ * 0 - no output
+ * 1 - errors only
+ * 2 - errors + warnings
+ * 3 - all messages (info, warnings, errors)
+ *
+ * @param verbosity Verbosity level [0, 3]
+ *
+ * @return Function call status
+ */
+ SPLA_API Status set_verbosity(int verbosity);
+
/**
* @brief Sets default library callback to log messages to console
*
diff --git a/src/core/logger.cpp b/src/core/logger.cpp
index 8fab9010d..5530759d9 100644
--- a/src/core/logger.cpp
+++ b/src/core/logger.cpp
@@ -31,10 +31,38 @@
namespace spla {
- void Logger::log_msg(Status status, const std::string& msg, const std::string& file, const std::string& function, int line) {
+ static int status_to_level(Status status) {
+ switch (status) {
+ case Status::Error:
+ case Status::PlatformNotFound:
+ case Status::DeviceNotFound:
+ case Status::InvalidArgument:
+ case Status::CompilationError:
+ return 1;
+
+ case Status::NoAcceleration:
+ case Status::InvalidState:
+ case Status::NoValue:
+ case Status::NotImplemented:
+ return 2;
+
+ case Status::Ok:
+ default:
+ return 3;
+ }
+ }
+
+ void Logger::log_msg(Status status, const std::string& msg,
+ const std::string& file, const std::string& function, int line) {
+
+ int level = status_to_level(status);
+ if (level > m_verbosity) return;
+
std::lock_guard guard(m_mutex);
std::filesystem::path file_path(file);
- if (m_callback) m_callback(status, msg, file_path.filename().string(), function, line);
+ if (m_callback) {
+ m_callback(status, msg, file_path.filename().string(), function, line);
+ }
}
void Logger::set_msg_callback(MessageCallback callback) {
@@ -42,4 +70,9 @@ namespace spla {
m_callback = std::move(callback);
}
+ void Logger::set_verbosity(int verbosity) {
+ std::lock_guard guard(m_mutex);
+ m_verbosity = verbosity;
+ }
+
}// namespace spla
\ No newline at end of file
diff --git a/src/core/logger.hpp b/src/core/logger.hpp
index 8afd7a04e..c00259c79 100644
--- a/src/core/logger.hpp
+++ b/src/core/logger.hpp
@@ -49,10 +49,11 @@ namespace spla {
public:
void log_msg(Status status, const std::string& msg, const std::string& file, const std::string& function, int line);
void set_msg_callback(MessageCallback callback);
+ void set_verbosity(int verbosity);
private:
- MessageCallback m_callback;
-
+ MessageCallback m_callback;
+ int m_verbosity = 3;
mutable std::mutex m_mutex;
};
diff --git a/src/library.cpp b/src/library.cpp
index d0a5494a1..d5acc0c31 100644
--- a/src/library.cpp
+++ b/src/library.cpp
@@ -143,6 +143,12 @@ namespace spla {
return Status::Ok;
}
+ Status Library::set_verbosity(int verbosity) {
+ m_logger->set_verbosity(verbosity);
+ LOG_MSG(Status::Ok, "set verbosity: " << verbosity);
+ return Status::Ok;
+ }
+
Status Library::set_default_callback() {
auto callback = [](spla::Status status,
const std::string& msg,
diff --git a/src/opencl/LibsCompare.md b/src/opencl/LibsCompare.md
new file mode 100644
index 000000000..14f5ce656
--- /dev/null
+++ b/src/opencl/LibsCompare.md
@@ -0,0 +1,126 @@
+# Сравнение библиотек для создания механизма конфигурирования
+Вся информация взята из репозиториев соответствующих проектов. Дата обращения: 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
+
+Механизм конфигурирования описывается в разделах: [Мануал пользователя](#UserManual) и [Руководство пользователя](#UserGuide)
\ No newline at end of file
diff --git a/src/opencl/UserGuide.md b/src/opencl/UserGuide.md
new file mode 100644
index 000000000..454d441cb
--- /dev/null
+++ b/src/opencl/UserGuide.md
@@ -0,0 +1,282 @@
+# User Guide
+
+Руководство по установке, проверке и настройке библиотеки spla.
+
+## Содержание
+
+1. [Установка](#установка)
+2. [Проверка работоспособности](#проверка-работоспособности)
+3. [Конфигурационный файл](#конфигурационный-файл)
+4. [Настройка](#настройка)
+5. [Профили](#профили)
+6. [Наследование профилей (`extends`)](#наследование-профилей)
+
+## Установка
+
+Инструкции по установке см. в [README](https://github.com/SparseLinearAlgebra/spla#installation).
+
+## Проверка работоспособности
+
+После установки выполните команду, которая подтверждает, что библиотека готова к использованию.
+
+```bash
+spla --softcheck
+```
+Команда выполняет следующую последовательность действий:
+
+1. Читает конфигурационный файл, предоставляющийся библиотекой.
+2. Инициализирует OpenCL, выбирает платформу и устройство.
+4. Запускает простое тестовое задание.
+5. Сообщает статус.
+
+Пример успешного вывода:
+```bash
+$ spla --softcheck
+[spla:check] Starting sanity check...
+[spla:check] Loading factory config: /usr/share/spla/spla_conf.json
+[spla:check] Initializing OpenCL...
+[spla:check] Selected platform: NVIDIA CUDA
+[spla:check] Selected device: NVIDIA GeForce RTX 3080
+[spla:check] Running test kernel: 2 + 3 = ...
+[spla:check] Result: 5 (expected 5)
+[spla:check] SUCCESS - spla works correctly
+```
+
+## Конфигурационный файл
+Конфигурационный файл, предоставляющийся библиотекой, имеет следующую структуру:
+
+```json
+{
+ "backend": "gpu",
+ "if_gpu_unavailable": "use_cpu",
+
+ "platform_index": null,
+ "device_index": null,
+
+ "queues_count": 1,
+ "profiling": false,
+ "allocator_type": "general",
+ "linear_alloc_size": 0,
+
+ "verbosity": 2,
+
+ "profile": null,
+ "profiles": {}
+}
+```
+| Параметр | Тип | Значение по умолчанию | Описание | Допустимые значения |
+| :--- | :--- | :--- | :--- | :--- |
+| backend | string | "gpu" | На каком устройстве работать | `gpu` - только GPU
`cpu` - только CPU
`any` - любое доступное устройство
`by_index` - выбрать по индексам ниже |
+| if_gpu_unavailable | string | "use_cpu" | Что делать, если GPU недоступен
Не применяется при backend: "`cpu`" или backend: "`by_index`" | `use_cpu` - переключиться на CPU
`abort` - вернуть ошибку |
+| platform_index | int/null | null | Индекс OpenCL платформы
Используются только при backend: "`by_index`"
При других значениях backend - игнорируются | ≥ 0, null |
+| device_index | int/null | null | Индекс устройства внутри платформы
Используются только при backend: "`by_index`"
При других значениях backend - игнорируются | ≥ 0, null |
+| queues_count | int | 1 | Число командных очередей | ≥ 1 |
+| profiling | bool | false | Профилирование очередей | true / false |
+| allocator_type | string | "general" | Тип аллокатора | general, linear |
+| linear_alloc_size | int | 0 | Размер линейного аллокатора (байт) | ≥ 0 (игнорируется для `general`) |
+| verbosity | int | 2 | Уровень логирования | `0` - нет вывода
`1` - только ошибки
`2` - ошибки, предупреждения
`3` - ошибки, предупреждения, дополнительная информация |
+| profile | string/null | null | Выбранный профиль | имя профиля из profiles или `null` |
+| profiles | object | {} | Словарь профилей | { "name": { ... override ... } } |
+| profiles.< name >.extends | array of string | [] | Список профилей-родителей | имена профилей из profiles |
+
+## Настройка
+
+Если параметры по умолчанию вас не устраивают, вы можете настроить библиотеку. Способы конфигурирования перечислены в порядке убывания приоритета:
+
+1. Аргументы командной строки.
+2. Переменные окружения.
+3. Пользовательский конфигурационный файл.
+4. Системный конфигурационный файл.
+5. Конфигурационный файл, поставляющийся библиотекой.
+
+### Расположение конфигурационных файлов
+
+Конфигурационный файл с параметрами по умолчанию:
+>Linux `/usr/share/spla/spla_conf.json` \
+macOS `/usr/local/share/spla/spla_conf.json` \
+Windows `%ProgramData%\spla\spla_conf.json`
+
+Системный конфигурационный файл:
+>Linux: `/etc/spla/spla_conf.json` \
+macOS: `/Library/Application Support/spla/spla_conf.json` \
+Windows: `%ProgramData%\spla\spla_conf.json`
+
+Пользовательский конфигурационный файл:
+>Linux: `~/.config/spla/spla_conf.json` \
+macOS: `~/Library/Application Support/spla/spla_conf.json` \
+Windows: `%APPDATA%\spla\spla_conf.json`
+
+
+## Профили
+
+Профили позволяют хранить несколько наборов настроек в одном конфигурационном файле и переключаться между ними при запуске. Это удобно, когда одну и ту же программу нужно запускать в разных режимах - например, на разных GPU или с разным уровнем логирования.
+
+- В конфигурационном файле описываются общие настройки, которые берутся в качестве базовых параметров.
+- Описывается секция `profiles` - именованные наборы параметров.
+- При запуске указывается имя профиля.
+- Библиотека переопределяет базовые параметры параметрами из выбранного профиля.
+
+### Пример
+```json
+{
+ "execution": {
+ "device": 0
+ },
+ "verbosity": 2,
+ "queues": 1,
+
+ "profiles": {
+ "gpu0": { "execution": { "device": 0 } },
+ "gpu1": { "execution": { "device": 1 } },
+ "gpu2": { "execution": { "device": 2 } },
+ "debug": { "verbosity": 3 },
+ "bench": { "verbosity": 0 }
+ }
+}
+```
+### Запуск
+```bash
+SPLA_PROFILE=gpu1 ./program
+SPLA_PROFILE=debug ./program
+SPLA_PROFILE=bench ./program
+```
+Или через CLI:
+```bash
+./program --spla-profile=gpu1
+```
+
+### Итоговый конфигурационный файл
+```json
+{
+ "execution": {
+ "device": 1
+ },
+
+ "queues": 1,
+ "verbosity": 2,
+}
+```
+
+## Наследование профилей
+
+Иногда профили пересекаются по смыслу - например, **debug_gpu1** должен включать и режим отладки, и выбор GPU 1. Чтобы не дублировать параметры, профиль может наследовать другие профили через поле `extends`.
+
+- Если профиль содержит поле `extends`, библиотека сначала применяет указанные в нём профили по порядку.
+- Затем к базовым параметрам применяются параметры каждого родительского профиля по очереди: последний в списке переопределяет предыдущие.
+- После этого применяются параметры самого выбранного профиля, которые переопределяют всё, что было задано раньше.
+
+### Пример
+
+``` json
+{
+ "execution": { "device": 0 },
+ "verbosity": 2,
+
+ "profiles": {
+ "gpu0": { "execution": { "device": 0 } },
+ "gpu1": { "execution": { "device": 1 } },
+ "gpu2": { "execution": { "device": 2 } },
+
+ "debug": { "verbosity": 3, "profiling": true },
+ "bench": { "verbosity": 0 },
+
+ "debug_gpu1": {
+ "extends": ["debug", "gpu1"]
+ },
+ "bench_gpu2": {
+ "extends": ["bench", "gpu2"]
+ }
+ }
+}
+```
+### Запуск
+```bash
+SPLA_PROFILE=debug_gpu1 ./program
+SPLA_PROFILE=bench_gpu2 ./program
+```
+Или через CLI:
+```bash
+./program --spla-profile=debug_gpu1
+```
+
+### Итоговый конфигурационный файл
+```json
+{
+ "execution": { "device": 1 },
+ "verbosity": 3,
+ "profiling": true
+}
+```
+
+---
+---
+
+# Распространенные сценарии использования библиотеки
+ 1. Быстрый запуск без настройки.
+ 2. Выбор между CPU и GPU без указания индексов.
+ 3. Многократный запуск на разных устройствах с разными параметрами.
+
+## Быстрый запуск без настройки
+
+Если вы хотите получить работоспособное приложение без настройки - ничего настраивать не нужно. Библиотека использует параметры по умолчанию из [конфигурационного файла](#конфигурационный-файл). Этот сценарий работает одинаково на Linux, macOS и Windows. Если GPU недоступен, библиотека автоматически переключится на CPU, сообщив об этом. \
+Перед первым запуском рекомендуется [выполнить](#проверка-работоспособности) `spla --softcheck`, чтобы убедиться, что всё необходимое для работы spla установлено.
+
+## Выбор между CPU и GPU без указания индексов
+
+Если вы хотите управлять типом устройства, но не указывать конкретные индексы OpenCL-платформ и устройств.
+
+### Управление через backend
+
+Тип устройства задаётся параметром backend
+```json
+{ "backend": "gpu" } //Использовать первое доступное GPU
+
+{ "backend": "cpu" } //Использовать CPU
+
+{ "backend": "any" } //Использовать первое доступное устройство любого типа
+```
+
+Индексы `platform_index` и `device_index` при этих значениях игнорируются. Это позволяет запускать программу на GPU или CPU без знания индексов.
+
+### Поведение при отсутствии GPU
+
+Если выбран backend: "gpu", но GPU недоступен, поведение определяется параметром `if_gpu_unavailable`:
+
+```json
+{
+ "backend": "gpu",
+ "if_gpu_unavailable": "use_cpu" //Библиотека переключается на CPU и продолжает работу
+}
+
+{
+ "backend": "gpu",
+ "if_gpu_unavailable": "abort" //Библиотека возвращает ошибку и не продолжает работу
+ //Стоит использовать, если требуется гарантировать выполнение на GPU
+}
+```
+`if_gpu_unavailable` применяется только при backend: "gpu" и backend: "any". При backend: "cpu" параметр игнорируется.
+
+## Многократный запуск на разных устройствах с разными параметрами
+
+Вы регулярно запускаете одну и ту же программу на разных GPU и в разных режимах - например, на GPU 0 и 1, в режиме отладки и в режиме измерения производительности. Вместо того чтобы держать несколько конфигурационных файлов, вы описываете всё в одном.
+
+Подробнее в главе про [профили](#профили) и [наследование профилей (`extends`)](#наследование-профилей).
+
+```json
+{
+ "execution": { "device": 0 },
+ "verbosity": 2,
+
+ "profiles": {
+ "gpu0": { "execution": { "device": 0 } },
+ "gpu1": { "execution": { "device": 1 } },
+ "gpu2": { "execution": { "device": 2 } },
+
+ "debug": { "verbosity": 3, "profiling": true },
+ "bench": { "verbosity": 0 },
+
+ "debug_gpu1": { "extends": ["debug", "gpu1"] },
+ "bench_gpu2": { "extends": ["bench", "gpu2"] }
+ }
+}
+```
\ No newline at end of file
diff --git a/src/opencl/UserManual.md b/src/opencl/UserManual.md
new file mode 100644
index 000000000..189983e9d
--- /dev/null
+++ b/src/opencl/UserManual.md
@@ -0,0 +1,239 @@
+# МАНУАЛ:
+
+## НАЗВАНИЕ
+spla - это фреймворк для математических вычислений с использованием GPU/ускорения
+
+
+## СИНОПСИС
+
+```cpp
+#include
+
+
+int main(int argc, char* argv[]) {
+
+ configure(argc, argv);
+
+ return 0;
+}
+```
+Функция `configure(argc, argv)` необходима для обработки аргументов командной строки, переменных окружения и конфигурационных файлов.
+
+
+## ОПИСАНИЕ
+**spla** - это библиотека с открытым исходным кодом, предоставляющая примитивы разреженной линейной алгебры (матрицы, векторы, скаляры) для математических вычислений с ускорением на GPU. Библиотека поддерживает широкий спектр операций, включая умножение матрицы на вектор, решение систем линейных уравнений и другие алгоритмы разреженной алгебры.
+
+Для большинства пользователей важно, на каком ускорителе будет запускаться их проект, поэтому необходима возможность явного указания графического ускорителя, OpenCL платформы, количества очередей команд и других параметров, влияющих на производительность и удобство пользователя.
+
+Библиотека предоставляет механизмы конфигурирования, показанные в порядке убывания приоритета.
+
+1. Аргументы командной строки 1.
+2. Переменные окружения 2.
+3. Пользовательский конфигурационный JSON файл 3.
+4. Системный конфигурационный JSON файл 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/` на Linux. \
+`/Library/Preferences/spla/` на macOS. \
+`%ProgramData%\spla\` на Windows. \
+По умолчанию выбирается первый json файл в соответствующей директории.
+
+```bash
+-su <путь>
+--spla-uconf <путь>
+```
+Путь к пользовательскому конфигурационному файлу.
+
+По умолчанию ищет файл в стандартном для данной ОС месте. \
+`~/.config/spla/` на Linux. \
+`~/Library/Application Support/spla/` на macOS. \
+`%APPDATA%\spla\` на Windows. \
+По умолчанию выбирается первый json файл в соответствующей директории.
+
+```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) и всех переменных окружения существуют аналоги в виде параметров конфигурационного файла.
+
+Формат файла - JSON.
+
+Типы значений.
+1. platform = int
+2. device = int
+3. queues = int
+4. verbosity = int
+5. profiling = true | false
+6. allocator = linear | general
+7. allocator_size = int
+
+Примеры конфигурационных файлов.\
+Этот формат справедлив как для пользовательского, так и для системного конфигурационного json файла.
+
+```json
+{
+ "platform": 1,
+ "device": 0,
+ "queues": 4,
+ "verbosity": 2,
+ "profiling": false,
+ "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..ef3f40538 100644
--- a/src/opencl/cl_accelerator.cpp
+++ b/src/opencl/cl_accelerator.cpp
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
#include
@@ -39,68 +40,103 @@ namespace spla {
CLAccelerator::CLAccelerator() = default;
CLAccelerator::~CLAccelerator() = default;
- Status CLAccelerator::init() {
- m_description = "no platform or device";
+ InitResult init_with_configure(int argc, char** argv) {
+ InitResult result;
+ result.config_status = configure(argc, argv);
+
+ if (result.config_status == ConfigStatus::HelpRequested ||
+ result.config_status == ConfigStatus::VersionRequested) {
+ result.status = Status::Ok;
+ return result;
+ }
- 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);
+ if (result.config_status != ConfigStatus::Ok) {
+ result.status = Status::Error;
+ return result;
+ }
+
+ auto* acc = get_acc_cl();
+ if (!acc) {
+ result.status = Status::NoAcceleration;
+ return result;
+ }
- if (set_platform(platform_index) != Status::Ok)
- return Status::PlatformNotFound;
+ result.status = acc->init();
+ return result;
+ }
+
+
+ Status CLAccelerator::init() {
+ int platform_index = config_final.platform.value();
+ int device_index = config_final.device.value();
+ int queues_count = config_final.queues.value();
+ bool profiling = config_final.profiling.value();
+ std::string allocator_type = config_final.allocator.value();
+ size_t lin_allocator_size;
+ if (allocator_type == "linear") lin_allocator_size = config_final.allocator_size.value();
+ int verbosity = config_final.verbosity.value();
+
+ Library::get()->set_verbosity(verbosity);
+
+ LOG_MSG(Status::Ok, "Initializing accelerator...");
+
+ LOG_MSG(Status::Ok, "Configuration parameters:");
+ LOG_MSG(Status::Ok, " platform = " << platform_index);
+ LOG_MSG(Status::Ok, " device = " << device_index);
+ LOG_MSG(Status::Ok, " queues = " << queues_count);
+ LOG_MSG(Status::Ok, " profiling = " << (profiling ? "true" : "false"));
+ LOG_MSG(Status::Ok, " allocator = " << allocator_type);
+ if (allocator_type == "linear") {
+ LOG_MSG(Status::Ok, " alloc_size= " << lin_allocator_size);
+ }
+ LOG_MSG(Status::Ok, " verbosity = " << verbosity);
- if (set_device(device_index) != Status::Ok)
- return Status::DeviceNotFound;
+ set_platform(platform_index);
+ set_device(device_index);
+ set_profiling(profiling);
+ set_queues_count(queues_count);
- if (set_queues_count(1) != Status::Ok)
- return Status::Error;
+ if (allocator_type == "linear") {
+ set_linear_allocator(lin_allocator_size);
+ } else {
+ set_general_allocator();
+ }
m_cache = std::make_unique();
+ LOG_MSG(Status::Ok, "program cache created");
- // Output handy info
- LOG_MSG(Status::Ok, "Initialize accelerator: " << get_description());
+ LOG_MSG(Status::Ok, "accelerator initialized: " << 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");
- return Status::PlatformNotFound;
- }
- if (available_platforms.size() <= index) {
- LOG_MSG(Status::InvalidArgument, "index out of list of available platforms");
- return Status::InvalidArgument;
- }
-
m_counter_pool.reset();
m_alloc_general.reset();
m_alloc_linear.reset();
- m_alloc_tmp = nullptr;
- m_device = cl::Device();
- m_platform = available_platforms[index];
- LOG_MSG(Status::Ok, "select OpenCL platform " << m_platform.getInfo());
+ m_alloc_tmp = nullptr;
+ m_device = cl::Device();
+ m_profiling_enabled = false;
+
+ m_platform = available_platforms[index];
+ LOG_MSG(Status::Ok, "select OpenCL platform: "
+ << m_platform.getInfo());
return Status::Ok;
}
+
+
Status CLAccelerator::set_device(int index) {
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");
- return Status::DeviceNotFound;
- }
- if (available_devices.size() <= index) {
- LOG_MSG(Status::DeviceNotFound, "index out of list of available devices");
- return Status::DeviceNotFound;
- }
+ m_platform.getDevices(CL_DEVICE_TYPE_ALL, &available_devices);
m_device = available_devices[index];
- LOG_MSG(Status::Ok, "select OpenCL device " << m_device.getInfo());
+ LOG_MSG(Status::Ok, "select OpenCL device: "
+ << m_device.getInfo());
m_vendor_code.clear();
m_vendor_name = m_device.getInfo();
@@ -108,7 +144,7 @@ namespace spla {
m_max_cu = m_device.getInfo();
m_max_wgs = m_device.getInfo();
m_max_local_mem = m_device.getInfo();
- m_addr_align = m_device.getInfo() / 8;// from bits to bytes
+ m_addr_align = m_device.getInfo() / 8;
m_is_nvidia = false;
m_is_amd = false;
@@ -144,7 +180,6 @@ namespace spla {
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 ||
@@ -158,26 +193,34 @@ namespace spla {
}
if (m_vendor_code.empty()) {
- LOG_MSG(Status::Error, "failed to match one of the pre-defined vendors");
+ LOG_MSG(Status::InvalidArgument, "unknown vendor: " << m_vendor_name
+ << ", using default parameters");
m_default_wgs = 64;
m_wave_size = 8;
}
-
std::stringstream desc;
- desc << "OpenCL Acc " << m_platform.getInfo()
- << " device: " << m_device.getInfo()
- << " vendor:" << m_vendor_code
- << " mcu:" << m_max_cu
- << " wave:" << m_wave_size
- << " mwgs:" << m_max_wgs;
+ desc << "OpenCL "
+ << m_platform.getInfo()
+ << " | device: " << m_device.getInfo()
+ << " | vendor: " << m_vendor_code
+ << " | mcu: " << m_max_cu
+ << " | wave: " << m_wave_size
+ << " | mwgs: " << m_max_wgs;
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) {
m_context = cl::Context(m_device);
m_queues.clear();
@@ -185,33 +228,38 @@ namespace spla {
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));
}
- m_counter_pool = std::make_unique();
- 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();
- }
+ m_counter_pool = std::make_unique();
- LOG_MSG(Status::Ok, "configure " << count << " queues for computations");
+ LOG_MSG(Status::Ok, "configure " << count << " queues"
+ << " (profiling: " << (m_profiling_enabled ? "ON" : "OFF") << ")");
return Status::Ok;
}
- const std::string& CLAccelerator::get_name() {
- return m_name;
- }
- const std::string& CLAccelerator::get_description() {
- return m_description;
+
+
+ Status CLAccelerator::set_linear_allocator(size_t size) {
+ 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;
}
- const std::string& CLAccelerator::get_suffix() {
- return m_suffix;
+
+
+ Status CLAccelerator::set_general_allocator() {
+ m_alloc_general = std::make_unique();
+ m_alloc_tmp = m_alloc_general.get();
+ LOG_MSG(Status::Ok, "set general allocator");
+ return Status::Ok;
}
-}// namespace spla
+ const std::string& CLAccelerator::get_name() { return m_name; }
+ const std::string& CLAccelerator::get_description() { return m_description; }
+ const std::string& CLAccelerator::get_suffix() { return m_suffix; }
+
+}// namespace spla
\ No newline at end of file
diff --git a/src/opencl/cl_accelerator.hpp b/src/opencl/cl_accelerator.hpp
index 52a0497e1..1844dbcfe 100644
--- a/src/opencl/cl_accelerator.hpp
+++ b/src/opencl/cl_accelerator.hpp
@@ -56,6 +56,12 @@
namespace spla {
+ struct InitResult {
+ Status status;
+ ConfigStatus config_status;
+ };
+ InitResult init_with_configure(int argc, char** argv);
+
/**
* @addtogroup internal
* @{
@@ -70,10 +76,14 @@ 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;
+ Status init() 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();
+
const std::string& get_name() override;
const std::string& get_description() override;
const std::string& get_suffix() override;
@@ -106,6 +116,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;
@@ -130,7 +141,7 @@ namespace spla {
bool m_is_intel = false;
bool m_is_img = false;
- ankerl::svector m_queues;
+ bool m_profiling_enabled = false;
};
/**
@@ -148,4 +159,4 @@ namespace spla {
}// namespace spla
-#endif//SPLA_CL_ACCELERATOR_HPP
+#endif//SPLA_CL_ACCELERATOR_HPP
\ No newline at end of file
diff --git a/src/opencl/cl_configure.cpp b/src/opencl/cl_configure.cpp
new file mode 100644
index 000000000..94ed1e99f
--- /dev/null
+++ b/src/opencl/cl_configure.cpp
@@ -0,0 +1,523 @@
+/**********************************************************************************/
+/* This file is part of spla project */
+/* https://github.com/SparseLinearAlgebra/spla */
+/**********************************************************************************/
+/* MIT License */
+/* */
+/* Copyright (c) 2023 SparseLinearAlgebra */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy */
+/* of this software and associated documentation files (the "Software"), to deal */
+/* in the Software without restriction, including without limitation the rights */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell */
+/* copies of the Software, and to permit persons to whom the Software is */
+/* furnished to do so, subject to the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */
+/* SOFTWARE. */
+/**********************************************************************************/
+
+#include "cl_configure.hpp"
+#include "CL/opencl.hpp"
+#include "cl_accelerator.hpp"
+
+#include
+#include
+
+namespace spla {
+
+ Config config_default;
+ Config config_system;
+ Config config_user;
+ Config config_cli_and_env;
+ Config config_final;
+
+
+ void Profile::merge(const Profile& src) {
+ if (src.platform.has_value()) platform = src.platform;
+ if (src.device.has_value()) device = src.device;
+ if (src.queues.has_value()) queues = src.queues;
+ if (src.profiling.has_value()) profiling = src.profiling;
+ if (src.allocator.has_value()) allocator = src.allocator;
+ if (src.allocator_size.has_value()) allocator_size = src.allocator_size;
+ if (src.verbosity.has_value()) verbosity = src.verbosity;
+ if (src.extends.has_value()) extends = src.extends;
+ }
+
+
+ void Config::merge(const Config& src) {
+ if (src.platform.has_value()) platform = src.platform;
+ if (src.device.has_value()) device = src.device;
+ if (src.queues.has_value()) queues = src.queues;
+ if (src.profiling.has_value()) profiling = src.profiling;
+ if (src.allocator.has_value()) allocator = src.allocator;
+ if (src.allocator_size.has_value()) allocator_size = src.allocator_size;
+ if (src.verbosity.has_value()) verbosity = src.verbosity;
+
+ if (src.profile.has_value()) profile = src.profile;
+
+ if (src.profiles.has_value()) {
+ if (!profiles.has_value()) {
+ profiles = src.profiles;
+ } else {
+ for (const auto& [name, src_prof] : *src.profiles) {
+ auto& dst_prof = (*profiles)[name];
+ dst_prof.merge(src_prof);
+ }
+ }
+ }
+ }
+
+ void Config::reset() {
+ *this = Config{};
+ }
+
+ std::string get_spla_version() {
+ return "SPLA version: 0.0.0";
+ }
+
+
+ std::string get_default_config_path() {
+#ifdef _WIN32
+ if (const char* pd = std::getenv("PROGRAMDATA")) {
+ return std::string(pd) + "\\spla\\spla_conf.json";
+ }
+ return "C:\\ProgramData\\spla\\spla_conf.json";
+
+#elif defined(__APPLE__)
+ return "/usr/local/share/spla/spla_conf.json";
+
+#elif defined(__linux__)
+ return "/usr/share/spla/spla_conf.json";
+
+#else
+ #error "spla supports only Windows, macOS and Linux"
+#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\\spla_conf.json";
+ }
+ return "C:\\ProgramData\\spla\\spla_conf.json";
+
+#elif defined(__APPLE__)
+ return "/Library/Application Support/spla/spla_conf.json";
+
+#elif defined(__linux__)
+ return "/etc/spla/spla_conf.json";
+
+#else
+ #error "spla supports only Windows, macOS and Linux"
+#endif
+ }
+
+
+ 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);
+ throw std::runtime_error("Cannot determine home directory");
+
+#elif defined(__APPLE__) || defined(__linux__)
+ 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);
+ throw std::runtime_error("Cannot determine home directory");
+
+#else
+ #error "spla supports only Windows, macOS and Linux"
+#endif
+ }
+
+
+ std::string get_default_user_config_path() {
+ std::string home = get_home_directory();
+
+#ifdef _WIN32
+ const char* app_data = std::getenv("APPDATA");
+ if (app_data) return std::string(app_data) + "\\spla\\spla_conf.json";
+ return home + "\\AppData\\Roaming\\spla\\spla_conf.json";
+
+#elif defined(__APPLE__)
+ return home + "/Library/Application Support/spla/spla_conf.json";
+
+#elif defined(__linux__) || defined(__unix__)
+ return home + "/.config/spla/spla_conf.json";
+
+#else
+ #error "spla supports only Windows, macOS and Linux"
+#endif
+ }
+
+
+ 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("-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("-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");
+
+ app.add_option("-sP,--spla-profile", cfg.profile,
+ "Configuration profile name\n"
+ "Overrides base settings with the named profile.\n"
+ "Config key: profile")
+ ->envname("SPLA_PROFILE");
+
+ try {
+ app.parse(argc, argv);
+ } catch (const CLI::ParseError& e) {
+ std::cerr << "[spla:cli_env] ERROR: failed to parse: " << e.what() << std::endl;
+ 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 parse_file(const std::string& path, Config& cfg) {
+
+ if (!std::filesystem::exists(path)) {
+ std::cerr << "[spla:config] WARNING: file not found: " << path << std::endl;
+ return ConfigStatus::Ok;
+ }
+
+ std::ifstream file(path);
+ if (!file.is_open()) {
+ std::cerr << "[spla:config] WARNING: cannot open: " << path << std::endl;
+ return ConfigStatus::OpenFileError;
+ }
+
+ try {
+ nlohmann::json data = nlohmann::json::parse(file);
+
+ if (data.contains("platform")) cfg.platform = data["platform"].get();
+ if (data.contains("device")) cfg.device = data["device"].get();
+ if (data.contains("queues")) cfg.queues = data["queues"].get();
+ if (data.contains("profiling")) cfg.profiling = data["profiling"].get();
+ if (data.contains("allocator")) cfg.allocator = data["allocator"].get();
+ if (data.contains("allocator_size")) cfg.allocator_size = data["allocator_size"].get();
+ if (data.contains("verbosity")) cfg.verbosity = data["verbosity"].get();
+ if (data.contains("profile")) cfg.profile = data["profile"].get();
+
+ if (data.contains("profiles")) {
+ std::map profiles;
+ for (auto& [name, p] : data["profiles"].items()) {
+ Profile prof;
+ if (p.contains("platform")) prof.platform = p["platform"].get();
+ if (p.contains("device")) prof.device = p["device"].get();
+ if (p.contains("queues")) prof.queues = p["queues"].get();
+ if (p.contains("profiling")) prof.profiling = p["profiling"].get();
+ if (p.contains("allocator")) prof.allocator = p["allocator"].get();
+ if (p.contains("allocator_size")) prof.allocator_size = p["allocator_size"].get();
+ if (p.contains("verbosity")) prof.verbosity = p["verbosity"].get();
+ if (p.contains("extends")) prof.extends = p["extends"].get>();
+ profiles[name] = prof;
+ }
+ cfg.profiles = profiles;
+ }
+
+ return ConfigStatus::Ok;
+
+ } catch (const nlohmann::json::exception& e) {
+ std::cerr << "[spla:config] ERROR: failed to parse '"
+ << path << "': " << e.what() << std::endl;
+ return ConfigStatus::ParseConfError;
+ }
+ }
+
+
+ ConfigStatus check_platform_and_device(int platform_index, int device_index) {
+
+ if (platform_index < 0) {
+ std::cerr << "[spla:opencl] 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 << "[spla:opencl] ERROR: no platform available" << std::endl;
+ return ConfigStatus::PlatformNotFound;
+ }
+
+ if (static_cast(platform_index) >= platforms.size()) {
+ std::cerr << "[spla:opencl] ERROR: platform index out of range (got "
+ << platform_index << ", max " << platforms.size() - 1 << ")"
+ << std::endl;
+ return ConfigStatus::PlatformNotFound;
+ }
+
+ if (device_index < 0) {
+ std::cerr << "[spla:opencl] 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 << "[spla:opencl] ERROR: no device available on platform "
+ << platform_index << std::endl;
+ return ConfigStatus::DeviceNotFound;
+ }
+
+ if (static_cast(device_index) >= devices.size()) {
+ std::cerr << "[spla:opencl] ERROR: device index out of range (got "
+ << device_index << ", max " << devices.size() - 1 << ")"
+ << std::endl;
+ return ConfigStatus::DeviceNotFound;
+ }
+
+ return ConfigStatus::Ok;
+ }
+
+
+ ConfigStatus validation(const Config& cfg) {
+
+ if (!cfg.platform.has_value()) {
+ std::cerr << "[spla:validation] ERROR: required: platform" << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+ if (!cfg.device.has_value()) {
+ std::cerr << "[spla:validation] ERROR: required: device" << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+ if (!cfg.queues.has_value()) {
+ std::cerr << "[spla:validation] ERROR: required: queues" << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+ if (!cfg.profiling.has_value()) {
+ std::cerr << "[spla:validation] ERROR: required: profiling" << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+ if (!cfg.allocator.has_value()) {
+ std::cerr << "[spla:validation] ERROR: required: allocator" << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+ if (!cfg.verbosity.has_value()) {
+ std::cerr << "[spla:validation] ERROR: required: verbosity" << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+
+ if (cfg.allocator.value() == "linear" && !cfg.allocator_size.has_value()) {
+ std::cerr << "[spla:validation] ERROR: allocator_size is required for 'linear' allocator"
+ << std::endl;
+ return ConfigStatus::MissedParameters;
+ }
+
+ ConfigStatus status;
+
+ status = check_platform_and_device(*cfg.platform, *cfg.device);
+ if (status != ConfigStatus::Ok) {
+ return status;
+ }
+
+ if (*cfg.queues <= 0) {
+ std::cerr << "[spla:validation] ERROR: queues must be > 0 (got "
+ << *cfg.queues << ")" << std::endl;
+ return ConfigStatus::InvalidConfigParams;
+ }
+
+ if (*cfg.allocator != "linear" && *cfg.allocator != "general") {
+ std::cerr << "[spla:validation] ERROR: allocator must be 'linear' or 'general' (got '"
+ << *cfg.allocator << "')" << std::endl;
+ return ConfigStatus::InvalidConfigParams;
+ }
+
+ if (*cfg.allocator == "linear" && *cfg.allocator_size <= 0) {
+ std::cerr << "[spla:validation] ERROR: allocator_size must be > 0 for 'linear' (got "
+ << *cfg.allocator_size << ")" << std::endl;
+ return ConfigStatus::InvalidConfigParams;
+ }
+
+ if (*cfg.verbosity < 0 || *cfg.verbosity > 3) {
+ std::cerr << "[spla:validation] ERROR: verbosity must be in [0, 3] (got "
+ << *cfg.verbosity << ")" << std::endl;
+ return ConfigStatus::InvalidConfigParams;
+ }
+
+ return ConfigStatus::Ok;
+ }
+
+
+ Profile apply_extends(const std::map& profiles,
+ const std::string& name,
+ std::set& stack) {
+ if (stack.count(name)) {
+ throw std::runtime_error("Profile cycle detected: " + name);
+ }
+ stack.insert(name);
+
+ auto it = profiles.find(name);
+ if (it == profiles.end()) {
+ throw std::runtime_error("Profile not found: " + name);
+ }
+
+ const Profile& prof = it->second;
+ Profile result;
+
+ if (prof.extends) {
+ for (const auto& parent_name : *prof.extends) {
+ Profile parent = apply_extends(profiles, parent_name, stack);
+ result.merge(parent);
+ }
+ }
+
+ result.merge(prof);
+
+ stack.erase(name);
+ return result;
+ }
+
+ ConfigStatus apply_profile(Config& cfg) {
+
+ if (!cfg.profile.has_value()) return ConfigStatus::Ok;
+
+ std::string profile_name = *cfg.profile;
+
+ if (!cfg.profiles || !cfg.profiles->count(profile_name)) {
+ std::cerr << "[spla:profile] ERROR: not found: " << profile_name << std::endl;
+ return ConfigStatus::ProfileNotFound;
+ }
+
+ std::set stack;
+ Profile resolved;
+ try {
+ resolved = apply_extends(*cfg.profiles, profile_name, stack);
+ } catch (const std::runtime_error& e) {
+ std::string msg = e.what();
+ if (msg.find("cycle") != std::string::npos) {
+ std::cerr << "[spla:profile] ERROR: cycle detected: "
+ << profile_name << std::endl;
+ return ConfigStatus::ProfileCycle;
+ }
+ std::cerr << "[spla:profile] ERROR: " << msg << std::endl;
+ return ConfigStatus::ProfileNotFound;
+ }
+
+ if (resolved.platform) cfg.platform = resolved.platform;
+ if (resolved.device) cfg.device = resolved.device;
+ if (resolved.queues) cfg.queues = resolved.queues;
+ if (resolved.profiling) cfg.profiling = resolved.profiling;
+ if (resolved.allocator) cfg.allocator = resolved.allocator;
+ if (resolved.allocator_size) cfg.allocator_size = resolved.allocator_size;
+ if (resolved.verbosity) cfg.verbosity = resolved.verbosity;
+
+ return ConfigStatus::Ok;
+ }
+
+
+ ConfigStatus configure(int argc, char** argv) {
+ config_default.reset();
+ config_system.reset();
+ config_user.reset();
+ config_cli_and_env.reset();
+ config_final.reset();
+
+ ConfigStatus status;
+
+ status = parse_cli_and_env(argc, argv, config_cli_and_env);
+ if (status != ConfigStatus::Ok) return status;
+
+ status = parse_file(get_default_user_config_path(), config_user);
+ if (status == ConfigStatus::ParseConfError) return status;
+ if (status == ConfigStatus::OpenFileError) {
+ std::cerr << "[spla:configure] WARNING: cannot open user config, skipping" << std::endl;
+ }
+
+ status = parse_file(get_default_system_config_path(), config_system);
+ if (status == ConfigStatus::ParseConfError) return status;
+ if (status == ConfigStatus::OpenFileError) {
+ std::cerr << "[spla:configure] WARNING: cannot open system config, skipping" << std::endl;
+ }
+
+ status = parse_file(get_default_config_path(), config_default);
+ if (status == ConfigStatus::ParseConfError) return status;
+ if (status == ConfigStatus::OpenFileError) {
+ std::cerr << "[spla:configure] WARNING: cannot open default config, skipping" << std::endl;
+ }
+
+ config_final.merge(config_default);
+ config_final.merge(config_system);
+ config_final.merge(config_user);
+ config_final.merge(config_cli_and_env);
+
+ status = apply_profile(config_final);
+ if (status != ConfigStatus::Ok) return status;
+
+ status = validation(config_final);
+ if (status != ConfigStatus::Ok) return status;
+
+ std::cerr << "[spla:configure]: configuration complete" << std::endl;
+ 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..6ddef1b2c
--- /dev/null
+++ b/src/opencl/cl_configure.hpp
@@ -0,0 +1,124 @@
+/**********************************************************************************/
+/* This file is part of spla project */
+/* https://github.com/SparseLinearAlgebra/spla */
+/**********************************************************************************/
+/* MIT License */
+/* */
+/* Copyright (c) 2023 SparseLinearAlgebra */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining a copy */
+/* of this software and associated documentation files (the "Software"), to deal */
+/* in the Software without restriction, including without limitation the rights */
+/* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell */
+/* copies of the Software, and to permit persons to whom the Software is */
+/* furnished to do so, subject to the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be included in all */
+/* copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR */
+/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, */
+/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE */
+/* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER */
+/* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, */
+/* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */
+/* SOFTWARE. */
+/**********************************************************************************/
+
+#pragma once
+
+#include