当前位置: 首页 > 工具软件 > Stats for iOS > 使用案例 >

在iOS或Android中集成Caffe2

籍昱
2023-12-01

以下内容翻译自 Integrating Caffe2 on iOS/Android

Caffe2针对移动集成进行了优化,灵活,易于更新,并且能够运行在低功耗设备上。 本文将介绍如何在移动项目中实现Caffe2。

相机演示项目

如果您希望在移动端看到可行的Caffe2实施(仅限目前的Android),请查看此演示项目。

AI Camera Demo

摘要

  • 将模型分配给设备(Asset Pipeline,移动端配置等)。
  • 实例化caffe2::Predictor实例(iOS)或Caffe2实例(Android)以将模型展示给您的代码。
  • 将输入传递给模型,返回输出。

关键类

  • caffe2::NetDef ——(通常是二进制序列化的)Google Protobuf实例,封装了计算图和预训练权重。
  • caffe2::Predictor——通过一个“初始化”NetDef和一个“预测”NetDef实例化的有状态类,并使用输入执行“预测”NetDef并返回输出。

库架构

Caffe2由以下组成:

  • 核心库,由WorkspaceBlob、Net和Operator类组成。
  • 运算符库,一系列运算符实现(如卷积等)

它是纯C++的,唯一的非可选依赖关系是:

  • Google Protobuf(精简版,〜300kb)
  • Eigen是某些原语所需要的BLAS库(在Android上),一个向量化的向量/矩阵操作库,Eigen是ARM中最快的基准。

对于某些用例,您还可以塞入NNPACK,这特别优化了ARM上的卷积。它是可选的(但推荐)。

错误处理是通过抛出异常,通常是caffe2::EnforceNotMet,它继承自std::exception

直观概述

模型由两部分组成:代表学习参数(训练期间更新)的一组权重(通常为浮点数),以及一组构成计算图的“操作”,表示如何将输入数据(随每个图通过而变化)与学习参数(不随每个图通过变化)组合起来。参数(和计算图中的中间状态存在于Workspace中,它基本上是一个std::unordered_map<string,Blob>,其中Blob表示任意类型的指针,通常是TensorCPU,它是一个n维数组(一个Python的numpy ndarray,Torch的Tensor等)。

核心类是caffe2::Predictor,它展示了构造函数:

Predictor(const NetDef& init_net, const NetDef& predict_net)

其中两个NetDef输入是表示上述两个计算图的Google协议缓冲区对象—— init_net通常运行一组将权重反序列化到Workspace中的操作,而predict_net指定如何为每个输入执行计算图。

使用注意事项

Predictor是一个有状态的类——通常,流程将实例化该类一次,并在多次请求中重用。 根据用例,安装开销可能是微不足道的或不容忽视的。构造函数执行以下操作:

一个关键点是,所有的初始化在某种意义上是“静态”可验证的——如果构造函数在一台机器上失败(通过抛出异常),那么它在每台机器上总是会失败。在导出NetDef实例之前,请验证Net构造是否可以正确执行。

性能考虑

目前,Caffe2针对具有NEON的ARM CPU(自2012年起基本为任何ARM CPU)进行了优化。 也许令人惊讶的是,ARM CPU的性能优于板载GPU(在老于iPhone 6的设备上我们的NNPACK ARM CPU实现优于苹果的MPSCNNConvolution)。将计算卸载到GPU/DSP上还有其他优势,并且我们正在积极开展Caffe2中的这些工作。

对于卷积实现,建议使用NNPACK,因为它比大多数框架中使用的标准im2col/sgemm实现要快得多(约2x3x)。 建议将OperatorDef::engine设置为NNPACK。 例:

def pick_engines(net):
    net = copy.deepcopy(net)
    for op in net.op:
        if op.type == "Conv":
            op.engine = "NNPACK"
        if op.type == "ConvTranspose":
            op.engine = "BLOCK"
    return net

对于非卷积(例如排序)工作负载,关键计算基元通常是全连接层(例如Caffe2中的FullyConnectedOp,Caffe中的InnerProductLayer,Torch中的nn.Linear)。对于这些用例,您可以切回到BLAS库,特别是加速iOS上的Accelerate和Android上的Eigen

内存考虑

实例化和运行Predictor模型的内存使用量是它的权重和激活的总和。没有分配“静态”内存,所有分配都与Predictor拥有的Workspace实例相关联,所以在删除所有Predictor实例后,应该不会再占用内存。

在导出运行之前,推荐使用以下命令:

def optimize_net(net):
    optimization = memonger.optimize_interference(
        net,
        [b for b in net.external_input] +
        [b for b in net.external_output])
    try:
        # This can fail if the blobs aren't in the workspace.'
        stats = memonger.compute_statistics(optimization.assignments)
        print("Memory saving: {:.2f}%".format(
            float(stats.optimized_nbytes) / stats.baseline_nbytes * 100))
    except Exception as e:
        print(e)
    return pick_engines(share_conv_buffers(rename_blobs(optimization.net)))

这将自动共享在图的拓扑顺序中有效的激活(有关更详细的讨论,请参阅Predictor)。

iOS上的启动注意事项

Caffe2使用注册表模式来注册运算符类。宏位于核心运算符operator.h的运算符注册表部分:

// The operator registry. Since we are not expecting a great number of devices,
// we will simply have an if-then type command and allocate the actual
// generation to device-specific registerers.
// Note that although we have CUDA and CUDNN here, the registerers themselves do
// not depend on specific cuda or cudnn libraries. This means that we will be
// able to compile it even when there is no cuda available - we simply do not
// link any cuda or cudnn operators.
CAFFE_DECLARE_REGISTRY(
    CPUOperatorRegistry,
    OperatorBase,
    const OperatorDef&,
    Workspace*);
#define REGISTER_CPU_OPERATOR_CREATOR(key, ...) \
  CAFFE_REGISTER_CREATOR(CPUOperatorRegistry, key, __VA_ARGS__)
#define REGISTER_CPU_OPERATOR(name, ...) \
  CAFFE_REGISTER_CLASS(CPUOperatorRegistry, name, __VA_ARGS__)
#define REGISTER_CPU_OPERATOR_STR(str_name, ...) \
  CAFFE_REGISTER_TYPED_CLASS(CPUOperatorRegistry, str_name, __VA_ARGS__)

#define REGISTER_CPU_OPERATOR_WITH_ENGINE(name, engine, ...) \
  CAFFE_REGISTER_CLASS(CPUOperatorRegistry, name##_ENGINE_##engine, __VA_ARGS__)

并使用,例如conv_op.cc

REGISTER_CPU_OPERATOR(Conv, ConvOp<float, CPUContext>);
REGISTER_CPU_OPERATOR(ConvGradient, ConvGradientOp<float, CPUContext>);
 类似资料: