# Класс CReferenceDnnFactory

<!-- TOC -->

- [Класс CReferenceDnnFactory](#класс-creferencednnfactory)
    - [Reference DNN](#reference-dnn)
    - [Использование](#использование)

<!-- /TOC -->

Этот класс может создавать референс копии нейро-сетей (reference dnn), согласно конфигурации оригинальной сети, которую он хранит внутри.
Эти референс копии будут делить с оригинальной сетью параметры весов слоёв для экономии памяти и более кэш-дружелюбного много-поточного инференса.
При много-поточном инференсе каждый поток независимо оперирует своей копией сети.
Обучение и сериализация более недоступны как для оригинальной сети, так и для рефернс копий.
Для каждой референс копии сети создаётся свой генератор случайных чисел, путём копирования актуального состояния генератора оригинальной сети,
каждой сетью будет использоваться свой генератор во время инференса.
```cpp
class CReferenceDnnFactory {
public:
    // Архив должен содержать обученную нейронную сеть, готовую для инференса
    // Примечание: mathEngine должен поддерживать только CPU и существовать дольше объекта CReferenceDnnFactory
    static CPtr<CReferenceDnnFactory> Create( IMathEngine& mathEngine, CArchive& archive,
        int seed = 42, bool optimizeDnn = true );
    // Нейронная сеть должна быть обучена и готова для инференса,
    // Она будет скопирована для внутреннего использования и может использоваться дальше как обычно
    // Примечание: mathEngine должен поддерживать только CPU и существовать дольше объекта CReferenceDnnFactory
    static CPtr<CReferenceDnnFactory> Create( IMathEngine& mathEngine, const CDnn& dnn, bool optimizeDnn = true );
    // Нейронная сеть должна быть обучена и готова для инференса, она будет перемещена внутрь и не может использоваться снаружи.
    // Примечание: mathEngine должен поддерживать только CPU и существовать дольше объекта CReferenceDnnFactory
    static CPtr<CReferenceDnnFactory> Create( CDnn&& dnn, bool optimizeDnn = true );

    // Потокобезопасное создание ссылочной нейронной сети, увеличивает счётчик
    // Примечание: Исходная нейронная сеть, используемая для создания ссылочных нейронных сетей, может также использоваться как ещё одна ссылочная нейронная сеть (оптимизация)
    //             Флаг получения originalDnn должен использовать строго для одного потока.
    CPtr<CDnnReference> CreateReferenceDnn( bool getOriginDnn = false );

private:
    CPtr<CDnnReference> origin; // Нейронная сеть для создания ссылочных нейронных сетей
};
```
Данную фабрику можно создать из:
1. Архива с сохранённой обученной моделью нейронной сети.
2. Путём копирования некоторой нейро-сети в памяти (можно дальше продолждать работать с исходной сетью независимо от рефернс копий)
3. Путём захвата состояния текущей нейро-сети в памяти (состояние исходной сети будет затёрто)

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

Обратите внимание, что если `CPtr<CReferenceDnnFactory>` больше не требуется и внешний указатель на него очищен, то он будет уничтожен только в деструкторе последней рефернс копии.
Циклов разделяемых указателей здесь нет, поскольку оригинальная dnn, указатель которой хранится внутри фабрики, в существовании фабрики не нуждается.
Если он был получен, оригинальная dnn может существовать дольше фабрики и может быть разрушена при очищении последнего указателя на неё, даже после разрушения фабрики.


## Reference DNN

Референс копия нейро-сети, разделющая параметры весов, отличается от обычной сети (CDnn) тем, что она вложена в класс:
```cpp
class CDnnReference {
public:
    CDnn Dnn;
protected:
    static CPtr<CDnnReference> Create( CRandom& random, IMathEngine& mathEngine );
};
```
Этот класс можно получить, как результат метода `CreateReferenceDnn` класса фабрики `CReferenceDnnFactory`.

Не нужно уделять внимание тому, когда `CPtr<CDnnReference>` будет очищен.

Для пользователя нет разницы между рефернс копией и оригинальной сетью.
Пользователь не может сериализовать или изменить оба экземпляра `CDnnReference` как рефернс копию и так и оригинальную сеть, которая спрятана внутри фабрики.
Оба они получены из метода `CreateReferenceDnn` фабрики. Новые экземпляры `CDnnReference` могут быть созданы только с использованием класса фабрики.

Если нет желания использовать оригинальную сеть для оптимизации, можно просто использовать параметры по умолчанию.
Класс `CDnnReference` не входит в иерархию `CDnn`.


## Использование

Алгоритм использования много-поточного инференса в общем виде можно предствить при помощи следующих двух частей кода.
```cpp
{
	// 1. Создаём и обучаем сеть
	CDnn dnn( random, mathEngine );
	createAndTrainDnn( dnn );

	// 2. Готовим сеть для инфенерса: удаляем слои образцов и потерь, применяем оптимизации
	optimizeDnn( dnn );

	// 3. Создаём фабрику согласно исходной сети
	CPtr<CReferenceDnnFactory> referenceDnnFactory = CReferenceDnnFactory::Create( mathEngine, dnn );

	// 4. Выполняем много-поточный инференс
	CPtrOwner<IThreadPool> threadPool( CreateThreadPool( threadsCount ) );
	NEOML_NUM_THREADS( *threadPool, referenceDnnFactory.Ptr(), runDnnCreation )
}
```
Нам потребуется обученная нейро-сеть, подготовленная для инференса, фабрика для создания рефернс копий сети.
Пул потоков ЦПУ, который будет выполнять работу по инференсу. Задачи на нём нужно запустить.

Также нам понадобится функция, выполняющая инференс внутри пула потоков:
```cpp
void runDnnCreation( int thread, void* arg )
{
	// 1. Создаём для данного потока необходимую сеть
	CThreadsParams& params = *static_cast<CThreadsParams*>( arg );
	CPtr<CDnnReference> dnnRef = params.referenceDnnFactory->CreateReferenceDnn();

	// 2. Задаём входы и применяем сеть для инференса
	for( int i = 0; i < n; ++i ) {
		setInputDnn( dnnRef->Dnn  );
		dnn->RunOnce();

		CPtr<CDnnBlob> result = CheckCast<CSinkLayer>( dnn->GetLayer( "sink" ).Ptr() )->GetBlob();
		// 3. Применяем результаты для своей собственной работы...
	}

	// 4. Может также понадобится очистить буферы в пулах для данного треда после вашей работы
	IMathEngine& mathEngine = dnn->GetMathEngine();
	dnnRef.Release();
	mathEngine.CleanUp();
}
```
В данном случае для каждого потока независимо она создаёт копию reference dnn.
Выполняет инференс на необходимых данных для своей сети.
Результаты пользователь может применить по своему усмотрению.

