Как добавить новый аппаратный сервер в MindSpore? Быстрое создание тестовых сред

искусственный интеллект

Резюме: описывает, как добавить в MindSpore новое аппаратное обеспечение.

Эта статья опубликована в сообществе HUAWEI CLOUD.Как добавить новый аппаратный сервер в MindSpore? Быстро создавайте тестовые среды! 》, оригинальный автор: HWCloudAI.

MindSporeЭто новое поколение вычислительной среды искусственного интеллекта с открытым исходным кодом, разработанное Huawei. Полнофункциональная среда глубокого обучения, которая лучше всего соответствует вычислительной мощности процессоров Ascend AI, предоставляя специалистам по данным и инженерам-алгоритмамДружественный дизайн,Эффективная разработка,Содействовать экологическому процветанию и развитию программных и аппаратных приложений искусственного интеллекта..

MindSpore поддерживает разнородные вычислительные мощности.Помимо поддержки архитектуры Ascend NPU собственной разработки Huawei DaVinci, он также поддерживает работу операторов ЦП (например, MKLDNN) и графического процессора (например, ядра CUDA). (Примечание: MindSpore поддерживает работу всей сети на разных аппаратных платформах и не поддерживает разные разделы одной и той же сети, работающие на разных аппаратных платформах, что отличается от гетерогенного режима работы графового раздела TensorFlow).

В настоящее время индустрия чипов ИИ «очень оживлена».В стране и за рубежом новые и старые производители, большие и малые, выпускают свои собственные чипы для ускорения ИИ. Теперь все должны ясно видеть,Если аппаратное обеспечение должно быть успешным, оно неотделимо от поддержки программного стека и экологии.. MindSpore не только служит для поддержки программного и аппаратного стека ИИ Huawei, но и хочет занять свое место во всей экосистеме ИИ.

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

Эта статья посвящена исходному коду версии MindSpore r1.1:Git Ee.com/minds Pore / No ...Mindspore и требования для соответствующих версий программного обеспечения, пожалуйста, обратитесь к:
www.mindspore.cn/install/

прецедент

В этой статье речь пойдет о простой сети с плотным слоем:
Woohoo.minds Pore.can/doc/API\_friends…Уровень работает на новом аппаратном бэкэнде. Примечание. Эта статья предназначена для базового режима выполнения статического графа:
ву ву ву пора разума талант/доктор/программа…

import mindspore
import numpy as np
import mindspore.nn as nn
from mindspore import context, Tensor


context.set_context(device_target="CPU", mode=context.GRAPH_MODE)


# 32, 16
net = nn.Dense(32, 16, weight_init='ones', bias_init=1.2)#, activation='relu')


# 48, 32
input_data = Tensor(np.ones([48, 32]).astype(np.float32), mindspore.float32)
output = net(input_data)


print(output.asnumpy())

Примечание. Здесь я закомментировал активацию ReLU, поэтому этот плотный слой эквивалентен небольшой сети только с двумя узлами (MatMul + BiasAdd). Результатом этого варианта использования является двумерная матрица 48 * 16, значения каждого элемента все 33,2)

В этой статье мы будем следовать процессу сверху вниз,Представьте компоненты, которые MindSpore необходимо изменить для поддержки нового аппаратного бэкэнда.. Мы называем новое оборудование, которое необходимо поддерживать здесь, как XPU.Эффект, которого мы хотим достичь после изменения кода MindSpore, состоит в том, чтобы изменить device_target в приведенном выше варианте использования на XPU и позволить слою Dense работать на ускорителе XPU. например

context.set_context(device_target="XPU", mode=context.GRAPH_MODE)

Примечание. В этой статье не приводятся подробности реализации конкретных классов и функций. Конкретные реализации см. в описании поддерживаемых аппаратных серверных частей в соответствующем каталоге, например: ЦП, ГП, Ascend.

Добавить новую опцию параметра целевого устройства

Сначала из внешнего слоя MEPython вам нужно добавить новые valid_targets:
git ee.com/minds pore/no…

