128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
/**
|
|
* Diagnose a GGUF on disk after load failure (magic / size / sha256).
|
|
*/
|
|
|
|
/** @type {Record<string, { size: number, sha256: string }>} */
|
|
export const BARE_OS_QVAC_KNOWN_GGUF = {
|
|
'Qwen3-0.6B-Q4_0.gguf': {
|
|
size: 382156480,
|
|
sha256: '33bcc57074ec7b6eada5a90651ee546ec0c2b271002c22baf9f1b2dd1e8f75cb'
|
|
},
|
|
'Qwen3-1.7B-Q4_0.gguf': {
|
|
size: 1056782912,
|
|
sha256: 'c876f159707a4e4f70e045106c69db15bfc935a4981706fd4f65c6e7ea1e81c5'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} filePath
|
|
* @returns {Promise<string>}
|
|
*/
|
|
export async function bareOsQvacDiagnoseGguf(filePath) {
|
|
const p = String(filePath || '').trim()
|
|
if (!p) return 'gguf: (no path)'
|
|
/** @type {string[]} */
|
|
const lines = ['gguf path: ' + p]
|
|
try {
|
|
let fs
|
|
try {
|
|
fs = await import('bare-fs')
|
|
} catch {
|
|
fs = await import('fs')
|
|
}
|
|
const fsp = fs.promises || fs
|
|
const st = await fsp.stat(p)
|
|
lines.push('size: ' + st.size + ' bytes')
|
|
|
|
const base =
|
|
p.split(/[/\\]/).pop() ||
|
|
''
|
|
const known = BARE_OS_QVAC_KNOWN_GGUF[base.replace(/^[0-9a-f]+_/i, '')] ||
|
|
Object.entries(BARE_OS_QVAC_KNOWN_GGUF).find(([name]) =>
|
|
base.endsWith(name)
|
|
)?.[1]
|
|
if (known) {
|
|
lines.push(
|
|
'expectedSize: ' +
|
|
known.size +
|
|
(st.size === known.size ? ' (ok)' : ' (MISMATCH)')
|
|
)
|
|
}
|
|
|
|
const buf = Buffer.alloc(4)
|
|
let fh
|
|
try {
|
|
if (typeof fsp.open === 'function') {
|
|
fh = await fsp.open(p, 'r')
|
|
if (typeof fh.read === 'function') {
|
|
await fh.read(buf, 0, 4, 0)
|
|
} else if (typeof fs.read === 'function' && fh.fd != null) {
|
|
await new Promise((resolve, reject) => {
|
|
fs.read(fh.fd, buf, 0, 4, 0, (err) => (err ? reject(err) : resolve()))
|
|
})
|
|
}
|
|
} else {
|
|
const fd = fs.openSync(p, 'r')
|
|
fs.readSync(fd, buf, 0, 4, 0)
|
|
fs.closeSync(fd)
|
|
}
|
|
} finally {
|
|
try {
|
|
if (fh && typeof fh.close === 'function') await fh.close()
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
const magic = buf.toString('utf8')
|
|
lines.push('magic: ' + JSON.stringify(magic) + (magic === 'GGUF' ? ' (ok)' : ' (BAD)'))
|
|
|
|
if (known && st.size === known.size && st.size <= 2 * 1024 * 1024 * 1024) {
|
|
try {
|
|
let crypto
|
|
try {
|
|
crypto = await import('bare-crypto')
|
|
} catch {
|
|
crypto = await import('crypto')
|
|
}
|
|
const createHash = crypto.createHash || crypto.default?.createHash
|
|
if (typeof createHash === 'function') {
|
|
const hash = createHash('sha256')
|
|
const stream = fs.createReadStream
|
|
? fs.createReadStream(p)
|
|
: null
|
|
if (stream) {
|
|
await new Promise((resolve, reject) => {
|
|
stream.on('data', (c) => hash.update(c))
|
|
stream.on('error', reject)
|
|
stream.on('end', resolve)
|
|
})
|
|
const dig = hash.digest('hex')
|
|
lines.push(
|
|
'sha256: ' +
|
|
dig +
|
|
(dig === known.sha256 ? ' (ok)' : ' (MISMATCH want ' + known.sha256 + ')')
|
|
)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
lines.push('sha256: (skip ' + String(e && e.message ? e.message : e) + ')')
|
|
}
|
|
}
|
|
} catch (e) {
|
|
lines.push('diagnose error: ' + String(e && e.message ? e.message : e))
|
|
}
|
|
return lines.join('; ')
|
|
}
|
|
|
|
/**
|
|
* Extract a GGUF path from a QVAC / llama error string.
|
|
* @param {string} msg
|
|
* @returns {string}
|
|
*/
|
|
export function bareOsQvacExtractGgufPath(msg) {
|
|
const m = /failed to load model '([^']+\.gguf)'/i.exec(String(msg || ''))
|
|
if (m) return m[1]
|
|
const m2 = /(\/[^\s']+\.gguf)/i.exec(String(msg || ''))
|
|
return m2 ? m2[1] : ''
|
|
}
|