> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.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>2026年6月29日</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 token） | **约 28 tok/s**（26 到 29） |
| 解码（长输出，约 800 token） |          **约 25 tok/s** |
| 首个 token 时间         |            **约 0.08 s** |
| 预填充（49 token 提示）    |       **约 1,240 tok/s** |
| 模型加载（执行器初始化）        |                   约 2 s |

说明：

* **burst 模式至关重要。** *不应用*补丁 2 时，从源码构建的版本解码约 **16 tok/s**；应用后为 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 位激活）：这就是一个约 20 亿参数的模型能装下并在 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 上的 QNN "骨架"库，通过 `ADSP_LIBRARY_PATH` 找到。因此三样东西必须一致：**主机** QNN 库、**DSP** skel 和模型内的上下文二进制文件，全部使用同一 QAIRT 版本。这就是版本步骤至关重要的原因。

### 为什么版本必须精确匹配

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

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

生成分为两个阶段。\*\*预填充（Prefill）\*\*将整个提示一次性通过 transformer，以构建 **KV 缓存**（每层的键/值张量）：它是计算密集型且高度并行的，因此按 token 计非常快。\*\*解码（Decode）\*\*随后逐个生成 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`。                                                           |

## 第一部分总结一句话

**让运行时与模型匹配**（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` 是非致命的。

## 第二部分：Qualcomm Linux 上的生产部署

Ubuntu（第一部分）是快速原型验证的途径。**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 从头开始；而是**沿用第一部分的成果**。模型、QAIRT 2.44、dispatch 到 QNN 到 FastRPC 到 Hexagon 的路径、`--backend npu`，以及第一部分的两个补丁（dispatch 目录加 burst）全部**不变**。从可用的 Ubuntu 二进制文件到可用的 QLI 运行，只有**两个功能性增量**——一个在构建时，一个在运行时——外加打包方式的变化（Yocto 镜像而非 `apt`）：

| 第一部分（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（就是第一部分中 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（紧急下载）模式**，并用 **`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)
```

注意，尽管 `machine` 显示为 `QCS8275`，**设备树称该 SoC 为 `qcs8300`**（它们是同一颗 v75 芯片；`soc_id` 675）。正是这个命名触发了我们接下来要修补的 QNN 缺陷。

### QLI 还需一个补丁：SoC 配置修复（`QnnDevice_create` 14001）

第一部分的二进制文件在 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` external 中，因此要在 `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 要\*\*固定（pin）\*\*模型约 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 token 输出 | 约 28 tok/s（26 到 29） | **约 32 tok/s（31 到 33）** |
| 解码，约 800 token 输出 |          约 25 tok/s |        **约 32.5 tok/s** |
| 首个 token 时间       |              0.08 s |              **0.06 s** |
| 预填充（49 token 提示）  |       约 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 重复版本匹配步骤，比较不同设备上的吞吐量。
