Intel 独显之“初”体验

装机文里面提到了这款 Intel Arc 770 16G 的显卡,买来主要是为了试试 ML 的一些新鲜玩意。看着隔壁 N 卡不费吹灰之力就能跑起来,实在有点后悔入了这款。不过好在 debian 内核已经支持了较新的驱动,少了一步折腾驱动程序的苦恼。

真正的初

机器刚到的时候试图折腾过一次,那个时候好像文档、参考资料还比较少,折腾了一气之后发现似乎不 work,也没搞清楚为啥,不过感觉 Intel 的 github 上都是一些国内的小伙伴,提过两问题好像答复的都很快。不过还是依稀记得有几个编译时的问题

  • 某个 AVX512 相关的实现,因为我是 AMD Ryzen 的 CPU,不支持这个指令集,结果代码编译到那里说是因为 GCC 12 的 regression 导致某个 warning,变成了错误,需要改编译参数
  • 后面似乎某个 test 编译不过去,干脆取消了 test

编译是个耗时的事情,pytorch 跟 intel 的 extension 都很慢,等得慌。

重试

隔了这么几个月,觉得是不是该重新试试。这次换了 virtualenvwrapper,方便写 postactivate 脚本。大概安装的流程是这样的

  • 安装 intel oneapi,这竟然可以装 XX Gb,疯掉了吧
  • 编译 torch 三件套和 intel extension for pytorch
  • 安装下游库,比如 whisper 之类的

第一步基本按照 intel 自己的 repo 提供的指令,很快就能搞定。装好之后这些东西会放在 /opt/intel/oneapi/ 里面,一般如果想激活所有的东西的话可以

$ source /opt/intel/oneapi/setvars.sh
:: initializing oneAPI environment ...
   bash: BASH_VERSION = 5.2.15(1)-release
   args: Using "$@" for setvars.sh arguments: post_activate
:: advisor -- latest
:: ccl -- latest
:: compiler -- latest
:: dal -- latest
:: debugger -- latest
:: dev-utilities -- latest
:: dnnl -- latest
:: dpcpp-ct -- latest
:: dpl -- latest
:: ipp -- latest
:: ippcp -- latest
:: ipp -- latest
:: mkl -- latest
:: mpi -- latest
:: tbb -- latest
:: vtune -- latest
:: oneAPI environment initialized ::

但是某些情况下可以仅仅打开部分 library(每个部分有个自己的子目录,里面也有类似的脚本)。

第二步,我是参考这篇,它的核心部分(我需要 GPU)在这里,但是我做了一些改变

  • 使用系统自带的 llvm-13,apt install llvm-13,因此我知道 LLVM_ROOT=/usr/lib/llvm-13
  • 提前装好 libpng-dev 与 libjpeg62-turbo-dev,这个大约 torchvision 会用

安装的时候尽量把环境变量放在一行里面,而不要使用 export(因为这样的话根本不晓得什么地方要用什么)。下面是编译过程:pytorch 是最烦人的,看看下面的命令就晓得了

USE_LLVM=/usr/lib/llvm-13 \
LLVM_DIR=/usr/lib/llvm-13/lib/cmake/llvm \
CMAKE_PREFIX_PATH=${VIRTUAL_ENV:-"$(dirname $(command -v python))/../"} \
USE_STATIC_MKL=1 \
_GLIBCXX_USE_CXX11_ABI=1 \
USE_NUMA=0 \
USE_CUDA=0 \
CFLAGS+=" -Wno-error=maybe-uninitialized -Wno-error=uninitialized -Wno-error=restrict" \
CXXFLAGS+=" -Wno-error=maybe-uninitialized -Wno-error=uninitialized -Wno-error=restrict" \
BUILD_TEST=0 \
python setup.py bdist_wheel

vision 最简单,因为只需要跟系统的 jpeg/png 库连接。audio 似乎需要跟 mkl 连接,所以得

