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 独显之“初”体验

视频编辑与 LLM 的一些观察

经过这两年编辑小娃的视频,发现了不少与 LLM 共通的地方,不晓得最近这股 LLM 上巨大突破什么时候会直接在视频创作里面发挥应有的作用。很多问题,其实在短视频方向应该有更容易的突破口。个人感觉 AI 在这个方面应该是辅助(作为视频编辑软件的插件而存在)而不是试图一次从头到底完全替换人在其中的作用(成本太大,迭代效率不如成熟的视频编辑高)。

视频编辑

粗选

对于我这种小虾米编辑人员,很多情况下是没有腹稿的(希望给小娃产生一段 30s 左右的一个月拍摄视频的 highlight),故事性、逻辑性、组织性的要求不是特别的严格,但是可能一个月下来积累的素材也会有几十到小几百 Gb。如果需要遍历这些片段,从中寻找有意思的片段,这是非常花费精力的。然而多数编辑软件仅仅提供了较为便捷的快速预览功能,比如按照某个顺序排列视频,然后加速播放,提供一些快捷键供用户停下来、继续,转换到正常播放速度,标记片段。

对于更多的 pro 用户,或者比如我出门旅游一次其实目的性会比前面的情况好一些,拍摄的片段更为集中在某个主题上,如果有预先的镜头设计腹稿,更多的是将多次拍摄的片段与这些设计关联起来,进行一定程度的裁切。

音乐

对于月报类型,通常会根据收集到的 highlight 选择一个合适的背景音乐。如果是短视频,通常有更为具体的时长限制(比如 30s),而选择的基本想法是赋予这个片段组合一个大致的倾向,比如欢快的或者是成长的,又或者滑稽的,这些大致的想法会帮助我们到音乐素材库里面通过搜索找到若干个可能合适的音乐或者片段,然后逐一试听,看看哪个更符合自己的要求。

对于 pro 类型的用户,他们可能有较为完善的丰富的素材库,选择的空间更大,但是很多有预设想法的脚本也会暗示或者明示音乐的调性(如与前后片段风格的一致性)。

这些素材获得之后需要进行一些加工(选择片段、音量的调节、标注节奏信息方便视频的对齐),某些音乐甚至给出多轨道(如乐器、人声分离),调节的时候可能是针对某些轨道单独进行的。

细剪

细剪的过程本质上是将视频、音频(对话、背景音乐)对齐的过程。较短的视频创作比较简单,因为使用的素材较少比较容易保持一致性,可能不涉及到复杂的时间线设计(倒叙、交叉等等)。简单的做法是将希望使用的音乐片段拉上时间线,然后将视频片段填充到音乐节奏点上,保持某个顺序即可,这个过程一般是调整视频时长让最关键的部分呈现出来,舍弃一些没有太大作用的边角。

较长的视频往往涉及到多个场景、风格的转换,需要根据预设的脚本调整音乐,将视频根据脚本和音乐对齐,甚至有资源的可能还能进一步调整音乐的构成满足更为细致的需求。

调色

这个过程往往是视觉系的工作。我基本没有太多的概念,因为我基本上仅仅做了基础调色让视觉上和拍摄的环境匹配。更为复杂的是风格上的设计。往往需要找专门的视觉专业人士给一些参考的图画,然后将视频色调调整成为匹配的样式。

动画

这个过程包括但不限于镜头的切换、扣绿幕、映射动画(比如很多超英类型、科幻类型需要对应的软件渲染等等)。对一些简单的短视频,主要是通过一些简单的滤镜产生氛围效果、修脸美图。稍微专业一点的 UP 主需要一些简单的抠图什么的展示额外的信息,实现一些串联(开门关门之类的场景切换)。

声效

Sound design 也是个很复杂的事情,拍摄中记录的一些声音与背景音乐不能很好的烘托氛围,强调信息,SFX 通过增加一些细节,比如脚步声(场景中真实存在)、钟表的滴答声(场景中并非真实的存在)、爆炸(强调)或者嗖嗖的(表示速度或者切换)声音强化观看者对场景的认知。这些需要设计者对场景有一定的理解,根据需要弥补的方向去音效库中寻找匹配设计风格的候选,然后补充到视频里面。

