mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-31 07:27:57 +00:00
fix(qualcomm): make install_cdi resolve QNN skels across BSP and Ubuntu/QAIRT layouts
The bundled CDI descriptors assume the Qualcomm Linux BSP filesystem layout (/usr/lib/rfsa/adsp). On the equally-official Ubuntu image for RB3 Gen 2 / Rubik Pi 3, the QAIRT apt packages board-select the Hexagon skels into /usr/lib/dsp/cdsp and leave /usr/lib/rfsa/adsp as self-referential symlink loops, so install_cdi silently dropped the skel mounts and NPU offload failed silently at runtime (or a host-symlink workaround broke on reboot with an ELOOP CDI mount error). install_cdi.py now resolves each missing/broken mount from a fallback library dir and rewrites it to the real file, injects ADSP_LIBRARY_PATH so the DSP loader finds the skels, and warns loudly (with an optional --strict) when a critical QNN/HTP library cannot be found instead of failing silently. Docs updated to cover both images and a stronger verify step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e769ba7a8c
commit
8a40ea5c87
@ -2,8 +2,62 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# The CDI descriptor is authored against the Qualcomm Linux BSP filesystem layout,
|
||||||
|
# where the QNN/HTP runtime libraries and the Hexagon "skel" files live under
|
||||||
|
# /usr/lib and /usr/lib/rfsa/adsp. On other officially-supported images for the
|
||||||
|
# same boards -- notably Ubuntu for RB3 Gen 2 / Rubik Pi 3 with the QAIRT apt
|
||||||
|
# packages (qairt-libs/qairt-tools) -- the same files are board-selected into a
|
||||||
|
# different directory, and /usr/lib/rfsa/adsp may even contain self-referential
|
||||||
|
# symlink loops. When a mount's authored hostPath is missing (or a broken/looping
|
||||||
|
# symlink), look the file up by name in these fallback directories and rewrite the
|
||||||
|
# mount to the real, resolved file so NPU offload works on both layouts.
|
||||||
|
FALLBACK_LIB_DIRS = [
|
||||||
|
"/usr/lib/dsp/cdsp", # QAIRT apt packages (board-selected from /usr/share/qcom/.../dsp/cdsp)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Libraries without which HTP/NPU offload cannot work. If any of these cannot be
|
||||||
|
# resolved we warn prominently (and abort under --strict) instead of silently
|
||||||
|
# producing a CDI that starts fine but never offloads to the NPU.
|
||||||
|
CRITICAL_BASENAMES = {
|
||||||
|
"libQnnTFLiteDelegate.so",
|
||||||
|
"libQnnHtp.so",
|
||||||
|
"libQnnHtpV68Skel.so",
|
||||||
|
"libQnnHtpV73Skel.so",
|
||||||
|
"libQnnHtpV75Skel.so",
|
||||||
|
"libQnnSystem.so",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_host_path(host_path):
|
||||||
|
"""Resolve a mount's hostPath to a usable, real file/dir on this host.
|
||||||
|
|
||||||
|
Returns the original path when it already resolves (the Qualcomm Linux BSP
|
||||||
|
layout). If it is missing or a broken/looping symlink, search FALLBACK_LIB_DIRS
|
||||||
|
by basename and return the real (symlink-resolved) path, so the rewritten mount
|
||||||
|
is stable across reboots / apt reconfigures. Returns None if not found anywhere.
|
||||||
|
"""
|
||||||
|
# os.path.exists() follows symlinks and returns False for a broken link or an
|
||||||
|
# ELOOP ("too many levels of symbolic links") -- exactly the treat-as-missing
|
||||||
|
# behavior we want before falling back.
|
||||||
|
if os.path.exists(host_path):
|
||||||
|
return host_path
|
||||||
|
|
||||||
|
basename = os.path.basename(host_path)
|
||||||
|
for fallback_dir in FALLBACK_LIB_DIRS:
|
||||||
|
candidate = os.path.join(fallback_dir, basename)
|
||||||
|
if candidate != host_path and os.path.exists(candidate):
|
||||||
|
return os.path.realpath(candidate)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Install CDI on Dragonwing IoT boards")
|
parser = argparse.ArgumentParser(description="Install CDI on Dragonwing IoT boards")
|
||||||
parser.add_argument("--file", type=str, required=True, help="CDI input file")
|
parser.add_argument("--file", type=str, required=True, help="CDI input file")
|
||||||
|
parser.add_argument(
|
||||||
|
"--strict",
|
||||||
|
action="store_true",
|
||||||
|
help="Exit non-zero if any critical QNN/HTP library cannot be resolved",
|
||||||
|
)
|
||||||
args, unknown = parser.parse_known_args()
|
args, unknown = parser.parse_known_args()
|
||||||
|
|
||||||
if os.path.exists("/etc/cdi/cdi-hw-acc.json"):
|
if os.path.exists("/etc/cdi/cdi-hw-acc.json"):
|
||||||
@ -32,18 +86,64 @@ else:
|
|||||||
with open(args.file, "r") as f:
|
with open(args.file, "r") as f:
|
||||||
cdi = json.loads(f.read())
|
cdi = json.loads(f.read())
|
||||||
|
|
||||||
print("Finding missing mount paths...")
|
print("Resolving mount paths...")
|
||||||
|
relocated = []
|
||||||
|
missing = []
|
||||||
for device in cdi["devices"]:
|
for device in cdi["devices"]:
|
||||||
new_mounts = []
|
new_mounts = []
|
||||||
|
|
||||||
for mount in device["containerEdits"]["mounts"]:
|
for mount in device["containerEdits"].get("mounts", []):
|
||||||
if not os.path.exists(mount["hostPath"]):
|
resolved = resolve_host_path(mount["hostPath"])
|
||||||
print(f" Missing {mount['hostPath']}")
|
|
||||||
else:
|
if resolved is None:
|
||||||
new_mounts.append(mount)
|
missing.append(mount["hostPath"])
|
||||||
|
print(f" Missing {mount['hostPath']}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if resolved != mount["hostPath"]:
|
||||||
|
relocated.append((mount["hostPath"], resolved))
|
||||||
|
print(f" Resolved {mount['hostPath']} -> {resolved}")
|
||||||
|
# Keep containerPath as authored; only the host source moves.
|
||||||
|
mount["hostPath"] = resolved
|
||||||
|
|
||||||
|
new_mounts.append(mount)
|
||||||
|
|
||||||
device["containerEdits"]["mounts"] = new_mounts
|
device["containerEdits"]["mounts"] = new_mounts
|
||||||
|
|
||||||
|
# The QNN HTP backend (libcdsprpc) locates the Hexagon skel purely via
|
||||||
|
# ADSP_LIBRARY_PATH. Point it at the in-container directory where the skels are
|
||||||
|
# mounted so offload works regardless of the host's on-disk layout.
|
||||||
|
container_edits = device["containerEdits"]
|
||||||
|
env = container_edits.setdefault("env", [])
|
||||||
|
if not any(e.startswith("ADSP_LIBRARY_PATH=") for e in env):
|
||||||
|
env.append("ADSP_LIBRARY_PATH=/usr/lib/rfsa/adsp")
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if relocated:
|
||||||
|
print(
|
||||||
|
f"Resolved {len(relocated)} mount(s) from a fallback library directory "
|
||||||
|
"(non-BSP image layout)."
|
||||||
|
)
|
||||||
|
|
||||||
|
critical_missing = [m for m in missing if os.path.basename(m) in CRITICAL_BASENAMES]
|
||||||
|
if missing:
|
||||||
|
print(f"WARNING: {len(missing)} expected host path(s) were missing and skipped.")
|
||||||
|
if critical_missing:
|
||||||
|
print("")
|
||||||
|
print(
|
||||||
|
"WARNING: critical QNN/HTP libraries could not be found on this host -- NPU "
|
||||||
|
"offload will NOT work and detection would fall back to CPU:"
|
||||||
|
)
|
||||||
|
for path in critical_missing:
|
||||||
|
print(f" {path}")
|
||||||
|
print(
|
||||||
|
"Confirm the QAIRT runtime is installed (e.g. the qairt-libs/qairt-tools "
|
||||||
|
"packages) and that the Hexagon skels exist as real files."
|
||||||
|
)
|
||||||
|
if args.strict:
|
||||||
|
print("Aborting due to --strict.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("Writing to /etc/cdi/cdi-hw-acc.json...")
|
print("Writing to /etc/cdi/cdi-hw-acc.json...")
|
||||||
with open("/etc/cdi/cdi-hw-acc.json", "w") as f:
|
with open("/etc/cdi/cdi-hw-acc.json", "w") as f:
|
||||||
|
|||||||
@ -485,7 +485,9 @@ The Qualcomm integration uses [CDI (Container Device Interface)](https://docs.do
|
|||||||
|
|
||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
- **Qualcomm Linux BSP**: Your board must be running the official Qualcomm Linux Board Support Package image. The QNN runtime libraries (including `libQnnTFLiteDelegate.so` and `libQnnHtp.so`) and NPU device nodes (`/dev/fastrpc-cdsp`) are provided by the BSP and are bind-mounted into the container via CDI.
|
- **QNN runtime on the host**: The QNN runtime libraries (including `libQnnTFLiteDelegate.so` and `libQnnHtp.so`), the Hexagon "skel" files, and the NPU device nodes (`/dev/fastrpc-cdsp`) must be present on the host; they are bind-mounted into the container via CDI. Both officially-supported images for these boards provide them:
|
||||||
|
- **Qualcomm Linux BSP** — ships them under the layout the bundled CDI descriptors target (`/usr/lib`, `/usr/lib/rfsa/adsp`).
|
||||||
|
- **Ubuntu for RB3 Gen 2 / Rubik Pi 3** — install the `qairt-libs` and `qairt-tools` packages from the Qualcomm IoT PPA. These board-select the Hexagon skels into a different directory (`/usr/lib/dsp/cdsp`), and `/usr/lib/rfsa/adsp` may contain self-referential symlink loops; `install_cdi.py` detects this and rewrites the affected mounts to the real files automatically (see below).
|
||||||
- **Docker 25.0+**: CDI device support requires Docker 25.0 or later. Check with `docker --version`.
|
- **Docker 25.0+**: CDI device support requires Docker 25.0 or later. Check with `docker --version`.
|
||||||
- **arm64 architecture**: The Qualcomm Docker image is built for `linux/arm64` only. If building locally, you must run the build on the target board itself.
|
- **arm64 architecture**: The Qualcomm Docker image is built for `linux/arm64` only. If building locally, you must run the build on the target board itself.
|
||||||
|
|
||||||
@ -503,12 +505,13 @@ The Qualcomm integration uses [CDI (Container Device Interface)](https://docs.do
|
|||||||
sudo python3 install_cdi.py --file cdi-hw-acc-6490.json
|
sudo python3 install_cdi.py --file cdi-hw-acc-6490.json
|
||||||
```
|
```
|
||||||
|
|
||||||
This writes a CDI descriptor to `/etc/cdi/cdi-hw-acc.json`. The script will automatically skip any host paths that do not exist on your system.
|
This writes a CDI descriptor to `/etc/cdi/cdi-hw-acc.json`. For any mount whose default (BSP) path is absent or a broken symlink, the script first tries to resolve the file from the QAIRT layout (e.g. `/usr/lib/dsp/cdsp`) and rewrites the mount to the real file; paths it cannot resolve anywhere are skipped with a warning. If a **critical** QNN/HTP library is missing it prints a prominent warning — and, with `--strict`, exits non-zero — so a misconfigured host fails at install time instead of silently falling back to CPU at runtime.
|
||||||
|
|
||||||
3. Verify CDI is set up by checking the file exists:
|
3. Verify the descriptor was written and that it contains the QNN delegate and Hexagon skel mounts (their absence means the QNN runtime is not installed on the host):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ls -l /etc/cdi/cdi-hw-acc.json
|
ls -l /etc/cdi/cdi-hw-acc.json
|
||||||
|
grep -oE 'libQnn(TFLiteDelegate|HtpV[0-9]+Skel|System)\.so' /etc/cdi/cdi-hw-acc.json | sort -u
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user