source /opt/intel/oneapi/mkl/latest/env/vars.sh
python setup.py bdist_wheel

最后编译 intel extension

source /opt/intel/oneapi/setvars.sh
USE_LLVM=/usr/lib/llvm-13 LLVM_DIR=/usr/lib/llvm-13/lib/cmake/llvm DNNL_GRAPH_BUILD_COMPILER_BACKEND=1 USE_AOT_DEVLIST='ats-m150' python setup.py bdist_wheel

这里 AOT 要跟显卡型号匹配。这样测试了一下(记得要设置 /opt/intel/oneapi/setvars.sh,我直接放在 postactivate 脚本里面了)

python -c "import torch; import torchvision; import torchaudio; import intel_extension_for_pytorch as ipex; print(f'torch_cxx11_abi:     {torch.compiled_with_cxx11_abi()}'); print(f'torch_version:       {torch.__version__}'); print(f'torchvision_version: {torchvision.__version__}'); print(f'torchaudio_version:  {torchaudio.__version__}'); print(f'ipex_version:        {ipex.__version__}');"
torch_cxx11_abi:     True
torch_version:       2.0.0a0+gite9ebda2
torchvision_version: 0.15.2a0+fa99a53
torchaudio_version:  2.0.2+31de77d
ipex_version:        2.0.110+gitf30a9a6

另外还可以

# in python interpreter
import pytorch
torch.xpu
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'torch' has no attribute 'xpu'

import intel_extension_for_pytorch as ipex
torch.xpu
<module 'intel_extension_for_pytorch.xpu' from '/virtual/env/lib/python3.11/site-packages/intel_extension_for_pytorch/xpu/__init__.py'>

可是我们怎么在下游的库/程序里面使用 GPU 呢?比如试试 whisper 行吗?答案是可以,但是得做点什么。比如这个 PR,可以看见通过测试是否安装了 intel_extension_for_pytorch 来激活这个 xpu 的支持。可惜这个不能自动的识别出来,看起来有点讨厌,这么几个月了也没 merge 进去

后话

也许我们可以复刻 tensorflow / jax,但是的确可能是后来者,大家已经习惯了 N 卡种种便利,Intel 任重而道远。

tensorflow

神奇的是似乎 TF 安装比想象的容易,这次直接上 pip 就搞定了

pip install tensorflow==2.13.0
pip install --upgrade intel-extension-for-tensorflow[xpu]

下面就能看见

import tensorflow as tf
# 2023-08-23 22:27:31.512594: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
# 2023-08-23 22:27:31.532775: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
# 2023-08-23 22:27:31.533009: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
# 2023-08-23 22:27:31.969931: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
# 2023-08-23 22:27:32.198645: I itex/core/wrapper/itex_cpu_wrapper.cc:52] Intel Extension for Tensorflow* AVX2 CPU backend is loaded.
# 2023-08-23 22:27:32.464141: I itex/core/wrapper/itex_gpu_wrapper.cc:35] Intel Extension for Tensorflow* GPU backend is loaded.
# 2023-08-23 22:27:32.484690: W itex/core/ops/op_init.cc:58] Op: _QuantizedMaxPool3D is already registered in Tensorflow
# 2023-08-23 22:27:32.506849: I itex/core/devices/gpu/itex_gpu_runtime.cc:129] Selected platform: Intel(R) Level-Zero
# 2023-08-23 22:27:32.507020: I itex/core/devices/gpu/itex_gpu_runtime.cc:154] number of sub-devices is zero, expose root device.
tf.config.list_physical_devices()
# [PhysicalDevice(name='/physical_device:CPU:0', device_type='CPU'), PhysicalDevice(name='/physical_device:XPU:0', device_type='XPU')]

JAX

参考这篇,似乎走到

bazel build //xla/tools/pip_package:build_pip_package
# ...
/usr/bin/ld: cannot find -lstdc++: No such file or directory
icx: error: linker command failed with exit code 1 (use -v to see invocation)
Target //xla/tools/pip_package:build_pip_package failed to build

