# Интерфейс IMathEngine

<!-- TOC -->

- [Интерфейс IMathEngine](#интерфейс-imathengine)
    - [Общие принципы](#общие-принципы)
        - [Работа с памятью](#работа-с-памятью)
            - [CMemoryHandle](#cmemoryhandle)
        - [Параметры методов](#параметры-методов)
        - [Возвращаемые значения методов](#возвращаемые-значения-методов)
        - [Синхронизация работы с GPU](#синхронизация-работы-с-gpu)
        - [Прогрев](#прогрев)
    - [Создание и настройка объекта IMathEngine](#создание-и-настройка-объекта-imathengine)
        - [Обработка исключений](#обработка-исключений)
        - [Создать движок по умолчанию для CPU](#создать-движок-по-умолчанию-для-cpu)
        - [Создать рекомендованный движок для GPU](#создать-рекомендованный-движок-для-gpu)
        - [Создать произвольный CPU движок](#создать-произвольный-cpu-движок)
        - [Создать произвольный GPU движок](#создать-произвольный-gpu-движок)
        - [Использовать менеджер движков на GPU](#использовать-менеджер-движков-на-gpu)

<!-- /TOC -->

Интерфейс вычислительного движка абстрагирует алгоритмическую часть библиотеки от реализации низкоуровневых платформозависимых операций.
Интерфейс содержит функции для работы с памятью и проведения вычислительных операций. Используется блобами, слоями и нейронными сетями.

Библиотека поддерживает различные вычислительные устройства и технологии их использования на различных платформах:

Платформа | CPU | GPU
----------|-----|-----
Windows | MKL | CUDA
Linux | MKL | -
MacOS | MKL | -
Android | ARM Neon | Vulkan
iOS | ARM Neon | Metal

## Общие принципы

Для работы с библиотекой вам понадобится лишь [создать](#создание-и-настройка-объекта-imathengine) и по окончании работы уничтожить объект IMathEngine. В этом разделе дана общая информация о принципах внутреннего устройства движков, хотя напрямую  с ним работать вам не придётся.

### Работа с памятью

Прямой доступ к памяти в библиотеке отсутствует, т.к. физически она может располагаться в памяти GPU устройства. Поэтому вся работа с памятью в `IMathEngine` ведется с помощью специальных типов и функций.

#### CMemoryHandle

Класс `CMemoryHandle` является базовым для всех классов-описателей данных.
Экземпляр этого типа описывает какой-либо блок памяти неизвестного/произвольного типа. Можно сказать, что данный класс является аналогом стандартного C/C++ типа `void*`.

Класс имеет двух наследников `CFloatHandle` и `CIntHandle`, описывающих блоки памяти типов `float` и `int` соответственно.

### Параметры методов

Вычислительный движок производит вычислительные операции исключительно над векторами специального типа; как входные данные, так и результат описываются векторами.

В таком виде можно представить любые типы данных: например, скаляр – это вектор длины 1, а матрица – это вектор, хранящий данные матрицы по строкам, и т.д. для тензоров размерности 3 и более. Такой подход позволяет абстрагироваться от конкретной реализации работы с памятью на разных платформах, включая GPU.

### Возвращаемые значения методов

За редким исключением методы вычислительного движка не поддерживают возвращаемых значений ни в каком виде. Т.е. они имеют тип `void`, и у них нет "выходных" параметров типа неконстантных ссылок/указателей. Это сделано для того, чтобы избежать лишней синхронизации и копирования данных между CPU и GPU, что уменьшило бы производительность.

### Синхронизация работы с GPU

Тем не менее, иногда синхронизация необходима; например, когда вычисления производились на GPU, а затем потребовалось вывести результат в "основную" систему.

Также синхронизация может понадобиться при некоторых других манипуляциях с памятью. Далее идет полный список ситуаций, когда возможна синхронизация работы GPU и CPU:

- Аллокация / очистка памяти 
- Чтение данных
- Запись данных большого размера

Во всех этих случаях из-за расхода ресурсов на синхронизацию возможно ухудшение быстродействия системы.

### Прогрев

Отдельно стоит упомянуть про необходимость так называемого "прогрева" системы. Так как многие внутренние объекты используют отложенную инициализацию, при первом запуске вычислительного движка возможно замедление работы.

Однако, гарантируется, что при массовом повторяющемся вызове одних и тех же функций на векторах одних и тех же размеров, "прогрев" будет однократным.
К примеру, при работе с нейросетью все необходимые буфера будут созданы при первом вызове, и все дальнейшие операции будут производиться быстрее.

## Создание и настройка объекта IMathEngine

Создать или получить вычислительный движок можно несколькими способами.
Если движок не уничтожается автоматически, то перед его уничтожением необходимо освободить всю используемую память, то есть удалить все созданные на этом движке блобы (в том числе могут содержаться в объектах слоёв и сетей, поэтому их также необходимо удалить).

### Обработка исключений

По умолчанию, в случае возникновения исключительной ситуации функции `NeoML` бросают исключения `std::logic_error` или `std::bad_alloc` при нехватке памяти.

Однако, это поведение может быть изменено путём установки обработчика исключений.

```c++
// Интерфейс обработчика ошибок
// Используется для изменения поведения программы при исключительных ситуациях
class NEOMATHENGINE_API IMathEngineExceptionHandler {
public:
	virtual ~IMathEngineExceptionHandler();
	// Во время вызова метода произошла ошибка
	// По умолчанию бросает std::logic_error
	virtual void OnAssert( const char* message, const wchar_t* file, int line, int errorCode ) = 0;

	// Не удалось выделить память на устройстве
	// По умолчанию бросает std::bad_alloc
	virtual void OnMemoryError() = 0;
};

// Установить обработчик исключительных ситуаций для всей программы
// exceptionHandler равный null означает использование обработчика по умолчанию
// Обработчик не по умолчанию должен быть удален вызвавшим после использования
NEOMATHENGINE_API void SetMathEngineExceptionHandler( IMathEngineExceptionHandler* exceptionHandler );

// Получить текущий обработчик исключительных ситуаций
// Возвращает null если используется обработчик по умолчанию
NEOMATHENGINE_API IMathEngineExceptionHandler* GetMathEngineExceptionHandler();
```

Для обработки исключений не по умолчанию рекомендуется устанавливать обработчик перед созданием вычислительных движков.

### Создать движок по умолчанию для CPU

```c++
IMathEngine& GetDefaultCpuMathEngine();
```
Данная функция возвращает интерфейс CPU-движка, использующего один поток и не имеющего ограничений на используемую память.

Этот движок не нужно уничтожать после использования (память и ресурсы будут освобождены автоматически при выгрузке библиотеки).

### Создать рекомендованный движок для GPU

```c++
IMathEngine* GetRecommendedGpuMathEngine( size_t memoryLimit );
```

Функция создает движок на рекомендованном GPU. Если GPU недоступно, возвращает `null`. Вызвавший сам должен уничтожить полученный объект после использования.

#### Параметры

* *memoryLimit* - задает максимальный лимит расхода памяти для этого движка; значение `0` позволяет использовать всю доступную память.

### Создать произвольный CPU движок

```c++
IMathEngine* CreateCpuMathEngine( size_t memoryLimit );
```
Функция создает вычислительный движок, работающий на CPU, с возможностью задать максимальный лимит расхода памяти, а также собственный обработчик исключений. Вызвавший сам должен уничтожить полученный объект после использования.

#### Параметры

* *memoryLimit* - ограничение используемой памяти; значение `0` позволяет использовать всю доступную память.

### Создать произвольный GPU движок

```c++
IMathEngine* CreateGpuMathEngine( size_t memoryLimit );
```

Функция создает вычислительный движок, работающий на GPU, с возможностью задать лимит памяти и собственный обработчик исключений. Вызвавший сам должен уничтожить полученный объект после использования.

#### Параметры

* *memoryLimit* - ограничение используемой памяти; значение `0` позволяет использовать всю доступную память.

### Использовать менеджер движков на GPU

Менеджер графических вычислительных устройств позволяет получить информацию о доступных в системе GPU и создать вычислительный движок, использующий один из них.

Менеджер имеет следующий интерфейс:

~~~c++
class IGpuMathEngineManager {
public:
	// Получить количество доступных графических вычислительных устройств.
	virtual int GetMathEngineCount() const = 0;

	// Получить информацию о графическом вычислительном устройстве.
	// index - индекс устройства в списке доступных ( от 0 до GetMathEngineCount() - 1 ).
	virtual void GetMathEngineInfo( int index, CMathEngineInfo& info ) const = 0;

	// Создать движок на графическом вычислительном устройстве.
	// index - индекс устройства в списке доступных ( от 0 до GetMathEngineCount() - 1 ).
	// memoryLimit - ограничение по памяти на устройстве. В случае превышения IMathEngineExceptionHandler::OnMemoryError().
	virtual IMathEngine* CreateMathEngine( int index, size_t memoryLimit ) const = 0;
};
~~~

Создание и уничтожение менеджера:

~~~c++
// Создать менеджер графических вычислительных устройств.
IGpuMathEngineManager* CreateGpuMathEngineManager();
~~~

Созданные с помощью менеджера вычислительные движки должны быть уничтожены после использования.
