> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs-staging.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 使用 LiteRT-LM 在 IQ8 NPU 上运行 Gemma-4 E2B

> 从源码构建 LiteRT-LM,并在 Dragonwing IQ-8275 的 Hexagon NPU 上运行 Google 的 Gemma-4 E2B,从 Ubuntu 原型到 Qualcomm Linux 生产镜像。

<div style={{ marginBottom: "2rem" }}>
  <div
    style={{
fontSize: "0.72rem",
fontWeight: 700,
color: "#31017D",
letterSpacing: "1.5px",
textTransform: "uppercase",
marginBottom: "0.5rem"
}}
  >
    AI / ML
  </div>

  <div style={{ fontSize: "0.85rem", color: "#888", display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}>
    <a href="https://www.linkedin.com/in/rami-mouro/" target="_blank" rel="noopener noreferrer" style={{ color: "#888", textDecoration: "none" }}>Rami Mouro</a>
    <span>·</span>
    <span>Jun 29, 2026</span>
    <span>·</span>
    <a href="/zh/tutorial" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>← 所有文章</a>
  </div>
</div>

<hr style={{ border: "none", borderTop: "1px solid #eee", margin: "0 0 2rem" }} />

本指南在 Dragonwing **IQ-8275 (QCS8275)** 的 **Hexagon NPU** 上运行 Google 的 **Gemma-4 E2B**。您将针对模型编译时使用的*完全相同*的 Qualcomm AI 运行时,从源码构建 Google 的 **LiteRT-LM** 运行时,然后直接在 NPU 上运行公开的、Apache-2.0 许可的 `.litertlm`,解码速度约为**每秒 28 个 token**。每条命令都可以直接复制粘贴。

* **目标设备:** IQ-8275 EVK,Ubuntu 24.04 (aarch64),Hexagon **v75** NSP。
* **模型:** [`litert-community/gemma-4-E2B-it-litert-lm`](https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm),具体为 `gemma-4-E2B-it_qualcomm_qcs8275.litertlm`(3.29 GB)。该文件**仅支持 NPU**。
* **范围:文本输入,文本输出。** `.litertlm` 中也包含视觉和音频塔,但本文不会用到它们:每条命令都以文本提示驱动 `litert_lm_main`。接入图像或音频输入超出了本指南的范围。
* **上下文窗口:4096 个 token**,包括提示和生成的输出,编译进模型且无法在运行时提高。参见[上下文长度](#context-length)。

总耗时约为 15 分钟的设备设置外加一次性的源码构建(在 IQ8 上构建约 45 分钟,在更强大的 aarch64 机器上更快)。

<Note>
  **如何阅读本指南。** 每条命令都以 `ubuntu` 用户身份**在 IQ-8275 上**运行。所有工作都位于一个目录 `~/iq8-gemma` 中,并且**每个代码块都以自己的 `cd` 开头**,因此您可以按顺序将任何代码块粘贴到任何新终端中,而无需跟踪当前所在目录。粘贴前无需编辑任何内容。
</Note>

## 您将完成的工作

1. 设置 IQ8 设备并确认 FastRPC 存在。
2. 下载公开的 NPU 模型并读取它所需的确切 QAIRT 版本。
3. 针对该 QAIRT 从 LiteRT-LM 构建 `litert_lm_main`,并应用两个 Linux 启用补丁。
4. 组装运行目录并在 NPU 上运行模型。
5. (可选)从 Ubuntu 原型迁移到 Qualcomm Linux 生产镜像。

## 前提条件

在执行以下任何操作之前,开发板需要启用 Qualcomm 外设并安装 AI 运行时:**FastRPC 用户空间**(`libcdsprpc.so`)、**QNN** 库(`libqnn-dev`、`qnn-tools`、`snpe-tools`、`tensorflow-lite-qcom-apps`)、`qcom-libdmabufheap`、GStreamer QCOM 插件,以及固件更新和重启。

请先按照 IQ8 设备页面完成设置,然后再回到这里:

* [Dragonwing IQ8 首次设置](/zh/Ubuntu/devices/iq8275-evk/setup)
* [安装所需的软件包](/zh/Ubuntu/devices/iq8275-evk/Install_required_software_packages)

重启后,重新连接开发板并确认 FastRPC 存在:

```bash theme={null}
ls /dev/fastrpc-cdsp                 # must exist
ldconfig -p | grep cdsprpc           # libcdsprpc.so[.1] present
```

如果 `/dev/fastrpc-*` 不存在,则内核缺少 FastRPC 支持。到此为止:这是 BSP 或镜像问题,不是您能在用户空间修复的。

## 获取模型并找出它所需的 QAIRT 版本

不要猜测版本。创建工作目录并将公开的 NPU 模型下载到其中(3.29 GB;`-C -` 可在连接中断时续传):

```bash theme={null}
mkdir -p ~/iq8-gemma
cd ~/iq8-gemma
curl -fL -C - -o gemma-4-E2B-it_qualcomm_qcs8275.litertlm \
  "https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it_qualcomm_qcs8275.litertlm"
```

Qualcomm 的 `.litertlm` 内嵌了 **QNN 上下文二进制文件**,它们与编译时所用的 QAIRT 版本锁定,设备端运行时必须匹配。该版本没有预先公布,而 LiteRT 的源码固定的是一个*更新*的版本,因此直接从文件中读取:

```bash theme={null}
cd ~/iq8-gemma
strings gemma-4-E2B-it_qualcomm_qcs8275.litertlm | grep -Eo '2\.4[0-9]\.0\.[0-9]{6}' | sort -u
# -> 2.44.0.260225
```

此模型需要 **QAIRT 2.44.0.260225**。下载这个确切的 SDK 并解压:

```bash theme={null}
cd ~/iq8-gemma
curl -fL -o v2.44.0.260225.zip \
  "https://softwarecenter.qualcomm.com/api/download/software/sdks/Qualcomm_AI_Runtime_Community/All/2.44.0.260225/v2.44.0.260225.zip"
mkdir -p v2.44.0.260225 && unzip -q v2.44.0.260225.zip -d v2.44.0.260225
# QAIRT root is now: ~/iq8-gemma/v2.44.0.260225/qairt/2.44.0.260225
```

## 针对该 QAIRT 构建 `litert_lm_main`

### 构建工具链

`Pre Requisites ` 不会安装编译器或 Bazel,因此需要添加它们(LiteRT-LM 使用 **clang-18** 构建;生成的二进制文件链接 GNU `libstdc++`):

```bash theme={null}
sudo apt-get install -y build-essential curl git git-lfs openjdk-17-jdk python3 python3-pip \
  python3-dev unzip wget zip llvm-18 clang-18 libc++-dev libc++abi-dev

# bazelisk as `bazel` (LiteRT-LM pins its Bazel version via .bazeliskrc)
curl -L -o /tmp/bazelisk \
  https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-arm64
chmod +x /tmp/bazelisk && sudo mv /tmp/bazelisk /usr/local/bin/bazel
```

### 检出固定了您的 QAIRT 版本的 LiteRT-LM 提交

`LITERT_QAIRT_SDK` 允许 Bazel 使用本地 SDK,但工作区的 `strip_prefix` 必须与*固定*版本的目录布局匹配,因此要检出 **2.44 分支线**上的提交。最新的此类提交是 **`cbf463d97fa3`**(它将 LiteRT `d865fd82` 固定到 QAIRT 2.44.0.260225;下一个提交跳到了 2.46)。

```bash theme={null}
cd ~/iq8-gemma
git clone https://github.com/google-ai-edge/LiteRT-LM.git
cd ~/iq8-gemma/LiteRT-LM
git checkout cbf463d97fa3
git lfs install && git lfs pull          # fetches the real libGemmaModelConstraintProvider.so (22 MB ELF, not an LFS pointer)
```

### 应用两个 Linux 启用补丁

LiteRT-LM 将两处 Qualcomm 设置置于 `#if defined(__ANDROID__)` 之后,因此在桌面或嵌入式 **Linux aarch64** 上它们会被静默跳过。两个补丁都是一行代码,添加 `|| defined(__linux__)`。

**补丁 1:dispatch 库目录。** LiteRT-LM 仅在 `__ANDROID__` 或 `__EMSCRIPTEN__` 下推导查找 `libLiteRtDispatch_Qualcomm.so` 的目录。没有这个补丁,NPU 加速器永远不会注册,`DISPATCH_OP` 保持未解析状态:

```bash theme={null}
cd ~/iq8-gemma/LiteRT-LM
sed -i 's/#if defined(__ANDROID__) || defined(__EMSCRIPTEN__)$/#if defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__linux__)/' \
  runtime/util/litert_util.cc
```

**补丁 2:HTP burst 模式。** 这是每秒 16 个和约 28 个 token 之间的差异。`CreateLiteRtNpuOptions()` **仅**在 `#if defined(__ANDROID__)` 下调用 `SetHtpPerformanceMode(kBurst)` 和 `SetLogLevel(kOff)`(源码中有一条 `TODO … Bug: 498622107` 承认了这一点)。在 Linux 上这些调用被跳过,因此 dispatch 插件获得 `HtpPerformanceMode::kDefault`:DSP 永远不会自行提升到 burst,解码以约**每秒 16 个 token** 运行,同时 QNN 调试日志刷屏 stdout。为 Linux 启用该代码块:

```bash theme={null}
cd ~/iq8-gemma/LiteRT-LM
python3 - runtime/executor/llm_litert_npu_compiled_model_executor.cc <<'PY'
p = "runtime/executor/llm_litert_npu_compiled_model_executor.cc"
s = open(p).read()
anchor = ("#if defined(__ANDROID__)\n"
          "  LITERT_ASSIGN_OR_RETURN(::litert::qualcomm::QualcommOptions & qnn_opts,\n"
          "                          options.GetQualcommOptions());")
assert anchor in s, "anchor not found (different commit?)"
s = s.replace(anchor, anchor.replace("#if defined(__ANDROID__)",
                                     "#if defined(__ANDROID__) || defined(__linux__)", 1), 1)
open(p, "w").write(s)
print("burst patch applied")
PY
```

(之所以使用定向补丁而不是 `sed`,是因为该文件中还有其他不能触碰的裸 `#if defined(__ANDROID__)` 行。)

### 构建

```bash theme={null}
cd ~/iq8-gemma/LiteRT-LM
export LITERT_QAIRT_SDK="$HOME/iq8-gemma/v2.44.0.260225/"     # TRAILING SLASH is required

bazel build -c opt --repo_env=CC=clang-18 --repo_env=CXX=clang++-18 \
  //runtime/engine:litert_lm_main \
  @litert//litert/vendors/qualcomm/dispatch:dispatch_api_so
```

输出(位于 `~/iq8-gemma/LiteRT-LM/bazel-bin/` 下):

* `runtime/engine/litert_lm_main`
* `libLiteRtDispatch_Qualcomm.so`(位于 `.../qualcomm/dispatch/` 目录树下)
* `libLiteRt.so`(LiteRT 核心运行时库)

## 组装运行目录

将二进制文件、dispatch 插件、LiteRT 核心库、约束提供程序和模型收集到 `~/iq8-gemma/run` 中。`find` 调用会定位 Bazel 放置的构建输出,因此此代码块可直接使用:

```bash theme={null}
cd ~/iq8-gemma/LiteRT-LM
mkdir -p ~/iq8-gemma/run
cp -fL bazel-bin/runtime/engine/litert_lm_main ~/iq8-gemma/run/
cp -fL "$(find -L bazel-bin -name libLiteRtDispatch_Qualcomm.so | head -n1)" ~/iq8-gemma/run/
cp -fL "$(find -L bazel-bin -name libLiteRt.so | head -n1)" ~/iq8-gemma/run/
cp -fL prebuilt/linux_arm64/libGemmaModelConstraintProvider.so ~/iq8-gemma/run/
ln -sf ~/iq8-gemma/gemma-4-E2B-it_qualcomm_qcs8275.litertlm ~/iq8-gemma/run/
```

运行目录现在包含:

```text theme={null}
~/iq8-gemma/run/
├── litert_lm_main
├── libLiteRtDispatch_Qualcomm.so
├── libLiteRt.so
├── libGemmaModelConstraintProvider.so      # from prebuilt/linux_arm64/
└── gemma-4-E2B-it_qualcomm_qcs8275.litertlm # symlink to the 3.29 GB model
```

验证插件的共享库依赖全部可解析。clang-18 构建链接的是 **GNU `libstdc++`**,它已随 `build-essential` 安装,因此这条命令应该没有任何输出:

```bash theme={null}
cd ~/iq8-gemma/run
ldd libLiteRtDispatch_Qualcomm.so | grep 'not found'   # should print nothing
```

(如果您改用 `-stdlib=libc++` 构建,则需要 `sudo apt-get install -y libc++1 libc++abi1`;这里的默认构建使用 `libstdc++`,所以不需要。)

## 在 NPU 上运行

此代码块将 `LD_LIBRARY_PATH` 指向运行目录和匹配的 QAIRT 主机库,将 `ADSP_LIBRARY_PATH` 指向 **Hexagon v75** skel,然后以 root 身份运行(FastRPC 和 cDSP 需要)。`$HOME` 和 `$PWD` 路径会在 `sudo` 之前由 shell 展开,因此无需编辑即可运行:

```bash theme={null}
cd ~/iq8-gemma/run
QAIRT="$HOME/iq8-gemma/v2.44.0.260225/qairt/2.44.0.260225"
sudo -E env \
  LD_LIBRARY_PATH="$PWD:$QAIRT/lib/aarch64-oe-linux-gcc11.2:/usr/lib" \
  ADSP_LIBRARY_PATH="$QAIRT/lib/hexagon-v75/unsigned" \
  ./litert_lm_main --backend npu \
    --model_path "$PWD/gemma-4-E2B-it_qualcomm_qcs8275.litertlm" \
    --input_prompt "Explain what Qualcomm is in two sentences."
```

预期结果:

```text theme={null}
Qualcomm is a global technology company that designs, develops, and sells wireless
communication solutions and processors. They are a leading provider of technology for
smartphones, tablets, IoT, and other mobile devices, as well as for various other industries.

BenchmarkInfo:
  Time to first token: 0.08 s
  Prefill Turns (Total 1 turns):
    Prefill Turn 1: Processed 17 tokens in 39.4ms duration.
      Prefill Speed: ~1240 tokens/sec.
  Decode Turns (Total 1 turns):
    Decode Turn 1: Processed 46 tokens in ~1.6s duration.
      Decode Speed: ~28 tokens/sec.
```

(`litert_lm_main` 默认打印基准测试信息。应用补丁 2 后,您会在运行早期看到 `Set HTP performance mode: 2`,并且 QNN 调试日志会安静下来:这就是 burst 模式生效了。)

## 确认它真的运行在 NPU 上

该模型**仅支持 NPU**:它没有 CPU 图。请求 CPU 后端可以证明这一点:

```bash theme={null}
cd ~/iq8-gemma/run
LD_LIBRARY_PATH="$PWD:/usr/lib" ./litert_lm_main --backend cpu \
  --model_path "$PWD/gemma-4-E2B-it_qualcomm_qcs8275.litertlm" \
  --input_prompt "hi"
# INVALID_ARGUMENT: Main backend constraint mismatch.
#                   Model requires one of [npu] but Main backend is CPU
```

既然它拒绝 CPU,却仍能在 `--backend npu` 下生成正确的文本,那么执行就是在 Hexagon NSP 上(HTP burst 模式,`HtpPerformanceMode: 2`,可在日志中看到)。

## 性能

在 IQ-8275 上测量,公开模型,NPU 后端,应用了两个 Linux 补丁,已预热,CPU 调速器固定为 `performance`:

| 指标                |                     测量值 |
| ----------------- | ----------------------: |
| 解码(短输出,约 200 tok) | **\~28 tok/s**(26 到 29) |
| 解码(长输出,约 800 tok) |          **\~25 tok/s** |
| 首个 token 时间       |            **\~0.08 s** |
| 预填充(49-tok 提示)    |       **\~1,240 tok/s** |
| 模型加载(执行器初始化)      |                   \~2 s |

说明:

* **Burst 模式至关重要。** *不*应用补丁 2 时,从源码构建的版本解码约为**每秒 16 tok**;应用后为 25 到 29。如果您看到约 16 和刷屏的 QNN 日志,说明补丁 2 没有生效。
* **解码速度随输出增长而下降。** 每个解码步骤都要对整个 KV 缓存做注意力计算,因此 200 token 的回答平均约 28 tok/s,800 token 的约 25;最开始的 token(短上下文)最快。这不是热问题:SoC 全程温度约 43 °C。
* **固定 CPU 调速器**以获得稳定的数字(启动后首次推理时 DSP 仍会自行提升时钟):
  ```bash theme={null}
  echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
  ```
* **不同提示长度的预填充 tok/s 不可比较。** 对于单行提示,约 1,240 的数字主要由固定开销主导,因此其本身没有意义。

## 底层原理:实际发生了什么

值得理解,因为上面的每一步都对应此技术栈中的一层。

### `.litertlm` 是一个容器,不是 tflite 文件

该文件以魔术字节 `LITERTLM` 开头。它内部打包了运行时所需的一切:**SentencePiece 分词器**、模型元数据(聊天模板、EOS/EOA token、使其仅支持 NPU 的后端约束)、LiteRT 模型图,以及对 NPU 而言最重要的部分——预编译的 **QNN 上下文二进制文件**。对于此模型,它们是两个图,`qnn_partition_0` 和 `qnn_partition_1`(transformer 被拆分到两个 HTP 上下文中)。权重为 **w4a16**(4 位权重,16 位激活):这就是一个约 2B 参数的模型能装进 NSP 并快速运行的原因。

容器还携带了本**纯文本**指南永远不会触及的图:一个**视觉编码器**及其适配器(签名 `vision_280` 和 `vision_adapter_280`,将 2,520 个图像块转换为 280 个软 token)和一个**音频编码器**及其适配器(`audio_adapter`,输入 816 个 mel 帧,输出 204 个软 token),以及图像结束和音频结束嵌入(`eoi`、`eoa`)。要使用它们需要一个能组装交错的 token 和软 token 序列的多模态前端;`litert_lm_main --input_prompt` 只会向模型提供文本,因此在下面的每次运行中,视觉和音频塔都处于闲置状态。

### 执行路径,逐层分解

```text theme={null}
litert_lm_main
  └─ LiteRT-LM Engine (tokenizer, sampler, KV-cache, prefill/decode loop)
       └─ LiteRT CompiledModel  ── graph contains a custom op: DISPATCH_OP
            └─ Dispatch delegate  → libLiteRtDispatch_Qualcomm.so   (the "NPU accelerator")
                 └─ QNN HTP backend → libQnnHtp.so / libQnnSystem.so   (host side)
                      └─ FastRPC → libcdsprpc.so → /dev/fastrpc-cdsp   (the RPC transport)
                           └─ Hexagon v75 NSP runs libQnnHtpV75Skel.so (the DSP side)
```

LiteRT 图不是普通的 tflite 网络:它主要是一个单独的 **`DISPATCH_OP`**,一个作为"运行这个预编译供应商图"占位符的自定义算子。当 NPU 加速器注册时,LiteRT 加载 `libLiteRtDispatch_Qualcomm.so`,它将 QNN 上下文二进制文件交给 **QNN HTP 后端**。QNN 通过 **FastRPC**(经由 `libcdsprpc.so` 和 `/dev/fastrpc-cdsp` 到 DSP 的远程过程调用传输)与 Hexagon NSP 通信;实际的矩阵乘法在 `libQnnHtpV75Skel.so` 内部执行,这是加载**在** v75 NSP 上、通过 `ADSP_LIBRARY_PATH` 找到的 QNN "骨架"。因此三样东西必须一致:**主机** QNN 库、**DSP** skel 和模型内的上下文二进制文件,全部为同一个 QAIRT 版本。这就是版本步骤重要的原因。

### 为什么版本必须完全匹配

QNN 上下文二进制文件是针对一个 QAIRT 版本**提前编译并序列化**的:其图格式、算子包集合以及它期望的 skel ABI 都被固化在内。在不同的运行时上加载它,最好的情况是反序列化被拒绝。该版本没有预先记录,而 LiteRT 的 `main` 固定的是更新的 2.47,因此两者都不权威。文件内序列化的构建 ID(`v2.44.0.260225…`)才是权威,这就是我们用 `strings` 读取它而不是相信固定版本的原因。

### 预填充与解码,以及 KV 缓存

生成分为两个阶段。**预填充**将整个提示通过 transformer 运行一次以构建 **KV 缓存**(每层的键/值张量):它是计算密集型且高度并行的,因此每个 token 的速度很快。**解码**随后逐个生成 token,每一步都要对不断增长的 KV 缓存做注意力计算:它受内存带宽限制(每个 token 都要将 4 位权重流经 NSP),这就是解码(Ubuntu 上约 28 tok/s,QLI 上约 32)每个 token 远慢于预填充,并且是真正限制交互延迟的数字的原因。

### 上下文长度

**提示加生成的输出必须容纳在 4096 个 token 内。** 该上限编译在模型中,而非在运行时选择:QNN 上下文二进制文件及其外围的 tflite 图都以静态形状构建,因此没有任何标志可以提高它,更长的窗口意味着重新编译模型。这些图直接说明了限制:

| 张量                        | 形状                  | 含义                                   |
| ------------------------- | ------------------- | ------------------------------------ |
| `decode_kv_cache_k_*`     | `[1, 1, 256, 4096]` | 缓存有 **4096** 个槽位                     |
| `decode_mask_global`      | `[1, 1, 1, 4097]`   | 一个新 token 对 4096 个缓存位置加上自身做注意力计算     |
| `prefill_128_mask_global` | `[1, 1, 128, 4224]` | 一个 128 的预填充分块对 4096 加上自身的 128 做注意力计算 |

而且只有一个预填充签名 `prefill_128`,因此长提示是以 **128-token 分块**而不是一次性预填充的。在满 4096 时,int8 KV 缓存为 36 MiB,再加上约 1.17 GB 的权重。

<Note>
  Hugging Face 模型卡称 Gemma-4 E2B *架构*"最高可支持 32k 上下文长度"。那是架构,不是这个文件:CPU 和 GPU 构建以 2048 发布,而这个 `qualcomm_qcs8275` 构建以 4096 编译。请从您实际部署的文件中读取限制,就像从中读取 QAIRT 版本一样。
</Note>

结合下面描述的解码衰减,接近上限运行的会话会明显慢于约 28 tok/s 的标称值,因为每个解码步骤都要对更长的缓存做注意力计算。

### Burst 模式:为什么构建在打补丁前只有 16 tok/s

Hexagon NSP 运行在 **DCVS**(动态时钟和电压缩放)之下:不加干预时它以低时钟空闲,只有在持续负载下才提升。QNN 暴露了一个**性能模式**来覆盖它。`HtpPerformanceMode::kBurst` 使运行时*投票*将 DSP 提升到最高时钟并保持(外加 RPC 轮询以降低 FastRPC 延迟)。LiteRT-LM 的 NPU 执行器确实会请求 burst,但仅在 `#if defined(__ANDROID__)` 内(补丁 2)。在 Linux 上,该代码块被编译掉后,dispatch 插件报告 `Failed to parse qnn options … Null Qualcomm options`,回退到 `HtpPerformanceMode::kDefault`,DSP 以其惰性的默认时钟运行:解码约 16 tok/s。应用补丁 2 后,日志显示 `Set HTP performance mode: 2`;解码跃升至 25 到 29。这一个门控是整个技术栈中最大的性能杠杆,远大于此处的任何其他因素。

### `err 1002` 权重缓冲区消息

在图初始化期间,您会看到 `fastrpc memory map for fd: … length: 1172307968 failed … err 1002`。这是 QNN 试图通过 FastRPC 一次性将约 **1.17 GB 的持久权重缓冲区**映射到 cDSP 的 IOMMU 中。原厂 Ubuntu BSP 没有预留大型 FastRPC DMA 区域(`dmesg`:`no reserved DMA memory for FASTRPC`,CMA 仅约 164 MB),因此该*单次*映射请求被拒绝。它是**非致命的**:QNN 回退到另一条路径将权重送到 NSP,图仍在 v75 上执行(无论哪种方式模型都能生成正确的文本)。它是否损失了解码吞吐量,很难与上面的 KV 缓存长度衰减区分开;在此 BSP 上,开启 burst 模式且该消息存在时,解码为 25 到 29 tok/s。

### 为什么解码随回答变长而变慢

解码受**内存带宽限制**,并且*随序列变长每个 token 变慢*:每一步都对整个 KV 缓存做注意力计算,而缓存随每个输出的 token 增长。因此 200 token 的回答平均约 28 tok/s,而 800 token 的平均约 25。最开始的 token(短上下文)最快。启动后的首次推理还有一个小的 DCVS 上速过程;将 CPU 调速器固定为 `performance` 并预热可以消除这部分。

## 故障排除

| 症状                                                                                                       | 原因和解决方法                                                                                       |
| -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `NPU accelerator could not be loaded and registered: InvalidArgument`,然后 `DISPATCH_OP failed to prepare` | Linux dispatch 目录门控。应用 `\|\| defined(__linux__)` 补丁(补丁 1)并重新构建。                               |
| 解码卡在 **\~16 tok/s**,外加刷屏的 QNN `[INFO]` 日志和 `Null Qualcomm options`                                       | Burst 模式未设置。应用**补丁 2** 并重新构建;之后您应该看到 `Set HTP performance mode: 2` 且日志安静下来。                   |
| `Failed to create device`,或没有 FastRPC                                                                    | 缺少 FastRPC 用户空间。重新运行软件包安装,确保 `libcdsprpc.so` 存在。                                              |
| `fastrpc memory map … err 1002`,或 `Failed to map weights buffer`                                         | **非致命:模型仍会运行并生成正确的文本。** 这是 QNN 无法一次性映射 1.17 GB 权重缓冲区(原厂 BSP 上没有预留 FastRPC DMA 区域);它会回退到另一条路径。 |
| `TF_LITE_PREFILL_DECODE not found`,或 mmap 错误                                                             | `.litertlm` 被截断。重新下载;检查大小为 3.29 GB 且 sha256 与 Hugging Face 匹配。                                |
| `Main backend constraint mismatch … requires [npu]`                                                      | 符合预期:该模型仅支持 NPU。使用 `--backend npu`。                                                           |

## 第 1 部分一句话总结

**让运行时与模型匹配**(QAIRT 2.44.0.260225,从文件中读取),**在固定该版本的提交处构建 LiteRT-LM**(带两个 Linux 启用补丁:dispatch 目录加 **HTP burst 模式**),**部署匹配的 QAIRT 主机库和 Hexagon v75 skel**,然后以 `--backend npu` 运行。Burst 模式是将 16 tok/s 的构建变成约 28 tok/s 的关键;吓人的 `err 1002` 是非致命的。

## 第 2 部分:在 Qualcomm Linux 上投入生产

Ubuntu(第 1 部分)是快速原型的方式。**Qualcomm Linux (QLI) 2.0** 是您真正会在这些开发板上**交付**的基于 Yocto 的嵌入式操作系统:一个您自己构建和控制的从源码构建的镜像,内置 Qualcomm AI 技术栈。相同的模型、相同的 QAIRT 2.44、相同的 `--backend npu`,但 QLI 要求两个 Ubuntu 没有的小而具体的代价:**一个重新构建补丁**(修复 QNN `14001` 的 SoC 配置修复)和**一行运行时命令**(`ulimit -l unlimited`)。因此流程是:构建镜像、刷写、用 SoC 补丁重新构建二进制文件,然后部署和运行。

与 Ubuntu 的不同之处:

* QLI **原生附带 FastRPC 用户空间、cDSP 固件和 QNN 运行时**(没有 `apt`;它在镜像中)。您不需要运行 `Pre Requisites`。
* rootfs 是 Yocto 镜像而非 Debian,因此您需要将 LiteRT-LM 二进制文件、QAIRT 2.44 和模型**部署**到其上(scp 或数据分区),而不是 `apt install`。
* 您需要在 Linux PC 上自己构建操作系统镜像,然后刷写到开发板。

### 相对于 Ubuntu 构建的变化(QLI 增量)

您不需要为 QLI 从头开始;您可以**沿用第 1 部分的工作**。模型、QAIRT 2.44、dispatch 到 QNN 到 FastRPC 到 Hexagon 的路径、`--backend npu`,以及第 1 部分的两个补丁(dispatch 目录加 burst)都**保持不变**。从可用的 Ubuntu 二进制文件到可用的 QLI 运行,只有**两个功能性增量**,一个在构建时,一个在运行时,外加打包方式的变化(Yocto 镜像而非 `apt`):

| 第 1 部分(Ubuntu)                        | QLI 额外需要                                                                   | 为什么需要                                                                                                 |
| ------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| 2 个构建补丁:dispatch 目录加 burst            | **+1 个构建补丁:** 一个 `htp_backend.cc` SoC 守卫,然后重新构建                            | QLI 的 QNN 运行时**拒绝** Ubuntu 静默容忍的强制 SoC 配置,报 `QnnDevice_create 14001`                                  |
| root 的 mlock 默认无限制                    | 启动前执行 **`ulimit -l unlimited`**                                            | QLI 将锁定内存上限设为 8 MB;FastRPC 必须*固定*约 1.17 GB 的权重缓冲区,否则报 `Could not allocate persistent weights buffer!` |
| `Pre Requisites ` 加 `apt install` 技术栈 | 改为**烘焙和部署**:构建 Yocto 镜像,部署 QAIRT 2.44 和模型(没有 `apt`;rootfs 中已有 `libstdc++`) | QLI 是从源码构建的 Yocto rootfs,不是 Debian                                                                    |
| (无)                                   | *(可选)* 调试期间设置 `download_mode=0`                                            | 这样 cDSP 故障会重启而不是跌入需要断电重启的 EDL ramdump(`900e`)                                                         |

注意 **burst 补丁不是 QLI 特有的**:它是*任何*从源码构建的 Linux 版本都需要的,包括 Ubuntu(它是第 1 部分中 16 到 28 tok/s 的修复)。真正 QLI 特有的增量只是 **SoC 守卫重新构建**和 **`ulimit -l`** 这一行。同一个在 Ubuntu 上约 28 tok/s 的二进制文件,加上这一个额外补丁重新构建后,在 QLI 上约为 32。

### 构建主机:要求和预期

**您不在 IQ8 上构建镜像。** 您在 Linux PC("构建主机")上构建,然后将结果刷写到开发板。所有内容都通过 `kas-container` 在容器内运行,因此主机的唯一依赖是 Docker。

**构建主机前提条件:**

|        | 要求                           | 说明                                                                   |
| ------ | ---------------------------- | -------------------------------------------------------------------- |
| 操作系统   | 任何 x86\_64 Linux             | 构建在 kas/Docker 容器中运行,因此主机发行版几乎无关紧要。                                  |
| Docker | 已安装,您的用户在 `docker` 组中        | `kas-container` 使用它。(Podman 也可以。)                                    |
| 磁盘     | **约 250 GB 可用空间**            | 下载约 30 GB,sstate 缓存约 30 GB,`build/tmp` 约 100 到 150 GB。SSD/NVMe 非常重要。 |
| 内存     | **最低 32 GB,64 GB 更舒适**       | 并行编译和链接(LLVM、mesa、内核)非常消耗内存。                                         |
| CPU    | **核心越多越好**                   | Yocto 编译约 14,800 个任务;它几乎随核心数线性扩展。                                    |
| 工具     | `git`、`wget`/`curl`、`docker` | 加上 `kas-container` 脚本(一次下载,见下文)。                                     |
| 网络     | 快速、不限流量                      | 首次构建会下载数十 GB 的源码。                                                    |

**机器推荐:** 这是**多核工作站物有所值**的一项工作。**Threadripper 或 EPYC(32 到 64 核)**约 **1 到 2 小时**就能完成冷构建;同样的构建在典型的 8 核笔记本上要花**一整个下午(约 8 到 10 小时)**。更多核心意味着成比例减少的实际耗时。

**预计构建时间:**

| 场景                       | 8 核笔记本           | 16 核服务器      | 32 到 64 核 Threadripper/EPYC |
| ------------------------ | ---------------- | ------------ | --------------------------- |
| **冷构建**(空缓存,首次构建)        | \~8 到 10 小时加下载时间 | \~3 到 4 小时   | **\~1 到 2 小时**              |
| **热构建**(存在 sstate 缓存,增量) | \~20 到 40 分钟     | **\~7 分钟** ✅ | \~5 分钟                      |

<Tip>
  **约 7 分钟的热构建**数字是在一台 16 核 AMD EPYC 7763 上测量的,其 `sstate-cache` 和 `downloads` 已填充。冷构建数字是估计值:变量是核心数和下载速度,其他因素不多。在构建之间保留您的 `sstate-cache` 和 `downloads` 目录(将 `SSTATE_DIR` 和 `DL_DIR` 指向它们);这就是 7 分钟和 4 小时之间的差别。
</Tip>

### 设置构建树

安装 Docker(一次),获取 `kas-container`,并按锁定的修订版拉取 QLI 2.0 层:

```bash theme={null}
# Docker (Ubuntu host example), once
sudo apt-get update && sudo apt-get install -y docker.io git
sudo usermod -aG docker "$USER"   # log out/in for this to take effect

# kas-container (the only build tool you need on the host)
wget -qO kas-container https://raw.githubusercontent.com/siemens/kas/refs/tags/5.1/kas-container
chmod +x kas-container

# QLI 2.0 release manifest plus all meta layers, pinned to one lockfile
git clone -b qli-2.0 https://github.com/qualcomm-linux/meta-qcom-releases
./kas-container checkout meta-qcom-releases/lock.yml     # clones meta-qcom + all deps at locked commits
cp meta-qcom-releases/lock.yml meta-qcom/ci/lock.yml
```

### 构建 IQ-8275 镜像

构建目标是一个用冒号连接的 kas 配置片段列表:机器、镜像、内核、锁文件:

```bash theme={null}
export KAS_CONTAINER_ENGINE=docker
./kas-container build \
  meta-qcom/ci/iq-8275-evk.yml:\
meta-qcom/ci/qcom-distro-multimedia-image.yml:\
meta-qcom/ci/linux-qcom-6.18.yml:\
meta-qcom/ci/lock.yml
```

这会产生可刷写的包(约 927 MB):

```text theme={null}
build/tmp/deploy/images/iq-8275-evk/qcom-multimedia-image-iq-8275-evk.rootfs.qcomflash.tar.gz
```

其内部包括:firehose 编程器(`prog_firehose_ddr.elf`)、分区表、SAIL 引导加载链,以及 `rawprogram*.xml`/`patch*.xml`,即 `qdl` 需要的一切。确认 AI 技术栈在镜像中:

```bash theme={null}
grep -E 'fastrpc|qairt|hexagon-dsp-binaries|tensorflow' \
  build/tmp/deploy/images/iq-8275-evk/qcom-multimedia-image-*.manifest
# fastrpc 1.0.4 / kernel-module-fastrpc / hexagon-dsp-binaries-…-iq8275-evk-cdsp / qairt-sdk-hexagon-v75 2.43 …
```

注意镜像附带的是 **QAIRT 2.43**;我们的模型需要 **2.44**,因此(与 Ubuntu 上一样)我们在下面自行部署 2.44。

### 刷写开发板(EDL 加 qdl)

将 IQ8 置于 **EDL(emergency download,紧急下载)模式**,并使用 **`qdl`**(Linux/macOS)或 `qdl.exe`(Windows)刷写。QLI 构建指南有完整的矩阵;简要路径:

```bash theme={null}
# 1) extract the bundle
tar -xzf qcom-multimedia-image-iq-8275-evk.rootfs.qcomflash.tar.gz
cd qcom-multimedia-image-iq-8275-evk

# 2) put the board in EDL: from a running shell `sudo reboot edl`, or the boot button method
#    (host then enumerates a "Qualcomm HS-USB QDLoader 9008" device)

# 3) flash
qdl prog_firehose_ddr.elf rawprogram*.xml patch*.xml
```

断电重启退出 EDL;开发板启动 QLI 2.0。以 **`root`** 登录(此镜像上的密码为 `oelinux123`),然后确认:

```bash theme={null}
tr '\0' '\n' < /proc/device-tree/compatible   # qcom,monaco-evk / qcom,qcs8300
cat /sys/devices/soc0/machine                 # QCS8275
ls /dev/fastrpc-cdsp                           # FastRPC present (shipped in the image)
```

注意**设备树将 SoC 称为 `qcs8300`**,尽管 `machine` 读出 `QCS8275`(它们是同一个 v75 部件;`soc_id` 675)。这个命名正是触发我们下一步要修补的 QNN 缺陷的原因。

### QLI 还需一个补丁:SoC 配置修复(`QnnDevice_create` 14001)

第 1 部分的二进制文件在 Ubuntu 上可以运行,但在 QLI 上会在初始化时报 `Failed to set up QNN manager` 或 `QnnDevice_create … 14001` 而失败。根本原因:LiteRT 的 QNN 后端在 **aarch64** 上会*强制*设置一个由在线检测到的 SoC 构建的 `QnnHtpDevice_CustomConfig` SOC 选项(`htp_backend.cc`)。Ubuntu 的 QNN 运行时容忍这种强制覆盖;**QLI 的会拒绝**。(它甚至不是错误的值:SoC 表将 `QCS8275` 和 `QCS8300` 都映射到同一个枚举,v75 和 8 MB VTCM。QLI 只是不接受此路径上的显式 SOC 覆盖。)解决办法是通过编译时排除强制块,让 aarch64 自动检测。它位于 `@litert` 外部依赖中,因此要在 `bazel fetch` *之后*、`bazel build` *之前*打补丁:

```bash theme={null}
cd ~/iq8-gemma/LiteRT-LM
export LITERT_QAIRT_SDK="$HOME/iq8-gemma/v2.44.0.260225/"

# 1) materialize the @litert external
bazel fetch -c opt --repo_env=CC=clang-18 --repo_env=CXX=clang++-18 \
  //runtime/engine:litert_lm_main @litert//litert/vendors/qualcomm/dispatch:dispatch_api_so

# 2) guard the forced SOC custom-config with #if x86 (so aarch64 auto-detects)
HB=$(find "$(bazel info output_base)/external" -path '*qualcomm/core/backends/htp_backend.cc' | head -1)
python3 - "$HB" <<'PY'
import sys; p=sys.argv[1]; s=open(p).read()
a1="  std::vector<QnnDevice_CustomConfig_t> device_custom_configs;\n"
a2=("  device_custom_configs.emplace_back(\n"
    "      static_cast<QnnDevice_CustomConfig_t>(htp_device_custom_config));\n")
assert a1 in s and a2 in s, "anchors not found (different commit?)"
s=s.replace(a1, a1+"#if defined(__x86_64__) || defined(_M_X64)\n",1)
s=s.replace(a2, a2+"#endif\n",1)
open(p,"w").write(s); print("htp soc-config patch applied")
PY
chmod u+w "$HB"

# 3) rebuild WITHOUT re-fetching (keeps the patched external)
bazel build --nofetch -c opt --repo_env=CC=clang-18 --repo_env=CXX=clang++-18 \
  //runtime/engine:litert_lm_main \
  @litert//litert/vendors/qualcomm/dispatch:dispatch_api_so
```

这个二进制文件是一个**超集**:去掉强制 SOC 配置在 Ubuntu 上是无操作,因此这一个二进制文件可在两个操作系统上运行。(如果您只针对 QLI,从一开始就应用全部三个补丁进行构建。)

### 在 QLI 上部署运行时

QLI 免费提供 FastRPC 和 cDSP 固件,但它是没有 `apt` 的 Yocto rootfs。您需要部署镜像不附带的三样东西:**`litert_lm_main` 及其 `.so` 文件**(带 SoC 补丁的重新构建版本)、**QAIRT 2.44** SDK 和**模型**。无需添加 C++ 运行时:二进制文件链接 GNU `libstdc++.so.6`,rootfs 中已有。开发板有网络,因此可以直接拉取大文件:

```bash theme={null}
# on the QLI board
mkdir -p /opt/iq8-gemma/run && cd /opt/iq8-gemma

# QAIRT 2.44 (matches the model)
curl -fL -o q.zip "https://softwarecenter.qualcomm.com/api/download/software/sdks/Qualcomm_AI_Runtime_Community/All/2.44.0.260225/v2.44.0.260225.zip"
mkdir -p v2.44.0.260225 && unzip -q q.zip -d v2.44.0.260225

# the model
curl -fL -o run/gemma-4-E2B-it_qualcomm_qcs8275.litertlm \
  "https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it_qualcomm_qcs8275.litertlm"
```

然后将三个重新构建的产物 `scp` 到 `run/` 中:`litert_lm_main`、`libLiteRtDispatch_Qualcomm.so` 和 `libGemmaModelConstraintProvider.so`。

### 在 NPU 上运行(以及唯一的运行时坑:`ulimit -l`)

QLI 的默认 **`max locked memory` 为 8 MB**(`ulimit -l` 返回 8192);Ubuntu 的 root 是无限制的。FastRPC 会**固定**模型约 1.17 GB 的持久权重缓冲区,这远超 8 MB,因此 QNN 报告 `Could not allocate persistent weights buffer!` 且加载中止(首次冷启动尝试实际上导致了 cDSP 故障)。**运行前提高它**,`err 1002` 权重映射就会退化为您在 Ubuntu 上看到的完全相同的无害回退:

```bash theme={null}
cd /opt/iq8-gemma/run
ulimit -l unlimited                       # <-- the QLI fix; without it the model won't load
QAIRT=/opt/iq8-gemma/v2.44.0.260225/qairt/2.44.0.260225
LD_LIBRARY_PATH="$PWD:$QAIRT/lib/aarch64-oe-linux-gcc11.2:/usr/lib" \
ADSP_LIBRARY_PATH="$QAIRT/lib/hexagon-v75/unsigned" \
  ./litert_lm_main --backend npu \
    --model_path "$PWD/gemma-4-E2B-it_qualcomm_qcs8275.litertlm" \
    --input_prompt "Explain what Qualcomm is in two sentences."
```

<Warning>
  **调试期间的崩溃安全。** QLI 附带 `qcom_scm.download_mode=1`,因此 cDSP 或内核故障会将 SoC 转储到 EDL ramdump 模式(USB `900e`),需要*物理*断电重启才能恢复。运行 `echo 0 > /sys/module/qcom_scm/parameters/download_mode`,这样故障只会重启。(提高 `ulimit -l` 后不会有故障;这只是一个安全网。)
</Warning>

您会看到 `HtpPerformanceMode: 2`、**没有 14001**、正确的生成文本,长时间运行时:

```text theme={null}
BenchmarkInfo:
  Time to first token: 0.06 s
  Prefill Turn 1: Processed 49 tokens in 32.3ms duration.
    Prefill Speed: ~1520 tokens/sec.
  Decode  Turn 1: Processed 799 tokens in 24.6s duration.
    Decode  Speed: 32.49 tokens/sec.
```

### 原型与生产的对比:回报

同一个模型、同一个 NPU、同一个 QAIRT,在**同一块物理开发板**上测量,先是 Ubuntu 原型,然后重新刷写为 QLI 2.0(调速器 `performance`,已预热):

| 指标              |         Ubuntu(测量值) |            QLI 2.0(测量值) |
| --------------- | ------------------: | ----------------------: |
| 解码,约 200-tok 输出 | \~28 tok/s(26 到 29) | **\~32 tok/s(31 到 33)** |
| 解码,约 800-tok 输出 |          \~25 tok/s |        **\~32.5 tok/s** |
| 首个 token 时间     |              0.08 s |              **0.06 s** |
| 预填充(49-tok 提示)  |       \~1,240 tok/s |       **\~1,520 tok/s** |

原型到生产之路的点睛之笔:**QLI 2.0 不是降级,它更快也更稳定。** 与 Ubuntu 不同,它**即使在 800-token 的生成中也能保持约 32 tok/s**,而不是下滑到约 25。可能的原因正是它作为生产目标的特点:精简的单一用途镜像使 CPU 和调度器的竞争少得多,因此 NSP 的 DCVS 能保持其时钟。您放弃了 `apt` 的便利,付出了两个 QLI 特有的代价(一个 SoC 配置的重新构建补丁和一行运行时命令 `ulimit -l`),换来的是一个可复现、版本锁定、从源码构建的镜像,它运行该模型比您做原型的机器*更好*。

## 后续步骤

* 换成您自己的提示,或将 `litert_lm_main` 包装在一个小型本地 API 之后,构建设备端助手。
* 尝试为 v75 NSP 构建的其他 LiteRT-LM 模型,部署前先从文件中读取每个模型所需的 QAIRT 版本。
* 对于生产路径,从一开始就纳入 SoC 配置补丁,并将您的运行目录烘焙进 Yocto 镜像。
* 通过对其他 Dragonwing 器件的 NSP 重复版本匹配步骤,比较不同器件上的吞吐量。