就挂了,而且为啥没见到 Arc 在支持的列表中 😦 而且我系统里面明明有 llvm 为啥又去下载 build 还挂了,无语。我系统里面可是有 libstdc++ 的呀

ls -l /usr/lib/gcc/x86_64-linux-gnu/12/libstdc++.so
lrwxrwxrwx 1 root root 40 Jul 10 08:11 /usr/lib/gcc/x86_64-linux-gnu/12/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6
Intel 独显之“初”体验

几个 python 开发的虚拟环境

python 的标准库引入了 setuptools 之类的以后,很快 pip 的出世解决了开发人员获得依赖包的问题。感觉现在 C++ 这么长时间了还没能够获得一个类似的标准化,确实有点落伍了。同时这也引入了一种开发模式:通常系统带的 python 包都比较陈旧(系统维护人员决定的更新速度),而开发人员可能会有一些不同的要求,系统包与开发需求不匹配,因此一个比较常见的问题就是怎么替换掉系统包。

venv

这也是 python 标准库提供的一个基本的解决方案,通常我们只需要

$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install --upgrade pip

就能创建一个 venv 目录然后进入到该环境中,然后通过一组 pip 命令安装需要的包,通过 pip freeze 获得一个 requirements.txt,这样方便重新建立,比如在 docker 的部署说明文件 Dockerfile 里面可以用同样的 pip install -r requirements.txt 获得与开发环境一致的环境。

virtualenvwrapper

一个比较烦人的事情是通过 source venv/bin/activate 进入这个环境有点不方便,比如我们想设置一些环境变量之类的,就需要在在 activate 的前后做一些事情,也就是说我们需要一些 shell script 的整合,virtualenvwrapper 大概就将这一些常见的 shell magic 整理出来一套,我们通过

  • workon 进入某个环境
  • mkvirtualenv 创建一个环境

这里通常设置

  • export WORKON_HOME=$HOME/.virtualenvs 指定这些环境的位置
  • 然后需要将 virtualenvwrapper 提供的 completion script /usr/share/bash-completion/completions/virtualenvwrapper 连接到 /etc/bash_completion.d/ 目录里

conda / mamba

Conda 基本上将 virtual env 的想法发挥到极致,从某种程度上它甚至可以提供系统库,比如通常 pip 安装某些包会提示安装一些 -dev 的系统包,这是因为某些库安装需要编译连接到系统提供的某些库。conda 更进一步也提供了一些系统库、应用程序,更像是一个 dpkg + virtualenvwrapper 的合体。当然这个实现性能不一定很好,这导致后面有一些诸如 mamba 的实现,提升了一些性能,简化了 conda 的安装等等。

某些情况下特别是如果想选择不同的 python 版本,可能 conda 比起上面的两种加 pyenv 还是要方便很多的。

如何选择

官方的版本其实最容易获得,因此使用的代价偏低,但是一旦项目变得复杂可能不得不迁移到后面的选项。如果不介意在系统上安装 conda / mamba 的话可能这反而是一个一劳永逸的解决方案。

几个 python 开发的虚拟环境

使用 raspberry pi 搭建延迟摄影平台(中)

数学

我们这里先玩玩简单的数学,估计一些事情。

存储:一张 a6000 的 RAW 大约 24M,如果一张 250G 的 SSD,大约能存 10k+ 多照片,这是什么概念呢,如果我们选择 30s 一张照片,一天能拍 2880 张照片,这个容量大约是 3.7 天,如果把拍摄间隔设置为 60s,这就能拍一个星期了。而如果换一张 1T 的 SSD(现在 40-50$),这就基本可以 1m/张拍摄一个月了。如果想把盘格式化成为 EXFAT,可以参看这个步骤

