Android 深度学习基础 – 系统和硬件

免责声明:这篇文章的所有内容都是我个人调研得到的,来源是网络的各种资料。难免出现各种错误,有的内容可能吸收了别人的工作成果。如果遇到问题,欢迎提出,我会及时更改。

API 和 ABI

这俩不是一个概念,API 是指 Application Program Interface,而 ABI 是指 Application Binary Interface。简单来说,API 和系统相关,ABI 和硬件相关。

API

打开一个 Android 项目,API 主要和 SDK Version 相关,项目主要有 三个 SDK Version,SDK 版本号和 API 级别对应,也和系统版本对应。

名称 含义 推荐取值
compileSdkVersion 编译时用到的 SDK 版本 最新的 SDK
targetSdkVersion 测试机的系统版本 测试机的系统版本
minSdkVersion 前向兼容的系统版本 想兼容的版本

API Level 和 Android 系统的映射没有什么规律,有时映射大版本,有时映射小版本号。更多信息可以参考官方文档

Code name Version API level Distribution% AccuDist%
Android 10 10 API level 29
Pie 9 API level 28 10.4 100.0
Oreo 8.1.0 API level 27 15.4 89.6
Oreo 8.0.0 API level 26 12.9 74.2
Nougat 7.1 API level 25 7.8 61.3
Nougat 7.0 API level 24 11.4 53.5
Marshmallow 6.0 API level 23 16.9 42.1
Lollipop 5.1 API level 22 11.5 25.2
Lollipop 5.0 API level 21 3.0 13.7
KitKat 4.4 - 4.4.4 API level 19 6.9 10.7
Jelly Bean 4.3.x API level 18 0.5 3.8
Jelly Bean 4.2.x API level 17 1.5 3.3
Jelly Bean 4.1.x API level 16 1.2 1.8
Ice Cream Sandwich 4.0.3 - 4.0.4 API level 15, NDK 8 0.3 0.6
Ice Cream Sandwich 4.0.1 - 4.0.2 API level 14, NDK 7 0.3 0.3
Honeycomb 3.2.x API level 13
Honeycomb 3.1 API level 12, NDK 6
Honeycomb 3.0 API level 11
Gingerbread 2.3.3 - 2.3.7 API level 10
Gingerbread 2.3 - 2.3.2 API level 9, NDK 5
Froyo 2.2.x API level 8, NDK 4
Eclair 2.1 API level 7, NDK 3
Eclair 2.0.1 API level 6
Eclair 2.0 API level 5
Donut 1.6 API level 4, NDK 2
Cupcake 1.5 API level 3, NDK 1
(no code name) 1.1 API level 2
(no code name) 1.0 API level 1

上表同时包含了各个 API Level 的 占比,最后一列是版本从低到高的累积占比,可以看到,陈旧系统能够的占比还是挺多的,比如 Android 6 之前的系统不保证 Neon 存在(Neon 后边会介绍),而占比达到了 25.2%,如果我们的代码只做了 Neon 优化,那么在 25.2% 的机器上无法运行。因为要考虑陈旧系统,所以 Android 的深度学习变得复杂。

ABI

在 Android 项目中,会看到 eabi 的字眼,例如有的教程里会说将某某类库放在 armeabi 文件夹下。ABI 全称是 Application Binary Interface,EABI 是指嵌入式的 ABI:Embedded Application Binary Interface
Android 支持的 ABI 如下,更详细信息可以参考 官网

flags
armeabi removed in NDK r17
mips removed in NDK r17
mips64 removed in NDK r17
armeabi-v7a -march=armv7-a -mthumb (-mfloat-abi=softfp)
armeabi-v7a-hard https://android.googlesource.com/platform/ndk/+/ndk-r12-release/docs/HardFloatAbi.md removed in NDK r12
arm64-v8a (-march=aarch64 -mfloat-abi=hard)
x86 (-march=i686 -mtune=intel -mssse3 -mfpmatch=sse -m32)
x86_64 (-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel)

Android 已经放弃支持 armeabimipsmips64 了,简单来说,现在 Android 仅支持 ARM 和 x86 了,而 ARM 也仅仅支持 v7av8a

ARM CPU

