npu-detect 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/cli.js +86 -0
- package/package.json +60 -0
- package/src/browser.js +205 -0
- package/src/index.js +400 -0
- package/src/types.d.ts +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dylan Meza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# npu-detect
|
|
2
|
+
|
|
3
|
+
Cross-platform NPU (Neural Processing Unit) detection for JavaScript.
|
|
4
|
+
|
|
5
|
+
Works in **Node.js** (server-side, shells out to OS tools) and **Browsers** (client-side, via the WebNN API).
|
|
6
|
+
|
|
7
|
+
## Why?
|
|
8
|
+
|
|
9
|
+
You can query CPU info with `os.cpus()` and GPU info with `navigator.gpu` — but there's no equivalent for NPUs. This library fills that gap.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install npu-detect
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Node.js
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
const { detectNPU, hasNPU, npuSummary } = require('npu-detect');
|
|
23
|
+
|
|
24
|
+
// Full detection with details
|
|
25
|
+
const info = await detectNPU();
|
|
26
|
+
console.log(info);
|
|
27
|
+
// {
|
|
28
|
+
// detected: true,
|
|
29
|
+
// platform: 'darwin',
|
|
30
|
+
// devices: [{ name: 'Apple Neural Engine (ANE)', cores: 16, ... }],
|
|
31
|
+
// cpuHeuristic: { likely: true, vendor: 'Apple', npuName: 'Apple Neural Engine' },
|
|
32
|
+
// ...
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
// Quick boolean
|
|
36
|
+
const has = await hasNPU(); // true
|
|
37
|
+
|
|
38
|
+
// One-liner summary
|
|
39
|
+
const summary = await npuSummary(); // "NPU detected: Apple Neural Engine (ANE)"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Browser
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import { detectNPU, hasNPU } from 'npu-detect/browser';
|
|
46
|
+
|
|
47
|
+
const info = await detectNPU({ benchmark: true });
|
|
48
|
+
console.log(info);
|
|
49
|
+
// {
|
|
50
|
+
// webnnSupported: true,
|
|
51
|
+
// npuAvailable: true,
|
|
52
|
+
// devices: {
|
|
53
|
+
// npu: { available: true, benchmark: { functional: true, execLatencyMs: 0.4 } },
|
|
54
|
+
// cpu: { available: true, ... },
|
|
55
|
+
// gpu: { available: true, ... },
|
|
56
|
+
// },
|
|
57
|
+
// ...
|
|
58
|
+
// }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### CLI
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx npu-detect
|
|
65
|
+
npx npu-detect --verbose
|
|
66
|
+
npx npu-detect --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
### Node.js Detection (per platform)
|
|
72
|
+
|
|
73
|
+
| Platform | Method |
|
|
74
|
+
|----------|--------|
|
|
75
|
+
| **Linux** | Checks `/dev/accel/*` devices, `lspci` for NPU-class hardware, `lsmod` for `intel_vpu`/`amdxdna` kernel modules, sysfs for NPU frequency |
|
|
76
|
+
| **Windows** | Queries Device Manager's "Neural processors" class via PowerShell (`Get-PnpDevice`), falls back to WMI |
|
|
77
|
+
| **macOS** | Uses `ioreg` to find AppleNeuralEngine, infers ANE core count and TOPS from chip model, reads `powermetrics` for ANE power (requires root) |
|
|
78
|
+
| **All** | CPU model string heuristic to identify known NPU-equipped processors (Intel Core Ultra, Snapdragon X, Ryzen AI, Apple M-series, etc.) |
|
|
79
|
+
|
|
80
|
+
### Browser Detection
|
|
81
|
+
|
|
82
|
+
Uses the **W3C Web Neural Network API** (`navigator.ml`):
|
|
83
|
+
|
|
84
|
+
1. Checks if `navigator.ml` exists
|
|
85
|
+
2. Attempts `navigator.ml.createContext({ deviceType: 'npu' })` — success means NPU is available
|
|
86
|
+
3. Optionally runs a minimal add-two-matrices computation graph to verify the device is functional
|
|
87
|
+
4. Also probes `cpu` and `gpu` device types for comparison
|
|
88
|
+
|
|
89
|
+
> **Note:** WebNN requires Chrome/Edge with the flag `chrome://flags/#web-machine-learning-neural-network` enabled, or Edge with `--disable_webnn_for_npu=0` for NPU specifically.
|
|
90
|
+
|
|
91
|
+
## Supported NPU Hardware
|
|
92
|
+
|
|
93
|
+
| Vendor | NPU | Detection |
|
|
94
|
+
|--------|-----|-----------|
|
|
95
|
+
| **Intel** | AI Boost (Core Ultra / Meteor Lake, Arrow Lake, Lunar Lake) | Linux: `intel_vpu` module + `/dev/accel/accel0`. Windows: Device Manager. |
|
|
96
|
+
| **AMD** | XDNA / XDNA2 (Ryzen AI, Hawk Point, Strix Point) | Linux: `amdxdna` module. Windows: Device Manager. |
|
|
97
|
+
| **Qualcomm** | Hexagon (Snapdragon X Elite/Plus) | Windows: Device Manager. Browser: WebNN. |
|
|
98
|
+
| **Apple** | Neural Engine (M1–M4, A11–A19) | macOS: `ioreg` + chip identification. |
|
|
99
|
+
|
|
100
|
+
## API Reference
|
|
101
|
+
|
|
102
|
+
### Node.js (`npu-detect`)
|
|
103
|
+
|
|
104
|
+
#### `detectNPU(options?): Promise<NPUDetectionResult>`
|
|
105
|
+
|
|
106
|
+
Full detection. Options:
|
|
107
|
+
- `cpuHeuristic` (boolean, default `true`) — also check CPU model string
|
|
108
|
+
- `verbose` (boolean, default `false`) — include raw OS command output
|
|
109
|
+
|
|
110
|
+
#### `hasNPU(): Promise<boolean>`
|
|
111
|
+
|
|
112
|
+
Quick boolean — confirmed NPU present?
|
|
113
|
+
|
|
114
|
+
#### `npuSummary(): Promise<string>`
|
|
115
|
+
|
|
116
|
+
Human-readable one-liner.
|
|
117
|
+
|
|
118
|
+
### Browser (`npu-detect/browser`)
|
|
119
|
+
|
|
120
|
+
#### `detectNPU(options?): Promise<BrowserNPUDetectionResult>`
|
|
121
|
+
|
|
122
|
+
Full browser detection. Options:
|
|
123
|
+
- `benchmark` (boolean, default `false`) — run a test computation on each device
|
|
124
|
+
- `probeAll` (boolean, default `true`) — also probe CPU and GPU
|
|
125
|
+
|
|
126
|
+
#### `hasNPU(): Promise<boolean>`
|
|
127
|
+
|
|
128
|
+
Quick boolean — NPU available via WebNN?
|
|
129
|
+
|
|
130
|
+
#### `probeDevice(deviceType): Promise<{available, error?, context?}>`
|
|
131
|
+
|
|
132
|
+
Low-level probe for a specific WebNN device type.
|
|
133
|
+
|
|
134
|
+
#### `benchmarkDevice(deviceType): Promise<{functional, buildLatencyMs?, execLatencyMs?, error?}>`
|
|
135
|
+
|
|
136
|
+
Run a minimal computation on a device and measure latency.
|
|
137
|
+
|
|
138
|
+
## Caveats
|
|
139
|
+
|
|
140
|
+
- **Linux:** NPU detection requires the kernel module to be loaded and `/dev/accel/` to be populated. Some sysfs paths require root.
|
|
141
|
+
- **Windows:** PowerShell queries may be slow (~1-2s). The "NeuralProcessor" PnP class requires up-to-date drivers.
|
|
142
|
+
- **macOS:** Apple provides almost no public API for ANE introspection. Detection relies on `ioreg` and chip identification. `powermetrics` requires `sudo`.
|
|
143
|
+
- **Browser:** WebNN is still an emerging standard. NPU support requires specific browser flags and up-to-date GPU/NPU drivers.
|
|
144
|
+
- **CPU Heuristic:** The CPU model string check is a "best guess" — it tells you the CPU *should* have an NPU, not that it's accessible.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
package/cli.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* npu-detect CLI
|
|
5
|
+
* Quick way to check NPU status from the terminal.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx npu-detect
|
|
9
|
+
* node cli.js
|
|
10
|
+
* node cli.js --verbose
|
|
11
|
+
* node cli.js --json
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { detectNPU, npuSummary } = require('./src/index');
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
18
|
+
const jsonOutput = args.includes('--json') || args.includes('-j');
|
|
19
|
+
|
|
20
|
+
(async () => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await detectNPU({ verbose, cpuHeuristic: true });
|
|
23
|
+
|
|
24
|
+
if (jsonOutput) {
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Pretty print
|
|
30
|
+
const BOLD = '\x1b[1m';
|
|
31
|
+
const GREEN = '\x1b[32m';
|
|
32
|
+
const RED = '\x1b[31m';
|
|
33
|
+
const YELLOW = '\x1b[33m';
|
|
34
|
+
const CYAN = '\x1b[36m';
|
|
35
|
+
const DIM = '\x1b[2m';
|
|
36
|
+
const RESET = '\x1b[0m';
|
|
37
|
+
|
|
38
|
+
console.log();
|
|
39
|
+
console.log(`${BOLD}╔══════════════════════════════════════╗${RESET}`);
|
|
40
|
+
console.log(`${BOLD}║ NPU Detection Report ║${RESET}`);
|
|
41
|
+
console.log(`${BOLD}╚══════════════════════════════════════╝${RESET}`);
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
console.log(` ${DIM}Platform:${RESET} ${result.platform}`);
|
|
45
|
+
console.log(` ${DIM}Time:${RESET} ${result.timestamp}`);
|
|
46
|
+
console.log();
|
|
47
|
+
|
|
48
|
+
if (result.detected) {
|
|
49
|
+
console.log(` ${GREEN}●${RESET} ${BOLD}NPU Detected${RESET}`);
|
|
50
|
+
for (const dev of result.devices) {
|
|
51
|
+
console.log(` ${CYAN}→${RESET} ${dev.name}`);
|
|
52
|
+
if (dev.status) console.log(` Status: ${dev.status}`);
|
|
53
|
+
if (dev.cores) console.log(` Cores: ${dev.cores}`);
|
|
54
|
+
if (dev.estimatedTops) console.log(` Est. TOPS: ${dev.estimatedTops}`);
|
|
55
|
+
if (dev.chip) console.log(` Chip: ${dev.chip}`);
|
|
56
|
+
console.log(` ${DIM}Source: ${dev.source}${RESET}`);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` ${RED}○${RESET} ${BOLD}No NPU Detected${RESET}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (result.likelyPresent) {
|
|
63
|
+
console.log();
|
|
64
|
+
console.log(` ${YELLOW}⚠${RESET} ${result.note}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (result.cpuHeuristic) {
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(` ${DIM}CPU Model:${RESET} ${result.cpuHeuristic.cpuModel}`);
|
|
70
|
+
if (result.cpuHeuristic.likely) {
|
|
71
|
+
console.log(` ${DIM}NPU Hint:${RESET} ${GREEN}${result.cpuHeuristic.npuName}${RESET} (${result.cpuHeuristic.vendor})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (verbose && result._raw) {
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(` ${DIM}─── Raw Data ───${RESET}`);
|
|
78
|
+
console.log(JSON.stringify(result._raw, null, 2).split('\n').map(l => ` ${DIM}${l}${RESET}`).join('\n'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error('Detection failed:', err.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "npu-detect",
|
|
3
|
+
"author": {
|
|
4
|
+
"name": "Dylan Meza",
|
|
5
|
+
"email": "dylanmmeza@gmail.com",
|
|
6
|
+
"url": "https://github.com/dylanmmeza"
|
|
7
|
+
},
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"description": "Cross-platform NPU (Neural Processing Unit) detection for JavaScript — Node.js + Browser (WebNN)",
|
|
10
|
+
"main": "src/index.js",
|
|
11
|
+
"types": "src/types.d.ts",
|
|
12
|
+
"bin": {
|
|
13
|
+
"npu-detect": "cli.js"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"require": "./src/index.js",
|
|
18
|
+
"types": "./src/types.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./browser": {
|
|
21
|
+
"import": "./src/browser.js",
|
|
22
|
+
"default": "./src/browser.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node test/test.js",
|
|
27
|
+
"detect": "node cli.js"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"npu",
|
|
31
|
+
"neural-processing-unit",
|
|
32
|
+
"ai",
|
|
33
|
+
"machine-learning",
|
|
34
|
+
"hardware-detection",
|
|
35
|
+
"webnn",
|
|
36
|
+
"apple-neural-engine",
|
|
37
|
+
"intel-ai-boost",
|
|
38
|
+
"amd-xdna",
|
|
39
|
+
"qualcomm-hexagon",
|
|
40
|
+
"system-info"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/dylanmmeza/npu-detect.git"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/dylanmmeza/npu-detect#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/dylanmmeza/npu-detect/issues"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=16.0.0"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"src/",
|
|
56
|
+
"cli.js",
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"README.md"
|
|
59
|
+
]
|
|
60
|
+
}
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npu-detect/browser
|
|
3
|
+
* Browser-side NPU detection using the WebNN API (navigator.ml)
|
|
4
|
+
*
|
|
5
|
+
* This module runs in the browser and probes for NPU availability
|
|
6
|
+
* using the W3C Web Neural Network API.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { detectNPU } from 'npu-detect/browser';
|
|
10
|
+
* const info = await detectNPU();
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Probe whether a specific WebNN device type is available
|
|
15
|
+
* by attempting to create an MLContext with that device.
|
|
16
|
+
*
|
|
17
|
+
* @param {'cpu'|'gpu'|'npu'} deviceType
|
|
18
|
+
* @returns {Promise<{available: boolean, error?: string, context?: MLContext}>}
|
|
19
|
+
*/
|
|
20
|
+
async function probeDevice(deviceType) {
|
|
21
|
+
try {
|
|
22
|
+
const context = await navigator.ml.createContext({ deviceType });
|
|
23
|
+
return { available: true, context };
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return { available: false, error: err.message || String(err) };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Attempt to run a minimal computation graph on a device to verify it actually works.
|
|
31
|
+
*
|
|
32
|
+
* @param {'cpu'|'gpu'|'npu'} deviceType
|
|
33
|
+
* @returns {Promise<{functional: boolean, latencyMs?: number, error?: string}>}
|
|
34
|
+
*/
|
|
35
|
+
async function benchmarkDevice(deviceType) {
|
|
36
|
+
try {
|
|
37
|
+
const context = await navigator.ml.createContext({ deviceType });
|
|
38
|
+
const builder = new MLGraphBuilder(context);
|
|
39
|
+
|
|
40
|
+
const descriptor = { dataType: 'float32', shape: [2, 2] };
|
|
41
|
+
const a = builder.input('A', descriptor);
|
|
42
|
+
const b = builder.input('B', descriptor);
|
|
43
|
+
const c = builder.add(a, b);
|
|
44
|
+
|
|
45
|
+
const start = performance.now();
|
|
46
|
+
const graph = await builder.build({ C: c });
|
|
47
|
+
const buildTime = performance.now() - start;
|
|
48
|
+
|
|
49
|
+
// Create tensors and run
|
|
50
|
+
const tensorA = await context.createTensor({
|
|
51
|
+
dataType: 'float32',
|
|
52
|
+
shape: [2, 2],
|
|
53
|
+
writable: true,
|
|
54
|
+
readable: true,
|
|
55
|
+
});
|
|
56
|
+
const tensorB = await context.createTensor({
|
|
57
|
+
dataType: 'float32',
|
|
58
|
+
shape: [2, 2],
|
|
59
|
+
writable: true,
|
|
60
|
+
readable: true,
|
|
61
|
+
});
|
|
62
|
+
const tensorC = await context.createTensor({
|
|
63
|
+
dataType: 'float32',
|
|
64
|
+
shape: [2, 2],
|
|
65
|
+
readable: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
context.writeTensor(tensorA, new Float32Array([1, 2, 3, 4]));
|
|
69
|
+
context.writeTensor(tensorB, new Float32Array([5, 6, 7, 8]));
|
|
70
|
+
|
|
71
|
+
const execStart = performance.now();
|
|
72
|
+
await context.dispatch(graph, { A: tensorA, B: tensorB }, { C: tensorC });
|
|
73
|
+
const execTime = performance.now() - execStart;
|
|
74
|
+
|
|
75
|
+
// Read result to verify correctness
|
|
76
|
+
const resultBuffer = await context.readTensor(tensorC);
|
|
77
|
+
const result = new Float32Array(resultBuffer);
|
|
78
|
+
const correct = result[0] === 6 && result[1] === 8 && result[2] === 10 && result[3] === 12;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
functional: correct,
|
|
82
|
+
buildLatencyMs: Math.round(buildTime * 100) / 100,
|
|
83
|
+
execLatencyMs: Math.round(execTime * 100) / 100,
|
|
84
|
+
};
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return { functional: false, error: err.message || String(err) };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Detect NPU and ML hardware acceleration in the browser.
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} [options]
|
|
94
|
+
* @param {boolean} [options.benchmark=false] Run a small computation to verify each device works
|
|
95
|
+
* @param {boolean} [options.probeAll=true] Also probe CPU and GPU devices
|
|
96
|
+
* @returns {Promise<BrowserNPUDetectionResult>}
|
|
97
|
+
*/
|
|
98
|
+
async function detectNPU(options = {}) {
|
|
99
|
+
const { benchmark = false, probeAll = true } = options;
|
|
100
|
+
|
|
101
|
+
const result = {
|
|
102
|
+
webnnSupported: false,
|
|
103
|
+
npuAvailable: false,
|
|
104
|
+
devices: {},
|
|
105
|
+
browser: {
|
|
106
|
+
userAgent: navigator.userAgent,
|
|
107
|
+
platform: navigator.platform,
|
|
108
|
+
},
|
|
109
|
+
timestamp: new Date().toISOString(),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// 1. Check if WebNN API exists
|
|
113
|
+
if (!('ml' in navigator)) {
|
|
114
|
+
result.note = 'WebNN API (navigator.ml) is not available in this browser. ' +
|
|
115
|
+
'Enable it in Chrome/Edge via chrome://flags/#web-machine-learning-neural-network';
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
result.webnnSupported = true;
|
|
120
|
+
|
|
121
|
+
// 2. Probe NPU
|
|
122
|
+
const npuProbe = await probeDevice('npu');
|
|
123
|
+
result.npuAvailable = npuProbe.available;
|
|
124
|
+
result.devices.npu = {
|
|
125
|
+
available: npuProbe.available,
|
|
126
|
+
error: npuProbe.error || null,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (benchmark && npuProbe.available) {
|
|
130
|
+
result.devices.npu.benchmark = await benchmarkDevice('npu');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 3. Optionally probe CPU and GPU
|
|
134
|
+
if (probeAll) {
|
|
135
|
+
const cpuProbe = await probeDevice('cpu');
|
|
136
|
+
result.devices.cpu = {
|
|
137
|
+
available: cpuProbe.available,
|
|
138
|
+
error: cpuProbe.error || null,
|
|
139
|
+
};
|
|
140
|
+
if (benchmark && cpuProbe.available) {
|
|
141
|
+
result.devices.cpu.benchmark = await benchmarkDevice('cpu');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const gpuProbe = await probeDevice('gpu');
|
|
145
|
+
result.devices.gpu = {
|
|
146
|
+
available: gpuProbe.available,
|
|
147
|
+
error: gpuProbe.error || null,
|
|
148
|
+
};
|
|
149
|
+
if (benchmark && gpuProbe.available) {
|
|
150
|
+
result.devices.gpu.benchmark = await benchmarkDevice('gpu');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 4. WebGPU info for context
|
|
155
|
+
if (navigator.gpu) {
|
|
156
|
+
try {
|
|
157
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
158
|
+
if (adapter) {
|
|
159
|
+
const info = await adapter.requestAdapterInfo?.() || {};
|
|
160
|
+
result.webgpuAdapter = {
|
|
161
|
+
vendor: info.vendor || null,
|
|
162
|
+
architecture: info.architecture || null,
|
|
163
|
+
device: info.device || null,
|
|
164
|
+
description: info.description || null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
} catch { /* ignore */ }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 5. CPU heuristic from User-Agent
|
|
171
|
+
const ua = navigator.userAgent;
|
|
172
|
+
if (/Snapdragon/i.test(ua)) {
|
|
173
|
+
result.cpuHint = 'Qualcomm Snapdragon (likely has Hexagon NPU)';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Quick boolean — is an NPU available via WebNN?
|
|
181
|
+
*/
|
|
182
|
+
async function hasNPU() {
|
|
183
|
+
if (!('ml' in navigator)) return false;
|
|
184
|
+
const probe = await probeDevice('npu');
|
|
185
|
+
return probe.available;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get short summary string.
|
|
190
|
+
*/
|
|
191
|
+
async function npuSummary() {
|
|
192
|
+
const result = await detectNPU();
|
|
193
|
+
if (!result.webnnSupported) return 'WebNN not supported in this browser';
|
|
194
|
+
if (result.npuAvailable) return 'NPU available via WebNN API';
|
|
195
|
+
return 'No NPU detected (WebNN supported, but NPU device unavailable)';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ESM + CJS compatible export
|
|
199
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
200
|
+
module.exports = { detectNPU, hasNPU, npuSummary, probeDevice, benchmarkDevice };
|
|
201
|
+
} else if (typeof window !== 'undefined') {
|
|
202
|
+
window.NPUDetect = { detectNPU, hasNPU, npuSummary, probeDevice, benchmarkDevice };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export { detectNPU, hasNPU, npuSummary, probeDevice, benchmarkDevice };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npu-detect
|
|
3
|
+
* Cross-platform NPU (Neural Processing Unit) detection library for JavaScript.
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Node.js (server-side) — shells out to OS-specific tools
|
|
7
|
+
* - Browser (client-side) — probes WebNN API (navigator.ml)
|
|
8
|
+
*
|
|
9
|
+
* Usage (Node.js):
|
|
10
|
+
* const { detectNPU } = require('npu-detect');
|
|
11
|
+
* const info = await detectNPU();
|
|
12
|
+
*
|
|
13
|
+
* Usage (Browser):
|
|
14
|
+
* import { detectNPU } from 'npu-detect/browser';
|
|
15
|
+
* const info = await detectNPU();
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const { execSync } = require('child_process');
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
function exec(cmd) {
|
|
26
|
+
try {
|
|
27
|
+
return execSync(cmd, { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
28
|
+
} catch {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseWindowsWmic(raw) {
|
|
34
|
+
// Parse WMIC / PowerShell CSV-ish output into objects
|
|
35
|
+
const lines = raw.split(/\r?\n/).filter(Boolean);
|
|
36
|
+
if (lines.length < 2) return [];
|
|
37
|
+
const headers = lines[0].split(/\s{2,}/).map(h => h.trim());
|
|
38
|
+
return lines.slice(1).map(line => {
|
|
39
|
+
const values = line.split(/\s{2,}/).map(v => v.trim());
|
|
40
|
+
const obj = {};
|
|
41
|
+
headers.forEach((h, i) => { obj[h] = values[i] || ''; });
|
|
42
|
+
return obj;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Platform-specific detectors
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Linux NPU detection
|
|
52
|
+
* - Check /dev/accel/* devices (DRM accel subsystem used by Intel VPU / AMD XDNA)
|
|
53
|
+
* - Parse lspci for NPU-class devices
|
|
54
|
+
* - Read sysfs for frequency info
|
|
55
|
+
* - Check loaded kernel modules (intel_vpu, amdxdna)
|
|
56
|
+
*/
|
|
57
|
+
function detectLinux() {
|
|
58
|
+
const devices = [];
|
|
59
|
+
|
|
60
|
+
// 1. Check accel subsystem devices
|
|
61
|
+
const accelDevices = exec('ls /dev/accel/ 2>/dev/null');
|
|
62
|
+
const hasAccelDevices = accelDevices.length > 0;
|
|
63
|
+
|
|
64
|
+
// 2. Check for Intel VPU / NPU kernel module
|
|
65
|
+
const intelVpuLoaded = exec('lsmod 2>/dev/null | grep intel_vpu').length > 0;
|
|
66
|
+
const amdXdnaLoaded = exec('lsmod 2>/dev/null | grep amdxdna').length > 0;
|
|
67
|
+
|
|
68
|
+
// 3. lspci — look for processing accelerators (class 12xx) or known NPU vendors
|
|
69
|
+
const lspciOutput = exec('lspci -nn 2>/dev/null');
|
|
70
|
+
const npuPatterns = [
|
|
71
|
+
/neural/i,
|
|
72
|
+
/npu/i,
|
|
73
|
+
/processing accelerator/i,
|
|
74
|
+
/\[0bxx\]/i, // PCI class for processing accelerators
|
|
75
|
+
/intel.*vpu/i,
|
|
76
|
+
/intel.*ai boost/i,
|
|
77
|
+
/amd.*xdna/i,
|
|
78
|
+
/amd.*ipu/i,
|
|
79
|
+
/qualcomm.*hexagon/i,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
if (lspciOutput) {
|
|
83
|
+
for (const line of lspciOutput.split('\n')) {
|
|
84
|
+
if (npuPatterns.some(p => p.test(line))) {
|
|
85
|
+
devices.push({
|
|
86
|
+
name: line.replace(/^\S+\s+/, '').trim(),
|
|
87
|
+
source: 'lspci',
|
|
88
|
+
raw: line.trim(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Try sysfs for Intel NPU frequency info
|
|
95
|
+
let frequencyMhz = null;
|
|
96
|
+
let maxFrequencyMhz = null;
|
|
97
|
+
if (hasAccelDevices) {
|
|
98
|
+
// Try multiple possible sysfs paths
|
|
99
|
+
const sysfsPaths = [
|
|
100
|
+
'/sys/class/accel/accel0/device/npu_current_frequency_mhz',
|
|
101
|
+
'/sys/class/accel/accel0/device/npu_max_frequency_mhz',
|
|
102
|
+
];
|
|
103
|
+
const currentFreq = exec(`cat ${sysfsPaths[0]} 2>/dev/null`);
|
|
104
|
+
const maxFreq = exec(`cat ${sysfsPaths[1]} 2>/dev/null`);
|
|
105
|
+
if (currentFreq) frequencyMhz = parseInt(currentFreq, 10);
|
|
106
|
+
if (maxFreq) maxFrequencyMhz = parseInt(maxFreq, 10);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 5. Check for intel_vpu module info
|
|
110
|
+
let driverVersion = null;
|
|
111
|
+
if (intelVpuLoaded) {
|
|
112
|
+
driverVersion = exec('modinfo intel_vpu 2>/dev/null | grep "^version:" | awk \'{print $2}\'') || null;
|
|
113
|
+
if (!devices.length) {
|
|
114
|
+
const desc = exec('modinfo intel_vpu 2>/dev/null | grep "^description:" | sed "s/description:\\s*//"') || 'Intel NPU (VPU)';
|
|
115
|
+
devices.push({ name: desc, source: 'kernel-module' });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (amdXdnaLoaded && !devices.length) {
|
|
120
|
+
const desc = exec('modinfo amdxdna 2>/dev/null | grep "^description:" | sed "s/description:\\s*//"') || 'AMD XDNA NPU';
|
|
121
|
+
devices.push({ name: desc, source: 'kernel-module' });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 6. Check dmesg for NPU init messages (may require root)
|
|
125
|
+
const dmesgNpu = exec('dmesg 2>/dev/null | grep -iE "(intel_vpu|amdxdna|npu|neural)" | tail -5');
|
|
126
|
+
|
|
127
|
+
const detected = devices.length > 0 || hasAccelDevices || intelVpuLoaded || amdXdnaLoaded;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
detected,
|
|
131
|
+
platform: 'linux',
|
|
132
|
+
devices,
|
|
133
|
+
accelDevices: hasAccelDevices ? accelDevices.split(/\s+/).filter(Boolean) : [],
|
|
134
|
+
kernelModules: {
|
|
135
|
+
intel_vpu: intelVpuLoaded,
|
|
136
|
+
amdxdna: amdXdnaLoaded,
|
|
137
|
+
},
|
|
138
|
+
driverVersion,
|
|
139
|
+
frequency: frequencyMhz ? {
|
|
140
|
+
currentMhz: frequencyMhz,
|
|
141
|
+
maxMhz: maxFrequencyMhz,
|
|
142
|
+
} : null,
|
|
143
|
+
dmesgHints: dmesgNpu ? dmesgNpu.split('\n').filter(Boolean) : [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Windows NPU detection
|
|
149
|
+
* - Query Device Manager "Neural processors" class via PowerShell
|
|
150
|
+
* - Check for DirectML NPU support
|
|
151
|
+
*/
|
|
152
|
+
function detectWindows() {
|
|
153
|
+
const devices = [];
|
|
154
|
+
|
|
155
|
+
// 1. PowerShell: query PnP devices in "Neural processors" class
|
|
156
|
+
const psCmd = `powershell -NoProfile -Command "Get-PnpDevice -Class 'NeuralProcessor' -ErrorAction SilentlyContinue | Select-Object -Property FriendlyName, Status, InstanceId | ConvertTo-Json"`;
|
|
157
|
+
const psOutput = exec(psCmd);
|
|
158
|
+
|
|
159
|
+
if (psOutput) {
|
|
160
|
+
try {
|
|
161
|
+
let parsed = JSON.parse(psOutput);
|
|
162
|
+
if (!Array.isArray(parsed)) parsed = [parsed];
|
|
163
|
+
for (const dev of parsed) {
|
|
164
|
+
devices.push({
|
|
165
|
+
name: dev.FriendlyName || 'Unknown NPU',
|
|
166
|
+
status: dev.Status || 'Unknown',
|
|
167
|
+
instanceId: dev.InstanceId || null,
|
|
168
|
+
source: 'pnp-device',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
} catch { /* JSON parse failed, try alternate approach */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 2. Fallback: try WMI query for processing accelerators
|
|
175
|
+
if (!devices.length) {
|
|
176
|
+
const wmiCmd = `powershell -NoProfile -Command "Get-WmiObject Win32_PnPEntity | Where-Object { $_.PNPClass -eq 'NeuralProcessor' -or $_.Name -match 'NPU|Neural|AI Boost' } | Select-Object Name, Status, DeviceID | ConvertTo-Json"`;
|
|
177
|
+
const wmiOutput = exec(wmiCmd);
|
|
178
|
+
if (wmiOutput) {
|
|
179
|
+
try {
|
|
180
|
+
let parsed = JSON.parse(wmiOutput);
|
|
181
|
+
if (!Array.isArray(parsed)) parsed = [parsed];
|
|
182
|
+
for (const dev of parsed) {
|
|
183
|
+
devices.push({
|
|
184
|
+
name: dev.Name || 'Unknown NPU',
|
|
185
|
+
status: dev.Status || 'Unknown',
|
|
186
|
+
deviceId: dev.DeviceID || null,
|
|
187
|
+
source: 'wmi',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch { /* ignore */ }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 3. Check CPU model for known NPU-equipped processors
|
|
195
|
+
const cpuModel = os.cpus()[0]?.model || '';
|
|
196
|
+
const hasNpuCpu = /core\s*(?:\(TM\)\s*)?ultra|snapdragon\s*x|ryzen\s*(ai|8040|9)/i.test(cpuModel);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
detected: devices.length > 0,
|
|
200
|
+
platform: 'windows',
|
|
201
|
+
devices,
|
|
202
|
+
cpuModel,
|
|
203
|
+
cpuHasKnownNPU: hasNpuCpu,
|
|
204
|
+
note: devices.length === 0 && hasNpuCpu
|
|
205
|
+
? 'CPU model suggests NPU may be present but was not found in Device Manager. Driver may need to be installed or updated.'
|
|
206
|
+
: undefined,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* macOS NPU detection
|
|
212
|
+
* - Check for Apple Neural Engine via ioreg / system_profiler
|
|
213
|
+
* - Detect Apple Silicon chip model
|
|
214
|
+
* - Check ANE power via powermetrics (requires root)
|
|
215
|
+
*/
|
|
216
|
+
function detectMacOS() {
|
|
217
|
+
const devices = [];
|
|
218
|
+
|
|
219
|
+
// 1. Determine chip type
|
|
220
|
+
const cpuBrand = exec('sysctl -n machdep.cpu.brand_string 2>/dev/null') || os.cpus()[0]?.model || '';
|
|
221
|
+
const arch = os.arch(); // 'arm64' for Apple Silicon
|
|
222
|
+
|
|
223
|
+
const isAppleSilicon = arch === 'arm64';
|
|
224
|
+
|
|
225
|
+
// 2. ioreg: search for ANE (AppleNeuralEngine or similar)
|
|
226
|
+
const ioregANE = exec('ioreg -r -c AppleNeuralEngine 2>/dev/null | head -20');
|
|
227
|
+
const hasANE = ioregANE.includes('AppleNeuralEngine') || ioregANE.includes('ANE');
|
|
228
|
+
|
|
229
|
+
// 3. Check for H13ANEIn (ANE interface class)
|
|
230
|
+
const ioregH13 = exec('ioreg -l 2>/dev/null | grep -i "ANE\\|NeuralEngine" | head -10');
|
|
231
|
+
|
|
232
|
+
if (hasANE || isAppleSilicon) {
|
|
233
|
+
// Determine ANE core count from chip model
|
|
234
|
+
let aneCores = null;
|
|
235
|
+
if (/M[1-4]/.test(cpuBrand) || /A1[4-9]/.test(cpuBrand)) {
|
|
236
|
+
aneCores = 16; // M1+ and A14+ all have 16-core ANE
|
|
237
|
+
}
|
|
238
|
+
if (/M[1-4]\s*Ultra/.test(cpuBrand)) {
|
|
239
|
+
aneCores = 32; // Ultra doubles via UltraFusion
|
|
240
|
+
}
|
|
241
|
+
if (/A1[1-3]/.test(cpuBrand)) {
|
|
242
|
+
// A11 = 2 cores, A12/A13 = 8 cores
|
|
243
|
+
if (/A11/.test(cpuBrand)) aneCores = 2;
|
|
244
|
+
else aneCores = 8;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Estimate TOPS from known data
|
|
248
|
+
let estimatedTops = null;
|
|
249
|
+
if (/M4/.test(cpuBrand)) estimatedTops = 38;
|
|
250
|
+
else if (/M3/.test(cpuBrand)) estimatedTops = 18;
|
|
251
|
+
else if (/M2/.test(cpuBrand)) estimatedTops = 15.8;
|
|
252
|
+
else if (/M1/.test(cpuBrand)) estimatedTops = 11;
|
|
253
|
+
if (/Ultra/.test(cpuBrand) && estimatedTops) estimatedTops *= 2;
|
|
254
|
+
|
|
255
|
+
devices.push({
|
|
256
|
+
name: 'Apple Neural Engine (ANE)',
|
|
257
|
+
chip: cpuBrand,
|
|
258
|
+
cores: aneCores,
|
|
259
|
+
estimatedTops: estimatedTops,
|
|
260
|
+
source: hasANE ? 'ioreg' : 'cpu-inference',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 4. powermetrics ANE power (requires root, likely fails without sudo)
|
|
265
|
+
const anePower = exec('sudo powermetrics --samplers ane_energy -n 1 -i 100 2>/dev/null | grep -i "ane"');
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
detected: devices.length > 0,
|
|
269
|
+
platform: 'darwin',
|
|
270
|
+
devices,
|
|
271
|
+
isAppleSilicon,
|
|
272
|
+
cpuModel: cpuBrand,
|
|
273
|
+
anePower: anePower || null,
|
|
274
|
+
note: !isAppleSilicon
|
|
275
|
+
? 'Intel Macs do not have an Apple Neural Engine.'
|
|
276
|
+
: undefined,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Unified CPU heuristic (cross-platform fallback)
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
function cpuNPUHeuristic() {
|
|
285
|
+
const cpuModel = os.cpus()[0]?.model || '';
|
|
286
|
+
|
|
287
|
+
const knownNPUCPUs = [
|
|
288
|
+
{ pattern: /Core\s*(?:\(TM\)\s*)?Ultra/i, vendor: 'Intel', npuName: 'Intel AI Boost' },
|
|
289
|
+
{ pattern: /Snapdragon\s*X/i, vendor: 'Qualcomm', npuName: 'Qualcomm Hexagon NPU' },
|
|
290
|
+
{ pattern: /Snapdragon\s*8\s*Gen\s*[3-9]/i, vendor: 'Qualcomm', npuName: 'Qualcomm Hexagon NPU' },
|
|
291
|
+
{ pattern: /Ryzen\s*AI/i, vendor: 'AMD', npuName: 'AMD XDNA NPU' },
|
|
292
|
+
{ pattern: /Ryzen\s*[79]\s*8[0-9]{3}/i, vendor: 'AMD', npuName: 'AMD XDNA NPU (Hawk Point)' },
|
|
293
|
+
{ pattern: /Ryzen\s*AI\s*9/i, vendor: 'AMD', npuName: 'AMD XDNA2 NPU (Strix Point)' },
|
|
294
|
+
{ pattern: /Apple\s*M[1-9]/i, vendor: 'Apple', npuName: 'Apple Neural Engine' },
|
|
295
|
+
{ pattern: /A1[4-9]\s*Bionic/i, vendor: 'Apple', npuName: 'Apple Neural Engine' },
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
for (const { pattern, vendor, npuName } of knownNPUCPUs) {
|
|
299
|
+
if (pattern.test(cpuModel)) {
|
|
300
|
+
return { likely: true, vendor, npuName, cpuModel };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { likely: false, cpuModel };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Main detection function
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Detect NPU presence and return information.
|
|
313
|
+
*
|
|
314
|
+
* @param {Object} [options]
|
|
315
|
+
* @param {boolean} [options.cpuHeuristic=true] Also check CPU model for known NPU-equipped processors
|
|
316
|
+
* @param {boolean} [options.verbose=false] Include raw diagnostic data
|
|
317
|
+
* @returns {Promise<NPUDetectionResult>}
|
|
318
|
+
*/
|
|
319
|
+
async function detectNPU(options = {}) {
|
|
320
|
+
const { cpuHeuristic: useCpuHeuristic = true, verbose = false } = options;
|
|
321
|
+
|
|
322
|
+
const platform = os.platform();
|
|
323
|
+
let platformResult;
|
|
324
|
+
|
|
325
|
+
switch (platform) {
|
|
326
|
+
case 'linux':
|
|
327
|
+
platformResult = detectLinux();
|
|
328
|
+
break;
|
|
329
|
+
case 'win32':
|
|
330
|
+
platformResult = detectWindows();
|
|
331
|
+
break;
|
|
332
|
+
case 'darwin':
|
|
333
|
+
platformResult = detectMacOS();
|
|
334
|
+
break;
|
|
335
|
+
default:
|
|
336
|
+
platformResult = {
|
|
337
|
+
detected: false,
|
|
338
|
+
platform,
|
|
339
|
+
devices: [],
|
|
340
|
+
note: `Platform "${platform}" is not yet supported for NPU detection.`,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const heuristic = useCpuHeuristic ? cpuNPUHeuristic() : null;
|
|
345
|
+
|
|
346
|
+
const result = {
|
|
347
|
+
detected: platformResult.detected,
|
|
348
|
+
platform: platformResult.platform,
|
|
349
|
+
devices: platformResult.devices,
|
|
350
|
+
cpuHeuristic: heuristic,
|
|
351
|
+
timestamp: new Date().toISOString(),
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
if (verbose) {
|
|
355
|
+
result._raw = platformResult;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// If platform detection failed but CPU heuristic says likely, note it
|
|
359
|
+
if (!result.detected && heuristic?.likely) {
|
|
360
|
+
result.detected = false; // Keep false — not confirmed
|
|
361
|
+
result.likelyPresent = true;
|
|
362
|
+
result.note = `NPU not confirmed by OS, but CPU model (${heuristic.cpuModel}) is known to include ${heuristic.npuName}. Driver may not be installed.`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// Convenience helpers
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
/** Quick boolean check — does this system have an NPU? */
|
|
373
|
+
async function hasNPU() {
|
|
374
|
+
const result = await detectNPU({ cpuHeuristic: false });
|
|
375
|
+
return result.detected;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Get a short summary string */
|
|
379
|
+
async function npuSummary() {
|
|
380
|
+
const result = await detectNPU();
|
|
381
|
+
if (result.detected && result.devices.length > 0) {
|
|
382
|
+
const names = result.devices.map(d => d.name).join(', ');
|
|
383
|
+
return `NPU detected: ${names}`;
|
|
384
|
+
}
|
|
385
|
+
if (result.likelyPresent) {
|
|
386
|
+
return `NPU likely present (${result.cpuHeuristic.npuName}) but not confirmed by OS`;
|
|
387
|
+
}
|
|
388
|
+
return 'No NPU detected';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
module.exports = {
|
|
392
|
+
detectNPU,
|
|
393
|
+
hasNPU,
|
|
394
|
+
npuSummary,
|
|
395
|
+
// Expose internals for advanced use
|
|
396
|
+
_detectLinux: detectLinux,
|
|
397
|
+
_detectWindows: detectWindows,
|
|
398
|
+
_detectMacOS: detectMacOS,
|
|
399
|
+
_cpuNPUHeuristic: cpuNPUHeuristic,
|
|
400
|
+
};
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Type definitions for npu-detect
|
|
2
|
+
|
|
3
|
+
export interface NPUDevice {
|
|
4
|
+
name: string;
|
|
5
|
+
source: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
instanceId?: string;
|
|
8
|
+
deviceId?: string;
|
|
9
|
+
raw?: string;
|
|
10
|
+
chip?: string;
|
|
11
|
+
cores?: number | null;
|
|
12
|
+
estimatedTops?: number | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CPUHeuristic {
|
|
16
|
+
likely: boolean;
|
|
17
|
+
vendor?: string;
|
|
18
|
+
npuName?: string;
|
|
19
|
+
cpuModel: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FrequencyInfo {
|
|
23
|
+
currentMhz: number;
|
|
24
|
+
maxMhz: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface NPUDetectionResult {
|
|
28
|
+
detected: boolean;
|
|
29
|
+
platform: string;
|
|
30
|
+
devices: NPUDevice[];
|
|
31
|
+
cpuHeuristic: CPUHeuristic | null;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
likelyPresent?: boolean;
|
|
34
|
+
note?: string;
|
|
35
|
+
_raw?: any;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DetectOptions {
|
|
39
|
+
/** Also check CPU model for known NPU-equipped processors (default: true) */
|
|
40
|
+
cpuHeuristic?: boolean;
|
|
41
|
+
/** Include raw diagnostic data (default: false) */
|
|
42
|
+
verbose?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Detect NPU presence and return detailed information (Node.js) */
|
|
46
|
+
export function detectNPU(options?: DetectOptions): Promise<NPUDetectionResult>;
|
|
47
|
+
|
|
48
|
+
/** Quick boolean check — does this system have an NPU? */
|
|
49
|
+
export function hasNPU(): Promise<boolean>;
|
|
50
|
+
|
|
51
|
+
/** Get a short human-readable summary string */
|
|
52
|
+
export function npuSummary(): Promise<string>;
|
|
53
|
+
|
|
54
|
+
// Browser module types
|
|
55
|
+
|
|
56
|
+
export interface DeviceBenchmark {
|
|
57
|
+
functional: boolean;
|
|
58
|
+
buildLatencyMs?: number;
|
|
59
|
+
execLatencyMs?: number;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface BrowserDeviceInfo {
|
|
64
|
+
available: boolean;
|
|
65
|
+
error: string | null;
|
|
66
|
+
benchmark?: DeviceBenchmark;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface BrowserNPUDetectionResult {
|
|
70
|
+
webnnSupported: boolean;
|
|
71
|
+
npuAvailable: boolean;
|
|
72
|
+
devices: {
|
|
73
|
+
npu?: BrowserDeviceInfo;
|
|
74
|
+
cpu?: BrowserDeviceInfo;
|
|
75
|
+
gpu?: BrowserDeviceInfo;
|
|
76
|
+
};
|
|
77
|
+
browser: {
|
|
78
|
+
userAgent: string;
|
|
79
|
+
platform: string;
|
|
80
|
+
};
|
|
81
|
+
webgpuAdapter?: {
|
|
82
|
+
vendor: string | null;
|
|
83
|
+
architecture: string | null;
|
|
84
|
+
device: string | null;
|
|
85
|
+
description: string | null;
|
|
86
|
+
};
|
|
87
|
+
cpuHint?: string;
|
|
88
|
+
timestamp: string;
|
|
89
|
+
note?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface BrowserDetectOptions {
|
|
93
|
+
/** Run a small computation to verify each device (default: false) */
|
|
94
|
+
benchmark?: boolean;
|
|
95
|
+
/** Also probe CPU and GPU devices (default: true) */
|
|
96
|
+
probeAll?: boolean;
|
|
97
|
+
}
|