Raspberry Pi 4 似乎需要 5W 左右的功率,之前电池在 46% 的状况下显示能跑 16h,差不多满电能运行 32h,可以覆盖一整天多。如果按照标 256Wh 称来看,大约 2.1。这个差异可能在我们添加了更多设备的时候会显得有问题。但是至少我们可以对付一整天的拍摄。在白天我们可以考虑使用太阳能板对其充电,差不多补充 180Wh 就够了。正好买了块 100W 的太阳能板… 似乎换一个更大型号的电池,比如 Delta 2 Max(差不多两度电,8 倍)可以一次充电撑 10+ 天,记得中间通过 AC 补电两次就行。

物理

尝试使用 USB type A 给 a6000 供电,之前给 a5100 试过,可以开机,但是我之前拿 a5100 做 video camera,并不需要拍照,而这次需要拍照,发现按下去之后死机必须换电池才能重启。据说是因为这玩意拍照的时候电流很大(4A?),一般的 USB type A 只提供了 2.4A,根本撑不起来这个拍照操作。可能唯一可行的是从 USB type C 这个 60W 输出的地方借电了 =.= 另外点烟器那边 12V 的(100W max)降下来也行。事实上因为这个过程需要电压转换,DC buck converter 本身也可以变成一个瓶颈,我这里尝试了两个版本

  • 5V 升压:之所以看中它是因为它可以直接从 USB 取电,这样就不必搞个特殊的插头,买到的版本是基于 LM2596 的(亚麻连接),号称支持 3A 电流,实际上不能搞定拍照,只能用来挡摄像头。也尝试过在输出端加一个电容,不过似乎没啥帮助
  • 12V 降压:这个一般称为 mini 360(亚麻链接),这个当然可以拿更高的电压(比如 v-mount 电池)作为输入,12V 的话不少 LED 的电源,汽车的点烟器都可以作为输入。然后调整输出到 8V 左右即可。经过测试,似乎这个搭配可以在移动电源上或者另外搭配 12V 电源实现驱动相机拍照。经过一段时间实验,感觉尽管大多数情况下没有问题,但是偶尔似乎相机会死机导致重启,添加了一个电容之后似乎也不能完全解决问题,但是至少在快门速度较慢的时候可以连续拍摄 700+ 照片

其实如果看这些电池还挺有意思的,一般锂电池电压为 3.7V,而相机使用的多为 7.4V(也有叫 8V,因为锂电充满后可到 4+V,这个感觉是两节串联的结果),而很多照明的直流电灯具使用的 15V(差不多 4 节串联):

  • Sony 相机电池的 NP-FW50(早期 A 系列相机),NP-FZ100(最新的 A 系列相机),还有专门扩展用 NP-F*50(有 9/7/5 不同的容量和体积大小的选择)都是 7-8V,因此可以使用一个假电池转接而不需要使用 buck converter
  • V-mount 电池使用的 14-15V,容量可以大很多,甚至接近 EcoFlow River 2,但是一般不提供这么丰富的接口,转接灯具不需要额外的 buck converter,但是驱动相机的话需要
  • 感觉 EcoFlow 应该加设一个 dtap 输出 14-15V DC 用来驱动灯具。现在似乎只能用逆变器,效率感觉更低

似乎在使用降压策略的时候偶尔相机拍摄会失败,这个时候需要等待一段时间。只好软件来处理这个问题了。

算法(自动、半自动拍摄程序)

我们这里先探讨一个均衡算法,它适用于使用自动或者半自动曝光拍摄程序。之所以称为均衡算法,因为本质上它通过调整一些可调节的参数,在等量曝光超平面上进行选择。我们拿下面的例子来看,如果我们以 A 档拍摄,在固定光圈的情况下,调节 ISO 和曝光时间(快门速度)会产生一对等效的组合:比如相机给出在 ISO 100 下,曝光时间为 0.4s,那么我们可以获得 (100, 0.4), (200, 0.2), (400, 0.1) … 等等组合,如果我们对每个参数有所限制,如 ISO 在 100-400 之间,我们就会得到较少的选择。这时,我们增加一个选择的策略:ISO 越低越好,我们就得到了拍摄的参数 (100, 0.4)。当然,这里如果我们还增加曝光时间的限制可能还能进一步减少候选组合,同时通过在上面增加策略,也可以导致不同的结果。

