pinokiod 7.5.14 → 7.5.16
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/api/hf/index.js +295 -1
- package/kernel/connect/index.js +6 -0
- package/kernel/connect/providers/huggingface/index.js +14 -0
- 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/server/public/htmlmodal.js +78 -9
- package/server/public/universal-launcher.js +30 -1
- package/test/amd-gpu-target.test.js +169 -0
- package/test/gpu-target-template-variable.test.js +69 -0
- package/test/hf-api.test.js +435 -0
- package/test/huggingface-connect.test.js +33 -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/api/hf/index.js
CHANGED
|
@@ -1,7 +1,301 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
1
|
const unparse = require('yargs-unparser-custom-flag');
|
|
3
2
|
const Shell = require('../shell')
|
|
3
|
+
const Util = require('../../util')
|
|
4
|
+
const HtmlModal = require('../htmlmodal')
|
|
5
|
+
|
|
6
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
7
|
+
const positiveNumber = (value, fallback) => {
|
|
8
|
+
const number = Number(value)
|
|
9
|
+
return Number.isFinite(number) && number > 0 ? number : fallback
|
|
10
|
+
}
|
|
11
|
+
const escapeHtml = (value) => String(value == null ? "" : value)
|
|
12
|
+
.replace(/&/g, "&")
|
|
13
|
+
.replace(/</g, "<")
|
|
14
|
+
.replace(/>/g, ">")
|
|
15
|
+
.replace(/"/g, """)
|
|
16
|
+
.replace(/'/g, "'")
|
|
17
|
+
|
|
4
18
|
class HF {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.htmlModal = new HtmlModal()
|
|
21
|
+
}
|
|
22
|
+
connect(kernel, method) {
|
|
23
|
+
if (!kernel || !kernel.connect || typeof kernel.connect[method] !== "function") {
|
|
24
|
+
throw new Error(`Hugging Face connect ${method} is not available`)
|
|
25
|
+
}
|
|
26
|
+
return kernel.connect
|
|
27
|
+
}
|
|
28
|
+
safeKeys(keys) {
|
|
29
|
+
if (!keys || !keys.access_token) {
|
|
30
|
+
return keys
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
token_path: keys.token_path
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async copyCode(login, ondata, options = {}) {
|
|
37
|
+
if (!login || !login.user_code) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await Util.clipboard({
|
|
42
|
+
type: "copy",
|
|
43
|
+
text: login.user_code
|
|
44
|
+
})
|
|
45
|
+
if (ondata && !options.silent) {
|
|
46
|
+
ondata({ raw: `\r\nHugging Face code copied to clipboard: ${login.user_code}\r\n` })
|
|
47
|
+
}
|
|
48
|
+
return { ok: true }
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const message = error && error.message ? error.message : String(error)
|
|
51
|
+
if (ondata && !options.silent) {
|
|
52
|
+
ondata({ raw: `\r\nCopy this Hugging Face code into the browser: ${login.user_code}\r\n` })
|
|
53
|
+
ondata({ raw: `Clipboard copy failed: ${message}\r\n` })
|
|
54
|
+
}
|
|
55
|
+
return { ok: false, error: message }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
canDispatchModal(req, ondata, kernel) {
|
|
59
|
+
return !!(
|
|
60
|
+
ondata
|
|
61
|
+
&& kernel
|
|
62
|
+
&& kernel.api
|
|
63
|
+
&& typeof kernel.api.wait === "function"
|
|
64
|
+
&& req
|
|
65
|
+
&& req.parent
|
|
66
|
+
&& (req.parent.id || req.parent.path)
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
canUseModal(req, ondata, kernel, url) {
|
|
70
|
+
return !!(url && this.canDispatchModal(req, ondata, kernel))
|
|
71
|
+
}
|
|
72
|
+
modalRequest(req, modalId, params = {}) {
|
|
73
|
+
return {
|
|
74
|
+
params: Object.assign({ id: modalId }, params),
|
|
75
|
+
parent: req.parent,
|
|
76
|
+
cwd: req.cwd
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
modalId(req) {
|
|
80
|
+
const id = req && req.parent ? (req.parent.id || req.parent.path) : "hf-login"
|
|
81
|
+
return `hf-login:${id}`
|
|
82
|
+
}
|
|
83
|
+
loginModalHtml(login, clipboard) {
|
|
84
|
+
const code = escapeHtml(login.user_code || "")
|
|
85
|
+
const expires = login.expires_in
|
|
86
|
+
? `<p class="hf-login-modal-note">Expires in ${escapeHtml(login.expires_in)} seconds.</p>`
|
|
87
|
+
: ""
|
|
88
|
+
let copyText = "Copy this code if Hugging Face asks for it."
|
|
89
|
+
let copyClass = "neutral"
|
|
90
|
+
if (clipboard && clipboard.ok) {
|
|
91
|
+
copyText = "The code has been copied to your clipboard."
|
|
92
|
+
copyClass = "success"
|
|
93
|
+
} else if (clipboard && clipboard.ok === false) {
|
|
94
|
+
copyText = `Clipboard copy failed. The code is displayed below.`
|
|
95
|
+
copyClass = "warning"
|
|
96
|
+
}
|
|
97
|
+
return `
|
|
98
|
+
<div class="hf-login-modal">
|
|
99
|
+
<div class="hf-login-modal-copy ${copyClass}">${escapeHtml(copyText)}</div>
|
|
100
|
+
<div class="hf-login-modal-code" aria-label="Hugging Face device code">${code}</div>
|
|
101
|
+
${expires}
|
|
102
|
+
<p>Open Hugging Face to finish login. Pinokio will continue automatically.</p>
|
|
103
|
+
</div>
|
|
104
|
+
`
|
|
105
|
+
}
|
|
106
|
+
async promptLoginModal(req, ondata, kernel, login, clipboard) {
|
|
107
|
+
const modalId = this.modalId(req)
|
|
108
|
+
const response = await this.htmlModal.open(
|
|
109
|
+
this.modalRequest(req, modalId, {
|
|
110
|
+
title: "Log in to Hugging Face",
|
|
111
|
+
variant: "minimal",
|
|
112
|
+
html: this.loginModalHtml(login, clipboard),
|
|
113
|
+
status: { text: "Waiting for you to open Hugging Face.", waiting: false },
|
|
114
|
+
actions: [{
|
|
115
|
+
id: "open",
|
|
116
|
+
label: "Open Hugging Face",
|
|
117
|
+
type: "submit",
|
|
118
|
+
href: login.verification_uri_complete,
|
|
119
|
+
target: "_blank",
|
|
120
|
+
features: "browser",
|
|
121
|
+
primary: true,
|
|
122
|
+
close: false,
|
|
123
|
+
icon: "fa-solid fa-arrow-up-right-from-square"
|
|
124
|
+
}, {
|
|
125
|
+
id: "cancel",
|
|
126
|
+
label: "Cancel",
|
|
127
|
+
type: "submit",
|
|
128
|
+
variant: "secondary"
|
|
129
|
+
}],
|
|
130
|
+
actionsAlign: "end",
|
|
131
|
+
await: true,
|
|
132
|
+
dismissible: false
|
|
133
|
+
}),
|
|
134
|
+
ondata,
|
|
135
|
+
kernel
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if (!response || response.action === "cancel" || response.action === "dismissed") {
|
|
139
|
+
await this.htmlModal.close(this.modalRequest(req, modalId), ondata, kernel)
|
|
140
|
+
throw new Error("Hugging Face login canceled.")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await this.htmlModal.update(
|
|
144
|
+
this.modalRequest(req, modalId, {
|
|
145
|
+
variant: "minimal",
|
|
146
|
+
status: { text: "Waiting for Hugging Face authorization to finish...", waiting: true },
|
|
147
|
+
actions: [{
|
|
148
|
+
id: "open-again",
|
|
149
|
+
label: "Open Again",
|
|
150
|
+
type: "link",
|
|
151
|
+
href: login.verification_uri_complete,
|
|
152
|
+
target: "_blank",
|
|
153
|
+
features: "browser",
|
|
154
|
+
variant: "secondary",
|
|
155
|
+
icon: "fa-solid fa-arrow-up-right-from-square"
|
|
156
|
+
}],
|
|
157
|
+
actionsAlign: "end",
|
|
158
|
+
dismissible: false
|
|
159
|
+
}),
|
|
160
|
+
ondata,
|
|
161
|
+
kernel
|
|
162
|
+
)
|
|
163
|
+
return response
|
|
164
|
+
}
|
|
165
|
+
async closeLoginModal(req, ondata, kernel) {
|
|
166
|
+
if (!this.canDispatchModal(req, ondata, kernel)) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
await this.htmlModal.close(this.modalRequest(req, this.modalId(req)), ondata, kernel)
|
|
170
|
+
}
|
|
171
|
+
async cancelLogin(connect, params) {
|
|
172
|
+
if (connect && typeof connect.cancelLogin === "function") {
|
|
173
|
+
await connect.cancelLogin("huggingface", params || {})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/*
|
|
177
|
+
{
|
|
178
|
+
"method": "hf.login",
|
|
179
|
+
"params": {
|
|
180
|
+
"force": false,
|
|
181
|
+
"open": true,
|
|
182
|
+
"modal": true,
|
|
183
|
+
"clipboard": true,
|
|
184
|
+
"wait": true,
|
|
185
|
+
"timeout": 120000,
|
|
186
|
+
"interval": 2000
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
Starts the existing Hugging Face device login provider. By default it shows a
|
|
191
|
+
blocking modal with the copied code and waits for the user to open the
|
|
192
|
+
verification URL before polling for the managed token.
|
|
193
|
+
*/
|
|
194
|
+
async login(req, ondata, kernel) {
|
|
195
|
+
const connect = this.connect(kernel, "keys")
|
|
196
|
+
const params = req.params || {}
|
|
197
|
+
const force = params.force === true
|
|
198
|
+
const shouldOpen = params.open !== false
|
|
199
|
+
const shouldModal = params.modal !== false
|
|
200
|
+
const shouldCopy = params.clipboard !== false
|
|
201
|
+
const shouldWait = params.wait !== false
|
|
202
|
+
const timeout = positiveNumber(params.timeout, 120000)
|
|
203
|
+
const interval = positiveNumber(params.interval, 2000)
|
|
204
|
+
|
|
205
|
+
if (!force) {
|
|
206
|
+
const existing = await connect.keys("huggingface")
|
|
207
|
+
if (existing && existing.access_token) {
|
|
208
|
+
return {
|
|
209
|
+
status: "success",
|
|
210
|
+
already_logged_in: true,
|
|
211
|
+
...this.safeKeys(existing)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (typeof connect.login !== "function") {
|
|
217
|
+
throw new Error("Hugging Face connect login is not available")
|
|
218
|
+
}
|
|
219
|
+
const response = await connect.login("huggingface", params)
|
|
220
|
+
if (!response || response.status === "error") {
|
|
221
|
+
return response
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const login = response.login || {}
|
|
225
|
+
const url = login.verification_uri_complete
|
|
226
|
+
const useModal = shouldModal && this.canUseModal(req, ondata, kernel, url)
|
|
227
|
+
if (shouldCopy && login.user_code) {
|
|
228
|
+
response.clipboard = await this.copyCode(login, ondata, { silent: useModal })
|
|
229
|
+
} else if (login.user_code && ondata && !useModal) {
|
|
230
|
+
ondata({ raw: `\r\nHugging Face code: ${login.user_code}\r\n` })
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (useModal) {
|
|
234
|
+
try {
|
|
235
|
+
response.open = await this.promptLoginModal(req, ondata, kernel, login, response.clipboard)
|
|
236
|
+
} catch (error) {
|
|
237
|
+
await this.cancelLogin(connect, params)
|
|
238
|
+
throw error
|
|
239
|
+
}
|
|
240
|
+
} else if (shouldOpen && url) {
|
|
241
|
+
if (ondata) {
|
|
242
|
+
if (!shouldModal) {
|
|
243
|
+
ondata({ raw: "\r\nHugging Face login modal disabled.\r\n" })
|
|
244
|
+
}
|
|
245
|
+
const codeHint = response.clipboard && response.clipboard.ok
|
|
246
|
+
? "The code is already on your clipboard; paste it if Hugging Face asks."
|
|
247
|
+
: "If Hugging Face asks for a code, use the code printed above."
|
|
248
|
+
ondata({ raw: `Opening Hugging Face login: ${url}\r\n${codeHint}\r\n` })
|
|
249
|
+
if (response.clipboard && response.clipboard.ok === false) {
|
|
250
|
+
ondata({ raw: "The browser is opening even though clipboard copy failed.\r\n" })
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
response.open = await Util.openURI(url)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!shouldWait) {
|
|
257
|
+
if (useModal) {
|
|
258
|
+
await this.closeLoginModal(req, ondata, kernel)
|
|
259
|
+
}
|
|
260
|
+
return response
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const startedAt = Date.now()
|
|
264
|
+
while (Date.now() - startedAt < timeout) {
|
|
265
|
+
const keys = await connect.keys("huggingface")
|
|
266
|
+
if (keys && keys.access_token) {
|
|
267
|
+
if (useModal) {
|
|
268
|
+
await this.closeLoginModal(req, ondata, kernel)
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
...response,
|
|
272
|
+
status: "success",
|
|
273
|
+
...this.safeKeys(keys)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
await delay(interval)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (useModal) {
|
|
280
|
+
await this.closeLoginModal(req, ondata, kernel)
|
|
281
|
+
}
|
|
282
|
+
await this.cancelLogin(connect, params)
|
|
283
|
+
return {
|
|
284
|
+
...response,
|
|
285
|
+
status: "timeout",
|
|
286
|
+
error: "Timed out waiting for Hugging Face login to finish."
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/*
|
|
290
|
+
{
|
|
291
|
+
"method": "hf.logout"
|
|
292
|
+
}
|
|
293
|
+
*/
|
|
294
|
+
async logout(req, ondata, kernel) {
|
|
295
|
+
const connect = this.connect(kernel, "logout")
|
|
296
|
+
await connect.logout("huggingface", req.params || {})
|
|
297
|
+
return { status: "success" }
|
|
298
|
+
}
|
|
5
299
|
/*
|
|
6
300
|
{
|
|
7
301
|
"method": "hf.download",
|
package/kernel/connect/index.js
CHANGED
|
@@ -32,6 +32,12 @@ class Connect {
|
|
|
32
32
|
let res = await this.clients[provider].logout(req)
|
|
33
33
|
return res
|
|
34
34
|
}
|
|
35
|
+
async cancelLogin(provider, req) {
|
|
36
|
+
if (this.clients[provider] && this.clients[provider].cancelLogin) {
|
|
37
|
+
return await this.clients[provider].cancelLogin(req)
|
|
38
|
+
}
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
35
41
|
async keys(provider) {
|
|
36
42
|
let res = await this.clients[provider].keys()
|
|
37
43
|
return res
|
|
@@ -227,6 +227,20 @@ class Huggingface {
|
|
|
227
227
|
await this.runHf(["auth", "logout"]).catch(() => {})
|
|
228
228
|
await fs.promises.rm(this.kernel.path("connect", "huggingface"), { recursive: true, force: true }).catch(() => {})
|
|
229
229
|
}
|
|
230
|
+
async cancelLogin() {
|
|
231
|
+
const session = this.loginSession
|
|
232
|
+
if (!session || session.status !== "pending") {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
session.status = "canceled"
|
|
236
|
+
session.error = "Hugging Face login canceled."
|
|
237
|
+
if (session.child) {
|
|
238
|
+
session.child.kill()
|
|
239
|
+
}
|
|
240
|
+
if (this.loginSession === session) {
|
|
241
|
+
this.loginSession = null
|
|
242
|
+
}
|
|
243
|
+
}
|
|
230
244
|
async logout() {
|
|
231
245
|
await this.destroy()
|
|
232
246
|
}
|
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
|