pinokiod 7.5.14 → 7.5.15
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/kernel/gpu/amd.js +30 -43
- package/kernel/gpu/amd_gfx_targets.json +67 -0
- package/kernel/gpu/nvidia.js +90 -4
- package/kernel/index.js +4 -3
- package/kernel/sysinfo.js +16 -25
- package/package.json +2 -1
- package/script/update-amd-gfx-targets.js +119 -0
- package/server/lib/app_log_report.js +1 -2
- package/test/amd-gpu-target.test.js +169 -0
- package/test/gpu-target-template-variable.test.js +69 -0
- package/test/nvidia-gpu-target.test.js +98 -0
- package/test/sysinfo-gpu-driver.test.js +134 -1
- package/kernel/gpu/apple.js +0 -8
- package/kernel/gpu/intel.js +0 -47
- package/release_notes/8.0.0/assets/app-ask-ai-drawer.png +0 -0
- package/release_notes/8.0.0/assets/app-detail-header-actions.png +0 -0
- package/release_notes/8.0.0/assets/autolaunch-dependencies.png +0 -0
- package/release_notes/8.0.0/assets/connect-huggingface-login.png +0 -0
- package/release_notes/8.0.0/assets/home-app-actions.png +0 -0
- package/release_notes/8.0.0/assets/home-sidebar-apps.png +0 -0
- package/release_notes/8.0.0/assets/logs-terminal-reporting.png +0 -0
- package/release_notes/8.0.0/assets/mobile-home-footer-nav.png +0 -0
- package/release_notes/8.0.0/assets/network-phone-access.png +0 -0
- package/release_notes/8.0.0/assets/plugins-agent-tools.png +0 -0
- package/release_notes/8.0.0/assets/skills-management.png +0 -0
- package/release_notes/8.0.0/assets/tools-runtime-management.png +0 -0
- package/release_notes/8.0.0/ui.md +0 -91
package/kernel/gpu/amd.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
const { normalize_model } = require("./common")
|
|
2
|
+
const amd_gfx_targets = require("./amd_gfx_targets.json")
|
|
3
|
+
|
|
4
|
+
const canonical_model_key = (model) => {
|
|
5
|
+
let key = normalize_model(model)
|
|
6
|
+
let previous
|
|
7
|
+
do {
|
|
8
|
+
previous = key
|
|
9
|
+
key = key
|
|
10
|
+
.replace(/^advanced micro devices\s+/, "")
|
|
11
|
+
.replace(/^amd\s+/, "")
|
|
12
|
+
.replace(/^instinct\s+/, "")
|
|
13
|
+
.trim()
|
|
14
|
+
} while (key !== previous)
|
|
15
|
+
return key
|
|
16
|
+
}
|
|
2
17
|
|
|
3
18
|
// Detect uninformative AMD APU names that need CPU-brand fallback matching.
|
|
4
19
|
const is_generic_gpu_model = (model) => {
|
|
@@ -15,58 +30,30 @@ const resolve_cpu_brand = async (cpu_brand) => {
|
|
|
15
30
|
}
|
|
16
31
|
}
|
|
17
32
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// AMD PyTorch ROCm install policy. These checks are based on the
|
|
23
|
-
// hardware families Pinokio should route to ROCm-flavored PyTorch wheels,
|
|
24
|
-
// not on whether ROCm is already installed on the machine.
|
|
25
|
-
|
|
26
|
-
// Radeon RX 7600+:
|
|
27
|
-
// - RX 7600 / 7600 XT / 7600M / 7600M XT
|
|
28
|
-
// - RX 7700 / 7800 / 7900 families
|
|
29
|
-
// - RX 9000 families such as RX 9060 and RX 9070
|
|
30
|
-
// - Future RX 8xxx/9xxx names are treated as ROCm-intended by policy.
|
|
31
|
-
let rx = normalized.match(/\brx\s*([7-9]\d{3})(?!\d)/)
|
|
32
|
-
if (rx && Number(rx[1]) >= 7600) {
|
|
33
|
-
return true
|
|
33
|
+
const resolve_rocm_gfx_target = (model) => {
|
|
34
|
+
let key = canonical_model_key(model)
|
|
35
|
+
if (/^gfx[0-9a-f]+$/i.test(key)) {
|
|
36
|
+
return key.toLowerCase()
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// - PRO W7700 / W7800 / W7900
|
|
38
|
-
// - Future PRO W 8xxx/9xxx names are treated as ROCm-intended by policy.
|
|
39
|
-
let pro = normalized.match(/\bpro\s*w\s*([7-9]\d{3})(?!\d)/)
|
|
40
|
-
if (pro && Number(pro[1]) >= 7700) {
|
|
41
|
-
return true
|
|
38
|
+
if (amd_gfx_targets.entries[key]) {
|
|
39
|
+
return amd_gfx_targets.entries[key]
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
return
|
|
45
|
-
// Radeon PRO V710
|
|
46
|
-
/\bpro\s*v\s*710\b/.test(normalized) ||
|
|
47
|
-
// Radeon AI PRO R9600/R9700 variants, including suffixes like R9700S.
|
|
48
|
-
/\bai\s*pro\s*r\s*9[67]\d{2}[a-z]?\b/.test(normalized) ||
|
|
49
|
-
// Supported APUs and codenames seen in PyTorch ROCm wheel support.
|
|
50
|
-
/\b(780m|820m|880m|890m|8050s|8060s|strix|phoenix|fire range)\b/.test(normalized)
|
|
51
|
-
)
|
|
42
|
+
return null
|
|
52
43
|
}
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
45
|
+
const resolve_gpu_target = async (model, cpu_brand) => {
|
|
46
|
+
let target = resolve_rocm_gfx_target(model)
|
|
47
|
+
if (target) {
|
|
48
|
+
return target
|
|
58
49
|
}
|
|
59
50
|
if (!is_generic_gpu_model(model)) {
|
|
60
|
-
return
|
|
51
|
+
return null
|
|
61
52
|
}
|
|
62
|
-
|
|
63
|
-
// In that generic case, use the CPU brand as a fallback because it often
|
|
64
|
-
// includes the Radeon model or codename, such as 8060S or Strix Halo.
|
|
65
|
-
return matches_rocm_torch_model(await resolve_cpu_brand(cpu_brand))
|
|
53
|
+
return resolve_rocm_gfx_target(await resolve_cpu_brand(cpu_brand))
|
|
66
54
|
}
|
|
67
55
|
|
|
68
56
|
module.exports = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
supports_torch_backend
|
|
57
|
+
resolve_gpu_target,
|
|
58
|
+
resolve_rocm_gfx_target
|
|
72
59
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "https://raw.githubusercontent.com/ROCm/ROCm/develop/docs/reference/gpu-arch-specs.rst",
|
|
3
|
+
"generated_by": "script/update-amd-gfx-targets.js",
|
|
4
|
+
"entries": {
|
|
5
|
+
"mi100": "gfx908",
|
|
6
|
+
"mi210": "gfx90a",
|
|
7
|
+
"mi25": "gfx900",
|
|
8
|
+
"mi250": "gfx90a",
|
|
9
|
+
"mi250x": "gfx90a",
|
|
10
|
+
"mi300a": "gfx942",
|
|
11
|
+
"mi300x": "gfx942",
|
|
12
|
+
"mi325x": "gfx942",
|
|
13
|
+
"mi350x": "gfx950",
|
|
14
|
+
"mi355x": "gfx950",
|
|
15
|
+
"mi50 16gb": "gfx906",
|
|
16
|
+
"mi50 32gb": "gfx906",
|
|
17
|
+
"mi6": "gfx803",
|
|
18
|
+
"mi60": "gfx906",
|
|
19
|
+
"mi8": "gfx803",
|
|
20
|
+
"radeon 780m": "gfx1103",
|
|
21
|
+
"radeon 8060s": "gfx1151",
|
|
22
|
+
"radeon 860m": "gfx1152",
|
|
23
|
+
"radeon 890m": "gfx1150",
|
|
24
|
+
"radeon ai pro r9600d": "gfx1201",
|
|
25
|
+
"radeon ai pro r9700": "gfx1201",
|
|
26
|
+
"radeon pro v620": "gfx1030",
|
|
27
|
+
"radeon pro v710": "gfx1101",
|
|
28
|
+
"radeon pro vii": "gfx906",
|
|
29
|
+
"radeon pro w5500": "gfx1012",
|
|
30
|
+
"radeon pro w6600": "gfx1032",
|
|
31
|
+
"radeon pro w6800": "gfx1030",
|
|
32
|
+
"radeon pro w7700": "gfx1101",
|
|
33
|
+
"radeon pro w7800": "gfx1100",
|
|
34
|
+
"radeon pro w7800 48gb": "gfx1100",
|
|
35
|
+
"radeon pro w7900": "gfx1100",
|
|
36
|
+
"radeon pro w7900 dual slot": "gfx1100",
|
|
37
|
+
"radeon rx 6600": "gfx1032",
|
|
38
|
+
"radeon rx 6600 xt": "gfx1032",
|
|
39
|
+
"radeon rx 6650 xt": "gfx1032",
|
|
40
|
+
"radeon rx 6700": "gfx1031",
|
|
41
|
+
"radeon rx 6700 xt": "gfx1031",
|
|
42
|
+
"radeon rx 6750 xt": "gfx1031",
|
|
43
|
+
"radeon rx 6800": "gfx1030",
|
|
44
|
+
"radeon rx 6800 xt": "gfx1030",
|
|
45
|
+
"radeon rx 6900 xt": "gfx1030",
|
|
46
|
+
"radeon rx 6950 xt": "gfx1030",
|
|
47
|
+
"radeon rx 7600": "gfx1102",
|
|
48
|
+
"radeon rx 7700": "gfx1101",
|
|
49
|
+
"radeon rx 7700 xt": "gfx1101",
|
|
50
|
+
"radeon rx 7800 xt": "gfx1101",
|
|
51
|
+
"radeon rx 7900 gre": "gfx1100",
|
|
52
|
+
"radeon rx 7900 xt": "gfx1100",
|
|
53
|
+
"radeon rx 7900 xtx": "gfx1100",
|
|
54
|
+
"radeon rx 9060": "gfx1200",
|
|
55
|
+
"radeon rx 9060 xt": "gfx1200",
|
|
56
|
+
"radeon rx 9060 xt lp": "gfx1200",
|
|
57
|
+
"radeon rx 9070": "gfx1201",
|
|
58
|
+
"radeon rx 9070 gre": "gfx1201",
|
|
59
|
+
"radeon rx 9070 xt": "gfx1201",
|
|
60
|
+
"radeon vii": "gfx906",
|
|
61
|
+
"ryzen 7 7840u": "gfx1103",
|
|
62
|
+
"ryzen 9 270": "gfx1103",
|
|
63
|
+
"ryzen ai 9 hx 375": "gfx1150",
|
|
64
|
+
"ryzen ai max pro 395": "gfx1151",
|
|
65
|
+
"ryzen al 7 350": "gfx1152"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/kernel/gpu/nvidia.js
CHANGED
|
@@ -1,8 +1,94 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const { execFile } = require("node:child_process")
|
|
2
|
+
|
|
3
|
+
const NVIDIA_SMI_COMPUTE_CAP_ARGS = [
|
|
4
|
+
"--query-gpu=pci.bus_id,compute_cap",
|
|
5
|
+
"--format=csv,noheader,nounits"
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
let cuda_sm_targets_promise = null
|
|
9
|
+
|
|
10
|
+
const normalize_pci_bus = (bus) => {
|
|
11
|
+
let normalized = String(bus || "").trim().toLowerCase()
|
|
12
|
+
let match = /(?:[0-9a-f]{4,8}:)?([0-9a-f]{2}:[0-9a-f]{2}\.[0-7])$/.exec(normalized)
|
|
13
|
+
return match ? match[1] : normalized
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const compute_cap_to_sm_target = (compute_cap) => {
|
|
17
|
+
let match = /^(\d+)\.(\d+)$/.exec(String(compute_cap || "").trim())
|
|
18
|
+
return match ? `sm_${match[1]}${match[2]}` : null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parse_nvidia_smi_compute_caps = (stdout) => {
|
|
22
|
+
return String(stdout || "")
|
|
23
|
+
.split(/\r?\n/)
|
|
24
|
+
.map((line) => line.trim())
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.map((line) => {
|
|
27
|
+
let parts = line.split(",").map((part) => part.trim())
|
|
28
|
+
if (parts.length < 2) return null
|
|
29
|
+
let target = compute_cap_to_sm_target(parts[1])
|
|
30
|
+
if (!target) return null
|
|
31
|
+
return {
|
|
32
|
+
pci_bus: normalize_pci_bus(parts[0]),
|
|
33
|
+
target
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const query_cuda_sm_targets = (exec_file = execFile) => {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
exec_file(
|
|
42
|
+
"nvidia-smi",
|
|
43
|
+
NVIDIA_SMI_COMPUTE_CAP_ARGS,
|
|
44
|
+
{ windowsHide: true, timeout: 5000 },
|
|
45
|
+
(error, stdout) => {
|
|
46
|
+
if (error) {
|
|
47
|
+
resolve([])
|
|
48
|
+
} else {
|
|
49
|
+
resolve(parse_nvidia_smi_compute_caps(stdout))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const cached_cuda_sm_targets = () => {
|
|
57
|
+
if (!cuda_sm_targets_promise) {
|
|
58
|
+
cuda_sm_targets_promise = query_cuda_sm_targets()
|
|
59
|
+
}
|
|
60
|
+
return cuda_sm_targets_promise
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const select_cuda_sm_target = (controller, records) => {
|
|
64
|
+
let targets = Array.isArray(records) ? records : []
|
|
65
|
+
if (targets.length === 0) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let controller_bus = normalize_pci_bus(controller && (controller.pciBus || controller.busAddress))
|
|
70
|
+
if (controller_bus) {
|
|
71
|
+
let match = targets.find((record) => record.pci_bus === controller_bus)
|
|
72
|
+
if (match) {
|
|
73
|
+
return match.target
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return targets.length === 1 ? targets[0].target : null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const resolve_cuda_sm_target = async (controller) => {
|
|
81
|
+
if (!controller) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
let records = await cached_cuda_sm_targets()
|
|
85
|
+
return select_cuda_sm_target(controller, records)
|
|
4
86
|
}
|
|
5
87
|
|
|
6
88
|
module.exports = {
|
|
7
|
-
|
|
89
|
+
compute_cap_to_sm_target,
|
|
90
|
+
parse_nvidia_smi_compute_caps,
|
|
91
|
+
query_cuda_sm_targets,
|
|
92
|
+
resolve_cuda_sm_target,
|
|
93
|
+
select_cuda_sm_target
|
|
8
94
|
}
|
package/kernel/index.js
CHANGED
|
@@ -92,10 +92,10 @@ class Kernel {
|
|
|
92
92
|
this.pinokio_configs = {}
|
|
93
93
|
this.shellpath = shellPath.sync()
|
|
94
94
|
this.favicon = new Favicon()
|
|
95
|
-
this.torch_backend = "cpu"
|
|
96
95
|
this.vram = 0
|
|
97
96
|
this.ram = 0
|
|
98
97
|
this.gpu_driver = null
|
|
98
|
+
this.gpu_target = null
|
|
99
99
|
this.readyState = new ReadyState(this)
|
|
100
100
|
this.app_ready_status = this.readyState.status
|
|
101
101
|
this.launchRequirements = new LaunchRequirements(this)
|
|
@@ -824,6 +824,7 @@ class Kernel {
|
|
|
824
824
|
procs: this.procs,
|
|
825
825
|
gpu: this.gpu,
|
|
826
826
|
gpus: this.gpus,
|
|
827
|
+
gpu_target: this.gpu_target,
|
|
827
828
|
...info
|
|
828
829
|
}
|
|
829
830
|
|
|
@@ -1152,7 +1153,7 @@ class Kernel {
|
|
|
1152
1153
|
system,
|
|
1153
1154
|
platform: this.platform,
|
|
1154
1155
|
arch: this.arch,
|
|
1155
|
-
|
|
1156
|
+
gpu_target: this.gpu_target,
|
|
1156
1157
|
vram: this.vram,
|
|
1157
1158
|
ram: this.ram,
|
|
1158
1159
|
proxy: (port) => {
|
|
@@ -1349,7 +1350,7 @@ class Kernel {
|
|
|
1349
1350
|
this.gpu = info.gpu
|
|
1350
1351
|
this.gpu_model = info.gpu_model
|
|
1351
1352
|
this.gpu_driver = info.gpu_driver || null
|
|
1352
|
-
this.
|
|
1353
|
+
this.gpu_target = info.gpu_target || null
|
|
1353
1354
|
this.gpus = info.gpus
|
|
1354
1355
|
this.vram = typeof info.vram === "number" ? info.vram : 0
|
|
1355
1356
|
this.ram = typeof info.ram === "number" ? info.ram : 0
|
package/kernel/sysinfo.js
CHANGED
|
@@ -3,8 +3,6 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const nvidia = require("./gpu/nvidia")
|
|
5
5
|
const amd = require("./gpu/amd")
|
|
6
|
-
const apple = require("./gpu/apple")
|
|
7
|
-
const intel = require("./gpu/intel")
|
|
8
6
|
class Sysinfo {
|
|
9
7
|
async init(kernel) {
|
|
10
8
|
this.kernel = kernel
|
|
@@ -74,7 +72,6 @@ class Sysinfo {
|
|
|
74
72
|
let is_nvidia = controllers.find(x => /nvidia/i.test(x.vendor || ""))
|
|
75
73
|
let is_amd = bestByVram(controllers.filter(x => /(amd|advanced micro devices)/i.test(x.vendor || "")))
|
|
76
74
|
let is_apple = controllers.find(x => /apple/i.test(x.vendor || ""))
|
|
77
|
-
let is_intel = controllers.find(x => /intel/i.test(x.vendor || ""))
|
|
78
75
|
|
|
79
76
|
let gpu
|
|
80
77
|
let gpu_model
|
|
@@ -108,14 +105,22 @@ class Sysinfo {
|
|
|
108
105
|
let vramMB = primaryController && primaryController.vram ? primaryController.vram : 0
|
|
109
106
|
let vramGB = vramMB > 0 ? Math.round(vramMB / 1024) : 0
|
|
110
107
|
let gpu_driver = driver(primaryController)
|
|
111
|
-
let
|
|
108
|
+
let cpu_brand_value
|
|
109
|
+
let cpu_brand = async () => {
|
|
110
|
+
if (cpu_brand_value === undefined) {
|
|
111
|
+
cpu_brand_value = await this.cpu_brand()
|
|
112
|
+
}
|
|
113
|
+
return cpu_brand_value
|
|
114
|
+
}
|
|
115
|
+
let detected = { is_nvidia, is_amd, gpu_model, primaryController, cpu_brand }
|
|
116
|
+
let gpu_target = await this.gpu_target(detected)
|
|
112
117
|
|
|
113
118
|
this.info.graphics = g
|
|
114
119
|
this.info.gpus = gpus
|
|
115
120
|
this.info.gpu = gpu
|
|
116
121
|
this.info.gpu_model = gpu_model
|
|
117
122
|
this.info.gpu_driver = gpu_driver
|
|
118
|
-
this.info.
|
|
123
|
+
this.info.gpu_target = gpu_target
|
|
119
124
|
this.info.vram = vramGB
|
|
120
125
|
}
|
|
121
126
|
// Read CPU brand only when generic iGPU names need CPU fallback matching.
|
|
@@ -130,27 +135,13 @@ class Sysinfo {
|
|
|
130
135
|
}
|
|
131
136
|
return cpu && cpu.brand
|
|
132
137
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return "cuda"
|
|
139
|
-
} else if (apple.supports_torch_backend(detected.is_apple, process.platform)) {
|
|
140
|
-
return "mps"
|
|
141
|
-
} else if (
|
|
142
|
-
detected.is_amd &&
|
|
143
|
-
process.platform !== "darwin" &&
|
|
144
|
-
await amd.supports_torch_backend(detected.gpu_model, () => this.cpu_brand())
|
|
145
|
-
) {
|
|
146
|
-
return "rocm"
|
|
147
|
-
} else if (
|
|
148
|
-
detected.is_intel &&
|
|
149
|
-
await intel.supports_torch_backend(detected.is_intel.model, () => this.cpu_brand())
|
|
150
|
-
) {
|
|
151
|
-
return "xpu"
|
|
138
|
+
async gpu_target(detected) {
|
|
139
|
+
if (detected.is_nvidia) {
|
|
140
|
+
return await nvidia.resolve_cuda_sm_target(detected.primaryController)
|
|
141
|
+
} else if (detected.is_amd) {
|
|
142
|
+
return await amd.resolve_gpu_target(detected.gpu_model, detected.cpu_brand || (() => this.cpu_brand()))
|
|
152
143
|
} else {
|
|
153
|
-
return
|
|
144
|
+
return null
|
|
154
145
|
}
|
|
155
146
|
}
|
|
156
147
|
// async time() {
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinokiod",
|
|
3
|
-
"version": "7.5.
|
|
3
|
+
"version": "7.5.15",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"download_readme": "wget -O kernel/proto/PINOKIO.md https://raw.githubusercontent.com/pinokiocomputer/home/refs/heads/main/docs/README.md",
|
|
8
|
+
"update:amd-gfx-targets": "node script/update-amd-gfx-targets.js",
|
|
8
9
|
"start": "node script/index"
|
|
9
10
|
},
|
|
10
11
|
"author": "",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const https = require('node:https')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
|
|
5
|
+
const { normalize_model } = require('../kernel/gpu/common')
|
|
6
|
+
|
|
7
|
+
const GPU_SPECS_URL = 'https://raw.githubusercontent.com/ROCm/ROCm/develop/docs/reference/gpu-arch-specs.rst'
|
|
8
|
+
const GENERATOR = 'script/update-amd-gfx-targets.js'
|
|
9
|
+
|
|
10
|
+
const repoRoot = path.resolve(__dirname, '..')
|
|
11
|
+
|
|
12
|
+
function fetchText(url) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
https.get(url, (response) => {
|
|
15
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
16
|
+
const nextUrl = new URL(response.headers.location, url).toString()
|
|
17
|
+
response.resume()
|
|
18
|
+
fetchText(nextUrl).then(resolve, reject)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
if (response.statusCode !== 200) {
|
|
22
|
+
response.resume()
|
|
23
|
+
reject(new Error(`${url} returned ${response.statusCode}`))
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
let body = ''
|
|
27
|
+
response.setEncoding('utf8')
|
|
28
|
+
response.on('data', (chunk) => {
|
|
29
|
+
body += chunk
|
|
30
|
+
})
|
|
31
|
+
response.on('end', () => resolve(body))
|
|
32
|
+
}).on('error', reject)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function canonicalKey(value) {
|
|
37
|
+
let key = normalize_model(value)
|
|
38
|
+
let previous
|
|
39
|
+
do {
|
|
40
|
+
previous = key
|
|
41
|
+
key = key
|
|
42
|
+
.replace(/^advanced micro devices\s+/, '')
|
|
43
|
+
.replace(/^amd\s+/, '')
|
|
44
|
+
.replace(/^instinct\s+/, '')
|
|
45
|
+
.trim()
|
|
46
|
+
} while (key !== previous)
|
|
47
|
+
return key
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function addEntry(entries, name, target) {
|
|
51
|
+
const key = canonicalKey(name)
|
|
52
|
+
if (key && /^gfx[0-9a-f]+$/i.test(target)) {
|
|
53
|
+
entries[key] = target.toLowerCase()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseGpuSpecs(rst) {
|
|
58
|
+
const entries = {}
|
|
59
|
+
let headers = null
|
|
60
|
+
let cells = []
|
|
61
|
+
let inRow = false
|
|
62
|
+
|
|
63
|
+
const flushRow = () => {
|
|
64
|
+
if (!inRow || cells.length === 0) return
|
|
65
|
+
if (cells.includes('Name') && cells.includes('LLVM target name')) {
|
|
66
|
+
headers = cells
|
|
67
|
+
} else if (headers) {
|
|
68
|
+
const nameIndex = headers.indexOf('Name')
|
|
69
|
+
const graphicsIndex = headers.indexOf('Graphics model')
|
|
70
|
+
const targetIndex = headers.indexOf('LLVM target name')
|
|
71
|
+
const target = cells[targetIndex]
|
|
72
|
+
if (target) {
|
|
73
|
+
addEntry(entries, cells[nameIndex], target)
|
|
74
|
+
if (graphicsIndex >= 0) {
|
|
75
|
+
addEntry(entries, cells[graphicsIndex], target)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
cells = []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const line of rst.split(/\r?\n/)) {
|
|
83
|
+
if (/^\s*\*\s*$/.test(line)) {
|
|
84
|
+
flushRow()
|
|
85
|
+
inRow = true
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
const cell = line.match(/^\s*-\s*(.*)$/)
|
|
89
|
+
if (inRow && cell) {
|
|
90
|
+
cells.push(cell[1].trim())
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
flushRow()
|
|
94
|
+
|
|
95
|
+
return entries
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeJson(relativePath, data) {
|
|
99
|
+
const file = path.join(repoRoot, relativePath)
|
|
100
|
+
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function main() {
|
|
104
|
+
const specs = await fetchText(GPU_SPECS_URL)
|
|
105
|
+
const entries = parseGpuSpecs(specs)
|
|
106
|
+
|
|
107
|
+
writeJson('kernel/gpu/amd_gfx_targets.json', {
|
|
108
|
+
source: GPU_SPECS_URL,
|
|
109
|
+
generated_by: GENERATOR,
|
|
110
|
+
entries: Object.fromEntries(Object.entries(entries).sort(([a], [b]) => a.localeCompare(b)))
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
console.log(`Wrote ${Object.keys(entries).length} AMD gfx target entries`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().catch((error) => {
|
|
117
|
+
console.error(error)
|
|
118
|
+
process.exit(1)
|
|
119
|
+
})
|
|
@@ -358,8 +358,7 @@ class AppLogReportService {
|
|
|
358
358
|
version: this.readPinokioVersion(),
|
|
359
359
|
node: process.version,
|
|
360
360
|
platform: kernel.platform || process.platform,
|
|
361
|
-
arch: kernel.arch || process.arch
|
|
362
|
-
torch_backend: kernel.torch_backend || info.torch_backend || null
|
|
361
|
+
arch: kernel.arch || process.arch
|
|
363
362
|
},
|
|
364
363
|
hardware: this.compactObject({
|
|
365
364
|
gpu: kernel.gpu || info.gpu || null,
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fs = require('node:fs')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
|
|
6
|
+
const system = require('systeminformation')
|
|
7
|
+
|
|
8
|
+
const amd = require('../kernel/gpu/amd')
|
|
9
|
+
const gfxTargets = require('../kernel/gpu/amd_gfx_targets.json')
|
|
10
|
+
const packageJson = require('../package.json')
|
|
11
|
+
const Sysinfo = require('../kernel/sysinfo')
|
|
12
|
+
|
|
13
|
+
const root = path.join(__dirname, '..')
|
|
14
|
+
|
|
15
|
+
test('AMD gfx generated data is checked in and self describing', () => {
|
|
16
|
+
assert.equal(
|
|
17
|
+
gfxTargets.source,
|
|
18
|
+
'https://raw.githubusercontent.com/ROCm/ROCm/develop/docs/reference/gpu-arch-specs.rst'
|
|
19
|
+
)
|
|
20
|
+
assert.equal(gfxTargets.generated_by, 'script/update-amd-gfx-targets.js')
|
|
21
|
+
|
|
22
|
+
assert.equal(gfxTargets.entries['radeon rx 6800 xt'], 'gfx1030')
|
|
23
|
+
assert.equal(gfxTargets.entries['radeon rx 7900 xtx'], 'gfx1100')
|
|
24
|
+
assert.equal(gfxTargets.entries.mi210, 'gfx90a')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('AMD gfx target refresh is an explicit maintainer command', () => {
|
|
28
|
+
assert.equal(packageJson.scripts['update:amd-gfx-targets'], 'node script/update-amd-gfx-targets.js')
|
|
29
|
+
assert.equal(Object.prototype.hasOwnProperty.call(packageJson.scripts, 'update:amd-rocm-targets'), false)
|
|
30
|
+
|
|
31
|
+
for (const script of ['preinstall', 'install', 'postinstall', 'prestart', 'start', 'poststart']) {
|
|
32
|
+
assert.doesNotMatch(packageJson.scripts[script] || '', /update-amd-gfx-targets/)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('AMD gfx resolver maps common product names to exact gfx targets', () => {
|
|
37
|
+
const cases = [
|
|
38
|
+
['AMD Radeon RX 6800 XT', 'gfx1030'],
|
|
39
|
+
['AMD Radeon RX 7600', 'gfx1102'],
|
|
40
|
+
['Advanced Micro Devices Radeon RX 7900 XTX', 'gfx1100'],
|
|
41
|
+
['AMD Radeon RX 9070 XT', 'gfx1201'],
|
|
42
|
+
['AMD Radeon AI PRO R9700', 'gfx1201'],
|
|
43
|
+
['AMD Instinct MI210', 'gfx90a'],
|
|
44
|
+
['AMD Radeon 780M', 'gfx1103'],
|
|
45
|
+
['AMD Radeon 890M', 'gfx1150'],
|
|
46
|
+
['gfx1030', 'gfx1030']
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
for (const [model, target] of cases) {
|
|
50
|
+
assert.equal(amd.resolve_rocm_gfx_target(model), target, model)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('AMD gfx resolver rejects unknown products and preserves raw gfx targets', () => {
|
|
55
|
+
const unknownModels = [
|
|
56
|
+
'AMD Radeon RX 480',
|
|
57
|
+
'AMD Radeon RX 5700 XT',
|
|
58
|
+
'AMD Radeon RX 9999',
|
|
59
|
+
'AMD Radeon RX 9999M XT'
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
for (const model of unknownModels) {
|
|
63
|
+
assert.equal(amd.resolve_rocm_gfx_target(model), null, model)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const target of ['gfx803', 'gfx1013', 'gfx1104', 'gfx1209', 'GFX90A']) {
|
|
67
|
+
assert.equal(amd.resolve_rocm_gfx_target(target), target.toLowerCase(), target)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('AMD gpu_target resolution uses CPU brand only for generic Radeon Graphics models', async () => {
|
|
72
|
+
let cpuBrandCalls = 0
|
|
73
|
+
const cpuBrand = async () => {
|
|
74
|
+
cpuBrandCalls += 1
|
|
75
|
+
return 'AMD Ryzen AI 9 HX 375'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
assert.equal(await amd.resolve_gpu_target('AMD Radeon RX 6800 XT', cpuBrand), 'gfx1030')
|
|
79
|
+
assert.equal(cpuBrandCalls, 0)
|
|
80
|
+
|
|
81
|
+
assert.equal(await amd.resolve_gpu_target('AMD Radeon RX 480', cpuBrand), null)
|
|
82
|
+
assert.equal(cpuBrandCalls, 0)
|
|
83
|
+
|
|
84
|
+
assert.equal(await amd.resolve_gpu_target('AMD Radeon Graphics', cpuBrand), 'gfx1150')
|
|
85
|
+
assert.equal(cpuBrandCalls, 1)
|
|
86
|
+
|
|
87
|
+
assert.equal(await amd.resolve_gpu_target('AMD Radeon Graphics', 'Unknown AMD CPU'), null)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('Sysinfo exposes AMD gpu_target for resolved discrete GPUs', async (t) => {
|
|
91
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
92
|
+
controllers: [
|
|
93
|
+
{
|
|
94
|
+
vendor: 'AMD',
|
|
95
|
+
model: 'AMD Radeon RX 6800 XT',
|
|
96
|
+
vram: 16384,
|
|
97
|
+
driverVersion: '31.0.1'
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
displays: []
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
const sys = new Sysinfo()
|
|
104
|
+
sys.info = {}
|
|
105
|
+
|
|
106
|
+
await sys.gpus()
|
|
107
|
+
|
|
108
|
+
assert.equal(sys.info.gpu, 'amd')
|
|
109
|
+
assert.equal(sys.info.gpu_target, 'gfx1030')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('Sysinfo leaves unresolved AMD gpu_target null', async (t) => {
|
|
113
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
114
|
+
controllers: [
|
|
115
|
+
{
|
|
116
|
+
vendor: 'AMD',
|
|
117
|
+
model: 'AMD Radeon RX 480',
|
|
118
|
+
vram: 8192,
|
|
119
|
+
driverVersion: '31.0.1'
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
displays: []
|
|
123
|
+
}))
|
|
124
|
+
|
|
125
|
+
const sys = new Sysinfo()
|
|
126
|
+
sys.info = {}
|
|
127
|
+
|
|
128
|
+
await sys.gpus()
|
|
129
|
+
|
|
130
|
+
assert.equal(sys.info.gpu, 'amd')
|
|
131
|
+
assert.equal(sys.info.gpu_target, null)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('Sysinfo exposes AMD gpu_target through generic APU CPU fallback', async (t) => {
|
|
135
|
+
let cpuBrandCalls = 0
|
|
136
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
137
|
+
controllers: [
|
|
138
|
+
{
|
|
139
|
+
vendor: 'AMD',
|
|
140
|
+
model: 'AMD Radeon Graphics',
|
|
141
|
+
vram: 16384,
|
|
142
|
+
driverVersion: '31.0.1'
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
displays: []
|
|
146
|
+
}))
|
|
147
|
+
t.mock.method(system, 'cpu', async () => {
|
|
148
|
+
cpuBrandCalls += 1
|
|
149
|
+
return { brand: 'AMD Ryzen AI 9 HX 375' }
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const sys = new Sysinfo()
|
|
153
|
+
sys.info = {}
|
|
154
|
+
|
|
155
|
+
await sys.gpus()
|
|
156
|
+
|
|
157
|
+
assert.equal(sys.info.gpu, 'amd')
|
|
158
|
+
assert.equal(sys.info.gpu_target, 'gfx1150')
|
|
159
|
+
assert.equal(cpuBrandCalls, 1)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('AMD gfx runtime resolver stays offline and data-only', () => {
|
|
163
|
+
const source = fs.readFileSync(path.join(root, 'kernel', 'gpu', 'amd.js'), 'utf8')
|
|
164
|
+
|
|
165
|
+
assert.doesNotMatch(source, /require\(["'](?:node:)?https?["']\)/)
|
|
166
|
+
assert.doesNotMatch(source, /require\(["'](?:node:)?child_process["']\)/)
|
|
167
|
+
assert.doesNotMatch(source, /\b(fetch|axios|rocminfo|hipInfo|ze_info)\b/)
|
|
168
|
+
assert.doesNotMatch(source, /\b(kfd|topology)\b/i)
|
|
169
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fs = require('node:fs/promises')
|
|
3
|
+
const os = require('node:os')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
const test = require('node:test')
|
|
6
|
+
|
|
7
|
+
const Kernel = require('../kernel')
|
|
8
|
+
|
|
9
|
+
test('Kernel propagates sysinfo gpu_target to template state and public info', async () => {
|
|
10
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-gpu-target-info-'))
|
|
11
|
+
const apiRoot = path.join(root, 'api')
|
|
12
|
+
await fs.mkdir(apiRoot, { recursive: true })
|
|
13
|
+
|
|
14
|
+
const templateUpdates = []
|
|
15
|
+
const kernel = {
|
|
16
|
+
version: 'test',
|
|
17
|
+
platform: 'linux',
|
|
18
|
+
arch: 'x64',
|
|
19
|
+
homedir: root,
|
|
20
|
+
shell: { shells: [] },
|
|
21
|
+
vars: {},
|
|
22
|
+
memory: {
|
|
23
|
+
local: {},
|
|
24
|
+
global: {},
|
|
25
|
+
key: {},
|
|
26
|
+
rpc: {},
|
|
27
|
+
input: {},
|
|
28
|
+
args: {}
|
|
29
|
+
},
|
|
30
|
+
procs: {},
|
|
31
|
+
api: {
|
|
32
|
+
running: {},
|
|
33
|
+
proxies: {},
|
|
34
|
+
userdir: apiRoot,
|
|
35
|
+
meta: async () => null
|
|
36
|
+
},
|
|
37
|
+
bin: { installed: {} },
|
|
38
|
+
template: {
|
|
39
|
+
update: (info) => templateUpdates.push(info)
|
|
40
|
+
},
|
|
41
|
+
sys: {
|
|
42
|
+
info: {
|
|
43
|
+
gpu: 'amd',
|
|
44
|
+
gpu_model: 'amd radeon rx 6800 xt',
|
|
45
|
+
gpu_driver: '31.0.1',
|
|
46
|
+
gpu_target: 'gfx1030',
|
|
47
|
+
gpus: [],
|
|
48
|
+
vram: 16,
|
|
49
|
+
ram: 64
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
path: (...parts) => path.join(root, ...parts),
|
|
53
|
+
dns: async () => {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await Kernel.prototype.update_sysinfo.call(kernel)
|
|
58
|
+
await Kernel.prototype.getInfo.call(kernel, false)
|
|
59
|
+
|
|
60
|
+
assert.equal(kernel.gpu, 'amd')
|
|
61
|
+
assert.equal(kernel.gpu_model, 'amd radeon rx 6800 xt')
|
|
62
|
+
assert.equal(kernel.gpu_target, 'gfx1030')
|
|
63
|
+
assert.equal(templateUpdates.length, 1)
|
|
64
|
+
assert.equal(templateUpdates[0].gpu_target, 'gfx1030')
|
|
65
|
+
assert.equal(kernel.i.gpu_target, 'gfx1030')
|
|
66
|
+
} finally {
|
|
67
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
68
|
+
}
|
|
69
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const childProcess = require('node:child_process')
|
|
3
|
+
const test = require('node:test')
|
|
4
|
+
|
|
5
|
+
const nvidia = require('../kernel/gpu/nvidia')
|
|
6
|
+
|
|
7
|
+
test('NVIDIA compute capability parser emits CUDA SM targets', () => {
|
|
8
|
+
assert.deepEqual(
|
|
9
|
+
nvidia.parse_nvidia_smi_compute_caps([
|
|
10
|
+
'00000000:01:00.0, 8.6',
|
|
11
|
+
'00000000:02:00.0, 8.9',
|
|
12
|
+
'00000000:03:00.0, 12.0',
|
|
13
|
+
'00000000:04:00.0, N/A'
|
|
14
|
+
].join('\n')),
|
|
15
|
+
[
|
|
16
|
+
{ pci_bus: '01:00.0', target: 'sm_86' },
|
|
17
|
+
{ pci_bus: '02:00.0', target: 'sm_89' },
|
|
18
|
+
{ pci_bus: '03:00.0', target: 'sm_120' }
|
|
19
|
+
]
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('NVIDIA target selection matches the selected controller by PCI bus', () => {
|
|
24
|
+
const records = [
|
|
25
|
+
{ pci_bus: '01:00.0', target: 'sm_86' },
|
|
26
|
+
{ pci_bus: '02:00.0', target: 'sm_89' }
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
assert.equal(nvidia.select_cuda_sm_target({ pciBus: '00000000:02:00.0' }, records), 'sm_89')
|
|
30
|
+
assert.equal(nvidia.select_cuda_sm_target({ busAddress: '01:00.0' }, records), 'sm_86')
|
|
31
|
+
assert.equal(nvidia.select_cuda_sm_target({ pciBus: '03:00.0' }, records), null)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('NVIDIA target selection uses the only record when the controller has no PCI bus', () => {
|
|
35
|
+
assert.equal(
|
|
36
|
+
nvidia.select_cuda_sm_target({}, [{ pci_bus: '01:00.0', target: 'sm_86' }]),
|
|
37
|
+
'sm_86'
|
|
38
|
+
)
|
|
39
|
+
assert.equal(
|
|
40
|
+
nvidia.select_cuda_sm_target({}, [
|
|
41
|
+
{ pci_bus: '01:00.0', target: 'sm_86' },
|
|
42
|
+
{ pci_bus: '02:00.0', target: 'sm_89' }
|
|
43
|
+
]),
|
|
44
|
+
null
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('NVIDIA nvidia-smi query failure resolves to no targets', async () => {
|
|
49
|
+
const records = await nvidia.query_cuda_sm_targets((_cmd, _args, _options, done) => {
|
|
50
|
+
done(new Error('missing nvidia-smi'))
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
assert.deepEqual(records, [])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('NVIDIA nvidia-smi query uses compute capability command and parses stdout', async () => {
|
|
57
|
+
const calls = []
|
|
58
|
+
const records = await nvidia.query_cuda_sm_targets((cmd, args, options, done) => {
|
|
59
|
+
calls.push({ cmd, args, options })
|
|
60
|
+
done(null, [
|
|
61
|
+
'00000000:01:00.0, 8.6',
|
|
62
|
+
'00000000:02:00.0, 8.9'
|
|
63
|
+
].join('\n'))
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(calls, [{
|
|
67
|
+
cmd: 'nvidia-smi',
|
|
68
|
+
args: [
|
|
69
|
+
'--query-gpu=pci.bus_id,compute_cap',
|
|
70
|
+
'--format=csv,noheader,nounits'
|
|
71
|
+
],
|
|
72
|
+
options: { windowsHide: true, timeout: 5000 }
|
|
73
|
+
}])
|
|
74
|
+
assert.deepEqual(records, [
|
|
75
|
+
{ pci_bus: '01:00.0', target: 'sm_86' },
|
|
76
|
+
{ pci_bus: '02:00.0', target: 'sm_89' }
|
|
77
|
+
])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('NVIDIA gpu_target caches the nvidia-smi compute capability query', async (t) => {
|
|
81
|
+
const modulePath = require.resolve('../kernel/gpu/nvidia')
|
|
82
|
+
delete require.cache[modulePath]
|
|
83
|
+
|
|
84
|
+
let calls = 0
|
|
85
|
+
t.mock.method(childProcess, 'execFile', (_cmd, _args, _options, done) => {
|
|
86
|
+
calls += 1
|
|
87
|
+
done(null, '00000000:01:00.0, 8.6\n')
|
|
88
|
+
})
|
|
89
|
+
t.after(() => {
|
|
90
|
+
delete require.cache[modulePath]
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const freshNvidia = require('../kernel/gpu/nvidia')
|
|
94
|
+
|
|
95
|
+
assert.equal(await freshNvidia.resolve_cuda_sm_target({ pciBus: '00000000:01:00.0' }), 'sm_86')
|
|
96
|
+
assert.equal(await freshNvidia.resolve_cuda_sm_target({ pciBus: '00000000:01:00.0' }), 'sm_86')
|
|
97
|
+
assert.equal(calls, 1)
|
|
98
|
+
})
|
|
@@ -2,6 +2,7 @@ const assert = require('node:assert/strict')
|
|
|
2
2
|
const test = require('node:test')
|
|
3
3
|
|
|
4
4
|
const system = require('systeminformation')
|
|
5
|
+
const nvidia = require('../kernel/gpu/nvidia')
|
|
5
6
|
const Sysinfo = require('../kernel/sysinfo')
|
|
6
7
|
|
|
7
8
|
test('GPU sysinfo exposes per-controller drivers and primary NVIDIA driver', async (t) => {
|
|
@@ -17,11 +18,16 @@ test('GPU sysinfo exposes per-controller drivers and primary NVIDIA driver', asy
|
|
|
17
18
|
vendor: 'NVIDIA',
|
|
18
19
|
model: 'NVIDIA RTX A4500',
|
|
19
20
|
vram: 20480,
|
|
20
|
-
driverVersion: '565.90'
|
|
21
|
+
driverVersion: '565.90',
|
|
22
|
+
pciBus: '00000000:02:00.0'
|
|
21
23
|
}
|
|
22
24
|
],
|
|
23
25
|
displays: []
|
|
24
26
|
}))
|
|
27
|
+
t.mock.method(nvidia, 'resolve_cuda_sm_target', async (controller) => {
|
|
28
|
+
assert.equal(controller.pciBus, '00000000:02:00.0')
|
|
29
|
+
return 'sm_86'
|
|
30
|
+
})
|
|
25
31
|
|
|
26
32
|
const sys = new Sysinfo()
|
|
27
33
|
sys.info = {}
|
|
@@ -31,6 +37,8 @@ test('GPU sysinfo exposes per-controller drivers and primary NVIDIA driver', asy
|
|
|
31
37
|
assert.equal(sys.info.gpu, 'nvidia')
|
|
32
38
|
assert.equal(sys.info.gpu_model, 'nvidia rtx a4500')
|
|
33
39
|
assert.equal(sys.info.gpu_driver, '565.90')
|
|
40
|
+
assert.equal(sys.info.gpu_target, 'sm_86')
|
|
41
|
+
assert.equal(sys.info.vram, 20)
|
|
34
42
|
assert.deepEqual(sys.info.gpus, [
|
|
35
43
|
{
|
|
36
44
|
name: 'intel',
|
|
@@ -72,6 +80,8 @@ test('GPU sysinfo uses selected highest-VRAM AMD controller for gpu_driver', asy
|
|
|
72
80
|
assert.equal(sys.info.gpu, 'amd')
|
|
73
81
|
assert.equal(sys.info.gpu_model, 'amd radeon rx 7900 xtx')
|
|
74
82
|
assert.equal(sys.info.gpu_driver, '31.0.2')
|
|
83
|
+
assert.equal(sys.info.gpu_target, 'gfx1100')
|
|
84
|
+
assert.equal(sys.info.vram, 24)
|
|
75
85
|
assert.deepEqual(sys.info.gpus, [
|
|
76
86
|
{
|
|
77
87
|
name: 'advanced micro devices',
|
|
@@ -85,3 +95,126 @@ test('GPU sysinfo uses selected highest-VRAM AMD controller for gpu_driver', asy
|
|
|
85
95
|
}
|
|
86
96
|
])
|
|
87
97
|
})
|
|
98
|
+
|
|
99
|
+
test('GPU sysinfo leaves gpu_target null for Intel targets', async (t) => {
|
|
100
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
101
|
+
controllers: [
|
|
102
|
+
{
|
|
103
|
+
vendor: 'Intel',
|
|
104
|
+
model: 'Intel Arc A770',
|
|
105
|
+
vram: 16384,
|
|
106
|
+
driverVersion: '31.0.101.5590'
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
displays: []
|
|
110
|
+
}))
|
|
111
|
+
|
|
112
|
+
const sys = new Sysinfo()
|
|
113
|
+
sys.info = {}
|
|
114
|
+
|
|
115
|
+
await sys.gpus()
|
|
116
|
+
|
|
117
|
+
assert.equal(sys.info.gpu, 'intel')
|
|
118
|
+
assert.equal(sys.info.gpu_model, 'intel arc a770')
|
|
119
|
+
assert.equal(sys.info.gpu_driver, '31.0.101.5590')
|
|
120
|
+
assert.equal(sys.info.gpu_target, null)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('GPU sysinfo resolves gpu_target for the selected NVIDIA controller when AMD is also present', async (t) => {
|
|
124
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
125
|
+
controllers: [
|
|
126
|
+
{
|
|
127
|
+
vendor: 'AMD',
|
|
128
|
+
model: 'AMD Radeon RX 7900 XTX',
|
|
129
|
+
vram: 24576,
|
|
130
|
+
driverVersion: '31.0.2'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
vendor: 'NVIDIA',
|
|
134
|
+
model: 'NVIDIA GeForce RTX 4090',
|
|
135
|
+
vram: 24576,
|
|
136
|
+
driverVersion: '565.90',
|
|
137
|
+
pciBus: '00000000:02:00.0'
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
displays: []
|
|
141
|
+
}))
|
|
142
|
+
t.mock.method(nvidia, 'resolve_cuda_sm_target', async (controller) => {
|
|
143
|
+
assert.equal(controller.model, 'NVIDIA GeForce RTX 4090')
|
|
144
|
+
return 'sm_89'
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const sys = new Sysinfo()
|
|
148
|
+
sys.info = {}
|
|
149
|
+
|
|
150
|
+
await sys.gpus()
|
|
151
|
+
|
|
152
|
+
assert.equal(sys.info.gpu, 'nvidia')
|
|
153
|
+
assert.equal(sys.info.gpu_model, 'nvidia geforce rtx 4090')
|
|
154
|
+
assert.equal(sys.info.gpu_target, 'sm_89')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('GPU sysinfo leaves gpu_target null for Apple targets', async (t) => {
|
|
158
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
159
|
+
controllers: [
|
|
160
|
+
{
|
|
161
|
+
vendor: 'Apple',
|
|
162
|
+
model: 'Apple M3',
|
|
163
|
+
vram: 0,
|
|
164
|
+
driverVersion: null
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
displays: []
|
|
168
|
+
}))
|
|
169
|
+
|
|
170
|
+
const sys = new Sysinfo()
|
|
171
|
+
sys.info = {}
|
|
172
|
+
|
|
173
|
+
await sys.gpus()
|
|
174
|
+
|
|
175
|
+
assert.equal(sys.info.gpu, 'apple')
|
|
176
|
+
assert.equal(sys.info.gpu_model, 'apple m3')
|
|
177
|
+
assert.equal(sys.info.gpu_target, null)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('GPU sysinfo leaves gpu_target null for unknown GPU vendors', async (t) => {
|
|
181
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
182
|
+
controllers: [
|
|
183
|
+
{
|
|
184
|
+
vendor: 'Qualcomm',
|
|
185
|
+
model: 'Adreno X1',
|
|
186
|
+
vram: 16384,
|
|
187
|
+
driverVersion: '1.2.3'
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
displays: []
|
|
191
|
+
}))
|
|
192
|
+
|
|
193
|
+
const sys = new Sysinfo()
|
|
194
|
+
sys.info = {}
|
|
195
|
+
|
|
196
|
+
await sys.gpus()
|
|
197
|
+
|
|
198
|
+
assert.equal(sys.info.gpu, 'qualcomm')
|
|
199
|
+
assert.equal(sys.info.gpu_model, 'adreno x1')
|
|
200
|
+
assert.equal(sys.info.gpu_driver, '1.2.3')
|
|
201
|
+
assert.equal(sys.info.gpu_target, null)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('GPU sysinfo leaves gpu_target null when no GPU controller is detected', async (t) => {
|
|
205
|
+
t.mock.method(system, 'graphics', async () => ({
|
|
206
|
+
controllers: [],
|
|
207
|
+
displays: []
|
|
208
|
+
}))
|
|
209
|
+
|
|
210
|
+
const sys = new Sysinfo()
|
|
211
|
+
sys.info = {}
|
|
212
|
+
|
|
213
|
+
await sys.gpus()
|
|
214
|
+
|
|
215
|
+
assert.equal(sys.info.gpu, 'none')
|
|
216
|
+
assert.equal(sys.info.gpu_model, undefined)
|
|
217
|
+
assert.equal(sys.info.gpu_target, null)
|
|
218
|
+
assert.deepEqual(sys.info.gpus, [])
|
|
219
|
+
assert.equal(sys.info.vram, 0)
|
|
220
|
+
})
|
package/kernel/gpu/apple.js
DELETED
package/kernel/gpu/intel.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const { normalize_model } = require("./common")
|
|
2
|
-
|
|
3
|
-
// Resolve CPU brand lazily so clear GPU model matches do not trigger CPU probing.
|
|
4
|
-
const resolve_cpu_brand = async (cpu_brand) => {
|
|
5
|
-
if (typeof cpu_brand === "function") {
|
|
6
|
-
return await cpu_brand()
|
|
7
|
-
} else {
|
|
8
|
-
return cpu_brand
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Match Intel GPU model names that Pinokio should route to PyTorch XPU wheels.
|
|
13
|
-
const matches_xpu_torch_model = (model) => {
|
|
14
|
-
let normalized = normalize_model(model)
|
|
15
|
-
return (
|
|
16
|
-
// Intel Arc A-Series / B-Series, Arc Pro, and Core Ultra iGPUs when the
|
|
17
|
-
// GPU model itself includes Arc.
|
|
18
|
-
/\barc\b/.test(normalized) ||
|
|
19
|
-
// Intel Data Center GPU Max Series. Do not match generic Data Center GPU
|
|
20
|
-
// names because Data Center GPU Flex is not in current PyTorch XPU docs.
|
|
21
|
-
/\b(data center gpu max|gpu max|ponte vecchio)\b/.test(normalized)
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Detect uninformative Intel iGPU names that need CPU-brand fallback matching.
|
|
26
|
-
const is_generic_gpu_model = (model) => {
|
|
27
|
-
let normalized = normalize_model(model)
|
|
28
|
-
return /^(intel )?graphics$/.test(normalized)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Decide Intel XPU install intent, using CPU brand only for generic iGPU names.
|
|
32
|
-
const supports_torch_backend = async (model, cpu_brand) => {
|
|
33
|
-
if (matches_xpu_torch_model(model)) {
|
|
34
|
-
return true
|
|
35
|
-
}
|
|
36
|
-
if (!is_generic_gpu_model(model)) {
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
let brand = normalize_model(await resolve_cpu_brand(cpu_brand))
|
|
40
|
-
return /\bcore\s*ultra\b/.test(brand)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
is_generic_gpu_model,
|
|
45
|
-
matches_xpu_torch_model,
|
|
46
|
-
supports_torch_backend
|
|
47
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Pinokio 8.0.0 UI Updates
|
|
2
|
-
|
|
3
|
-
This release brings Pinokio's main surfaces into a quieter, more consistent workspace: app discovery, app launch controls, logs, tools, plugins, skills, autolaunch, setup, and mobile navigation now share the same visual language and action patterns.
|
|
4
|
-
|
|
5
|
-
The notes below are grouped by what users will notice, not by commit history. Screenshots were captured from the live local UI after comparing `de83785` to the current `HEAD`.
|
|
6
|
-
|
|
7
|
-
## Cleaner App Discovery
|
|
8
|
-
|
|
9
|
-
Pinokio's home screen now behaves more like a compact app manager. The sidebar is unified with the rest of the product, the app list is easier to scan, search and sort stay prominent, and each app row exposes quick actions without forcing users into the full app page first.
|
|
10
|
-
|
|
11
|
-

|
|
12
|
-
|
|
13
|
-
The new action panel gives installed apps a focused command surface for common launch, install, update, reset, file, and dev actions. Related commands are grouped in one drawer, which reduces row clutter while keeping the next action close to the app.
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-
|
|
17
|
-
## App Workspace And Assistance
|
|
18
|
-
|
|
19
|
-
App pages have been rebuilt around a more durable workspace shell. The page chrome, left command rail, right utility panels, app title area, status indicators, resource labels, and launch actions have been tightened so app-specific work feels connected to the rest of Pinokio.
|
|
20
|
-
|
|
21
|
-

|
|
22
|
-
|
|
23
|
-
Ask AI now opens as an in-app assistance drawer with searchable terminal and desktop agents. Instead of a generic help entry point, users can choose from bundled agents such as Codex, Claude Code, Qwen Code, Antigravity, Cursor, VS Code, and desktop integrations directly inside the app context.
|
|
24
|
-
|
|
25
|
-

|
|
26
|
-
|
|
27
|
-
## Plugins, Agents, And Skills
|
|
28
|
-
|
|
29
|
-
The Plugins page now separates terminal plugins from desktop plugins and shows install/manage affordances in a single list. This makes the new bundled native tools easier to understand: terminal agents launch inside Pinokio, while desktop tools launch externally.
|
|
30
|
-
|
|
31
|
-

|
|
32
|
-
|
|
33
|
-
Pinokio also has a new Skills management surface. Users can see where managed skills are stored, where they sync, whether each skill is valid, and whether it is currently enabled. The page also supports downloading skills from a Git URL.
|
|
34
|
-
|
|
35
|
-

|
|
36
|
-
|
|
37
|
-
## Startup And Launch Requirements
|
|
38
|
-
|
|
39
|
-
Autolaunch is now a first-class configuration page. Users can search installed apps, choose which launch script should run at startup, see how many apps are enabled, and toggle startup behavior without digging through app internals.
|
|
40
|
-
|
|
41
|
-

|
|
42
|
-
|
|
43
|
-
Setup and requirements screens now present blocked routes as a clear checklist. When a destination such as Local network needs a package update, Pinokio shows what is installed, what needs attention, and the action required before continuing.
|
|
44
|
-
|
|
45
|
-

|
|
46
|
-
|
|
47
|
-
## Logs, Diagnostics, And Issue Reporting
|
|
48
|
-
|
|
49
|
-
Logs have been redesigned around a file tree and live reader. Users can inspect logs from the machine, stream a selected file, clear the view, keep auto-scroll on, and generate a diagnostic zip from the same screen.
|
|
50
|
-
|
|
51
|
-

|
|
52
|
-
|
|
53
|
-
Behind that surface, the issue-reporting flow is more capable: recent logs can be summarized, redacted, copied, handed to Ask AI, or prepared as a registry draft. Those states require an app failure or report context, so they are not shown in this static screenshot set.
|
|
54
|
-
|
|
55
|
-
## Tools And Runtime Readiness
|
|
56
|
-
|
|
57
|
-
The Tools page now groups package bundles with readiness counts, per-bundle install state, and package-level status. Users can see which runtime bundles are ready, which need installation, and which package manager modules are present without reading terminal output first.
|
|
58
|
-
|
|
59
|
-

|
|
60
|
-
|
|
61
|
-
## Login And Connected Services
|
|
62
|
-
|
|
63
|
-
The Hugging Face login flow has been redesigned as a clear device-authorization page. It states the connection state, the credential provider, the reason to log in, and the next action without mixing auth details into unrelated setup UI.
|
|
64
|
-
|
|
65
|
-

|
|
66
|
-
|
|
67
|
-
GitHub and git-related modals also received layout cleanup: modal tabs, footers, repository status controls, checkpoint rows, and history/diff areas were tightened so longer repository workflows fit better in the available space.
|
|
68
|
-
|
|
69
|
-
## Mobile And Remote Access
|
|
70
|
-
|
|
71
|
-
Mobile layouts now prioritize the app list and move primary navigation to a bottom bar. Search, sort, app cards, favorites, and action buttons remain reachable on a phone-width viewport without the desktop sidebar competing for space.
|
|
72
|
-
|
|
73
|
-

|
|
74
|
-
|
|
75
|
-
Phone and LAN access also received targeted polish. QR and network access entry points are gated behind setup readiness, install terminals attach correctly over LAN, and the old mobile "Tap to connect" curtain has been replaced with a quieter first-interaction audio primer.
|
|
76
|
-
|
|
77
|
-
## Smaller Visual Polish
|
|
78
|
-
|
|
79
|
-
Several changes are intentionally broad but subtle:
|
|
80
|
-
|
|
81
|
-
- Shared sidebar styling now covers home, app, tools, plugins, skills, logs, settings, setup, network, and auth pages.
|
|
82
|
-
- App navigation is more responsive, especially where action labels previously wrapped or crowded the header.
|
|
83
|
-
- Screenshot/capture modals, footer status indicators, popovers, tabs, and terminal controls use flatter, tighter styling.
|
|
84
|
-
- Checkpoints rows, git history dialogs, network troubleshoot panels, and auth pages were normalized to match the newer page shell.
|
|
85
|
-
- Hidden or unused toolbar buttons were removed from the visible chrome so users see fewer inactive controls.
|
|
86
|
-
|
|
87
|
-
## Screenshot Coverage Notes
|
|
88
|
-
|
|
89
|
-
Captured screenshots cover the major visible themes: home discovery, app actions, app workspace chrome, Ask AI, plugins, skills, autolaunch, setup requirements, logs, tools, connected-service login, and mobile navigation.
|
|
90
|
-
|
|
91
|
-
Some UI states were not practical to screenshot without changing runtime state or forcing failures: failed-terminal fallback, report-draft creation, live dependency orchestration while an app is launching, completed peer-network routing, and git/checkpoint modal edge cases. Those changes are still included above because they are user-visible when the relevant workflow is active.
|