这里有一个小小的 bug,一般相机自动/半自动程序会有一个曝光时间的上限,一般是 30s,这会导致当我们的曝光量慢慢上升到 ISO100 30s 后,我们并不会根据环境调整到下一档,这时我们需要允许程序主动调节 ISO 到更高的 ISO 120 看看曝光时间会不会变化(比如仍然停留在 30s 的话说明 ISO100 是欠曝的状态)。类似的,相机也有曝光时间的下限(如 1/4000s),我们也需要做类似的处理。

如果我们考虑复杂的情况,还允许我们修改光圈,这样候选空间就会变大到 3D,但是再怎么搞一共可能也就几百个选项,对于计算机来说很容易穷尽。

那么还有一个与此相关的参数是曝光补偿,这个与上面的参数不大一样。相机的程序曝光是将测光获得的统计量归一到中性灰获得的,而补偿就是在这个中性灰曝光上进行调整。比如如果觉得晚上相机的程序曝光有点过曝,我们就需要将补偿设为负的,而且白天如果天空占据画面太多可能导致欠曝,我们就需要将补偿设为正的。当然,如果觉得相机曝光程序比较准,其实没有必要设置这个。我们是否有必要为此改进程序呢?

连接

户外的连接是一个挺烦人的事情。之前讨论过一个路由器的方法,后来想到其实 raspberry pi 本身也可以作为路由器对不对?即它自己可以把自己设置为一个热点,这样一来手机就可以使用诸如 RaspController 这类软件完成连接了。那么我们如何做到这一点呢?

我们有两个思路:

  • 我们可以让 rpi4 自带的 wifi 连接到家里面的 wifi 上,如果连不上,我们就将其转换到 Host AP 模式,变成一个路由
  • 我们可以买一个 USB wifi 适配器,这样一来就有两个 wlan 设备,我们将自己的那个配置为连接家里面的网络,而另一个配置为 host AP 模式即可。

事实上,之前老版本的内核还需要为这类 USB 无线网卡编译内核模块,最近的已经内置了,TP-link 这种便宜 N/AC 协议的网卡可以轻松识别,剩下的主要是软件方面的配置,我们需要安装

sudo apt install hostapd dnsmasq

然后配置,这里我们可以看到

# lsusb
...
Bus 001 Device 003: ID 0bda:8179 Realtek Semiconductor Corp. RTL8188EUS 802.11n Wireless Network Adapter
...
# dmesg | tail -n 20
...
[   32.379476] usb 1-1.4: new high-speed USB device number 3 using xhci_hcd
[   32.480517] usb 1-1.4: New USB device found, idVendor=0bda, idProduct=8179, bcdDevice= 0.00
[   32.480552] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[   32.480568] usb 1-1.4: Product: 802.11n NIC
[   32.480583] usb 1-1.4: Manufacturer: Realtek
[   32.480596] usb 1-1.4: SerialNumber: 00E04C0001
[   32.605393] r8188eu: module is from the staging directory, the quality is unknown, you have been warned.
[   32.634939] usbcore: registered new interface driver r8188eu
[   32.714600] r8188eu 1-1.4:1.0: Firmware Version 11, SubVersion 1, Signature 0x88e1
# lsmod | grep 8188
r8188eu               331776  0
libarc4                16384  1 r8188eu
# iwconfig
...
wlan1     unassociated  ESSID:""  Nickname:"<WIFI@REALTEK>"
          Mode:Auto  Frequency=2.412 GHz  Access Point: Not-Associated
          Sensitivity:0/0
          Retry:off   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:off
          Link Quality=0/100  Signal level=0 dBm  Noise level=0 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:0   Missed beacon:0

