Source snapshot of github.com/Scottcjn/npu-linux-kit@edee964
Browse files- .github/workflows/ci.yml +58 -0
- .gitignore +11 -0
- COMMERCIAL.md +42 -0
- LICENSE +661 -0
- NOTICE +16 -0
- README.md +77 -0
- amd_dev_community_showcase.md +41 -0
- amd_devcloud_credits_answer.md +30 -0
- audio/README.md +5 -0
- camera/README.md +61 -0
- camera/blur_pipeline.py +351 -0
- camera/blur_plane.py +106 -0
- camera/kernels/blur3x3.cc +74 -0
- camera/npu_background_blur.py +168 -0
- camera/npu_camera_daemon.py +124 -0
- camera/npu_camera_fps.py +47 -0
- camera/test_blur.py +32 -0
- embeddings/README.md +6 -0
- ggml-backend/README.md +5 -0
- reply_to_poizone.txt +7 -0
- requirements.txt +7 -0
.github/workflows/ci.yml
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: CI (software-only)
|
| 2 |
+
|
| 3 |
+
# Every module in this kit is an application layer on top of the open-xdna
|
| 4 |
+
# NPU stack (IRON/mlir-aie + Peano + a real XDNA1 NPU + amdxdna driver).
|
| 5 |
+
# None of that toolchain or hardware exists on a normal GitHub-hosted
|
| 6 |
+
# runner, so this workflow does NOT and CANNOT verify:
|
| 7 |
+
# - any camera/*.py effect actually running on the NPU
|
| 8 |
+
# - camera/kernels/blur3x3.cc compiling (needs the Peano AIE2
|
| 9 |
+
# cross-compiler and aie_api headers)
|
| 10 |
+
# - any of the measured FPS/latency numbers in camera/README.md
|
| 11 |
+
#
|
| 12 |
+
# What IS hardware-independent and checked here:
|
| 13 |
+
# - every Python file parses (byte-compiles) cleanly
|
| 14 |
+
# - the host-side pip requirements (opencv, onnxruntime, numpy,
|
| 15 |
+
# ml_dtypes, pyvirtualcam) install cleanly on Linux/x86_64
|
| 16 |
+
# - each camera/*.py module imports up through all of ITS OWN
|
| 17 |
+
# host-side deps and fails only at `import aie.iron` (i.e. the NPU
|
| 18 |
+
# toolchain boundary), never earlier on a host-side bug. If a
|
| 19 |
+
# module fails on anything other than a missing `aie` module, that
|
| 20 |
+
# is a real bug this CI will catch.
|
| 21 |
+
|
| 22 |
+
on:
|
| 23 |
+
push:
|
| 24 |
+
branches: [main]
|
| 25 |
+
pull_request:
|
| 26 |
+
|
| 27 |
+
jobs:
|
| 28 |
+
syntax-and-host-deps:
|
| 29 |
+
runs-on: ubuntu-latest
|
| 30 |
+
steps:
|
| 31 |
+
- uses: actions/checkout@v4
|
| 32 |
+
|
| 33 |
+
- uses: actions/setup-python@v5
|
| 34 |
+
with:
|
| 35 |
+
python-version: "3.11"
|
| 36 |
+
|
| 37 |
+
- name: Byte-compile all Python sources (syntax only, no NPU/aie needed)
|
| 38 |
+
run: python -m compileall -q camera
|
| 39 |
+
|
| 40 |
+
- name: Install host-side requirements (no NPU stack involved)
|
| 41 |
+
run: pip install -r requirements.txt
|
| 42 |
+
|
| 43 |
+
- name: Import each camera module up to the NPU-toolchain boundary
|
| 44 |
+
run: |
|
| 45 |
+
set -e
|
| 46 |
+
fail=0
|
| 47 |
+
for f in camera/*.py; do
|
| 48 |
+
echo "--- $f ---"
|
| 49 |
+
out=$(python3 "$f" 2>&1) && { echo "ran to completion (unexpected without hardware, but not a failure)"; continue; }
|
| 50 |
+
if echo "$out" | grep -q "ModuleNotFoundError: No module named 'aie'"; then
|
| 51 |
+
echo "OK: fails only at the expected NPU-toolchain (aie) boundary"
|
| 52 |
+
else
|
| 53 |
+
echo "$out"
|
| 54 |
+
echo "FAIL: $f errored before reaching the aie import boundary"
|
| 55 |
+
fail=1
|
| 56 |
+
fi
|
| 57 |
+
done
|
| 58 |
+
exit $fail
|
.gitignore
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cam_sample_*.png
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.venv/
|
| 5 |
+
blur_*.png
|
| 6 |
+
cam_sample_*.png
|
| 7 |
+
_scene.png
|
| 8 |
+
bgblur_*.png
|
| 9 |
+
*.onnx
|
| 10 |
+
_*.png
|
| 11 |
+
_*.py
|
COMMERCIAL.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Commercial & Proprietary Licensing
|
| 2 |
+
|
| 3 |
+
`open-xdna` is released under the **GNU Affero General Public License v3.0 (AGPLv3)** β
|
| 4 |
+
see [`LICENSE`](LICENSE). The AGPLv3 is a strong network-copyleft license: if you modify
|
| 5 |
+
this software or use it to provide a service over a network (including AI inference APIs,
|
| 6 |
+
SaaS pipelines, or cloud-side compute offloading), AGPLv3 Β§13 requires you to make the
|
| 7 |
+
complete corresponding source of your application available to its users under the same
|
| 8 |
+
terms.
|
| 9 |
+
|
| 10 |
+
## When you need a commercial license
|
| 11 |
+
|
| 12 |
+
The AGPLv3 is ideal for open-source and research use. You should obtain a **separate
|
| 13 |
+
commercial / proprietary license** if you want to:
|
| 14 |
+
|
| 15 |
+
- Integrate `open-xdna` (or its techniques as implemented here) into a closed-source
|
| 16 |
+
product or internal service **without** the AGPLv3 source-disclosure obligation;
|
| 17 |
+
- Offer a hosted/SaaS service built on this code without publishing your backend;
|
| 18 |
+
- Ship it inside proprietary firmware, an appliance, or an OEM product;
|
| 19 |
+
- Obtain a warranty, indemnification, or a support/SLA arrangement.
|
| 20 |
+
|
| 21 |
+
This is a standard open-core dual-licensing model: the same code is available under
|
| 22 |
+
AGPLv3 (free, copyleft) **or** a commercial license (paid, no copyleft obligations).
|
| 23 |
+
|
| 24 |
+
## Acquisition & partnership
|
| 25 |
+
|
| 26 |
+
Inquiries about commercial licensing, technical partnership, or acquisition of this
|
| 27 |
+
work and the underlying research are welcome.
|
| 28 |
+
|
| 29 |
+
**Contact β Elyan Labs**
|
| 30 |
+
Scott Boudreaux
|
| 31 |
+
Lake Charles, Louisiana, USA
|
| 32 |
+
Web: https://elyanlabs.ai
|
| 33 |
+
|
| 34 |
+
Please include your intended use, deployment scale, and whether you are seeking a
|
| 35 |
+
per-deployment license, a site license, or an acquisition discussion.
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
*Copyright Β© 2026 Scott Boudreaux / Elyan Labs. Original scripts, documentation, and
|
| 40 |
+
benchmark harnesses in this repository are the copyrightable work being licensed. AMD's
|
| 41 |
+
XDNA/XRT and IRON/mlir-aie toolchains are licensed separately (Apache-2.0 WITH
|
| 42 |
+
LLVM-exception) and are not redistributed here.*
|
LICENSE
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
GNU AFFERO GENERAL PUBLIC LICENSE
|
| 2 |
+
Version 3, 19 November 2007
|
| 3 |
+
|
| 4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
| 5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
| 6 |
+
of this license document, but changing it is not allowed.
|
| 7 |
+
|
| 8 |
+
Preamble
|
| 9 |
+
|
| 10 |
+
The GNU Affero General Public License is a free, copyleft license for
|
| 11 |
+
software and other kinds of works, specifically designed to ensure
|
| 12 |
+
cooperation with the community in the case of network server software.
|
| 13 |
+
|
| 14 |
+
The licenses for most software and other practical works are designed
|
| 15 |
+
to take away your freedom to share and change the works. By contrast,
|
| 16 |
+
our General Public Licenses are intended to guarantee your freedom to
|
| 17 |
+
share and change all versions of a program--to make sure it remains free
|
| 18 |
+
software for all its users.
|
| 19 |
+
|
| 20 |
+
When we speak of free software, we are referring to freedom, not
|
| 21 |
+
price. Our General Public Licenses are designed to make sure that you
|
| 22 |
+
have the freedom to distribute copies of free software (and charge for
|
| 23 |
+
them if you wish), that you receive source code or can get it if you
|
| 24 |
+
want it, that you can change the software or use pieces of it in new
|
| 25 |
+
free programs, and that you know you can do these things.
|
| 26 |
+
|
| 27 |
+
Developers that use our General Public Licenses protect your rights
|
| 28 |
+
with two steps: (1) assert copyright on the software, and (2) offer
|
| 29 |
+
you this License which gives you legal permission to copy, distribute
|
| 30 |
+
and/or modify the software.
|
| 31 |
+
|
| 32 |
+
A secondary benefit of defending all users' freedom is that
|
| 33 |
+
improvements made in alternate versions of the program, if they
|
| 34 |
+
receive widespread use, become available for other developers to
|
| 35 |
+
incorporate. Many developers of free software are heartened and
|
| 36 |
+
encouraged by the resulting cooperation. However, in the case of
|
| 37 |
+
software used on network servers, this result may fail to come about.
|
| 38 |
+
The GNU General Public License permits making a modified version and
|
| 39 |
+
letting the public access it on a server without ever releasing its
|
| 40 |
+
source code to the public.
|
| 41 |
+
|
| 42 |
+
The GNU Affero General Public License is designed specifically to
|
| 43 |
+
ensure that, in such cases, the modified source code becomes available
|
| 44 |
+
to the community. It requires the operator of a network server to
|
| 45 |
+
provide the source code of the modified version running there to the
|
| 46 |
+
users of that server. Therefore, public use of a modified version, on
|
| 47 |
+
a publicly accessible server, gives the public access to the source
|
| 48 |
+
code of the modified version.
|
| 49 |
+
|
| 50 |
+
An older license, called the Affero General Public License and
|
| 51 |
+
published by Affero, was designed to accomplish similar goals. This is
|
| 52 |
+
a different license, not a version of the Affero GPL, but Affero has
|
| 53 |
+
released a new version of the Affero GPL which permits relicensing under
|
| 54 |
+
this license.
|
| 55 |
+
|
| 56 |
+
The precise terms and conditions for copying, distribution and
|
| 57 |
+
modification follow.
|
| 58 |
+
|
| 59 |
+
TERMS AND CONDITIONS
|
| 60 |
+
|
| 61 |
+
0. Definitions.
|
| 62 |
+
|
| 63 |
+
"This License" refers to version 3 of the GNU Affero General Public License.
|
| 64 |
+
|
| 65 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
| 66 |
+
works, such as semiconductor masks.
|
| 67 |
+
|
| 68 |
+
"The Program" refers to any copyrightable work licensed under this
|
| 69 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
| 70 |
+
"recipients" may be individuals or organizations.
|
| 71 |
+
|
| 72 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
| 73 |
+
in a fashion requiring copyright permission, other than the making of an
|
| 74 |
+
exact copy. The resulting work is called a "modified version" of the
|
| 75 |
+
earlier work or a work "based on" the earlier work.
|
| 76 |
+
|
| 77 |
+
A "covered work" means either the unmodified Program or a work based
|
| 78 |
+
on the Program.
|
| 79 |
+
|
| 80 |
+
To "propagate" a work means to do anything with it that, without
|
| 81 |
+
permission, would make you directly or secondarily liable for
|
| 82 |
+
infringement under applicable copyright law, except executing it on a
|
| 83 |
+
computer or modifying a private copy. Propagation includes copying,
|
| 84 |
+
distribution (with or without modification), making available to the
|
| 85 |
+
public, and in some countries other activities as well.
|
| 86 |
+
|
| 87 |
+
To "convey" a work means any kind of propagation that enables other
|
| 88 |
+
parties to make or receive copies. Mere interaction with a user through
|
| 89 |
+
a computer network, with no transfer of a copy, is not conveying.
|
| 90 |
+
|
| 91 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
| 92 |
+
to the extent that it includes a convenient and prominently visible
|
| 93 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
| 94 |
+
tells the user that there is no warranty for the work (except to the
|
| 95 |
+
extent that warranties are provided), that licensees may convey the
|
| 96 |
+
work under this License, and how to view a copy of this License. If
|
| 97 |
+
the interface presents a list of user commands or options, such as a
|
| 98 |
+
menu, a prominent item in the list meets this criterion.
|
| 99 |
+
|
| 100 |
+
1. Source Code.
|
| 101 |
+
|
| 102 |
+
The "source code" for a work means the preferred form of the work
|
| 103 |
+
for making modifications to it. "Object code" means any non-source
|
| 104 |
+
form of a work.
|
| 105 |
+
|
| 106 |
+
A "Standard Interface" means an interface that either is an official
|
| 107 |
+
standard defined by a recognized standards body, or, in the case of
|
| 108 |
+
interfaces specified for a particular programming language, one that
|
| 109 |
+
is widely used among developers working in that language.
|
| 110 |
+
|
| 111 |
+
The "System Libraries" of an executable work include anything, other
|
| 112 |
+
than the work as a whole, that (a) is included in the normal form of
|
| 113 |
+
packaging a Major Component, but which is not part of that Major
|
| 114 |
+
Component, and (b) serves only to enable use of the work with that
|
| 115 |
+
Major Component, or to implement a Standard Interface for which an
|
| 116 |
+
implementation is available to the public in source code form. A
|
| 117 |
+
"Major Component", in this context, means a major essential component
|
| 118 |
+
(kernel, window system, and so on) of the specific operating system
|
| 119 |
+
(if any) on which the executable work runs, or a compiler used to
|
| 120 |
+
produce the work, or an object code interpreter used to run it.
|
| 121 |
+
|
| 122 |
+
The "Corresponding Source" for a work in object code form means all
|
| 123 |
+
the source code needed to generate, install, and (for an executable
|
| 124 |
+
work) run the object code and to modify the work, including scripts to
|
| 125 |
+
control those activities. However, it does not include the work's
|
| 126 |
+
System Libraries, or general-purpose tools or generally available free
|
| 127 |
+
programs which are used unmodified in performing those activities but
|
| 128 |
+
which are not part of the work. For example, Corresponding Source
|
| 129 |
+
includes interface definition files associated with source files for
|
| 130 |
+
the work, and the source code for shared libraries and dynamically
|
| 131 |
+
linked subprograms that the work is specifically designed to require,
|
| 132 |
+
such as by intimate data communication or control flow between those
|
| 133 |
+
subprograms and other parts of the work.
|
| 134 |
+
|
| 135 |
+
The Corresponding Source need not include anything that users
|
| 136 |
+
can regenerate automatically from other parts of the Corresponding
|
| 137 |
+
Source.
|
| 138 |
+
|
| 139 |
+
The Corresponding Source for a work in source code form is that
|
| 140 |
+
same work.
|
| 141 |
+
|
| 142 |
+
2. Basic Permissions.
|
| 143 |
+
|
| 144 |
+
All rights granted under this License are granted for the term of
|
| 145 |
+
copyright on the Program, and are irrevocable provided the stated
|
| 146 |
+
conditions are met. This License explicitly affirms your unlimited
|
| 147 |
+
permission to run the unmodified Program. The output from running a
|
| 148 |
+
covered work is covered by this License only if the output, given its
|
| 149 |
+
content, constitutes a covered work. This License acknowledges your
|
| 150 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
| 151 |
+
|
| 152 |
+
You may make, run and propagate covered works that you do not
|
| 153 |
+
convey, without conditions so long as your license otherwise remains
|
| 154 |
+
in force. You may convey covered works to others for the sole purpose
|
| 155 |
+
of having them make modifications exclusively for you, or provide you
|
| 156 |
+
with facilities for running those works, provided that you comply with
|
| 157 |
+
the terms of this License in conveying all material for which you do
|
| 158 |
+
not control copyright. Those thus making or running the covered works
|
| 159 |
+
for you must do so exclusively on your behalf, under your direction
|
| 160 |
+
and control, on terms that prohibit them from making any copies of
|
| 161 |
+
your copyrighted material outside their relationship with you.
|
| 162 |
+
|
| 163 |
+
Conveying under any other circumstances is permitted solely under
|
| 164 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
| 165 |
+
makes it unnecessary.
|
| 166 |
+
|
| 167 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
| 168 |
+
|
| 169 |
+
No covered work shall be deemed part of an effective technological
|
| 170 |
+
measure under any applicable law fulfilling obligations under article
|
| 171 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
| 172 |
+
similar laws prohibiting or restricting circumvention of such
|
| 173 |
+
measures.
|
| 174 |
+
|
| 175 |
+
When you convey a covered work, you waive any legal power to forbid
|
| 176 |
+
circumvention of technological measures to the extent such circumvention
|
| 177 |
+
is effected by exercising rights under this License with respect to
|
| 178 |
+
the covered work, and you disclaim any intention to limit operation or
|
| 179 |
+
modification of the work as a means of enforcing, against the work's
|
| 180 |
+
users, your or third parties' legal rights to forbid circumvention of
|
| 181 |
+
technological measures.
|
| 182 |
+
|
| 183 |
+
4. Conveying Verbatim Copies.
|
| 184 |
+
|
| 185 |
+
You may convey verbatim copies of the Program's source code as you
|
| 186 |
+
receive it, in any medium, provided that you conspicuously and
|
| 187 |
+
appropriately publish on each copy an appropriate copyright notice;
|
| 188 |
+
keep intact all notices stating that this License and any
|
| 189 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
| 190 |
+
keep intact all notices of the absence of any warranty; and give all
|
| 191 |
+
recipients a copy of this License along with the Program.
|
| 192 |
+
|
| 193 |
+
You may charge any price or no price for each copy that you convey,
|
| 194 |
+
and you may offer support or warranty protection for a fee.
|
| 195 |
+
|
| 196 |
+
5. Conveying Modified Source Versions.
|
| 197 |
+
|
| 198 |
+
You may convey a work based on the Program, or the modifications to
|
| 199 |
+
produce it from the Program, in the form of source code under the
|
| 200 |
+
terms of section 4, provided that you also meet all of these conditions:
|
| 201 |
+
|
| 202 |
+
a) The work must carry prominent notices stating that you modified
|
| 203 |
+
it, and giving a relevant date.
|
| 204 |
+
|
| 205 |
+
b) The work must carry prominent notices stating that it is
|
| 206 |
+
released under this License and any conditions added under section
|
| 207 |
+
7. This requirement modifies the requirement in section 4 to
|
| 208 |
+
"keep intact all notices".
|
| 209 |
+
|
| 210 |
+
c) You must license the entire work, as a whole, under this
|
| 211 |
+
License to anyone who comes into possession of a copy. This
|
| 212 |
+
License will therefore apply, along with any applicable section 7
|
| 213 |
+
additional terms, to the whole of the work, and all its parts,
|
| 214 |
+
regardless of how they are packaged. This License gives no
|
| 215 |
+
permission to license the work in any other way, but it does not
|
| 216 |
+
invalidate such permission if you have separately received it.
|
| 217 |
+
|
| 218 |
+
d) If the work has interactive user interfaces, each must display
|
| 219 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
| 220 |
+
interfaces that do not display Appropriate Legal Notices, your
|
| 221 |
+
work need not make them do so.
|
| 222 |
+
|
| 223 |
+
A compilation of a covered work with other separate and independent
|
| 224 |
+
works, which are not by their nature extensions of the covered work,
|
| 225 |
+
and which are not combined with it such as to form a larger program,
|
| 226 |
+
in or on a volume of a storage or distribution medium, is called an
|
| 227 |
+
"aggregate" if the compilation and its resulting copyright are not
|
| 228 |
+
used to limit the access or legal rights of the compilation's users
|
| 229 |
+
beyond what the individual works permit. Inclusion of a covered work
|
| 230 |
+
in an aggregate does not cause this License to apply to the other
|
| 231 |
+
parts of the aggregate.
|
| 232 |
+
|
| 233 |
+
6. Conveying Non-Source Forms.
|
| 234 |
+
|
| 235 |
+
You may convey a covered work in object code form under the terms
|
| 236 |
+
of sections 4 and 5, provided that you also convey the
|
| 237 |
+
machine-readable Corresponding Source under the terms of this License,
|
| 238 |
+
in one of these ways:
|
| 239 |
+
|
| 240 |
+
a) Convey the object code in, or embodied in, a physical product
|
| 241 |
+
(including a physical distribution medium), accompanied by the
|
| 242 |
+
Corresponding Source fixed on a durable physical medium
|
| 243 |
+
customarily used for software interchange.
|
| 244 |
+
|
| 245 |
+
b) Convey the object code in, or embodied in, a physical product
|
| 246 |
+
(including a physical distribution medium), accompanied by a
|
| 247 |
+
written offer, valid for at least three years and valid for as
|
| 248 |
+
long as you offer spare parts or customer support for that product
|
| 249 |
+
model, to give anyone who possesses the object code either (1) a
|
| 250 |
+
copy of the Corresponding Source for all the software in the
|
| 251 |
+
product that is covered by this License, on a durable physical
|
| 252 |
+
medium customarily used for software interchange, for a price no
|
| 253 |
+
more than your reasonable cost of physically performing this
|
| 254 |
+
conveying of source, or (2) access to copy the
|
| 255 |
+
Corresponding Source from a network server at no charge.
|
| 256 |
+
|
| 257 |
+
c) Convey individual copies of the object code with a copy of the
|
| 258 |
+
written offer to provide the Corresponding Source. This
|
| 259 |
+
alternative is allowed only occasionally and noncommercially, and
|
| 260 |
+
only if you received the object code with such an offer, in accord
|
| 261 |
+
with subsection 6b.
|
| 262 |
+
|
| 263 |
+
d) Convey the object code by offering access from a designated
|
| 264 |
+
place (gratis or for a charge), and offer equivalent access to the
|
| 265 |
+
Corresponding Source in the same way through the same place at no
|
| 266 |
+
further charge. You need not require recipients to copy the
|
| 267 |
+
Corresponding Source along with the object code. If the place to
|
| 268 |
+
copy the object code is a network server, the Corresponding Source
|
| 269 |
+
may be on a different server (operated by you or a third party)
|
| 270 |
+
that supports equivalent copying facilities, provided you maintain
|
| 271 |
+
clear directions next to the object code saying where to find the
|
| 272 |
+
Corresponding Source. Regardless of what server hosts the
|
| 273 |
+
Corresponding Source, you remain obligated to ensure that it is
|
| 274 |
+
available for as long as needed to satisfy these requirements.
|
| 275 |
+
|
| 276 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
| 277 |
+
you inform other peers where the object code and Corresponding
|
| 278 |
+
Source of the work are being offered to the general public at no
|
| 279 |
+
charge under subsection 6d.
|
| 280 |
+
|
| 281 |
+
A separable portion of the object code, whose source code is excluded
|
| 282 |
+
from the Corresponding Source as a System Library, need not be
|
| 283 |
+
included in conveying the object code work.
|
| 284 |
+
|
| 285 |
+
A "User Product" is either (1) a "consumer product", which means any
|
| 286 |
+
tangible personal property which is normally used for personal, family,
|
| 287 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
| 288 |
+
into a dwelling. In determining whether a product is a consumer product,
|
| 289 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
| 290 |
+
product received by a particular user, "normally used" refers to a
|
| 291 |
+
typical or common use of that class of product, regardless of the status
|
| 292 |
+
of the particular user or of the way in which the particular user
|
| 293 |
+
actually uses, or expects or is expected to use, the product. A product
|
| 294 |
+
is a consumer product regardless of whether the product has substantial
|
| 295 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
| 296 |
+
the only significant mode of use of the product.
|
| 297 |
+
|
| 298 |
+
"Installation Information" for a User Product means any methods,
|
| 299 |
+
procedures, authorization keys, or other information required to install
|
| 300 |
+
and execute modified versions of a covered work in that User Product from
|
| 301 |
+
a modified version of its Corresponding Source. The information must
|
| 302 |
+
suffice to ensure that the continued functioning of the modified object
|
| 303 |
+
code is in no case prevented or interfered with solely because
|
| 304 |
+
modification has been made.
|
| 305 |
+
|
| 306 |
+
If you convey an object code work under this section in, or with, or
|
| 307 |
+
specifically for use in, a User Product, and the conveying occurs as
|
| 308 |
+
part of a transaction in which the right of possession and use of the
|
| 309 |
+
User Product is transferred to the recipient in perpetuity or for a
|
| 310 |
+
fixed term (regardless of how the transaction is characterized), the
|
| 311 |
+
Corresponding Source conveyed under this section must be accompanied
|
| 312 |
+
by the Installation Information. But this requirement does not apply
|
| 313 |
+
if neither you nor any third party retains the ability to install
|
| 314 |
+
modified object code on the User Product (for example, the work has
|
| 315 |
+
been installed in ROM).
|
| 316 |
+
|
| 317 |
+
The requirement to provide Installation Information does not include a
|
| 318 |
+
requirement to continue to provide support service, warranty, or updates
|
| 319 |
+
for a work that has been modified or installed by the recipient, or for
|
| 320 |
+
the User Product in which it has been modified or installed. Access to a
|
| 321 |
+
network may be denied when the modification itself materially and
|
| 322 |
+
adversely affects the operation of the network or violates the rules and
|
| 323 |
+
protocols for communication across the network.
|
| 324 |
+
|
| 325 |
+
Corresponding Source conveyed, and Installation Information provided,
|
| 326 |
+
in accord with this section must be in a format that is publicly
|
| 327 |
+
documented (and with an implementation available to the public in
|
| 328 |
+
source code form), and must require no special password or key for
|
| 329 |
+
unpacking, reading or copying.
|
| 330 |
+
|
| 331 |
+
7. Additional Terms.
|
| 332 |
+
|
| 333 |
+
"Additional permissions" are terms that supplement the terms of this
|
| 334 |
+
License by making exceptions from one or more of its conditions.
|
| 335 |
+
Additional permissions that are applicable to the entire Program shall
|
| 336 |
+
be treated as though they were included in this License, to the extent
|
| 337 |
+
that they are valid under applicable law. If additional permissions
|
| 338 |
+
apply only to part of the Program, that part may be used separately
|
| 339 |
+
under those permissions, but the entire Program remains governed by
|
| 340 |
+
this License without regard to the additional permissions.
|
| 341 |
+
|
| 342 |
+
When you convey a copy of a covered work, you may at your option
|
| 343 |
+
remove any additional permissions from that copy, or from any part of
|
| 344 |
+
it. (Additional permissions may be written to require their own
|
| 345 |
+
removal in certain cases when you modify the work.) You may place
|
| 346 |
+
additional permissions on material, added by you to a covered work,
|
| 347 |
+
for which you have or can give appropriate copyright permission.
|
| 348 |
+
|
| 349 |
+
Notwithstanding any other provision of this License, for material you
|
| 350 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
| 351 |
+
that material) supplement the terms of this License with terms:
|
| 352 |
+
|
| 353 |
+
a) Disclaiming warranty or limiting liability differently from the
|
| 354 |
+
terms of sections 15 and 16 of this License; or
|
| 355 |
+
|
| 356 |
+
b) Requiring preservation of specified reasonable legal notices or
|
| 357 |
+
author attributions in that material or in the Appropriate Legal
|
| 358 |
+
Notices displayed by works containing it; or
|
| 359 |
+
|
| 360 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
| 361 |
+
requiring that modified versions of such material be marked in
|
| 362 |
+
reasonable ways as different from the original version; or
|
| 363 |
+
|
| 364 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
| 365 |
+
authors of the material; or
|
| 366 |
+
|
| 367 |
+
e) Declining to grant rights under trademark law for use of some
|
| 368 |
+
trade names, trademarks, or service marks; or
|
| 369 |
+
|
| 370 |
+
f) Requiring indemnification of licensors and authors of that
|
| 371 |
+
material by anyone who conveys the material (or modified versions of
|
| 372 |
+
it) with contractual assumptions of liability to the recipient, for
|
| 373 |
+
any liability that these contractual assumptions directly impose on
|
| 374 |
+
those licensors and authors.
|
| 375 |
+
|
| 376 |
+
All other non-permissive additional terms are considered "further
|
| 377 |
+
restrictions" within the meaning of section 10. If the Program as you
|
| 378 |
+
received it, or any part of it, contains a notice stating that it is
|
| 379 |
+
governed by this License along with a term that is a further
|
| 380 |
+
restriction, you may remove that term. If a license document contains
|
| 381 |
+
a further restriction but permits relicensing or conveying under this
|
| 382 |
+
License, you may add to a covered work material governed by the terms
|
| 383 |
+
of that license document, provided that the further restriction does
|
| 384 |
+
not survive such relicensing or conveying.
|
| 385 |
+
|
| 386 |
+
If you add terms to a covered work in accord with this section, you
|
| 387 |
+
must place, in the relevant source files, a statement of the
|
| 388 |
+
additional terms that apply to those files, or a notice indicating
|
| 389 |
+
where to find the applicable terms.
|
| 390 |
+
|
| 391 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
| 392 |
+
form of a separately written license, or stated as exceptions;
|
| 393 |
+
the above requirements apply either way.
|
| 394 |
+
|
| 395 |
+
8. Termination.
|
| 396 |
+
|
| 397 |
+
You may not propagate or modify a covered work except as expressly
|
| 398 |
+
provided under this License. Any attempt otherwise to propagate or
|
| 399 |
+
modify it is void, and will automatically terminate your rights under
|
| 400 |
+
this License (including any patent licenses granted under the third
|
| 401 |
+
paragraph of section 11).
|
| 402 |
+
|
| 403 |
+
However, if you cease all violation of this License, then your
|
| 404 |
+
license from a particular copyright holder is reinstated (a)
|
| 405 |
+
provisionally, unless and until the copyright holder explicitly and
|
| 406 |
+
finally terminates your license, and (b) permanently, if the copyright
|
| 407 |
+
holder fails to notify you of the violation by some reasonable means
|
| 408 |
+
prior to 60 days after the cessation.
|
| 409 |
+
|
| 410 |
+
Moreover, your license from a particular copyright holder is
|
| 411 |
+
reinstated permanently if the copyright holder notifies you of the
|
| 412 |
+
violation by some reasonable means, this is the first time you have
|
| 413 |
+
received notice of violation of this License (for any work) from that
|
| 414 |
+
copyright holder, and you cure the violation prior to 30 days after
|
| 415 |
+
your receipt of the notice.
|
| 416 |
+
|
| 417 |
+
Termination of your rights under this section does not terminate the
|
| 418 |
+
licenses of parties who have received copies or rights from you under
|
| 419 |
+
this License. If your rights have been terminated and not permanently
|
| 420 |
+
reinstated, you do not qualify to receive new licenses for the same
|
| 421 |
+
material under section 10.
|
| 422 |
+
|
| 423 |
+
9. Acceptance Not Required for Having Copies.
|
| 424 |
+
|
| 425 |
+
You are not required to accept this License in order to receive or
|
| 426 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
| 427 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
| 428 |
+
to receive a copy likewise does not require acceptance. However,
|
| 429 |
+
nothing other than this License grants you permission to propagate or
|
| 430 |
+
modify any covered work. These actions infringe copyright if you do
|
| 431 |
+
not accept this License. Therefore, by modifying or propagating a
|
| 432 |
+
covered work, you indicate your acceptance of this License to do so.
|
| 433 |
+
|
| 434 |
+
10. Automatic Licensing of Downstream Recipients.
|
| 435 |
+
|
| 436 |
+
Each time you convey a covered work, the recipient automatically
|
| 437 |
+
receives a license from the original licensors, to run, modify and
|
| 438 |
+
propagate that work, subject to this License. You are not responsible
|
| 439 |
+
for enforcing compliance by third parties with this License.
|
| 440 |
+
|
| 441 |
+
An "entity transaction" is a transaction transferring control of an
|
| 442 |
+
organization, or substantially all assets of one, or subdividing an
|
| 443 |
+
organization, or merging organizations. If propagation of a covered
|
| 444 |
+
work results from an entity transaction, each party to that
|
| 445 |
+
transaction who receives a copy of the work also receives whatever
|
| 446 |
+
licenses to the work the party's predecessor in interest had or could
|
| 447 |
+
give under the previous paragraph, plus a right to possession of the
|
| 448 |
+
Corresponding Source of the work from the predecessor in interest, if
|
| 449 |
+
the predecessor has it or can get it with reasonable efforts.
|
| 450 |
+
|
| 451 |
+
You may not impose any further restrictions on the exercise of the
|
| 452 |
+
rights granted or affirmed under this License. For example, you may
|
| 453 |
+
not impose a license fee, royalty, or other charge for exercise of
|
| 454 |
+
rights granted under this License, and you may not initiate litigation
|
| 455 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
| 456 |
+
any patent claim is infringed by making, using, selling, offering for
|
| 457 |
+
sale, or importing the Program or any portion of it.
|
| 458 |
+
|
| 459 |
+
11. Patents.
|
| 460 |
+
|
| 461 |
+
A "contributor" is a copyright holder who authorizes use under this
|
| 462 |
+
License of the Program or a work on which the Program is based. The
|
| 463 |
+
work thus licensed is called the contributor's "contributor version".
|
| 464 |
+
|
| 465 |
+
A contributor's "essential patent claims" are all patent claims
|
| 466 |
+
owned or controlled by the contributor, whether already acquired or
|
| 467 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
| 468 |
+
by this License, of making, using, or selling its contributor version,
|
| 469 |
+
but do not include claims that would be infringed only as a
|
| 470 |
+
consequence of further modification of the contributor version. For
|
| 471 |
+
purposes of this definition, "control" includes the right to grant
|
| 472 |
+
patent sublicenses in a manner consistent with the requirements of
|
| 473 |
+
this License.
|
| 474 |
+
|
| 475 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
| 476 |
+
patent license under the contributor's essential patent claims, to
|
| 477 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
| 478 |
+
propagate the contents of its contributor version.
|
| 479 |
+
|
| 480 |
+
In the following three paragraphs, a "patent license" is any express
|
| 481 |
+
agreement or commitment, however denominated, not to enforce a patent
|
| 482 |
+
(such as an express permission to practice a patent or covenant not to
|
| 483 |
+
sue for patent infringement). To "grant" such a patent license to a
|
| 484 |
+
party means to make such an agreement or commitment not to enforce a
|
| 485 |
+
patent against the party.
|
| 486 |
+
|
| 487 |
+
If you convey a covered work, knowingly relying on a patent license,
|
| 488 |
+
and the Corresponding Source of the work is not available for anyone
|
| 489 |
+
to copy, free of charge and under the terms of this License, through a
|
| 490 |
+
publicly available network server or other readily accessible means,
|
| 491 |
+
then you must either (1) cause the Corresponding Source to be so
|
| 492 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
| 493 |
+
patent license for this particular work, or (3) arrange, in a manner
|
| 494 |
+
consistent with the requirements of this License, to extend the patent
|
| 495 |
+
license to downstream recipients. "Knowingly relying" means you have
|
| 496 |
+
actual knowledge that, but for the patent license, your conveying the
|
| 497 |
+
covered work in a country, or your recipient's use of the covered work
|
| 498 |
+
in a country, would infringe one or more identifiable patents in that
|
| 499 |
+
country that you have reason to believe are valid.
|
| 500 |
+
|
| 501 |
+
If, pursuant to or in connection with a single transaction or
|
| 502 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
| 503 |
+
covered work, and grant a patent license to some of the parties
|
| 504 |
+
receiving the covered work authorizing them to use, propagate, modify
|
| 505 |
+
or convey a specific copy of the covered work, then the patent license
|
| 506 |
+
you grant is automatically extended to all recipients of the covered
|
| 507 |
+
work and works based on it.
|
| 508 |
+
|
| 509 |
+
A patent license is "discriminatory" if it does not include within
|
| 510 |
+
the scope of its coverage, prohibits the exercise of, or is
|
| 511 |
+
conditioned on the non-exercise of one or more of the rights that are
|
| 512 |
+
specifically granted under this License. You may not convey a covered
|
| 513 |
+
work if you are a party to an arrangement with a third party that is
|
| 514 |
+
in the business of distributing software, under which you make payment
|
| 515 |
+
to the third party based on the extent of your activity of conveying
|
| 516 |
+
the work, and under which the third party grants, to any of the
|
| 517 |
+
parties who would receive the covered work from you, a discriminatory
|
| 518 |
+
patent license (a) in connection with copies of the covered work
|
| 519 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
| 520 |
+
for and in connection with specific products or compilations that
|
| 521 |
+
contain the covered work, unless you entered into that arrangement,
|
| 522 |
+
or that patent license was granted, prior to 28 March 2007.
|
| 523 |
+
|
| 524 |
+
Nothing in this License shall be construed as excluding or limiting
|
| 525 |
+
any implied license or other defenses to infringement that may
|
| 526 |
+
otherwise be available to you under applicable patent law.
|
| 527 |
+
|
| 528 |
+
12. No Surrender of Others' Freedom.
|
| 529 |
+
|
| 530 |
+
If conditions are imposed on you (whether by court order, agreement or
|
| 531 |
+
otherwise) that contradict the conditions of this License, they do not
|
| 532 |
+
excuse you from the conditions of this License. If you cannot convey a
|
| 533 |
+
covered work so as to satisfy simultaneously your obligations under this
|
| 534 |
+
License and any other pertinent obligations, then as a consequence you may
|
| 535 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
| 536 |
+
to collect a royalty for further conveying from those to whom you convey
|
| 537 |
+
the Program, the only way you could satisfy both those terms and this
|
| 538 |
+
License would be to refrain entirely from conveying the Program.
|
| 539 |
+
|
| 540 |
+
13. Remote Network Interaction; Use with the GNU General Public License.
|
| 541 |
+
|
| 542 |
+
Notwithstanding any other provision of this License, if you modify the
|
| 543 |
+
Program, your modified version must prominently offer all users
|
| 544 |
+
interacting with it remotely through a computer network (if your version
|
| 545 |
+
supports such interaction) an opportunity to receive the Corresponding
|
| 546 |
+
Source of your version by providing access to the Corresponding Source
|
| 547 |
+
from a network server at no charge, through some standard or customary
|
| 548 |
+
means of facilitating copying of software. This Corresponding Source
|
| 549 |
+
shall include the Corresponding Source for any work covered by version 3
|
| 550 |
+
of the GNU General Public License that is incorporated pursuant to the
|
| 551 |
+
following paragraph.
|
| 552 |
+
|
| 553 |
+
Notwithstanding any other provision of this License, you have
|
| 554 |
+
permission to link or combine any covered work with a work licensed
|
| 555 |
+
under version 3 of the GNU General Public License into a single
|
| 556 |
+
combined work, and to convey the resulting work. The terms of this
|
| 557 |
+
License will continue to apply to the part which is the covered work,
|
| 558 |
+
but the work with which it is combined will remain governed by version
|
| 559 |
+
3 of the GNU General Public License.
|
| 560 |
+
|
| 561 |
+
14. Revised Versions of this License.
|
| 562 |
+
|
| 563 |
+
The Free Software Foundation may publish revised and/or new versions of
|
| 564 |
+
the GNU Affero General Public License from time to time. Such new versions
|
| 565 |
+
will be similar in spirit to the present version, but may differ in detail to
|
| 566 |
+
address new problems or concerns.
|
| 567 |
+
|
| 568 |
+
Each version is given a distinguishing version number. If the
|
| 569 |
+
Program specifies that a certain numbered version of the GNU Affero General
|
| 570 |
+
Public License "or any later version" applies to it, you have the
|
| 571 |
+
option of following the terms and conditions either of that numbered
|
| 572 |
+
version or of any later version published by the Free Software
|
| 573 |
+
Foundation. If the Program does not specify a version number of the
|
| 574 |
+
GNU Affero General Public License, you may choose any version ever published
|
| 575 |
+
by the Free Software Foundation.
|
| 576 |
+
|
| 577 |
+
If the Program specifies that a proxy can decide which future
|
| 578 |
+
versions of the GNU Affero General Public License can be used, that proxy's
|
| 579 |
+
public statement of acceptance of a version permanently authorizes you
|
| 580 |
+
to choose that version for the Program.
|
| 581 |
+
|
| 582 |
+
Later license versions may give you additional or different
|
| 583 |
+
permissions. However, no additional obligations are imposed on any
|
| 584 |
+
author or copyright holder as a result of your choosing to follow a
|
| 585 |
+
later version.
|
| 586 |
+
|
| 587 |
+
15. Disclaimer of Warranty.
|
| 588 |
+
|
| 589 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
| 590 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
| 591 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
| 592 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
| 593 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
| 594 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
| 595 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
| 596 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
| 597 |
+
|
| 598 |
+
16. Limitation of Liability.
|
| 599 |
+
|
| 600 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
| 601 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
| 602 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
| 603 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
| 604 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
| 605 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
| 606 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
| 607 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
| 608 |
+
SUCH DAMAGES.
|
| 609 |
+
|
| 610 |
+
17. Interpretation of Sections 15 and 16.
|
| 611 |
+
|
| 612 |
+
If the disclaimer of warranty and limitation of liability provided
|
| 613 |
+
above cannot be given local legal effect according to their terms,
|
| 614 |
+
reviewing courts shall apply local law that most closely approximates
|
| 615 |
+
an absolute waiver of all civil liability in connection with the
|
| 616 |
+
Program, unless a warranty or assumption of liability accompanies a
|
| 617 |
+
copy of the Program in return for a fee.
|
| 618 |
+
|
| 619 |
+
END OF TERMS AND CONDITIONS
|
| 620 |
+
|
| 621 |
+
How to Apply These Terms to Your New Programs
|
| 622 |
+
|
| 623 |
+
If you develop a new program, and you want it to be of the greatest
|
| 624 |
+
possible use to the public, the best way to achieve this is to make it
|
| 625 |
+
free software which everyone can redistribute and change under these terms.
|
| 626 |
+
|
| 627 |
+
To do so, attach the following notices to the program. It is safest
|
| 628 |
+
to attach them to the start of each source file to most effectively
|
| 629 |
+
state the exclusion of warranty; and each file should have at least
|
| 630 |
+
the "copyright" line and a pointer to where the full notice is found.
|
| 631 |
+
|
| 632 |
+
<one line to give the program's name and a brief idea of what it does.>
|
| 633 |
+
Copyright (C) <year> <name of author>
|
| 634 |
+
|
| 635 |
+
This program is free software: you can redistribute it and/or modify
|
| 636 |
+
it under the terms of the GNU Affero General Public License as published by
|
| 637 |
+
the Free Software Foundation, either version 3 of the License, or
|
| 638 |
+
(at your option) any later version.
|
| 639 |
+
|
| 640 |
+
This program is distributed in the hope that it will be useful,
|
| 641 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 642 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 643 |
+
GNU Affero General Public License for more details.
|
| 644 |
+
|
| 645 |
+
You should have received a copy of the GNU Affero General Public License
|
| 646 |
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
| 647 |
+
|
| 648 |
+
Also add information on how to contact you by electronic and paper mail.
|
| 649 |
+
|
| 650 |
+
If your software can interact with users remotely through a computer
|
| 651 |
+
network, you should also make sure that it provides a way for users to
|
| 652 |
+
get its source. For example, if your program is a web application, its
|
| 653 |
+
interface could display a "Source" link that leads users to an archive
|
| 654 |
+
of the code. There are many ways you could offer source, and different
|
| 655 |
+
solutions will be better for different programs; see section 13 for the
|
| 656 |
+
specific requirements.
|
| 657 |
+
|
| 658 |
+
You should also get your employer (if you work as a programmer) or school,
|
| 659 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
| 660 |
+
For more information on this, and how to apply and follow the GNU AGPL, see
|
| 661 |
+
<https://www.gnu.org/licenses/>.
|
NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
npu-linux-kit
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Scott Boudreaux / Elyan Labs LLC
|
| 4 |
+
|
| 5 |
+
This program is free software: you can redistribute it and/or modify it
|
| 6 |
+
under the terms of the GNU Affero General Public License as published by the Free
|
| 7 |
+
Software Foundation, either version 3 of the License, or (at your option)
|
| 8 |
+
any later version. The full text is in LICENSE.
|
| 9 |
+
|
| 10 |
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
| 11 |
+
|
| 12 |
+
Commercial licensing
|
| 13 |
+
--------------------
|
| 14 |
+
The copyleft terms above are not the only option. If they do not suit your
|
| 15 |
+
use, a separate commercial licence is available from the copyright holder.
|
| 16 |
+
Contact: scott@elyanlabs.ai
|
README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: agpl-3.0
|
| 3 |
+
tags:
|
| 4 |
+
- elyan-labs
|
| 5 |
+
- llm-inference
|
| 6 |
+
- npu
|
| 7 |
+
- linux
|
| 8 |
+
- edge-inference
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
> Source snapshot of [github.com/Scottcjn/npu-linux-kit](https://github.com/Scottcjn/npu-linux-kit), also on [GitLab](https://gitlab.com/elyan-labs/npu-linux-kit) with full history. Code only: nothing here runs on Hugging Face infrastructure.
|
| 12 |
+
|
| 13 |
+
<div align="center">
|
| 14 |
+
|
| 15 |
+
# π§° npu-linux-kit
|
| 16 |
+
|
| 17 |
+
**Practical applications for the AMD XDNA NPU on Linux** β the things the NPU is *actually* good at,
|
| 18 |
+
in one kit. Built on the [open-xdna](https://github.com/Scottcjn/open-xdna) gen-1 (XDNA1 / Phoenix)
|
| 19 |
+
bring-up.
|
| 20 |
+
|
| 21 |
+
[](LICENSE)
|
| 22 |
+
[](COMMERCIAL.md)
|
| 23 |
+
|
| 24 |
+
</div>
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## Why
|
| 29 |
+
|
| 30 |
+
AMD's NPU is underused on Linux: the official LLM stack targets XDNA2, and the NPU's real strengths
|
| 31 |
+
(**low-power, conv/vision, batchable compute, offload**) aren't packaged into usable Linux apps. This
|
| 32 |
+
kit does that β each module plays to a **measured** NPU strength, and the status of each is stated
|
| 33 |
+
honestly (no "coming soon" dressed up as done).
|
| 34 |
+
|
| 35 |
+
## Modules
|
| 36 |
+
|
| 37 |
+
| Module | What it does | NPU fit | Status |
|
| 38 |
+
|--------|--------------|---------|--------|
|
| 39 |
+
| **[camera/](camera/)** | Real-time webcam effects β a **v4l2 virtual camera** (Zoom/OBS/browser) | conv/vision β its native strength | β
**working** (edge-stylize) |
|
| 40 |
+
| camera/ β background blur | Sharp foreground + blurred background (real MODNet segmentation) | conv/CNN | β
**working** (clean CPU blur + real seg; NPU blur path experimental) |
|
| 41 |
+
| **[embeddings/](embeddings/)** | Batch sentence/RAG embeddings, offloaded from CPU/iGPU | batchable matmul β amortizes dispatch | π¬ candidate |
|
| 42 |
+
| **[audio/](audio/)** | Wake-word / real-time denoise | the NPU's classic edge job | π¬ candidate (untested) |
|
| 43 |
+
| **[ggml-backend/](ggml-backend/)** | XDNA backend for ggml/llama.cpp | foundational enabler | π§ planned (biggest lift) |
|
| 44 |
+
|
| 45 |
+
## Featured: camera effects (working today)
|
| 46 |
+
|
| 47 |
+
Live webcam β NPU effect β virtual camera, **measured on a Ryzen 7 8845HS (XDNA1)**:
|
| 48 |
+
|
| 49 |
+
- Full per-frame conv/colorspace pipeline: **220 FPS @1080p / 451 FPS @720p** (incl. host DMA round-trip).
|
| 50 |
+
- In the live daemon the NPU effect adds **~2 ms/frame at ~6.6 W** β end-to-end is **camera-capture-bound**, never NPU-bound. So there's large headroom for heavier effects (segmentation).
|
| 51 |
+
- Outputs to a **v4l2loopback** device β any app sees `NPU Camera`.
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
# (after the open-xdna NPU stack is up + this kit's requirements installed)
|
| 55 |
+
sudo modprobe v4l2loopback video_nr=10 card_label="NPU Camera" exclusive_caps=1
|
| 56 |
+
python3 camera/npu_camera_fps.py # FPS feasibility gate
|
| 57 |
+
python3 camera/npu_camera_daemon.py --loopback /dev/video10 --stream-frames 0 # live β virtual cam
|
| 58 |
+
# then pick "NPU Camera" in Zoom/OBS/your browser
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## Requirements
|
| 62 |
+
|
| 63 |
+
A working **XDNA NPU stack** (XRT + amd/xdna-driver + IRON/mlir-aie + Peano) β see
|
| 64 |
+
[open-xdna](https://github.com/Scottcjn/open-xdna) for the bring-up. Plus host deps:
|
| 65 |
+
`pip install -r requirements.txt` (opencv-python-headless, pyvirtualcam, numpy, ml_dtypes) and
|
| 66 |
+
`v4l2loopback` for the virtual camera.
|
| 67 |
+
|
| 68 |
+
## Honesty / scope
|
| 69 |
+
|
| 70 |
+
Verified on **XDNA1 (Phoenix)**, Ubuntu 25.10 / kernel 6.17. The **camera module is real and
|
| 71 |
+
measured**; the other modules are marked candidate/planned and make **no working claims** until built.
|
| 72 |
+
Research/community project, not a production stack.
|
| 73 |
+
|
| 74 |
+
## License
|
| 75 |
+
|
| 76 |
+
AGPLv3 + commercial dual-license β see [LICENSE](LICENSE) and [COMMERCIAL.md](COMMERCIAL.md).
|
| 77 |
+
Β© 2026 Scott Boudreaux / Elyan Labs.
|
amd_dev_community_showcase.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
TITLE (one line):
|
| 2 |
+
npu-linux-kit: practical AMD XDNA NPU apps for Linux β a working NPU webcam-effects daemon (220 FPS pipeline), plus an honest roadmap
|
| 3 |
+
|
| 4 |
+
CATEGORY: Showcase (also fits Projects & Demos)
|
| 5 |
+
|
| 6 |
+
----- BODY -----
|
| 7 |
+
|
| 8 |
+
**TL;DR.** A kit of practical Linux applications for the AMD XDNA NPU β built on my open-source
|
| 9 |
+
gen-1 (XDNA1 / Phoenix) bring-up. The first module is a **working real-time NPU webcam-effects
|
| 10 |
+
daemon** that outputs a v4l2 virtual camera (so Zoom/OBS/browsers use it). The other modules
|
| 11 |
+
(background-blur, embeddings, audio, a ggml backend) are on a roadmap and **clearly marked as
|
| 12 |
+
candidate/planned β no working claims until built.** Repo: https://github.com/Scottcjn/npu-linux-kit
|
| 13 |
+
(foundation: https://github.com/Scottcjn/open-xdna)
|
| 14 |
+
|
| 15 |
+
**Why.** AMD's NPU is underused on Linux β the official LLM stack targets XDNA2, and the NPU's real
|
| 16 |
+
strengths (low-power, conv/vision, batchable compute, offload) aren't packaged into usable Linux
|
| 17 |
+
apps. This kit does that, one measured module at a time.
|
| 18 |
+
|
| 19 |
+
**Working today β NPU webcam effects.** Live camera β NPU conv/colorspace effect (edge-stylize:
|
| 20 |
+
rgba2gray β 3Γ3 filter2d β threshold β blend) β **v4l2loopback virtual camera**. Measured on a
|
| 21 |
+
Ryzen 7 8845HS (XDNA1), Ubuntu 25.10 / kernel 6.17:
|
| 22 |
+
|
| 23 |
+
- Full per-frame pipeline incl. host DMA round-trip: **220 FPS @1080p, 451 FPS @720p** (7β32Γ headroom over 30fps).
|
| 24 |
+
- In the live daemon, the NPU effect adds **~2 ms/frame at ~6.6 W** β end-to-end is **camera-capture-bound** (~15 FPS on a USB webcam), *never* NPU-bound.
|
| 25 |
+
|
| 26 |
+
So the NPU runs the effect essentially for free, leaving the CPU/iGPU idle β exactly the low-power
|
| 27 |
+
offload role it's built for. There's huge headroom for heavier effects.
|
| 28 |
+
|
| 29 |
+
**Roadmap (honestly marked):**
|
| 30 |
+
- **Background blur** β the marquee "Studio Effects" feature; needs a segmentation model on the NPU. The headroom says feasible; not built.
|
| 31 |
+
- **Embeddings** β batch sentence/RAG embeddings; matmul-heavy + batchable β amortizes the NPU's dispatch overhead. Candidate.
|
| 32 |
+
- **Audio** β wake-word / denoise; the NPU's classic edge job, but **untested here** β listed as a candidate, not a claim.
|
| 33 |
+
- **ggml backend** β an XDNA backend for ggml/llama.cpp; the foundational enabler. Biggest lift.
|
| 34 |
+
|
| 35 |
+
**Honesty / scope.** Verified on XDNA1 (Phoenix). The camera module is real and measured; the rest
|
| 36 |
+
make no working claims. Research/community project, not a production stack. The kit builds on
|
| 37 |
+
open-xdna, where I also filed the bring-up issues/PRs upstream (amd/xdna-driver #1447/#1448/#1449,
|
| 38 |
+
plus an mlir-aie discussion on the AIE2 vector ISA).
|
| 39 |
+
|
| 40 |
+
Feedback very welcome β especially from anyone on Phoenix/Hawk Point hardware who wants NPU camera
|
| 41 |
+
effects on Linux. π https://github.com/Scottcjn/npu-linux-kit
|
amd_devcloud_credits_answer.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
How do you plan to use your AMD GPUs on the AMD Developer Cloud?
|
| 2 |
+
|
| 3 |
+
I maintain open-source AMD-stack projects: **open-xdna** (the first-gen XDNA1 / Phoenix NPU brought
|
| 4 |
+
up on Linux end-to-end with a 100% open stack β XRT + amd/xdna-driver + IRON/mlir-aie + Peano) and
|
| 5 |
+
**npu-linux-kit** (practical NPU apps, including a working webcam-effects daemon with real-time
|
| 6 |
+
background blur). From that work I've filed upstream contributions to amd/xdna-driver (issue #1447,
|
| 7 |
+
docs PR #1448, code PR #1449) and a Xilinx/mlir-aie discussion. I'd use Instinct GPUs on the
|
| 8 |
+
Developer Cloud for the **datacenterβclient pipeline** the full AMD stack is built for:
|
| 9 |
+
|
| 10 |
+
1. **Train & quantize models for NPU deployment.** Fine-tune and INT8-quantize lightweight
|
| 11 |
+
vision/segmentation models (MODNet-class portrait matting, and the conv stages for my NPU camera
|
| 12 |
+
kit) on MI300X, then deploy them to the Ryzen AI NPU via IRON/mlir-aie β the "train on Instinct,
|
| 13 |
+
infer on XDNA" loop. My kit currently runs segmentation on the CPU; this is how I move it onto AMD
|
| 14 |
+
silicon end-to-end.
|
| 15 |
+
|
| 16 |
+
2. **Settle a live, community-reviewed open question: ROCm vs Vulkan GEMM.** I measured that on a
|
| 17 |
+
Ryzen 8845HS, CPU prompt-prefill beats the Radeon 780M iGPU ~2.6x on llama.cpp's Vulkan/RADV
|
| 18 |
+
backend (while decode favors the iGPU). A community reviewer rightly questioned whether that's a
|
| 19 |
+
hardware result or an immature open Vulkan GEMM path; my on-silicon re-test showed it's robust to
|
| 20 |
+
ubatch/length tuning, which points at the Mesa/RADV Q4_K GEMM kernels rather than the hardware.
|
| 21 |
+
The clean way to settle it is to run the **identical workload on a mature ROCm/hipBLAS GEMM path**
|
| 22 |
+
and profile achieved FLOPS/occupancy with rocprof β isolating software-stack vs hardware. Instinct
|
| 23 |
+
GPUs on the Dev Cloud let me do exactly that, then port my hand-authored matmul/conv/prune kernels
|
| 24 |
+
to ROCm and publish honest heterogeneous-partitioning guidance for Ryzen AI builders.
|
| 25 |
+
|
| 26 |
+
3. **LLM inference & quantization research.** Benchmark and quantize LLMs (Gemma/Qwen) on MI300X to
|
| 27 |
+
feed the integrated-NPU deployment work, and validate my non-bijunctive prune/collapse experiments
|
| 28 |
+
at scale.
|
| 29 |
+
|
| 30 |
+
All outputs are open-source, continuing the public track record above (github.com/Scottcjn).
|
audio/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# audio (candidate β untested)
|
| 2 |
+
|
| 3 |
+
Wake-word spotting / RNNoise-class real-time denoise on the NPU. **Status: design-domain, NOT yet
|
| 4 |
+
tested.** This is the NPU's classic edge use case, but no audio has been run on it in this project
|
| 5 |
+
yet β listed honestly as a candidate, not a claim.
|
camera/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# camera β NPU webcam effects for Linux
|
| 2 |
+
|
| 3 |
+
Real-time webcam effects on the AMD XDNA NPU, output as a **v4l2 virtual camera** so any app
|
| 4 |
+
(Zoom, OBS, browsers) can use it. This is an open-source take on the "Studio Effects" idea that
|
| 5 |
+
Windows ships on the NPU and Linux lacks.
|
| 6 |
+
|
| 7 |
+
## Status
|
| 8 |
+
|
| 9 |
+
- β
**edge-stylize** (`--effect edge`) β rgba2gray β 3Γ3 Laplacian β threshold β blend.
|
| 10 |
+
- β
**blur** (`--effect blur`, `blur_pipeline.py`) β NPU 3Γ3 box-blur conv, **449 FPS @720p**, verified
|
| 11 |
+
(row variance 16256β255 on a 1px test pattern; `test_blur.py`). v1 is grayscale + a single 3Γ3 pass (mild blur).
|
| 12 |
+
- β
**background blur** (`npu_background_blur.py`) β clean, working end-to-end:
|
| 13 |
+
- **Segmentation = real**: MODNet via onnxruntime (`--onnx modnet.onnx`) β accurate person cutout
|
| 14 |
+
(~136 ms/frame CPU). Center-ellipse placeholder only if no model is given.
|
| 15 |
+
- **Blur = clean, on the NPU**: a **custom AIE kernel** (`kernels/blur3x3.cc`, bound by `blur_plane.py`)
|
| 16 |
+
gives a correct, artifact-free unity-gain Gaussian β int32 acc + int16 coeffs + `>>12` + `[0,255]`
|
| 17 |
+
clamp, where the stock `filter2d` saturated bright pixels. Enable with `--npu-blur`.
|
| 18 |
+
- **Composite**: sharp foreground + blurred background, feathered edges.
|
| 19 |
+
- **Perf (measured):** with `--npu-scale 4`, the NPU blur runs at reduced res (bokeh is low-frequency,
|
| 20 |
+
so visually identical) and is **free within the frame budget**. End-to-end runs at the **camera's full
|
| 21 |
+
rate β ~16 FPS on this USB webcam, which is the capture ceiling, NOT compute-bound** (NPU blur + MODNet
|
| 22 |
+
seg both keep up; passes 1/2/3 all hit ~16 FPS). A faster camera yields more FPS with the NPU keeping up.
|
| 23 |
+
`--npu-scale 4` is the validated config; higher scales can ERT-timeout (AIE dimension-sensitive).
|
| 24 |
+
- Get MODNet: `curl -L -o modnet.onnx https://huggingface.co/Xenova/modnet/resolve/main/onnx/model.onnx`
|
| 25 |
+
- Still test: `python3 npu_background_blur.py --image scene.png --onnx modnet.onnx [--npu-blur]`
|
| 26 |
+
|
| 27 |
+
## Measured (Ryzen 7 8845HS, XDNA1, Ubuntu 25.10 / kernel 6.17)
|
| 28 |
+
|
| 29 |
+
`npu_camera_fps.py` β full per-frame pipeline incl. host DMA round-trip:
|
| 30 |
+
|
| 31 |
+
| resolution | ms/frame | FPS | headroom @30 |
|
| 32 |
+
|---|---|---|---|
|
| 33 |
+
| 640Γ480 | 1.02 | **985** | 32Γ |
|
| 34 |
+
| 1280Γ720 | 2.21 | **451** | 15Γ |
|
| 35 |
+
| 1920Γ1080 | 4.54 | **220** | 7Γ |
|
| 36 |
+
|
| 37 |
+
`npu_camera_daemon.py` β live `/dev/video0` β NPU effect β `/dev/video10` virtual cam:
|
| 38 |
+
the NPU effect adds **~2 ms/frame (~6.6 W)**; end-to-end is **camera-capture-bound** (~15 FPS on a
|
| 39 |
+
typical USB webcam), **never** NPU-bound. The NPU is essentially free here.
|
| 40 |
+
|
| 41 |
+
## Usage
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
# 1) create the virtual camera (once per boot)
|
| 45 |
+
sudo modprobe v4l2loopback video_nr=10 card_label="NPU Camera (open-xdna)" exclusive_caps=1
|
| 46 |
+
|
| 47 |
+
# 2) feasibility gate (optional)
|
| 48 |
+
python3 npu_camera_fps.py
|
| 49 |
+
|
| 50 |
+
# 3) run the daemon: live camera -> NPU effect -> virtual cam
|
| 51 |
+
python3 npu_camera_daemon.py --loopback /dev/video10 --stream-frames 0 # 0 = run until Ctrl-C
|
| 52 |
+
|
| 53 |
+
# 4) in Zoom/OBS/your browser, select camera "NPU Camera (open-xdna)"
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Notes:
|
| 57 |
+
- Needs the XDNA NPU stack up (see [open-xdna](https://github.com/Scottcjn/open-xdna)); run under the
|
| 58 |
+
IRON venv. `/dev/accel` access typically needs `sudo` or the `render` group.
|
| 59 |
+
- Set `MLIR_AIE_DIR` if your mlir-aie checkout isn't at `~/open-xdna/mlir-aie` (the daemon imports the
|
| 60 |
+
`edge_detect` IRON pipeline from there).
|
| 61 |
+
- Without `--loopback`, the daemon runs a capture-vs-NPU **benchmark** and saves a before/after sample.
|
camera/blur_pipeline.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NPU blur design (adapted from edge_detect). GAIN FIX 2026-06-24: add_weighted alpha 32767->16384
|
| 2 |
+
# (unity on uniform 64). KNOWN: per-channel round-trip + multipass still adds diagonal color speckle
|
| 3 |
+
# + mild under-gain on bright values. A clean NPU blur needs a PURPOSE-BUILT single-channel/RGBA
|
| 4 |
+
# design (no rgba2gray round-trip, no add_weighted) β see camera/README 'NEXT'.
|
| 5 |
+
# vision/edge_detect/edge_detect.py -*- Python -*-
|
| 6 |
+
#
|
| 7 |
+
# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
|
| 8 |
+
# See https://llvm.org/LICENSE.txt for license information.
|
| 9 |
+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
| 10 |
+
#
|
| 11 |
+
# (c) Copyright 2024-2026 AMD Inc.
|
| 12 |
+
"""Vision edge-detect pipeline -- ``@iron.jit`` design.
|
| 13 |
+
|
| 14 |
+
shim --> rgba2gray --> filter2d (3x3 Laplacian) --> threshold -->
|
| 15 |
+
gray2rgba+addWeighted --> shim
|
| 16 |
+
|
| 17 |
+
The filter2d worker reuses an inner Buffer holding the constant Laplacian
|
| 18 |
+
kernel. The gray2rgba+addWeighted worker combines the thresholded edge
|
| 19 |
+
map with the original RGBA input (forwarded via ``inOF_L2L1``).
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
import sys
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
import aie.iron as iron
|
| 28 |
+
from aie.iron import (
|
| 29 |
+
Buffer,
|
| 30 |
+
CompileTime,
|
| 31 |
+
In,
|
| 32 |
+
ObjectFifo,
|
| 33 |
+
Out,
|
| 34 |
+
Program,
|
| 35 |
+
Runtime,
|
| 36 |
+
Worker,
|
| 37 |
+
kernels,
|
| 38 |
+
)
|
| 39 |
+
from aie.iron.controlflow import range_
|
| 40 |
+
from aie.utils.hostruntime.argparse import (
|
| 41 |
+
device_from_args,
|
| 42 |
+
add_compile_args,
|
| 43 |
+
)
|
| 44 |
+
from aie.utils.hostruntime.cli import run_design_cli
|
| 45 |
+
from aie.utils.verify import assert_pass
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@iron.jit(aiecc_flags=["--alloc-scheme=basic-sequential"])
|
| 49 |
+
def blur(
|
| 50 |
+
in_tensor: In,
|
| 51 |
+
_b_unused: In,
|
| 52 |
+
out_tensor: Out,
|
| 53 |
+
*,
|
| 54 |
+
width: CompileTime[int] = 1920,
|
| 55 |
+
height: CompileTime[int] = 1080,
|
| 56 |
+
):
|
| 57 |
+
height_minus_1 = height - 1
|
| 58 |
+
line_width = width
|
| 59 |
+
line_width_in_bytes = width * 4 # 4 channels (RGBA)
|
| 60 |
+
|
| 61 |
+
line_bytes_ty = np.ndarray[(line_width_in_bytes,), np.dtype[np.uint8]]
|
| 62 |
+
line_ty = np.ndarray[(line_width,), np.dtype[np.uint8]]
|
| 63 |
+
|
| 64 |
+
rgba2gray_line_kernel = kernels.rgba2gray(line_width=line_width)
|
| 65 |
+
filter2d_line_kernel = kernels.filter2d(line_width=line_width)
|
| 66 |
+
threshold_line_kernel = kernels.threshold(line_width=line_width, dtype=np.uint8)
|
| 67 |
+
gray2rgba_line_kernel = kernels.gray2rgba(line_width=line_width)
|
| 68 |
+
# add_weighted operates byte-wise over the flattened RGBA buffer, so its
|
| 69 |
+
# "line width" is the full RGBA stride in bytes.
|
| 70 |
+
add_weighted_line_kernel = kernels.add_weighted(
|
| 71 |
+
line_width=line_width_in_bytes, dtype=np.uint8
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
in_of_l3l2 = ObjectFifo(line_bytes_ty, name="inOF_L3L2")
|
| 75 |
+
in_of_l2l1 = in_of_l3l2.cons(7).forward(depth=7, name="inOF_L2L1")
|
| 76 |
+
out_of_l1l2 = ObjectFifo(line_bytes_ty, name="outOF_L1L2")
|
| 77 |
+
out_of_l2l3 = out_of_l1l2.cons().forward(name="outOF_L2L3")
|
| 78 |
+
|
| 79 |
+
intermediate_depths = [4, 2, 2]
|
| 80 |
+
of_intermediates = [
|
| 81 |
+
ObjectFifo(line_ty, depth=intermediate_depths[i], name=f"OF_{i + 2}to{i + 3}")
|
| 82 |
+
for i in range(3)
|
| 83 |
+
]
|
| 84 |
+
of_local = ObjectFifo(line_bytes_ty, depth=1, name="OF_local")
|
| 85 |
+
|
| 86 |
+
# Laplacian edge-detect kernel: cross stencil with -16384 center, 4096 edges.
|
| 87 |
+
# Gaussian blur: int16 coeffs >>8 -> int8 [[1,2,1],[2,4,2],[1,2,1]] (sum 16), then >>4 (SRS_SHIFT-8)
|
| 88 |
+
# = exact unity-gain weighted mean. (A box can't be unity-gain here: 9/16 under-gains.)
|
| 89 |
+
filter_kernel_buff = Buffer(
|
| 90 |
+
np.ndarray[(3, 3), np.dtype[np.int16]],
|
| 91 |
+
name="kernel",
|
| 92 |
+
initial_value=np.array(
|
| 93 |
+
[[256, 512, 256], [512, 1024, 512], [256, 512, 256]], dtype=np.int16
|
| 94 |
+
),
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
workers = []
|
| 98 |
+
|
| 99 |
+
def rgba2gray_fn(of_in, of_out, rgba2gray_line):
|
| 100 |
+
elem_in = of_in.acquire(1)
|
| 101 |
+
elem_out = of_out.acquire(1)
|
| 102 |
+
rgba2gray_line(elem_in, elem_out, line_width)
|
| 103 |
+
of_in.release(1)
|
| 104 |
+
of_out.release(1)
|
| 105 |
+
|
| 106 |
+
workers.append(
|
| 107 |
+
Worker(
|
| 108 |
+
rgba2gray_fn,
|
| 109 |
+
[in_of_l3l2.cons(), of_intermediates[0].prod(), rgba2gray_line_kernel],
|
| 110 |
+
)
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
def filter_fn(of_in, of_out, filter_kernel, filter2d_line):
|
| 114 |
+
# 3-line stencil over height rows. Top/bottom borders duplicate the
|
| 115 |
+
# adjacent row; the steady-state middle uses real (i-1, i, i+1).
|
| 116 |
+
for _ in range_(sys.maxsize):
|
| 117 |
+
# Top border
|
| 118 |
+
elems_in_pre = of_in.acquire(2)
|
| 119 |
+
elem_pre_out = of_out.acquire(1)
|
| 120 |
+
filter2d_line(
|
| 121 |
+
elems_in_pre[0],
|
| 122 |
+
elems_in_pre[0],
|
| 123 |
+
elems_in_pre[1],
|
| 124 |
+
elem_pre_out,
|
| 125 |
+
line_width,
|
| 126 |
+
filter_kernel,
|
| 127 |
+
)
|
| 128 |
+
of_out.release(1)
|
| 129 |
+
|
| 130 |
+
# Steady-state
|
| 131 |
+
for _ in range_(1, height_minus_1):
|
| 132 |
+
elems_in = of_in.acquire(3)
|
| 133 |
+
elem_out = of_out.acquire(1)
|
| 134 |
+
filter2d_line(
|
| 135 |
+
elems_in[0],
|
| 136 |
+
elems_in[1],
|
| 137 |
+
elems_in[2],
|
| 138 |
+
elem_out,
|
| 139 |
+
line_width,
|
| 140 |
+
filter_kernel,
|
| 141 |
+
)
|
| 142 |
+
of_in.release(1)
|
| 143 |
+
of_out.release(1)
|
| 144 |
+
|
| 145 |
+
# Bottom border
|
| 146 |
+
elems_in_post = of_in.acquire(2)
|
| 147 |
+
elem_post_out = of_out.acquire(1)
|
| 148 |
+
filter2d_line(
|
| 149 |
+
elems_in_post[0],
|
| 150 |
+
elems_in_post[1],
|
| 151 |
+
elems_in_post[1],
|
| 152 |
+
elem_post_out,
|
| 153 |
+
line_width,
|
| 154 |
+
filter_kernel,
|
| 155 |
+
)
|
| 156 |
+
of_in.release(2)
|
| 157 |
+
of_out.release(1)
|
| 158 |
+
|
| 159 |
+
workers.append(
|
| 160 |
+
Worker(
|
| 161 |
+
filter_fn,
|
| 162 |
+
[
|
| 163 |
+
of_intermediates[0].cons(),
|
| 164 |
+
of_intermediates[1].prod(),
|
| 165 |
+
filter_kernel_buff,
|
| 166 |
+
filter2d_line_kernel,
|
| 167 |
+
],
|
| 168 |
+
while_true=False,
|
| 169 |
+
)
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
def gray2rgba_add_weight_fn(
|
| 173 |
+
of_in,
|
| 174 |
+
of_in2,
|
| 175 |
+
of_out_self,
|
| 176 |
+
of_in_self,
|
| 177 |
+
of_out,
|
| 178 |
+
gray2rgba_line,
|
| 179 |
+
add_weighted_line,
|
| 180 |
+
):
|
| 181 |
+
elem_in = of_in.acquire(1)
|
| 182 |
+
elem_out = of_out_self.acquire(1)
|
| 183 |
+
gray2rgba_line(elem_in, elem_out, line_width)
|
| 184 |
+
of_in.release(1)
|
| 185 |
+
of_out_self.release(1)
|
| 186 |
+
|
| 187 |
+
elem_in1 = of_in_self.acquire(1)
|
| 188 |
+
elem_in2 = of_in2.acquire(1)
|
| 189 |
+
elem_out2 = of_out.acquire(1)
|
| 190 |
+
|
| 191 |
+
alpha, beta, gamma = 16384, 0, 0
|
| 192 |
+
add_weighted_line(
|
| 193 |
+
elem_in1,
|
| 194 |
+
elem_in2,
|
| 195 |
+
elem_out2,
|
| 196 |
+
line_width_in_bytes,
|
| 197 |
+
alpha,
|
| 198 |
+
beta,
|
| 199 |
+
gamma,
|
| 200 |
+
)
|
| 201 |
+
of_in_self.release(1)
|
| 202 |
+
of_in2.release(1)
|
| 203 |
+
of_out.release(1)
|
| 204 |
+
|
| 205 |
+
workers.append(
|
| 206 |
+
Worker(
|
| 207 |
+
gray2rgba_add_weight_fn,
|
| 208 |
+
[
|
| 209 |
+
of_intermediates[1].cons(),
|
| 210 |
+
in_of_l2l1.cons(),
|
| 211 |
+
of_local.prod(),
|
| 212 |
+
of_local.cons(),
|
| 213 |
+
out_of_l1l2.prod(),
|
| 214 |
+
gray2rgba_line_kernel,
|
| 215 |
+
add_weighted_line_kernel,
|
| 216 |
+
],
|
| 217 |
+
)
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
tensor_size = width * height * 4
|
| 221 |
+
tensor_ty = np.ndarray[(tensor_size,), np.dtype[np.int8]]
|
| 222 |
+
tensor_16x16_ty = np.ndarray[(16, 16), np.dtype[np.int32]]
|
| 223 |
+
|
| 224 |
+
rt = Runtime()
|
| 225 |
+
with rt.sequence(tensor_ty, tensor_16x16_ty, tensor_ty) as (i_in, _b, o_out):
|
| 226 |
+
rt.start(*workers)
|
| 227 |
+
rt.fill(in_of_l3l2.prod(), i_in)
|
| 228 |
+
rt.drain(out_of_l2l3.cons(), o_out, wait=True)
|
| 229 |
+
|
| 230 |
+
return Program(iron.get_current_device(), rt).resolve_program()
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def _make_argparser():
|
| 234 |
+
p = argparse.ArgumentParser(prog="AIE Edge Detect")
|
| 235 |
+
add_compile_args(p)
|
| 236 |
+
p.add_argument("-W", "--width", type=int, default=1920)
|
| 237 |
+
p.add_argument("-H", "--height", type=int, default=1080)
|
| 238 |
+
return p
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def _compile_kwargs(opts):
|
| 242 |
+
return dict(width=opts.width, height=opts.height)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _rgba2gray_ref(rgba_uint8, height, width):
|
| 246 |
+
"""Numpy port of ``rgba2gray_aie`` (SRS_SHIFT=15)."""
|
| 247 |
+
rgba = rgba_uint8.reshape(height, width, 4)
|
| 248 |
+
r = rgba[..., 0].astype(np.int32)
|
| 249 |
+
g = rgba[..., 1].astype(np.int32)
|
| 250 |
+
b = rgba[..., 2].astype(np.int32)
|
| 251 |
+
wt_r = int(round(0.299 * (1 << 15))) # 9798
|
| 252 |
+
wt_g = int(round(0.587 * (1 << 15))) # 19235
|
| 253 |
+
wt_b = int(round(0.114 * (1 << 15))) # 3736
|
| 254 |
+
y = (wt_r * r + wt_g * g + wt_b * b + (1 << 14)) >> 15
|
| 255 |
+
return np.clip(y, 0, 255).astype(np.uint8)
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def _filter2d_cv_ref(gray_uint8, height, width):
|
| 259 |
+
"""Numpy equivalent of cv::filter2D with the unscaled Laplacian
|
| 260 |
+
``[[0,1,0],[1,-4,1],[0,1,0]]`` + BORDER_REPLICATE."""
|
| 261 |
+
img = gray_uint8.reshape(height, width).astype(np.float32)
|
| 262 |
+
padded = np.pad(img, 1, mode="edge")
|
| 263 |
+
out = (
|
| 264 |
+
-4.0 * padded[1:-1, 1:-1]
|
| 265 |
+
+ padded[:-2, 1:-1]
|
| 266 |
+
+ padded[2:, 1:-1]
|
| 267 |
+
+ padded[1:-1, :-2]
|
| 268 |
+
+ padded[1:-1, 2:]
|
| 269 |
+
)
|
| 270 |
+
return np.clip(out, 0, 255).astype(np.uint8)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def _threshold_binary_ref(arr_uint8, thresh, max_val):
|
| 274 |
+
"""cv::threshold with THRESH_BINARY: out = (in > thresh) ? max : 0."""
|
| 275 |
+
return np.where(arr_uint8 > thresh, np.uint8(max_val), np.uint8(0))
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def _gray2rgba_ref(gray_uint8):
|
| 279 |
+
"""Replicate gray to R/G/B with alpha=255 (matches ``gray2rgba_aie``)."""
|
| 280 |
+
flat = gray_uint8.reshape(-1)
|
| 281 |
+
out = np.zeros((flat.size, 4), dtype=np.uint8)
|
| 282 |
+
out[:, 0] = flat
|
| 283 |
+
out[:, 1] = flat
|
| 284 |
+
out[:, 2] = flat
|
| 285 |
+
out[:, 3] = 255
|
| 286 |
+
return out.reshape(-1)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def _add_weighted_cv_ref(a_uint8, b_uint8, alpha, beta, gamma):
|
| 290 |
+
"""Numpy equivalent of cv::addWeighted: ``saturate(alpha*a + beta*b + gamma)``.
|
| 291 |
+
test.cpp passes alpha=beta=1.0, gamma=0.0; the AIE kernel computes
|
| 292 |
+
``(a+b)/2`` in fixed-point β the diffs land under ``_EPSILON``.
|
| 293 |
+
"""
|
| 294 |
+
tmp = a_uint8.astype(np.int32) * alpha + b_uint8.astype(np.int32) * beta + gamma
|
| 295 |
+
return np.clip(tmp, 0, 255).astype(np.uint8)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def _edge_detect_ref(rgba_uint8, height, width):
|
| 299 |
+
"""End-to-end reference mirroring test.cpp's edgeDetect() OpenCV pipeline."""
|
| 300 |
+
gray = _rgba2gray_ref(rgba_uint8, height, width)
|
| 301 |
+
edges = _filter2d_cv_ref(gray, height, width)
|
| 302 |
+
thresholded = _threshold_binary_ref(edges, 10, 255)
|
| 303 |
+
mask_rgba = _gray2rgba_ref(thresholded)
|
| 304 |
+
return _add_weighted_cv_ref(rgba_uint8, mask_rgba, 1, 1, 0)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
# Matches the C++ test.cpp's ``epsilon = 2.0`` tolerance on
|
| 308 |
+
# ``error_per_pixel = sum(abs(actual - golden)) / num_pixels``.
|
| 309 |
+
_EPSILON = 2.0
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def _run_and_verify(opts):
|
| 313 |
+
tensor_size = opts.width * opts.height * 4
|
| 314 |
+
rng = np.random.default_rng(0)
|
| 315 |
+
in_np = rng.integers(-128, 127, size=(tensor_size,), dtype=np.int8)
|
| 316 |
+
in_t = iron.tensor(in_np, dtype=np.int8, device="npu")
|
| 317 |
+
b_t = iron.zeros(16 * 16, dtype=np.int32, device="npu")
|
| 318 |
+
out_t = iron.zeros(tensor_size, dtype=np.int8, device="npu")
|
| 319 |
+
|
| 320 |
+
edge_detect(in_t, b_t, out_t, **_compile_kwargs(opts))
|
| 321 |
+
|
| 322 |
+
in_uint8 = in_np.view(np.uint8)
|
| 323 |
+
expected_uint8 = _edge_detect_ref(in_uint8, opts.height, opts.width)
|
| 324 |
+
actual = out_t.numpy().view(np.uint8)
|
| 325 |
+
|
| 326 |
+
n_diff = int(np.sum(actual != expected_uint8))
|
| 327 |
+
error_per_pixel = float(
|
| 328 |
+
np.sum(np.abs(actual.astype(np.int32) - expected_uint8.astype(np.int32)))
|
| 329 |
+
) / (opts.width * opts.height)
|
| 330 |
+
print(f"Number of differences: {n_diff}, average L1 error: {error_per_pixel:.6f}")
|
| 331 |
+
|
| 332 |
+
assert_pass(
|
| 333 |
+
error_per_pixel < _EPSILON,
|
| 334 |
+
True,
|
| 335 |
+
fail_msg=f"error_per_pixel {error_per_pixel:.6f} >= epsilon {_EPSILON}",
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def main():
|
| 340 |
+
opts = _make_argparser().parse_args()
|
| 341 |
+
run_design_cli(
|
| 342 |
+
edge_detect,
|
| 343 |
+
opts,
|
| 344 |
+
compile_kwargs=_compile_kwargs,
|
| 345 |
+
run_and_verify=_run_and_verify,
|
| 346 |
+
device=device_from_args,
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
if __name__ == "__main__":
|
| 351 |
+
main()
|
camera/blur_plane.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
| 2 |
+
# Copyright (C) 2026 Scott Boudreaux / Elyan Labs. Commercial license: see ../COMMERCIAL.md
|
| 3 |
+
#
|
| 4 |
+
# Purpose-built NPU blur kernel: a CLEAN single-channel 3x3 Gaussian filter2d on the AIE.
|
| 5 |
+
# No rgba2gray round-trip, no add_weighted overlay (the speckle + 2x-gain sources). The host
|
| 6 |
+
# feeds each color channel as a uint8 plane (multi-pass = re-feed) and recombines -> clean color blur.
|
| 7 |
+
#
|
| 8 |
+
# Dataflow FIX 2026-06-24: the filter's 3-line stencil consumes a WORKER-PRODUCED line stream
|
| 9 |
+
# (a passthrough copy producer), exactly like edge_detect's verified rgba2gray->filter structure β
|
| 10 |
+
# NOT a raw DMA forward (which caused value-dependent gain + line streaks).
|
| 11 |
+
#
|
| 12 |
+
# Unity gain: int16 coeffs >>8 -> int8 [[1,2,1],[2,4,2],[1,2,1]] (sum 16); filter2d stores
|
| 13 |
+
# acc >> (SRS_SHIFT-8 = 4) = acc/16 = exact weighted mean.
|
| 14 |
+
|
| 15 |
+
import sys, os
|
| 16 |
+
#
|
| 17 |
+
# DEFINITIVE DIAGNOSIS 2026-06-24: with the correct (worker-produced) dataflow AND unity-gain coeffs,
|
| 18 |
+
# the stock kernels.filter2d STILL produces value-dependent output that SATURATES bright pixels to the
|
| 19 |
+
# int8-positive range: uniform 100->100 but 200->132, 255->150. The kernel's accumulation/store path
|
| 20 |
+
# is built for the Laplacian (zero-centered edge outputs) and clamps a unity-sum blur of bright images.
|
| 21 |
+
# This is NOT a dataflow or coeff bug (both fixed). CONCLUSION: a clean NPU blur needs a CUSTOM AIE
|
| 22 |
+
# blur kernel (.cc, hand-written vector intrinsics with a proper uint8 saturate-store), not filter2d.
|
| 23 |
+
# Until then the kit's background blur uses the clean CPU Gaussian (camera/README).
|
| 24 |
+
import numpy as np
|
| 25 |
+
import aie.iron as iron
|
| 26 |
+
from aie.iron import Buffer, CompileTime, In, ObjectFifo, Out, Program, Runtime, Worker, kernels
|
| 27 |
+
from aie.iron.controlflow import range_
|
| 28 |
+
from aie.iron.kernels._common import _make_extern
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@iron.jit(aiecc_flags=["--alloc-scheme=basic-sequential"])
|
| 32 |
+
def blur_plane(
|
| 33 |
+
in_tensor: In,
|
| 34 |
+
out_tensor: Out,
|
| 35 |
+
*,
|
| 36 |
+
width: CompileTime[int] = 1280,
|
| 37 |
+
height: CompileTime[int] = 720,
|
| 38 |
+
):
|
| 39 |
+
height_minus_1 = height - 1
|
| 40 |
+
line_width = width
|
| 41 |
+
line_ty = np.ndarray[(line_width,), np.dtype[np.uint8]]
|
| 42 |
+
|
| 43 |
+
pass_through_line = kernels.passthrough(tile_size=line_width, dtype=np.uint8)
|
| 44 |
+
_kernel_ty = np.ndarray[(3, 3), np.dtype[np.int16]]
|
| 45 |
+
_blur_src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "kernels", "blur3x3.cc")
|
| 46 |
+
filter2d_line_kernel = _make_extern(
|
| 47 |
+
"blurLine", _blur_src,
|
| 48 |
+
[line_ty, line_ty, line_ty, line_ty, np.int32, _kernel_ty],
|
| 49 |
+
)
|
| 50 |
+
# unity-gain Gaussian (>>8 -> int8 [1,2,1;2,4,2;1,2,1], sum 16; filter2d >>4 = /16)
|
| 51 |
+
filter_kernel_buff = Buffer(
|
| 52 |
+
np.ndarray[(3, 3), np.dtype[np.int16]],
|
| 53 |
+
name="kernel",
|
| 54 |
+
initial_value=np.array(
|
| 55 |
+
[[256, 512, 256], [512, 1024, 512], [256, 512, 256]], dtype=np.int16
|
| 56 |
+
),
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
of_in = ObjectFifo(line_ty, name="in") # DMA input
|
| 60 |
+
of_lines = ObjectFifo(line_ty, depth=4, name="lines") # worker-produced line stream (filter input)
|
| 61 |
+
of_out = ObjectFifo(line_ty, name="out") # DMA output
|
| 62 |
+
|
| 63 |
+
workers = []
|
| 64 |
+
|
| 65 |
+
# producer: copy DMA input lines into of_lines, one per call (IRON auto-iterates over height)
|
| 66 |
+
def copy_fn(of_i, of_o, k):
|
| 67 |
+
ein = of_i.acquire(1)
|
| 68 |
+
eout = of_o.acquire(1)
|
| 69 |
+
k(ein, eout, line_width)
|
| 70 |
+
of_i.release(1)
|
| 71 |
+
of_o.release(1)
|
| 72 |
+
|
| 73 |
+
workers.append(Worker(copy_fn, [of_in.cons(), of_lines.prod(), pass_through_line]))
|
| 74 |
+
|
| 75 |
+
# filter: 3-line stencil over the produced line stream
|
| 76 |
+
def filter_fn(of_i, of_o, kernel, f2d):
|
| 77 |
+
for _ in range_(sys.maxsize):
|
| 78 |
+
ein = of_i.acquire(2) # top border: dup first row
|
| 79 |
+
eout = of_o.acquire(1)
|
| 80 |
+
f2d(ein[0], ein[0], ein[1], eout, line_width, kernel)
|
| 81 |
+
of_o.release(1)
|
| 82 |
+
for _ in range_(1, height_minus_1): # steady (i-1, i, i+1)
|
| 83 |
+
ein = of_i.acquire(3)
|
| 84 |
+
eout = of_o.acquire(1)
|
| 85 |
+
f2d(ein[0], ein[1], ein[2], eout, line_width, kernel)
|
| 86 |
+
of_i.release(1)
|
| 87 |
+
of_o.release(1)
|
| 88 |
+
ein = of_i.acquire(2) # bottom border: dup last row
|
| 89 |
+
eout = of_o.acquire(1)
|
| 90 |
+
f2d(ein[0], ein[1], ein[1], eout, line_width, kernel)
|
| 91 |
+
of_i.release(2)
|
| 92 |
+
of_o.release(1)
|
| 93 |
+
|
| 94 |
+
workers.append(
|
| 95 |
+
Worker(filter_fn, [of_lines.cons(), of_out.prod(), filter_kernel_buff, filter2d_line_kernel],
|
| 96 |
+
while_true=False)
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
plane_ty = np.ndarray[(width * height,), np.dtype[np.uint8]]
|
| 100 |
+
rt = Runtime()
|
| 101 |
+
with rt.sequence(plane_ty, plane_ty) as (i_in, o_out):
|
| 102 |
+
rt.start(*workers)
|
| 103 |
+
rt.fill(of_in.prod(), i_in)
|
| 104 |
+
rt.drain(of_out.cons(), o_out, wait=True)
|
| 105 |
+
|
| 106 |
+
return Program(iron.get_current_device(), rt).resolve_program()
|
camera/kernels/blur3x3.cc
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//===- blur3x3.cc -------------------------------------------------*- C++ -*-===//
|
| 2 |
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
| 3 |
+
// Copyright (C) 2026 Scott Boudreaux / Elyan Labs. Commercial license: see ../../COMMERCIAL.md
|
| 4 |
+
//
|
| 5 |
+
// Custom AIE2 3x3 blur kernel for the XDNA NPU. Unlike mlir-aie's stock filter2d (whose VECTOR
|
| 6 |
+
// path truncates the int16 kernel to int8 via >>8 and saturates bright unity-gain outputs to the
|
| 7 |
+
// int8-positive range β fine for the zero-centered Laplacian, broken for a blur), this uses an
|
| 8 |
+
// int32 accumulator with the int16 kernel directly, >>SRS_SHIFT, and a proper [0,255] uint8 clamp.
|
| 9 |
+
// For a unity-sum kernel (e.g. Gaussian [[256,512,256],[512,1024,512],[256,512,256]] summing 4096):
|
| 10 |
+
// uniform N -> N*4096 >> 12 = N. Exact unity gain, correctly saturated.
|
| 11 |
+
//
|
| 12 |
+
// Symbol: blurLine(l0,l1,l2,out,width,kernel) β same ABI as filter2d's filter2dLine, so it drops
|
| 13 |
+
// into the same 3-line-stencil worker. (Scalar path: correctness over peak throughput; the camera
|
| 14 |
+
// pipeline has large FPS headroom.)
|
| 15 |
+
|
| 16 |
+
#define NOCPP
|
| 17 |
+
#include <stdint.h>
|
| 18 |
+
#include <aie_api/aie.hpp>
|
| 19 |
+
|
| 20 |
+
const int32_t SRS_SHIFT = 12;
|
| 21 |
+
|
| 22 |
+
static inline void blur3x3_scalar(uint8_t *lineIn0, uint8_t *lineIn1,
|
| 23 |
+
uint8_t *lineIn2, uint8_t *output,
|
| 24 |
+
const int32_t width, int16_t *kernel) {
|
| 25 |
+
int32_t acc;
|
| 26 |
+
|
| 27 |
+
// left column: border extension by replicating column 0
|
| 28 |
+
acc = 0;
|
| 29 |
+
acc += ((int32_t)lineIn0[0]) * kernel[0 * 3 + 0];
|
| 30 |
+
acc += ((int32_t)lineIn1[0]) * kernel[1 * 3 + 0];
|
| 31 |
+
acc += ((int32_t)lineIn2[0]) * kernel[2 * 3 + 0];
|
| 32 |
+
for (int ki = 1; ki < 3; ki++) {
|
| 33 |
+
acc += ((int32_t)lineIn0[0 + ki - 1]) * kernel[0 * 3 + ki];
|
| 34 |
+
acc += ((int32_t)lineIn1[0 + ki - 1]) * kernel[1 * 3 + ki];
|
| 35 |
+
acc += ((int32_t)lineIn2[0 + ki - 1]) * kernel[2 * 3 + ki];
|
| 36 |
+
}
|
| 37 |
+
acc = ((acc + (1 << (SRS_SHIFT - 1))) >> SRS_SHIFT);
|
| 38 |
+
acc = (acc > UINT8_MAX) ? UINT8_MAX : (acc < 0) ? 0 : acc;
|
| 39 |
+
output[0] = (uint8_t)acc;
|
| 40 |
+
|
| 41 |
+
// interior
|
| 42 |
+
for (int i = 1; i < width - 1; i++) {
|
| 43 |
+
acc = 0;
|
| 44 |
+
for (int ki = 0; ki < 3; ki++) {
|
| 45 |
+
acc += ((int32_t)lineIn0[i + ki - 1]) * kernel[0 * 3 + ki];
|
| 46 |
+
acc += ((int32_t)lineIn1[i + ki - 1]) * kernel[1 * 3 + ki];
|
| 47 |
+
acc += ((int32_t)lineIn2[i + ki - 1]) * kernel[2 * 3 + ki];
|
| 48 |
+
}
|
| 49 |
+
acc = ((acc + (1 << (SRS_SHIFT - 1))) >> SRS_SHIFT);
|
| 50 |
+
acc = (acc > UINT8_MAX) ? UINT8_MAX : (acc < 0) ? 0 : acc;
|
| 51 |
+
output[i] = (uint8_t)acc;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
// right column: border extension by replicating last column
|
| 55 |
+
acc = 0;
|
| 56 |
+
for (int ki = 0; ki < 2; ki++) {
|
| 57 |
+
acc += ((int32_t)lineIn0[width + ki - 2]) * kernel[0 * 3 + ki];
|
| 58 |
+
acc += ((int32_t)lineIn1[width + ki - 2]) * kernel[1 * 3 + ki];
|
| 59 |
+
acc += ((int32_t)lineIn2[width + ki - 2]) * kernel[2 * 3 + ki];
|
| 60 |
+
}
|
| 61 |
+
acc += ((int32_t)lineIn0[width - 1]) * kernel[0 * 3 + 2];
|
| 62 |
+
acc += ((int32_t)lineIn1[width - 1]) * kernel[1 * 3 + 2];
|
| 63 |
+
acc += ((int32_t)lineIn2[width - 1]) * kernel[2 * 3 + 2];
|
| 64 |
+
acc = ((acc + (1 << (SRS_SHIFT - 1))) >> SRS_SHIFT);
|
| 65 |
+
acc = (acc > UINT8_MAX) ? UINT8_MAX : (acc < 0) ? 0 : acc;
|
| 66 |
+
output[width - 1] = (uint8_t)acc;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
extern "C" {
|
| 70 |
+
void blurLine(uint8_t *lineIn0, uint8_t *lineIn1, uint8_t *lineIn2,
|
| 71 |
+
uint8_t *out, int32_t lineWidth, int16_t *blurKernel) {
|
| 72 |
+
blur3x3_scalar(lineIn0, lineIn1, lineIn2, out, lineWidth, blurKernel);
|
| 73 |
+
}
|
| 74 |
+
} // extern "C"
|
camera/npu_background_blur.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
| 3 |
+
# Copyright (C) 2026 Scott Boudreaux / Elyan Labs. Commercial license: see ../COMMERCIAL.md
|
| 4 |
+
#
|
| 5 |
+
# open-xdna / npu-linux-kit :: background blur β the marquee Studio-Effects feature.
|
| 6 |
+
#
|
| 7 |
+
# (1) COLOR blur β the grayscale blur design is run PER CHANNEL (R/G/B), recombined β color blur.
|
| 8 |
+
# (2) MULTI-PASS β each channel is blurred N times for strong bokeh (449 FPS/pass β headroom).
|
| 9 |
+
# (3) SEGMENTATION β a person/background mask composites sharp-foreground + NPU-blurred-background.
|
| 10 |
+
# Mask source: MediaPipe Selfie (if installed) β ONNX via opencv-DNN (if a model is given) β
|
| 11 |
+
# a center-ellipse PLACEHOLDER (honest fallback; not real segmentation). A seg model running
|
| 12 |
+
# ON the NPU is the future drop-in β the blur engine is already proven on-NPU.
|
| 13 |
+
#
|
| 14 |
+
# Honesty (v1): blur defaults to a clean CPU Gaussian; the NPU blur path (--npu-blur) is
|
| 15 |
+
# EXPERIMENTAL (fixed-point quality WIP). Segmentation runs on the CPU. The proven NPU effect is
|
| 16 |
+
# the edge-stylize in npu_camera_daemon.py; a clean on-NPU blur + on-NPU seg are the next builds.
|
| 17 |
+
|
| 18 |
+
import os, sys, time, argparse
|
| 19 |
+
_NPU_SCALE = 4
|
| 20 |
+
import numpy as np, cv2
|
| 21 |
+
|
| 22 |
+
MLIR_AIE = os.environ.get("MLIR_AIE_DIR", os.path.expanduser("~/open-xdna/mlir-aie"))
|
| 23 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 24 |
+
import aie.iron as iron
|
| 25 |
+
from blur_plane import blur_plane # custom clean NPU 3x3 blur kernel (blur3x3.cc)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
_plane_in = _plane_out = _plane_n = None
|
| 29 |
+
def _blur_planes_npu(img, w, h, passes):
|
| 30 |
+
"""Blur each color channel (uint8 plane) with the custom blur3x3 NPU kernel, N passes."""
|
| 31 |
+
global _plane_in, _plane_out, _plane_n
|
| 32 |
+
if _plane_n != w * h:
|
| 33 |
+
_plane_in = iron.tensor(np.zeros(w * h, np.uint8), dtype=np.uint8, device="npu")
|
| 34 |
+
_plane_out = iron.zeros(w * h, dtype=np.uint8, device="npu")
|
| 35 |
+
_plane_n = w * h
|
| 36 |
+
out = img.copy()
|
| 37 |
+
for c in range(3):
|
| 38 |
+
ch = np.ascontiguousarray(out[:, :, c])
|
| 39 |
+
for _ in range(passes):
|
| 40 |
+
_plane_in.numpy()[:] = ch.reshape(-1)
|
| 41 |
+
blur_plane(_plane_in, _plane_out, width=w, height=h)
|
| 42 |
+
ch = _plane_out.numpy().reshape(h, w).copy()
|
| 43 |
+
out[:, :, c] = ch
|
| 44 |
+
return out
|
| 45 |
+
|
| 46 |
+
def npu_color_blur(bgr, W, H, passes=1, scale=4):
|
| 47 |
+
"""Clean color blur on the NPU. Real-time path: blur at reduced resolution (scale) then upscale β
|
| 48 |
+
background bokeh is low-frequency, so a downscaled blur is visually ~identical and ~scale^2 faster.
|
| 49 |
+
The blur itself is bit-clean (custom blur3x3 kernel: no rgba2gray round-trip, no speckle)."""
|
| 50 |
+
if scale > 1:
|
| 51 |
+
w2 = max(64, ((W // scale) // 64) * 64) # width must be a multiple of 64 (AIE granularity)
|
| 52 |
+
h2 = max(2, ((H // scale) // 2) * 2) # even height
|
| 53 |
+
small = cv2.resize(bgr, (w2, h2), interpolation=cv2.INTER_AREA)
|
| 54 |
+
sb = _blur_planes_npu(small, w2, h2, passes)
|
| 55 |
+
return cv2.resize(sb, (W, H), interpolation=cv2.INTER_LINEAR)
|
| 56 |
+
return _blur_planes_npu(bgr, W, H, passes)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
_mask_cache = {"m": None, "i": -1, "every": 3}
|
| 60 |
+
def get_mask(bgr, W, H, seg):
|
| 61 |
+
"""Person mask (1=foreground). Recomputed every Nth frame and reused between (people move slowly)."""
|
| 62 |
+
_mask_cache["i"] += 1
|
| 63 |
+
if _mask_cache["m"] is not None and seg[0] != "placeholder" and (_mask_cache["i"] % _mask_cache["every"]):
|
| 64 |
+
return _mask_cache["m"]
|
| 65 |
+
kind, obj = seg
|
| 66 |
+
if kind == "mediapipe":
|
| 67 |
+
res = obj.process(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB))
|
| 68 |
+
m = (res.segmentation_mask > 0.5).astype(np.uint8)
|
| 69 |
+
return cv2.resize(m, (W, H), interpolation=cv2.INTER_NEAREST)
|
| 70 |
+
if kind == "onnx": # onnxruntime session (MODNet-style matte)
|
| 71 |
+
inp = cv2.resize(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB), (256, 256)).astype(np.float32)
|
| 72 |
+
inp = ((inp / 255.0 - 0.5) / 0.5).transpose(2, 0, 1)[None] # [-1,1], NCHW RGB
|
| 73 |
+
out = obj.run(None, {obj.get_inputs()[0].name: inp})[0]
|
| 74 |
+
m = (cv2.resize(out[0, 0], (W, H)) > 0.5).astype(np.uint8); _mask_cache["m"] = m; return m
|
| 75 |
+
# placeholder: center ellipse (NOT real segmentation β demo stub)
|
| 76 |
+
m = np.zeros((H, W), np.uint8)
|
| 77 |
+
cv2.ellipse(m, (W // 2, H // 2), (W // 5, int(H * 0.45)), 0, 0, 360, 1, -1)
|
| 78 |
+
return m
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def load_seg(onnx_path=None):
|
| 82 |
+
try:
|
| 83 |
+
import mediapipe as mp
|
| 84 |
+
return ("mediapipe", mp.solutions.selfie_segmentation.SelfieSegmentation(model_selection=1))
|
| 85 |
+
except Exception:
|
| 86 |
+
pass
|
| 87 |
+
if onnx_path and os.path.exists(onnx_path):
|
| 88 |
+
try:
|
| 89 |
+
import onnxruntime as ort
|
| 90 |
+
return ("onnx", ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"]))
|
| 91 |
+
except Exception as e:
|
| 92 |
+
print(f" [seg] onnxruntime load failed: {e}")
|
| 93 |
+
print(" [seg] no mediapipe/onnx β using CENTER-ELLIPSE PLACEHOLDER (not real segmentation)")
|
| 94 |
+
return ("placeholder", None)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def background_blur(bgr, W, H, in_t, b_t, out_t, seg, passes, npu_blur=False):
|
| 98 |
+
mask = get_mask(bgr, W, H, seg) # CPU segmentation
|
| 99 |
+
if npu_blur:
|
| 100 |
+
# EXPERIMENTAL: NPU color multi-pass blur. Known quality issues (fixed-point: the
|
| 101 |
+
# filter2d int16->int8 coeff truncation + multi-pass gain compounding β artifacts /
|
| 102 |
+
# saturation). Needs a purpose-built unity-gain NPU blur kernel. Not production-ready.
|
| 103 |
+
blurred = npu_color_blur(bgr, W, H, passes, _NPU_SCALE)
|
| 104 |
+
else:
|
| 105 |
+
# Default: strong CPU Gaussian (clean bokeh). The NPU's proven role here is the edge
|
| 106 |
+
# effect; a clean NPU blur is WIP (see npu_blur above).
|
| 107 |
+
blurred = cv2.GaussianBlur(bgr, (0, 0), sigmaX=8 + 2 * passes)
|
| 108 |
+
m3 = cv2.GaussianBlur(mask.astype(np.float32), (7, 7), 0)[:, :, None] # feather mask edges
|
| 109 |
+
return (bgr * m3 + blurred * (1 - m3)).astype(np.uint8)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def main():
|
| 113 |
+
ap = argparse.ArgumentParser()
|
| 114 |
+
ap.add_argument("--device", default="/dev/video0")
|
| 115 |
+
ap.add_argument("-W", "--width", type=int, default=1280)
|
| 116 |
+
ap.add_argument("-H", "--height", type=int, default=720)
|
| 117 |
+
ap.add_argument("--passes", type=int, default=4, help="blur strength")
|
| 118 |
+
ap.add_argument("--npu-blur", action="store_true", help="blur on the NPU (custom kernel); default = CPU Gaussian")
|
| 119 |
+
ap.add_argument("--npu-scale", type=int, default=4, help="NPU blur downscale factor (4 = validated/known-good; >4 can ERT-timeout, AIE dim-sensitive)")
|
| 120 |
+
ap.add_argument("--onnx", default=None, help="optional segmentation ONNX model for opencv-DNN")
|
| 121 |
+
ap.add_argument("--loopback", default=None)
|
| 122 |
+
ap.add_argument("--frames", type=int, default=60)
|
| 123 |
+
ap.add_argument("--image", default=None, help="blur a still image instead of the camera (test)")
|
| 124 |
+
opts = ap.parse_args()
|
| 125 |
+
W, H = opts.width, opts.height
|
| 126 |
+
global _NPU_SCALE; _NPU_SCALE = opts.npu_scale
|
| 127 |
+
seg = load_seg(opts.onnx)
|
| 128 |
+
ts = W * H * 4
|
| 129 |
+
in_t = iron.tensor(np.zeros(ts, np.int8), dtype=np.int8, device="npu")
|
| 130 |
+
b_t = iron.zeros(16 * 16, dtype=np.int32, device="npu")
|
| 131 |
+
out_t = iron.zeros(ts, dtype=np.int8, device="npu")
|
| 132 |
+
|
| 133 |
+
if opts.image: # still-image test path
|
| 134 |
+
img = cv2.resize(cv2.imread(opts.image), (W, H))
|
| 135 |
+
background_blur(img, W, H, in_t, b_t, out_t, seg, opts.passes, opts.npu_blur) # warm
|
| 136 |
+
t0 = time.perf_counter(); res = background_blur(img, W, H, in_t, b_t, out_t, seg, opts.passes, opts.npu_blur)
|
| 137 |
+
dt = time.perf_counter() - t0
|
| 138 |
+
cv2.imwrite("./bgblur_before.png", img); cv2.imwrite("./bgblur_after.png", res)
|
| 139 |
+
print(f" background-blur still: {dt*1e3:.1f} ms ({opts.passes} passes/ch, seg={seg[0]}) -> saved bgblur_*.png")
|
| 140 |
+
return
|
| 141 |
+
|
| 142 |
+
cap = cv2.VideoCapture(opts.device, cv2.CAP_V4L2)
|
| 143 |
+
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
|
| 144 |
+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, W); cap.set(cv2.CAP_PROP_FRAME_HEIGHT, H)
|
| 145 |
+
ok, f = cap.read()
|
| 146 |
+
if not ok: print("ERROR: no camera frame"); sys.exit(1)
|
| 147 |
+
f = cv2.resize(f, (W, H)); background_blur(f, W, H, in_t, b_t, out_t, seg, opts.passes, opts.npu_blur) # warm
|
| 148 |
+
n = 0; t0 = time.perf_counter()
|
| 149 |
+
sink = None
|
| 150 |
+
if opts.loopback:
|
| 151 |
+
import pyvirtualcam
|
| 152 |
+
sink = pyvirtualcam.Camera(width=W, height=H, fps=30, device=opts.loopback,
|
| 153 |
+
fmt=pyvirtualcam.PixelFormat.RGB)
|
| 154 |
+
while n < opts.frames or (opts.loopback and opts.frames == 0):
|
| 155 |
+
ok, f = cap.read()
|
| 156 |
+
if not ok: break
|
| 157 |
+
if f.shape[1] != W or f.shape[0] != H: f = cv2.resize(f, (W, H))
|
| 158 |
+
res = background_blur(f, W, H, in_t, b_t, out_t, seg, opts.passes, opts.npu_blur)
|
| 159 |
+
if sink: sink.send(cv2.cvtColor(res, cv2.COLOR_BGR2RGB)); sink.sleep_until_next_frame()
|
| 160 |
+
n += 1
|
| 161 |
+
dt = time.perf_counter() - t0; cap.release()
|
| 162 |
+
if sink: sink.close()
|
| 163 |
+
print(f" background-blur live: {n} frames, {n/dt:.1f} FPS end-to-end "
|
| 164 |
+
f"({opts.passes} strength, seg={seg[0]}, blur={'NPU-exp' if opts.npu_blur else 'CPU'})")
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
+
main()
|
camera/npu_camera_daemon.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
| 3 |
+
# Copyright (C) 2026 Scott Boudreaux / Elyan Labs. Commercial license: see ../COMMERCIAL.md
|
| 4 |
+
#
|
| 5 |
+
# open-xdna :: NPU camera-effects daemon (A1) β core path.
|
| 6 |
+
# Captures real frames from a v4l2 camera, runs the per-frame conv/colorspace effect on the
|
| 7 |
+
# XDNA1 NPU (rgba2gray -> 3x3 filter2d -> threshold -> gray2rgba -> blend = edge-stylize), and
|
| 8 |
+
# measures LIVE end-to-end FPS on real camera data. Saves a before/after sample as proof.
|
| 9 |
+
#
|
| 10 |
+
# This is the core of an open-source Linux "Studio Effects"-style NPU webcam filter. The virtual
|
| 11 |
+
# v4l2loopback output (so Zoom/OBS see it as a camera) is the next layer; --loopback enables it
|
| 12 |
+
# if /dev/videoN (v4l2loopback) + pyvirtualcam are present.
|
| 13 |
+
|
| 14 |
+
import os, sys, time, argparse
|
| 15 |
+
import numpy as np
|
| 16 |
+
import cv2
|
| 17 |
+
|
| 18 |
+
MLIR_AIE = os.environ.get("MLIR_AIE_DIR", os.path.expanduser("~/open-xdna/mlir-aie"))
|
| 19 |
+
sys.path.insert(0, os.path.join(MLIR_AIE, "programming_examples/vision/edge_detect"))
|
| 20 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # for blur_pipeline (same dir)
|
| 21 |
+
import aie.iron as iron
|
| 22 |
+
_DESIGN = None # @iron.jit per-frame pipeline; set in main() from --effect
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def npu_effect(frame_bgr, W, H, in_t, b_t, out_t):
|
| 26 |
+
"""One frame: BGR uint8 -> NPU effect -> BGR uint8."""
|
| 27 |
+
rgba = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGBA).reshape(-1) # H*W*4 uint8
|
| 28 |
+
in_t.numpy()[:] = rgba.view(np.int8) # in-place into NPU tensor
|
| 29 |
+
_DESIGN(in_t, b_t, out_t, width=W, height=H)
|
| 30 |
+
out_rgba = out_t.numpy().view(np.uint8).reshape(H, W, 4)
|
| 31 |
+
return cv2.cvtColor(out_rgba, cv2.COLOR_RGBA2BGR)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def main():
|
| 35 |
+
ap = argparse.ArgumentParser()
|
| 36 |
+
ap.add_argument("--device", default="/dev/video0")
|
| 37 |
+
ap.add_argument("-W", "--width", type=int, default=1280)
|
| 38 |
+
ap.add_argument("-H", "--height", type=int, default=720)
|
| 39 |
+
ap.add_argument("--frames", type=int, default=120)
|
| 40 |
+
ap.add_argument("--loopback", default=None, help="v4l2loopback device to output to, e.g. /dev/video10")
|
| 41 |
+
ap.add_argument("--stream-frames", type=int, default=0, help="frames to stream (0 = until Ctrl-C)")
|
| 42 |
+
ap.add_argument("--effect", choices=["edge", "blur"], default="edge", help="NPU effect")
|
| 43 |
+
ap.add_argument("--out", default="./cam_sample")
|
| 44 |
+
opts = ap.parse_args()
|
| 45 |
+
W, H = opts.width, opts.height
|
| 46 |
+
|
| 47 |
+
global _DESIGN
|
| 48 |
+
if opts.effect == "blur":
|
| 49 |
+
from blur_pipeline import blur as _DESIGN
|
| 50 |
+
else:
|
| 51 |
+
from edge_detect import edge_detect as _DESIGN
|
| 52 |
+
|
| 53 |
+
cap = cv2.VideoCapture(opts.device, cv2.CAP_V4L2)
|
| 54 |
+
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG")) # MJPG β higher USB capture FPS
|
| 55 |
+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, W); cap.set(cv2.CAP_PROP_FRAME_HEIGHT, H)
|
| 56 |
+
if not cap.isOpened():
|
| 57 |
+
print(f"ERROR: cannot open {opts.device}"); sys.exit(1)
|
| 58 |
+
|
| 59 |
+
ts = W * H * 4
|
| 60 |
+
in_t = iron.tensor(np.zeros(ts, dtype=np.int8), dtype=np.int8, device="npu")
|
| 61 |
+
b_t = iron.zeros(16 * 16, dtype=np.int32, device="npu")
|
| 62 |
+
out_t = iron.zeros(ts, dtype=np.int8, device="npu")
|
| 63 |
+
|
| 64 |
+
print(f"open-xdna :: NPU camera daemon β {opts.device} @ {W}x{H}, {opts.frames} frames")
|
| 65 |
+
# warm: grab one frame, compile the pipeline
|
| 66 |
+
ok, f = cap.read()
|
| 67 |
+
if not ok: print("ERROR: no frame from camera"); sys.exit(1)
|
| 68 |
+
f = cv2.resize(f, (W, H))
|
| 69 |
+
npu_effect(f, W, H, in_t, b_t, out_t)
|
| 70 |
+
cv2.imwrite(opts.out + "_before.png", f)
|
| 71 |
+
cv2.imwrite(opts.out + "_after.png", npu_effect(f, W, H, in_t, b_t, out_t))
|
| 72 |
+
|
| 73 |
+
# Stream mode: capture -> NPU effect -> v4l2loopback virtual cam (so Zoom/OBS/browser see it)
|
| 74 |
+
if opts.loopback:
|
| 75 |
+
import pyvirtualcam
|
| 76 |
+
nf = opts.stream_frames
|
| 77 |
+
print(f" streaming NPU-effected frames -> {opts.loopback}"
|
| 78 |
+
+ (f" for {nf} frames" if nf else " (Ctrl-C to stop)"))
|
| 79 |
+
i = 0
|
| 80 |
+
with pyvirtualcam.Camera(width=W, height=H, fps=30, device=opts.loopback,
|
| 81 |
+
fmt=pyvirtualcam.PixelFormat.RGB) as vcam:
|
| 82 |
+
while nf == 0 or i < nf:
|
| 83 |
+
ok, f = cap.read()
|
| 84 |
+
if not ok: break
|
| 85 |
+
if f.shape[1] != W or f.shape[0] != H: f = cv2.resize(f, (W, H))
|
| 86 |
+
out_bgr = npu_effect(f, W, H, in_t, b_t, out_t)
|
| 87 |
+
vcam.send(cv2.cvtColor(out_bgr, cv2.COLOR_BGR2RGB))
|
| 88 |
+
vcam.sleep_until_next_frame()
|
| 89 |
+
i += 1
|
| 90 |
+
cap.release()
|
| 91 |
+
print(f" streamed {i} frames to {opts.loopback} β select 'NPU Camera (open-xdna)' in any app.")
|
| 92 |
+
return
|
| 93 |
+
|
| 94 |
+
# A) capture-only (find the camera ceiling)
|
| 95 |
+
n = 0; t0 = time.perf_counter()
|
| 96 |
+
while n < opts.frames:
|
| 97 |
+
ok, f = cap.read()
|
| 98 |
+
if not ok: break
|
| 99 |
+
if f.shape[1] != W or f.shape[0] != H: f = cv2.resize(f, (W, H))
|
| 100 |
+
n += 1
|
| 101 |
+
cap_fps = n / (time.perf_counter() - t0)
|
| 102 |
+
|
| 103 |
+
# B) capture + NPU effect (end-to-end)
|
| 104 |
+
n = 0; t0 = time.perf_counter()
|
| 105 |
+
while n < opts.frames:
|
| 106 |
+
ok, f = cap.read()
|
| 107 |
+
if not ok: break
|
| 108 |
+
if f.shape[1] != W or f.shape[0] != H: f = cv2.resize(f, (W, H))
|
| 109 |
+
npu_effect(f, W, H, in_t, b_t, out_t)
|
| 110 |
+
n += 1
|
| 111 |
+
e2e_fps = n / (time.perf_counter() - t0)
|
| 112 |
+
cap.release()
|
| 113 |
+
|
| 114 |
+
npu_only = 1000.0 / 2.21 if (W, H) == (1280, 720) else None # from npu_camera_fps.py
|
| 115 |
+
print(f" capture-only: {cap_fps:5.1f} FPS (camera/USB ceiling)")
|
| 116 |
+
print(f" capture + NPU effect:{e2e_fps:5.1f} FPS (end-to-end)")
|
| 117 |
+
if npu_only: print(f" NPU effect alone: {npu_only:5.0f} FPS (2.21 ms/frame @720p β not the bottleneck)")
|
| 118 |
+
print(f" sample saved: {opts.out}_before.png / _after.png")
|
| 119 |
+
overhead = (1.0/e2e_fps - 1.0/cap_fps) * 1000 if e2e_fps > 0 and cap_fps > 0 else 0
|
| 120 |
+
print(f" VERDICT: NPU adds ~{overhead:.1f} ms/frame; the limit is CAMERA CAPTURE ({cap_fps:.0f} FPS), not the NPU.")
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
main()
|
camera/npu_camera_fps.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
| 3 |
+
# Copyright (C) 2026 Scott Boudreaux / Elyan Labs. Commercial license: see ../COMMERCIAL.md
|
| 4 |
+
#
|
| 5 |
+
# open-xdna :: FPS gate for an NPU camera-effects daemon (A1).
|
| 6 |
+
# Times the full per-frame conv/colorspace pipeline (rgba2gray -> 3x3 filter2d -> threshold ->
|
| 7 |
+
# gray2rgba -> blend) on the XDNA1 NPU at camera resolutions, INCLUDING the host DMA round-trip
|
| 8 |
+
# (fill input frame -> run -> drain output) β exactly what a v4l2 daemon pays per frame.
|
| 9 |
+
# Gate: >=30 FPS at 720p/1080p means real-time camera effects on the NPU are feasible.
|
| 10 |
+
|
| 11 |
+
import os, sys, time
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
MLIR_AIE = os.environ.get("MLIR_AIE_DIR", os.path.expanduser("~/open-xdna/mlir-aie"))
|
| 15 |
+
sys.path.insert(0, os.path.join(MLIR_AIE, "programming_examples/vision/edge_detect"))
|
| 16 |
+
import aie.iron as iron
|
| 17 |
+
from edge_detect import edge_detect # the @iron.jit per-frame vision pipeline
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def bench(W, H, iters=50):
|
| 21 |
+
ts = W * H * 4 # RGBA int8 frame
|
| 22 |
+
rng = np.random.default_rng(0)
|
| 23 |
+
in_t = iron.tensor(rng.integers(-128, 127, size=(ts,), dtype=np.int8), dtype=np.int8, device="npu")
|
| 24 |
+
b_t = iron.zeros(16 * 16, dtype=np.int32, device="npu")
|
| 25 |
+
out_t = iron.zeros(ts, dtype=np.int8, device="npu")
|
| 26 |
+
# warm/compile (JIT specializes per W,H)
|
| 27 |
+
edge_detect(in_t, b_t, out_t, width=W, height=H)
|
| 28 |
+
edge_detect(in_t, b_t, out_t, width=W, height=H)
|
| 29 |
+
t0 = time.perf_counter()
|
| 30 |
+
for _ in range(iters):
|
| 31 |
+
edge_detect(in_t, b_t, out_t, width=W, height=H)
|
| 32 |
+
dt = (time.perf_counter() - t0) / iters
|
| 33 |
+
return dt * 1e3, 1.0 / dt
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def main():
|
| 37 |
+
print("open-xdna :: NPU camera-effects FPS gate (full conv/colorspace pipeline + DMA round-trip)")
|
| 38 |
+
print(f" {'res':>8} {'pixels':>10} {'ms/frame':>10} {'FPS':>8} verdict")
|
| 39 |
+
for W, H, name in [(640, 480, "480p"), (1280, 720, "720p"), (1920, 1080, "1080p")]:
|
| 40 |
+
ms, fps = bench(W, H)
|
| 41 |
+
v = "OK β₯60" if fps >= 60 else ("OK β₯30" if fps >= 30 else "below 30 β too slow")
|
| 42 |
+
print(f" {name:>8} {W*H:>10} {ms:>10.2f} {fps:>8.1f} {v}")
|
| 43 |
+
print(" (per-frame = full pipeline + host fill/drain; this is the daemon's real per-frame cost)")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
main()
|
camera/test_blur.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys, time, os
|
| 2 |
+
import numpy as np, cv2
|
| 3 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 4 |
+
sys.path.insert(0, HERE)
|
| 5 |
+
import aie.iron as iron
|
| 6 |
+
from blur_pipeline import blur
|
| 7 |
+
|
| 8 |
+
W, H = 1280, 720
|
| 9 |
+
ts = W * H * 4
|
| 10 |
+
# sharp checkerboard test pattern β blur must smooth it
|
| 11 |
+
img = np.zeros((H, W, 4), np.uint8); img[:, :, 3] = 255
|
| 12 |
+
mask = (np.add.outer(np.arange(H), np.arange(W))) % 2 == 0
|
| 13 |
+
img[mask] = [255, 255, 255, 255]
|
| 14 |
+
|
| 15 |
+
in_t = iron.tensor(img.reshape(-1).view(np.int8), dtype=np.int8, device="npu")
|
| 16 |
+
b_t = iron.zeros(16 * 16, dtype=np.int32, device="npu")
|
| 17 |
+
out_t = iron.zeros(ts, dtype=np.int8, device="npu")
|
| 18 |
+
blur(in_t, b_t, out_t, width=W, height=H)
|
| 19 |
+
blur(in_t, b_t, out_t, width=W, height=H)
|
| 20 |
+
t0 = time.perf_counter()
|
| 21 |
+
for _ in range(50):
|
| 22 |
+
blur(in_t, b_t, out_t, width=W, height=H)
|
| 23 |
+
dt = (time.perf_counter() - t0) / 50
|
| 24 |
+
o = out_t.numpy().view(np.uint8).reshape(H, W, 4)
|
| 25 |
+
|
| 26 |
+
sharp_var = float(img[H // 2, :, 0].astype(np.float32).var())
|
| 27 |
+
blur_var = float(o[H // 2, :, 0].astype(np.float32).var())
|
| 28 |
+
cv2.imwrite(os.path.join(HERE, "blur_before.png"), cv2.cvtColor(img, cv2.COLOR_RGBA2BGR))
|
| 29 |
+
cv2.imwrite(os.path.join(HERE, "blur_after.png"), cv2.cvtColor(o, cv2.COLOR_RGBA2BGR))
|
| 30 |
+
smoothed = "SMOOTHED ok" if blur_var < sharp_var * 0.8 else "NOT smoothed?"
|
| 31 |
+
print(f"blur @720p: {dt*1e3:.2f} ms/frame -> {1/dt:.0f} FPS")
|
| 32 |
+
print(f"row variance sharp={sharp_var:.0f} -> blurred={blur_var:.0f} ({smoothed})")
|
embeddings/README.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# embeddings (planned)
|
| 2 |
+
|
| 3 |
+
Batch sentence/RAG embedding generation on the NPU. **Status: candidate, not built.**
|
| 4 |
+
Rationale: embeddings are matmul-heavy and *batchable*, so they amortize the NPU's dispatch
|
| 5 |
+
overhead toward its ~64 GFLOP/s peak (measured in open-xdna) and offload the CPU/iGPU at ~6.6 W.
|
| 6 |
+
Needs: an embedding model's matmuls expressed via IRON, + a server interface.
|
ggml-backend/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ggml-backend (planned β the foundational enabler)
|
| 2 |
+
|
| 3 |
+
An XDNA NPU backend for ggml/llama.cpp so the NPU can participate in LLM inference directly
|
| 4 |
+
(not just as a coprocessor). **Status: not built β the biggest lift, biggest payoff.** Until then,
|
| 5 |
+
the NPU's measured LLM role is the prune/vision coprocessor (see open-xdna).
|
reply_to_poizone.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Thanks β and that's a sharp catch, you're right to push on it. I re-ran it and put it through an independent model review, and the honest answer lands on your side:
|
| 2 |
+
|
| 3 |
+
It's a real measurement but most likely a software-stack artifact, not a hardware result. Re-test (7B Q4_K, pp512, 780M via llama.cpp Vulkan/RADV): sweeping ubatch 256 β 1024 β 2048 left prefill flat at ~300 t/s, and pp1024 was ~295 β so it's robust, not an under-tuned batch-size knob. But that ~2.6x CPU lead almost certainly reflects the immaturity of the Mesa/RADV Vulkan Q4_K GEMM kernels (tiling / register-blocking / quantized paths) vs llama.cpp's hand-tuned AVX-512 CPU GEMM β not the 780M's hardware ceiling. RDNA3 has the FP16 throughput; the open Vulkan path just isn't realizing it on prefill yet.
|
| 4 |
+
|
| 5 |
+
So I've narrowed the claim in the repo to: "on the llama.cpp Vulkan/RADV stack, CPU prefill beats the 780M ~2.6x, robust to tuning β a software-stack characteristic, not proven hardware superiority." A ROCm comparison on the same silicon would settle it (no ROCm on my box yet β it's exactly what I'd test on AMD Dev Cloud). Decode still favors the 780M (+38β44%), as expected.
|
| 6 |
+
|
| 7 |
+
Updated BENCHMARKS.md with the re-test + this caveat, credited to your review. π
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Host-side deps (install into your IRON/mlir-aie venv):
|
| 2 |
+
opencv-python-headless>=4.8
|
| 3 |
+
pyvirtualcam>=0.11
|
| 4 |
+
onnxruntime>=1.16
|
| 5 |
+
numpy>=1.24
|
| 6 |
+
ml_dtypes>=0.3
|
| 7 |
+
# Plus a working XDNA NPU stack (XRT + amd/xdna-driver + IRON/mlir-aie + Peano) β see open-xdna.
|