字幕

这个比较简单,通常是对白、旁白转换到文字。

生成式 AI 的问题

summarization

与传统文字的“小结”问题不同,我们这里可以认为是从一个指定的视频库(比如一个月的拍摄)根据一个文字描述获得粗剪的过程。当然另外一种可能是让人先选择一两个片段,让程序去找到一些相关的片段。训练数据的获得可以看成是根据以往编辑做出的选择。

prompt-driven creativity

前面提到的获得背景音乐、声效、动画这些过程多多少少涉及到一些文本作为中间媒介的搜索,人通过理解场景需求提出了文字上的需求,然后在一个专业的数据库上根据文字需求进行搜索获得一些相关的匹配。实际上类似 MusicLM 这类模型已经可以摆脱在固定的音乐库上的限制,生成一些更加符合定制需求的音乐,特别是一些较短的片段也许足够符合短视频创作的需求了。

当然更进一步是把人生成文本描述的这个过程也剥离出来,直接将视频序列作为输入,让程序提供不同可能的风格。个人感觉这个可能太困难。通常一个视频通过不同风格的加持可以获得不同的表意,所以人工的 prompt 是一个较好的切入点。视频编辑中可以直接点选添加背景音乐,要求填入一个文本描述,通过 API 获得几个 candidate,人完成试听后就可以修改 prompt 或者完成选择。声效其实跟这个非常类似,只是节选的内容不大一样。

prompt-driven programming

动画创作、调色本质上可以归结到一定程度的程序化,只是多数编辑软件并不直接将 API 在 UI 上呈现出来,比如 DaVinci Resolve 使用的 fusion、color 都使用了 node 来对底层的 API 进行了可视化的处理,这个好处大概是允许非专业编程人士(artist)可以比较容易的上手,但坏处是往往一些简单的操作最后需要大量的重复,最后还需要专业的插件编写人员(可能并没有 artist / designer 的审美)来完善。这本质上是把自然语言转换成为程序语言的过程,现代的 GPT 等模型都拥有了一定程度的能力。

收费模式

生成式 AI 往往需要大量的算力,但是对于视频编辑而言,专业的公司往往对显卡的算力也有较强的需求,因此很可能长远上他们倾向于使用自己的硬件减少成本,而不是使用云服务商提供的服务。另一种可能是插件的提供商收取一次性费用(或者免费)而让用户使用自己的云服务商账号获得结果(或者与云服务商分成),或者与云服务商一样提供一个订阅式服务或者按次使用的服务。可能在模型过于巨大复杂的今天后者才可行,也许将来随着算力的提高模型的简化,我们能够在本地调用属于自己的模型。

视频编辑与 LLM 的一些观察

Coral TPU

其实一直比较纳闷为什么 Google 没有更大程度上开源 TPU 这个生态系统,的确 GCP 是一个不错的“卖点”,但是现在 AI 的天下几乎都是 NVIDIA 的硬件一家支棱起来,这个事情就像当年的 MapReduce,Google 做好了实现、发了篇论文,工业界选择了 hadoop,到头来在 GCP 上还要兼容 hadoop 的 API。感觉现在的 TPU 捣鼓出来了这么多代,一直在告诉大家 TPU 更有效率(参看这里),但是现在事实上除了 Midjourney 这一家比较有名的 AIGC 公司在 GCP 上,OpenAI 是 NVIDIA 的客户,不少大一点的公司(Amazon、Meta 之类)也不会在 GCP 上购买 TPU 算力,TF 一度输给 PyTorch 的情况(参看这里),而其中赢面比较大的算是部分公开了 Coral TPU 作为 edge computing 的这部分,正是因为它,使得我们可以通过 tf-lite 将一些小型模型能够部署到一些工业应用的场景里:

  • 比如部署了摄像头,可以同时部署一个 edge computing 的机器用于一些比较复杂的计算:估算人流、车流
  • 比如部署了麦克风,可以部署类似的设备处理声音的识别