我们可以看到对应的 r8188eu 模块已经被自动的加载(从 firmware-realtek 包里面找到的固件),产生了一个新的 wlan1 设备。剩下的就是让 hostapd 为我们创建一个热点,不过很遗憾,这个内核自带的 realtek 模块可以上网,但不能作为 AP(what),我们还是得自己编译,下面是一些准备工作(参考这里),我们这次使用 dkms 简化编译过程(bullseye 下可行)

# echo "blacklist r8188eu" > /etc/modprobe.d/blacklist-r8188eu.conf
# apt install dkms raspberrypi-kernel-headers bc
# cd /usr/src
# git clone https://github.com/ivanovborislav/rtl8188eu.git rtl8188eu-5.13.3
# # edit rtl8188eu-5.13.3/Makefile
# # CONFIG_PLATFORM_I386_PC = n
# # CONFIG_PLATFORM_RPI_ARM64 = y
# dkms add -m rtl8188eu -v 5.13.3
# dkms autoinstall
# dkms status

搞完以后写个内核模块的配置文件

# echo "options 8188eu rtw_ips_mode=1 rtw_drv_log_level=4 rtw_power_mgnt=2 rtw_led_ctrl=1" > /etc/modprobe.d/8188eu.conf

重启后我们可以看到

# lsusb
...
Bus 001 Device 003: ID 0bda:8179 Realtek Semiconductor Corp. RTL8188EUS 802.11n Wireless Network Adapter
# # lsmod | grep 8188
8188eu               1925120  0
cfg80211              925696  2 8188eu,brcmfmac

注意这里通过之前的禁用 r8188eu 已经成功地将内核模块替换到我们编译的 8188eu 了。hostapd 配置文件参看这里。比较搞笑的是我之前用 wpa_supplicant 配置的自带的网卡,文件放在 /etc/wpa_supplicant/wpa_supplicant.conf 结果导致自动把 wlan1 也给连接上了家里面的 wifi,还以为 hostapd 不 work。解决方案也很简单

# mv /etc/wpa_supplicant/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant-wlan0.conf

重启相关的服务后,

# systemctl unmask hostapd
# systemctl enable hostapd
# systemctl start hostapd

hostapd 就可以成功的配置 wlan1,

# iwconfig
...
wlan1     IEEE 802.11bgn  ESSID:"rpi4tl"  Nickname:"<WIFI@REALTEK>"
...

并且能在其他设备上看到这个热点了,我们需要进一步配置相关的网络。首先需要在 dhcpcd 里面配置作为 wlan1 网络上面的地址(因为自己变成了 router,没人分配地址,我们需要设置一个静态的地址),修改 /etc/dhcpcd.conf,最后加入两行

interface wlan1
static ip_address=192.168.1.3/24

然后需要在我们的 dhcp 服务器里面为连接上的终端(如手机)配置地址,我们可以在 /etc/dnsmasq.conf 加入如下信息

interface=wlan1
dhcp-range=192.168.1.5,192.168.1.100,255.255.255.0,12h

这样重启相关服务后,我们就可以开始测试链接,该链接不需要设置为自动连接。到此我们就有了一个自带热点的 rpi。当时购买的这个 TP-link 型号为 TL-WN725N(150Mbps 的卡,应该妥妥的够用了),好像也没打折 8$ 一个,如果买 SBC 没有无线网卡需要扩展的话还是一个不错的选择了。另外实测 Comfast CF-WU810N 这款(3-4$)使用 RTL8166eus 这个芯片可以同样被该驱动支持。

有了这个结合 systemd 启动用户的进程(如 screen / vncserver / server)我们就可以启动 raspberry 皮之后开启一个自己的热点,通过手机、平板连接之后就可以使用终端或者 vncviewer 操纵该设备了。

使用 raspberry pi 搭建延迟摄影平台(中)