def set_device_target(self, target):
        valid_targets = ["CPU", "GPU", "Ascend", "Davinci", "XPU"] # 将新的后端添加到此list中
        if not target in valid_targets:
            raise ValueError(f"Target device name {target} is invalid! It must be one of {valid_targets}")
        if target == "Davinci":
            target = "Ascend"
        self.set_param(ms_ctx_param.device_target, target)
        if self.enable_debug_runtime and target == "CPU":
            self.set_backend_policy("vm") 

Затем вам нужно добавить новую цель в компонент контекста ms C++:
git ee.com/minds pore/no…

const int kGraphMode = 0;
const int kPynativeMode = 1;
const char kCPUDevice[] = "CPU";
const char kGPUDevice[] = "GPU";
const char kXPUDevice[] = "XPU";  // 添加新的硬件target
const char kAscendDevice[] = "Ascend";
const char kDavinciInferenceDevice[] = "AscendInference";
const char kDavinciDevice[] = "Davinci";
const char KNpuLog[] = "_npu_log";
const unsigned int MAX_CALL_DEPTH_DEFAULT = 1000;


// 添加新的硬件到以下set中
const std::set<std::string> kTargetSet = {kCPUDevice, kGPUDevice, kXPUDevice, kAscendDevice, kDavinciDevice};

Добавить новое устройство среды выполнения

В каталоге устройства среды выполнения:
git ee.com/minds pore/no…адресное пространство на стороне, управление памятью (распределение, переработка) на стороне устройства, компоненты среды выполнения ядра и т. д., а также некоторые компоненты связи, относящиеся к аппаратным устройствам, такие как компоненты MPI, поддерживающие распределенную связь. Сначала мы добавляем папку с именем xpu в указанный ниже каталог (обратите внимание на то, чтобы изменить CMakeLists.txt, чтобы добавить папку):

Вот 3 новых базовых компонента, которые будут созданы для ускорителя xpu:

xpu_device_address : в основном указывает информацию об адресе памяти на стороне устройства ускорителя, а также интерфейс API для передачи памяти между стороной хоста и стороной устройства, например, это может быть обертка на GPU NVIDIA:
cudaMemcpyAsyncxpu_device_address.h

#include <string>
#include <vector>
#include "runtime/device/device_address.h"
#include "utils/shape_utils.h"


namespace mindspore {
namespace device {
namespace xpu {
class XPUDeviceAddress : public DeviceAddress {
 public:
  XPUDeviceAddress(void *ptr, size_t size) : DeviceAddress(ptr, size) {}


  XPUDeviceAddress(void *ptr, size_t size, const string &format, TypeId type_id)
      : DeviceAddress(ptr, size, format, type_id) {}


  ~XPUDeviceAddress() override = default;


  bool SyncDeviceToHost(const ShapeVector &shape, size_t size, TypeId type, void *host_ptr) const override;
  bool SyncHostToDevice(const ShapeVector &shape, size_t size, TypeId type, const void *host_ptr) const override;
  DeviceAddressType DeviceType() const override { return DeviceAddressType::kXPU; }
};
}  // namespace xpu
}  // namespace device
}  // namespace mindspore

xpu_resource_manager: в основном отвечает за управление, выделение и планирование памяти и других ресурсов на стороне устройства. xpu_resource_manager.h

#include <vector>
#include <map>
#include "backend/session/kernel_graph.h"
#include "backend/session/session_basic.h"
#include "runtime/device/device_address.h"
#include "runtime/device/xpu/xpu_simple_mem_plan.h"
namespace mindspore {
namespace device {
namespace xpu {
class XPUResourceManager {
 public:
  XPUResourceManager() = default;
  ~XPUResourceManager();


  void AssignMemory(const session::KernelGraph *graph);
  void IncreaseAddressRefCount(const session::KernelGraph *graph);
  void DecreaseAddressRefCount(const AnfNodePtr &kernel);
  void *MemMalloc(size_t mem_size);
  void MemFree(void *ptr);