我们常说的高通 855,麒麟980 不是 CPU 是 SoC(System On Chip),SoC 除了 CPU 外,还有 GPU,还有可选的浮点数加速器,专用于深度模型的加速器,这些都和本文相关。除此以外,SoC 还包括运存,基带芯片等等,这些和深度学习关系不大。
ARM 和各个 SoC 的关系:所有 ARM 的 CPU 都是 ARM 公司授权的,授权的形式是 IP 核,各个商场得到授权,生产自己的 SoC。

ARM 架构

历史上,ARM 定义了 ARMv1ARMv2ARMv3ARMv4ARMv4TARMv5TEARMv6 这些架构,都已经过时了。
在定义 ARMv7 时,引入了 profile 的概念,将 ARMv7 划分为:

  • ARMv7-A: Application
  • ARMv7-R:Real-time
  • ARMv7-M:Microcontroller
    ARMv6-M 是后来出现的产品,由 ARMv7-M 裁减得到。
    在定义 ARMv8 时,沿用了三个 profile,只有 ARMv8-A 支持 64 位。ARMv8-A 迭代了多次,目前最新的是 ARMv8.6-A
    每个架构下,有多个 IP 核,目前 IP 核都以 Cortex 命名,例如 Cortex-A5ARMv7-A 的一个 IP 核。
    Android 仅支持 ARMv7-AARMv8-A 两种架构,对应的 EABI 是 armeabi-v7aarm64-v8a
    常见的 ARM 架构和 IP核 如下表
Architecture bit-width Cortex
ARMv7-A 32 A5, A7, A8, A9, A12, A15, A17
ARMv7-R 32 R4, R5, R7, R8
ARMv7-M 32 M3
ARMv7E-M 32 M4, M7
ARMv8-A 32 A32
ARMv8-A 64 A34
ARMv8-A 32/64 A35, A53, A57, A72, A73
ARMv8-R 32 R52
ARMv8-M 32 M23, M33
ARMv8.2-A 32/64 A55
ARMv8.2-A 32/64 A75
ARMv8.3-A 64 A76

高通公司的 SoC 对 ARM 的 IP 核做了二次包装,把 32位的 CPU 命名为 Krait 系列,把 64 位的 CPU 命名为 Kryo 系列。例如 骁龙855 使用的 CPU 是 Kryo 485,实际是由 Cortex-A55 + Cortex-A76 实现的。

Snapdragon CPU GPU DSP
800 Krait 400 Adreno 330 Hexagon QDSP6 V5
835 Kryo 280 (A73) Adreno 540 Hexagon 682
845 Kryo 385 (A55+A75) Adreno 630 Hexagon 685
855 Kryo 485 (A55+A76) Adreno 640 Hexagon 690
855+ Kryo 485 (A55+A76) Adreno 640 Hexagon 690

32位和64位

参考官网,未来的 app 都要支持 64bit,主要是 native code 要支持

  • 自己开发的 native code 要支持
  • 调用的第三方 native lib 也要支持
    最终生成的 apk 里,同时有 32位 和 64位的内容
Platform 32 bit 64 bit
ARM lib/armeabi-v7a/ lib/arm64-v8a/
x86 lib/x86/ lib/x86_64/

如何设置 64位

在 gradle 中设置 build.gradle