不过 Coral 设备并不便宜,一款 USB 接口的 accelerator 卖到 60$,还经常买不到(现在一块二手的 GTX 960 差不多也这个价格,感觉是不是一下子就觉得贵了)。这次大概等了两个月终于拿到了。另有一款多 TPU 的 PCI-E 接口的加速卡竟然卖到了 1000$,不晓得谁会是冤大头。其实吧如果早些时候拿出来卖,像学术界这种比较在意成本的很可能会跟进,TF 到后面其他的 JAX 也好也不至于输这么大的用户群体吧。当然一家之言,不足为信,历史的进程往往就是让人难以预料吧

上手

from pi3g.com

其实 Coral 设备的软件(datasheet)做的确实挺好,上手很快,比如跟着这篇文章就能很快在 Raspberry Pi 上运行起来一个识别鸟类的 MobileNet 模型,而这里可以找到一些已经训练好、并且可以部署在 Coral 设备上的模型,对于 AI 小白来说非常友好。其实对应的 python 接口也比较容易,我们这里摘一段

from PIL import Image
from pycoral.adapters import classify
from pycoral.adapters import common
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter

def main():
  # ...
  labels = read_label_file(args.labels) if args.labels else {}
  interpreter = make_interpreter(*args.model.split('@'))
  interpreter.allocate_tensors()

  # Model must be uint8 quantized
  if common.input_details(interpreter, 'dtype') != np.uint8:
    raise ValueError('Only support uint8 input type.')

  size = common.input_size(interpreter)
  image = Image.open(args.input).convert('RGB').resize(size, Image.ANTIALIAS)

  # Image data must go through two transforms before running inference:
  # 1. normalization: f = (input - mean) / std
  # 2. quantization: q = f / scale + zero_point
  # The following code combines the two steps as such:
  # q = (input - mean) / (std * scale) + zero_point
  # However, if std * scale equals 1, and mean - zero_point equals 0, the input
  # does not need any preprocessing (but in practice, even if the results are
  # very close to 1 and 0, it is probably okay to skip preprocessing for better
  # efficiency; we use 1e-5 below instead of absolute zero).
  params = common.input_details(interpreter, 'quantization_parameters')
  scale = params['scales']
  zero_point = params['zero_points']
  mean = args.input_mean
  std = args.input_std
  if abs(scale * std - 1) < 1e-5 and abs(mean - zero_point) < 1e-5:
    # Input data does not require preprocessing.
    common.set_input(interpreter, image)
  else:
    # Input data requires preprocessing
    normalized_input = (np.asarray(image) - mean) / (std * scale) + zero_point
    np.clip(normalized_input, 0, 255, out=normalized_input)
    common.set_input(interpreter, normalized_input.astype(np.uint8))

  # Run inference
  interpreter.invoke()
  classes = classify.get_classes(interpreter, args.top_k, args.threshold)

其实还蛮直接的,需要注意的是这里在 CPU 上还是需要干一点活的,比如图像的预处理(缩放、归一化),TPU 处理的图片仅有 224x224x3 的大小。简单的改写一下,估计可以作为家里面的摄像头数据处理的服务器用用了。

tf-lite 模型

比较常见的做法是将一个 TF/JAX 生成的 saved model(通常使用 proto buffer 保存相关的数据)通过 tf.lite.TFLiteConverter.from_*_model API 转换成为一个 tf-lite 模型(.tflite 后缀),这是使用 flatbuffer 存储的格式,同时还会做一些 quantization 的操作。我们后面拿一个具体例子来试试。

TF 的操作关系

不是所有的模型都可以在 TF-lite 里面工作的,其实转换本身是将一系列 tf.ops 转换到 tfl.ops,当有一些不能转换的操作时就会失败,比如 tf.float16、字符串这些数据类型,这意味着我们很多情况下需要一定的预处理(以上例子中分类的结果不能直接输出 string 而要通过一个单独的查询获得实际的类别名称大概就这个原因了)。

Coral TPU