 private:
  void MemFree();
  XPUSimpleMemPlan mem_plan_;


  size_t mem_size_{0};
  uint8_t *mem_ptr_{nullptr};
  bool dynamic_malloc_{false};
  std::map<void *, size_t> dynamic_mem_;
};
}  // namespace xpu
}  // namespace device
}  // namespace mindspore

xpu_kernel_runtime: модуль управления выполнением аппаратного оператора, который в основном отвечает за запуск аппаратной среды выполнения (Init()), выполнение сети на аппаратном обеспечении (Run(..)) и очистку после аппаратного был выполнен (ReleaseDeviceRes()) xpu_kernel_runtime .h

#include <memory>
#include <vector>
#include <string>
#include <map>
#include <set>
#include "runtime/device/kernel_runtime.h"
#include "runtime/device/kernel_runtime_manager.h"
#include "backend/session/kernel_graph.h"
#include "backend/session/session_basic.h"
#include "runtime/device/xpu/xpu_resource_manager.h"
#include "backend/session/anf_runtime_algorithm.h"
#include "utils/any.h"
namespace mindspore {
namespace device {
namespace xpu {
class XPUKernelRuntime : public KernelRuntime {
 public:
  XPUKernelRuntime() = default;
  ~XPUKernelRuntime() override = default;


  bool Init() override;
  void ReleaseDeviceRes() override;
  bool Run(session::KernelGraph *graph, bool is_task_sink) override;
  void AssignKernelAddress(session::KernelGraph *kernel_graph);
  void CreateOutputTensors(session::KernelGraph *kernel_graph, const std::vector<tensor::TensorPtr> &inputs,
                           VectorRef *outputs);
  void BindInputOutput(session::KernelGraph *kernel_graph, const std::vector<tensor::TensorPtr> &inputs,
                       VectorRef *outputs);


 protected:
  bool SyncStream() override { return true; };
  DeviceAddressPtr CreateDeviceAddress(void *device_ptr, size_t device_size, const string &format,
                                       TypeId type_id) override;


 private:
  XPUResourceManager resource_manager_;
  std::set<DeviceAddressPtr> bound_addresses_;
  std::map<AnfNodePtr, tensor::TensorPtr> input_param_tensor_map_;
};


MS_REG_KERNEL_RUNTIME(kXPUDevice, XPUKernelRuntime);


}  // namespace xpu
}  // namespace device
}  // namespace mindspore

Добавить новый целевой сеанс

Сессия MindSpore предоставляет среду для выполнения ядра Op и оценки Tensor. Сеанс — это основной модуль, который управляет графом потока данных, представляющим нейронную сеть. В основном он состоит из трех основных этапов компиляции графа (генерация ядра), оптимизации графа и выполнения графа. MindSpore будет иметь собственный компонент сеанса для каждой серверной аппаратной платформы.Соответствующий код находится в каталоге backend/session:
git ee.com/minds pore/no…

Мы создаем новый класс сеанса для xpu: xpu_session.h

#include <string>
#include <memory>
#include <map>
#include <vector>
#include "backend/session/session_basic.h"
#include "backend/session/kernel_graph.h"
#include "runtime/device/xpu/xpu_kernel_runtime.h" // use the new xpu kernel runtime
#include "backend/session/session_factory.h"
namespace mindspore {
namespace session {
class XPUSession : public SessionBasic {
 public:
  XPUSession() = default;
  ~XPUSession() override = default;
  void Init(uint32_t device_id) override { InitExecutor(kXPUDevice, device_id); }


  GraphId CompileGraphImpl(const AnfNodePtrList &lst, const AnfNodePtrList &outputs) override;
  void RunGraphImpl(const GraphId &graph_id, const std::vector<tensor::TensorPtr> &inputs, VectorRef *outputs) override;
  void Optimize(const std::shared_ptr<KernelGraph> &kernel_graph);


 protected:
  void UnifyMindIR(const KernelGraphPtr &graph) override { return; }
  void CreateOutputTensors(const GraphId &graph_id, const std::vector<tensor::TensorPtr> &input_tensors, VectorRef *,
                           std::map<tensor::TensorPtr, session::KernelWithIndex> *tensor_to_node) override;


 private:
  void SetKernelInfo(const KernelGraph *kernel_graph);
  void BuildKernel(const KernelGraph *kernel_graph);
  device::xpu::XPUKernelRuntime *runtime_ = dynamic_cast<device::xpu::XPUKernelRuntime*>(device::KernelRuntimeManager::Instance().GetKernelRuntime(kXPUDevice, 0));
};
MS_REG_SESSION(kXPUDevice, XPUSession);
}  // namespace session
}  // namespace mindspore

На этапе компиляции графа (CompileGraphImpl(..)) в основном нужно сгенерировать (BuildKernel(..)) ядро, соответствующее каждому узлу в графе потока данных нейронной сети, и сохранить информацию о ядре каждого узла. граф (SetKernelInfo(..)) для вызова на более позднем этапе выполнения графа (RunGraphImpl(..)).

Добавить ядра для нового оборудования

Аппаратный бэкэнд, поддерживаемый MindSpore, поддерживает каждый оператор операции в каталоге backend/kernel_compiler:
git ee.com/minds pore/no…

Здесь мы видим, что для нескольких аппаратных бэкэндов каждая папка представляет отдельный тип ядра, среди которых:

cpu: Есть операторы, вызывающие MKLDNN (oneDNN), а есть операторы, написанные на чистом C++.

gpu: есть операторы, вызывающие cudnn/cublas, операторы, написанные на cuda, и операторы, связанные с NCCL, которые поддерживают распределенное обучение.

Ascend: папки ядра оператора, связанные с чипом искусственного интеллекта Huawei DaVinci, включают: tbe, aiccpu, akg, hccl и т. д.

Давайте представим компоненты, необходимые для добавления поддержки ядра для нашего нового аппаратного бэкенда. Во-первых, создайте папку с именем xpu в указанном выше каталоге (обратите внимание на изменение CMakeLists.txt, чтобы добавить папку). В новой папке мы сначала создаем базовый класс для ядро xpu: xpu_kernel.h:

#include <string>
#include <vector>
#include <memory>
#include <numeric>
#include <functional>
#include "backend/kernel_compiler/kernel.h"
#include "ir/anf.h"
#include "backend/session/anf_runtime_algorithm.h"
#include "utils/ms_utils.h"


using mindspore::kernel::Address;
using mindspore::kernel::AddressPtr;
namespace mindspore {
namespace kernel {


class XPUKernel : public kernel::KernelMod {
 public:
  XPUKernel() = default;
  ~XPUKernel() override = default;


  void Init(const CNodePtr &kernel_node);
  virtual void InitKernel(const CNodePtr &kernel_node) = 0;
  bool Launch(const std::vector<AddressPtr> &inputs, const std::vector<AddressPtr> &workspace,
              const std::vector<AddressPtr> &outputs, void * stream_ptr) override {
    return Launch(inputs, workspace, outputs);
  };


  virtual bool Launch(const std::vector<AddressPtr> &inputs, const std::vector<AddressPtr> &workspace,
                      const std::vector<AddressPtr> &outputs) = 0;
  const std::vector<size_t> &GetInputSizeList() const override { return input_size_list_; }
  const std::vector<size_t> &GetOutputSizeList() const override { return output_size_list_; }
  const std::vector<size_t> &GetWorkspaceSizeList() const override { return workspace_size_list_; }


  void SetOpName(const std::string &op_name) { op_name_ = op_name; }
  const std::string GetOpName() const { return op_name_; }


 protected:
  virtual void InitInputOutputSize(const CNodePtr &kernel_node);
  std::vector<size_t> input_size_list_ = {};
  std::vector<size_t> output_size_list_ = {};
  std::vector<size_t> workspace_size_list_ = {};


