elyanlabs commited on
Commit
200d040
Β·
verified Β·
1 Parent(s): 04e67b5

Source snapshot of github.com/Scottcjn/npu-linux-kit@edee964

Browse files
.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: AGPL v3](https://img.shields.io/badge/License-AGPLv3-blue.svg)](LICENSE)
22
+ [![Dual License: Commercial](https://img.shields.io/badge/License-Commercial-green.svg)](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.