1
2
3
4
5
6
7
8
9
10
android {
  compileSdkVersion 27
  defaultConfig {
    appId "com.google.example.64bit"
    minSdkVersion 15
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
    // ...

在 cmake 中设置

1
2
cmake -DANDROID_ABI=arm64-v8a ...
cmake -DANDROID_ABI=x86_64 ...

在 ndk-build 中设置 Application.mk

1
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64

ARM 的浮点数运算

首先介绍 VFP(Vector Floating Point)和 Neon:两者都是浮点数运算器,在 ARMv7-A 中浮点数运算器是可选的(即有的手机没有)。VFP 和 Neon 实现细节不同, Neon 支持并行运算,速度更快。在 ARMv8-A 中,Neon 是必备的。此外,Android ABI 约束 armeabi-v7a 必须支持 vfp,Android 6 约束 ARMv-A 必须包含 Neon。所以 ARMv8-A 可以直接使用 Neon,ARMv7-A 需要判断使用
对于 ARMv7-A 来说,另一个需要关注的问题是浮点数运算的函数调用方式,google 在 NDKr12 中移除了 hard-float 的支持,浮点数会先转换为整数,放在整数寄存器里,然后计算,这里会损失性能。曾经有 armeabi-v7a-hard 这一 ABI 可以解决此问题,不过已经废弃了。

FPU Function call
armeabi-v7a-hard yes hard-float
armeabi-v7a optional soft-float
arm64-v8a yes hard-float

浮点数调用以及浮点数运算器总结如下,表格从上到下, 运算速度递增。

Type Flag Register, FPU Android 备注
soft -mfloat-abi=soft Integer 不使用 FPU
softfp -mfloat-abi=softfp Integer + VFP 默认使用 vfpv3-d16
softfp-vfp -mfloat-abi=softfp -mfpu=vfpv3-d16 Integer + VFP armv7a 默认 同 softfp
softfp-neon -mfloat-abi=softfp -mfpu=neon Integer + Neon armv7a 有 neon 时
hard -mfloat-abi=hard Float + VFP armv7-a 无法用 默认使用 vfpv3-d16
hard-neon -mfloat-abi=hard -mfpu=neon Float + Neon armv8-a 默认

最简单的加速方法就是开启 Neon。
在 gradle 中开启,设置 build.gradle

1
2
3
4
5
6
7
8
9
android {
  defautConfig {
    externalNativeBuild {
      cmake {
        arguments "-DANDROID_ARM_NEON=ON"
      }
    }
  }
}

在 cmake 中开启

1
cmake -DANDROID_ARM_NEON=ON ...

或者编译 CMakeLists.txt

1
2
3
4
5
6
7
if(ANDROID_ABI STREQ ameabi-v7a)
  set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS -mfpu=neon)
endif()
# or
if(ANDROID_ABI STREQ armeabi-v7a)
  set_source_files_properties(foo.cpp PROPERTIES COMPILE_FLAGS -mfpu=neon)
endif()

在 ndk 中开启

1
2
3
LOCAL_ARM_NEON := true
# or add .neon
LOCLA_SRC_FILES := foo.c.neon bar.c

因为 ARMv7-A 的兼容性问题,不是所有的手机都支持 Neon,需要在运行时进行判断
java 版本的检测示例:

1
2
3
4
5
6
7
8
9
10
11
12
if (Library.isSupported(ArmCpuSimdFeature.NEON)) {
    if (Library.getMicroarchitecture().equals(CpuMicroarchitecture.Krait)) {
        /* Special NEON implementation for recent Qualcomm processors */
        nativeClass.processKrait(); // v7 neon
    } else {
        /* Generic NEON implementation */
        nativeClass.processNeon(); // v8 neon
    }
} else {
    /* Generic implementation without NEON */
    nativeClass.processGeneric(); // vfp
}

native 代码的检测示例

1
2
3
4
5
6
7
8
#include <cpu-features.h>

if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM &&
   (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0) {
  // use Neon-optimized routines
} else {
  // use non-Neon fallback routines instead
}

最后需要说明的是,ARMv8-A 的 Neon 兼容 ARMv7-A 的(写一套代码是可行的),而且性能更好,功能更强大:参考 ARM提供的资料

8512.pastedImage_5F00_46.png

ARMv7-A ARMv8-A AArch32 ARMv8-A AArch64
Register 32x64-bit 32x64-bit 32x128-bit
Integer 8/16/32/64-bit 8/16/32/64-bit 8/16/32/64-bit
Float float float float/double
Operation 16x8-bit 16x8-bit 16x8-bit
Instruction Set 最少 比 ARMv7-A 多点 截然不同的

GPU

Android 主流的 GPU 只有两种,ARM 的 Mali 和高通的 Adreno。华为,MTK,三星都使用 Mali
Mali 是 ARM公司的,最早是 Falanx Microsystems 设计的,后边合并为 ARM 挪威公司。

GFLOPS SoC Date
Mali-T830 40.8 Kirin 650/655, Exynos 7870/7880 2015Q4
Mali-T860 Helio P10 2015Q4
Mali-T880 122.4 Kirin 950/955, Helio P25/X20 2016Q2
Mali-G71 282 Kirin 860, Helo P23/P30, Exynos 5 8782 2016Q2
Mali-G72 330 Kirin 870, Helio P60/P70, Exynos 9 9810 2017Q2
Mali-G76 489.6 Kirin 980/990, Helio G90/G90T, Exynos 9 9820 2018Q2

Adreno 是 ARM Radeon 字母换序所得,Adreon 100 系列是高通自研的,Adreon 200 和 AMD 合作的(Imageon),后来 ADM 把部门卖给了高通。

GFLOPS SoC Date
Adreon 130 2007
Adreon 200 2.1 Snapdragon S1 2008
Adreon 200 enhanced 3.2/3.9 Sanpdragon S1 2008
Adreno 203 7.8/9.4 Snapdragon S4 Play 2008
Adreno 205 7.8/8.5 Snapdragon S2 2008
...
Adreno 508 163.2 Snapdragon 630 2017
Adreno 512 217.6 Snapdragon 660 2017
Adreno 540 567 Snapdragon 835 2017
Adreno 630 727 Snapdragon 845 2018
Adreno 640 899/1037 Snapdragon 855/855+ 2019
Qualcomm Kryo 485 47.36 (2.96x16) Snapdragon 855+
Intel Haswell/Skylake 83.2 (2.6*32) Xeon E5-4627 V4
TU102 13450 Geforce RTX 2080 Ti

最后三行分别是 高通CPU、PC端 CPU,以及 Nvidia GPU 的性能,可以有个直观的对比
Android 下的 GPU 没有好用的加速框架,一般使用 Vulkan,OpenGL ES

Vulkan OpenGL ES OpenCL
Mali-T8XX 1.0 3.2 1.2
Mali-GXX 1.1 3.2 2.0
Adreno 5XX 1.0 3.2 2.0
Adreno 6XX 1.0, 1.1 3.2 2.0
Android 1.0+ X 1.0, 1.1 no official
Android 2.2+ X 2.0 no official
Android 4.3+ X 3.0 no official
Android 5.0-7 X 3.1 no official
Android 7-9 1.0 3.1 no official
Android 9+ 1.1 3.1 no official
iOS X 3.0 X
iOS 12+ X deprecated X

版本范围都是左闭右开的。比较合理的加速方案是使用 OpenGL ES,适用范围最广。

专用于 AI 的加速方案

NNAPI

自 Android 8.1 后,Android 支持 NNAPI,对高通的支持较好。详情

各个厂商有自己的加速方案,然而没有统一的SDK,这给开发适配也带来了困难。

Huawei NPU

早期华为使用寒武纪开发的 NPU,目前华为的 NPU 是 Da Vinci 系列

NPU GPU
Kirin 990 5G 2x Da Vinci Lite + 1x Da Vinci Tiny Mali-G76 MP16
Kirin 990 1x Da Vinci Lite + 1x Da Vinci Tiny Mali-G76 MP16
Kirin 980 2x Cambricon 1H Mali-G76 MP10
Kirin 970 1x Cambricon 1A Mali-G72 MP12
Kirin 810 1x Da Vinci Tiny Mali-G52 MP6
Kirin 710 Mali-G51 MP4

华为的 SDK 是 HiAI DDK:官网

Qualcomm DSP

外称 Hexagon,型号是 QDSP6

Version Date
QDSP6 V1 2006
QDSP6 V2 2007
QDSP6 V3 2009
QDSP6 V4 2010-2011
QDSP6 V5 2013 Snapdragon 800
QDSP6 V6 68X 2016-2019

比较新的 SoC 都包含 Hexagon 600 系列的 DSP了

Snapdragon CPU GPU DSP
800 Krait 400 Adreno 330 Hexagon QDSP6 V5
801/805/808/810 Hexagon 500
820/821 Hexagon 600
835 Kryo 280 (A73) Adreno 540 Hexagon 682
845 Kryo 385 (A55+A75) Adreno 630 Hexagon 685
855 Kryo 485 (A55+A76) Adreno 640 Hexagon 690
855+ Kryo 485 (A55+A76) Adreno 640 Hexagon 690

高通的 SDK 是 SNPE(现在改名了):官网

MediaTek APU

MTK 的加速器叫做 APU,资料较少没有找到 SDK 信息,目前已经做到 3.0 版本了

GPU APU
Helio P1X/P2X Mali-T8X0 / PowerVR GE8320
Helio X1X/X2X Mali-T880 / PowerVR G6200
Helio P30/P35 Mali-G71 1x P5 DSP
Helio X30 PowerVR 7XTP 1x P5 DSP
Helio P60/P65/P70 Mali-G52 / Mali-G72 2x P6 DSP
Helio M70 Mali-G77 未知 (3.0)
Helio P90 PowerVR GM9446 2x R6 DSP (2.0)
Helio G90 Mali-G76 2x R6 DSP (2.0)