  std::string bin_path_ = {};
  std::string tilingName_ = {};


};
}  // namespace kernel
}  // namespace mindspore

В настоящее время популярные фреймворки обычно поддерживают ядра операторов, называя ядро ​​именем оператора (кодом операции). Например, ядра процессора mkldnn в mindspore: MindSpore/mindspore конкретные свойства могут быть легко выражены. Недостатком является то, что может быть некоторая повторяющаяся логика кода. Поскольку вариант использования для этой статьи очень прост, на самом деле необходимо поддерживать только два оператора: MatMul и BiasAdd, мы будем использовать метод реализации класса ядра, названный в соответствии с количеством входных и выходных тензоров.

Поскольку MatMul и BiasAdd являются операторами с двумя входами и одним выходом, мы определяем имя нашего класса ядра как:
two_in_one_out_xpu_kernel.h

#include "backend/kernel_compiler/xpu/xpu_kernel.h" // xpu kernel base class
#include "backend/kernel_compiler/xpu/xpu_kernel_factory.h"


#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <algorithm>


#include <fstream>
#include <iostream>


namespace mindspore {
namespace kernel {


class TwoInOneOutXPUKernel : public XPUKernel {
 public:
  TwoInOneOutXPUKernel() = default;
  ~TwoInOneOutXPUKernel() override = default;


  void InitKernel(const CNodePtr &kernel_node) override;


  bool Launch(const std::vector<AddressPtr> &inputs,
              const std::vector<AddressPtr> &workspace,
              const std::vector<AddressPtr> &outputs) override;


 private:
  bool NeedsFormatTransformation();


  char trans_a_{TRANSPOSE_NO};
  char trans_b_{TRANSPOSE_NO};
  int32_t dim_m_{0};
  int32_t dim_n_{0};
  int32_t dim_k_{0};


  std::vector<size_t> inputA_shape_;
  std::vector<size_t> inputB_shape_;
  std::vector<size_t> output_shape_;


  size_t input_a_size_ = 0;
  size_t input_b_size_ = 0;
  size_t output_size_ = 0;


  void *inputA_data_ = nullptr;
  void *inputB_data_ = nullptr;
  void *output_data_ = nullptr;
};


MS_REG_XPU_KERNEL(
  TwoInOneOutXPU,
  mindspore::device::xpu::KernelAttr().AddInputAttr(kNumberTypeFloat32).AddInputAttr(kNumberTypeFloat32).AddOutputAttr(kNumberTypeFloat32),
  TwoInOneOutXPUKernel);
}  // namespace kernel
}  // namespace mindspore

Здесь мы используем "backend/kernel_compiler/xpu/xpu_kernel_factory.h". Мы не будем подробно останавливаться на создании класса фабрики ядра. Подробности смотрите в cpu_kernel_factory.h:
git ee.com/minds pore/no…

Две самые основные функции для каждого ядра являются initkernel (..) и lightkernel (..), которые несут ответственность за инициализацию и эксплуатацию ядра соответственно. Здесь следует отметить, что для выполнения статических графов, таких как CNN, initkernel (..), будет работать только один раз, когда ядро ​​создано (в процессе Compilegraphraph из вышеуказанного сеанса), а запускаемое место (..) будет выполняться каждый Время выполняется график. Процедура называется. Например, запуск вывод CNN требует изображения Infernce64, размер партии сетевой сети составляет 32, а вся картина должна быть выполнена в два раза, то есть для каждого ядра INITKERNEL (..) будет вызываться один раз, и Launchkernel будет вызван один раз. (..) будет называться 2 раза.

Мы не будем описывать здесь конкретную реализацию ядра MatMul и BiasAdd, а только представим некоторые базовые API, которые необходимы для ядра оператора в MindSpore:

Получите входную и выходную информацию формы TwoInOneOutXPUKernel:

inputA_shape_ = AnfAlgo::GetInputDeviceShape(kernel_node, 0);
inputB_shape_ = AnfAlgo::GetInputDeviceShape(kernel_node, 1);
output_shape_ = AnfAlgo::GetOutputDeviceShape(kernel_node, 0);

Получить информацию об атрибутах оператора, например информацию о транспонировании MatMul:

bool trans_a = AnfAlgo::GetNodeAttr<bool>(kernel_node, TRANSPOSE_A);
bool trans_b = AnfAlgo::GetNodeAttr<bool>(kernel_node, TRANSPOSE_B);

Получить ввод в Launch, вывести указатель памяти:

auto input_a = reinterpret_cast<float *>(inputs[0]->addr);
auto input_b = reinterpret_cast<float *>(inputs[1]->addr);
auto output = reinterpret_cast<float *>(outputs[0]->addr);

Другие соображения

Как и другие основные фреймворки, MindSpore также будет иметь некоторые собственные стандарты и спецификации.Вот некоторые «ямы», на которые я наступил, чтобы поделиться с вами:

Формат Tensor по умолчанию в Mindspore — nchw.. Если формат, поддерживаемый добавляемым аппаратным бэкендом, отличается, обратите внимание на добавление преобразования формата. Преобразование формата может выполняться до и после каждого вызова ядра (неэффективно), или можно использовать проход оптимизации графа для эффективной вставки узлов преобразования формата с точки зрения всей сети.

прецизионное преобразование, если ваша аппаратная платформа поддерживает только определенную точность, например fp16, а сеть — fp32, то вам следует обратить внимание на преобразование точности, аналогичное приведенному выше преобразованию формата. Прецизионное преобразование может быть выполнено на стороне хоста или на стороне устройства (если аппаратное обеспечение поддерживает это).

заЛогика кода для каждого ядраЧтобы различать, какие данные являются постоянными, а какие будут меняться, их необходимо повторно инициализировать перед каждым выполнением, чтобы различные логические коды могли быть разумно и правильно назначены соответствующим ядрам InitKernel(..) или LaunchKernel(..).

заLayerAPI для некоторых интерфейсов Python, MindSpore имеет некоторые собственные настройки свойств, например, для Denselayer:
git ee.com/minds pore/no…Две входные матрицы транспонируются:

self.matmul = P.MatMul(transpose_b=True)
self.batch_matmul = P.BatchMatMul(transpose_b=True)
self.activation = get_activation(activation) if isinstance(activation, str) else activation
if activation is not None and not isinstance(self.activation, (Cell, Primitive)):
    raise TypeError("The activation must be str or Cell or Primitive,"" but got {}.".format(activation))
self.activation_flag = self.activation is not None

заDebug, вы можете добавить следующие переменные среды, чтобы упростить вывод информации:

export GLOG_v=1
export SLOG_PRINT_TO_STDOUT=1

заМодификация файлов CMake, вы можете добавить недавно добавленные файлы в if (ENABLE_CPU) при запуске теста.ЦП эквивалентен базовой платформе для MindSpore, то есть независимо от того, создаете ли вы графический процессор или цель Huawei D/Ascend, связанные с ЦП файлы будут построены.

Суммировать

Эта статья является технической статьей о том, как изменить исходный код MindSpore, чтобы добавить новую аппаратную серверную часть, исходя из моего понимания MindSpore. Успех платформы программного обеспечения с открытым исходным кодом неотделим от поддержки сообщества и участия различных производителей.Есть надежда, что эта статья может сыграть роль в привлечении большего количества производителей оборудования и разработчиков к участию в экологическом развитии MindSpore. Вы также можете обсудить Paizhuan вместе!

Вы рады узнать о ключевых технологиях MindSpore? торопиться【нажмите на ссылку】И [зарегистрируйтесь сейчас], вы можете изучить классический кейс на платформе ModelArts, чтобы освоить глубокое обучение на основе MindSpore!

Нажмите «Подписаться», чтобы впервые узнать о новых технологиях HUAWEI CLOUD~