Download forge.js from LJTSG/npu-forge: direct link, hf CLI and curl.
- Browser
- Download file 7.11 kB
-
https://huggingface.co/LJTSG/npu-forge/resolve/main/forge.js
- Command line
-
hf download hf://LJTSG/npu-forge/forge.js
-
curl -L -o forge.js https://huggingface.co/LJTSG/npu-forge/resolve/main/forge.js
7.11 kB
| // forge β tune and convert models for AMD Ryzen AI NPUs, one command. | |
| // | |
| // forge tune <chats.jsonl> [--name grandma] [--base unsloth/Llama-3.2-1B-Instruct] | |
| // [--donor-tag llama3.2:1b] [--epochs 3] | |
| // forge convert <hf-repo> <file.gguf> [--name mymodel] [--donor-tag llama3.2:1b] | |
| // forge register merge registry.json into FLM's list (UAC if needed) | |
| // forge list stock models + forge customs with status | |
| // forge doctor NPU stack / registry / port health checks | |
| // forge serve <tag> flm serve passthrough (performance pmode) | |
| ; | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { spawnSync, spawn } = require('child_process'); | |
| const FORGE = __dirname; | |
| const FLM_LIST = 'C:\\Program Files\\flm\\model_list.json'; | |
| const FLM_MODELS = path.join(process.env.USERPROFILE, 'Documents', 'flm', 'models'); | |
| const ENV = { ...process.env, PYTHONIOENCODING: 'utf-8' }; | |
| const argv = process.argv.slice(2); | |
| const cmd = argv[0]; | |
| function flag(name, def) { const i = argv.indexOf('--' + name); return i > 0 ? argv[i + 1] : def; } | |
| function run(exe, args, opts = {}) { | |
| const r = spawnSync(exe, args, { stdio: 'inherit', env: ENV, shell: true, ...opts }); | |
| return r.status === 0; | |
| } | |
| function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8').replace(/^ο»Ώ/, '')); } catch (_) { return null; } } | |
| function assemble(outName, tag, donorTag, donor) { | |
| return run('node', [path.join(FORGE, 'bin', 'assemble.js'), '--out', outName, | |
| '--donor', donor, '--tag', tag, '--donor-tag', donorTag]); | |
| } | |
| function donorFolderFor(donorTag) { | |
| const ml = readJson(FLM_LIST) || { models: {} }; | |
| const [k, s] = (donorTag || 'llama3.2:1b').split(':'); | |
| const e = ml.models[k] && ml.models[k][s]; | |
| if (!e) { console.error('donor tag not found in FLM list: ' + donorTag); process.exit(1); } | |
| return e.name; | |
| } | |
| if (cmd === 'tune') { | |
| const data = argv[1]; | |
| if (!data || !fs.existsSync(data)) { console.error('forge tune <chats.jsonl> β file not found'); process.exit(1); } | |
| const name = flag('name', path.basename(data, path.extname(data))); | |
| const base = flag('base', 'unsloth/Llama-3.2-1B-Instruct'); | |
| const donorTag = flag('donor-tag', 'llama3.2:1b'); | |
| const epochs = flag('epochs', '3'); | |
| const outName = 'forge-' + name.toLowerCase().replace(/[^a-z0-9]+/g, '-'); | |
| // stage data where the modal mount expects it | |
| const stage = path.join(FORGE, 'scratch', 'tune-data'); | |
| fs.mkdirSync(stage, { recursive: true }); | |
| fs.copyFileSync(data, path.join(stage, 'data.jsonl')); | |
| console.log(`forge tune: ${data} -> ${base} (${epochs} epochs) -> NPU as ${name}-forge`); | |
| if (!run('modal', ['run', path.join(FORGE, 'modal', 'tune_npu.py'), | |
| '--base', base, '--data-file', 'data.jsonl', '--out-name', outName, '--epochs', epochs])) { | |
| console.error('tune job failed β see output above'); process.exit(1); | |
| } | |
| if (!assemble(outName, name.toLowerCase() + '-forge:1b', donorTag, donorFolderFor(donorTag))) process.exit(1); | |
| console.log('\nDone. Run: forge register then: flm run ' + name.toLowerCase() + '-forge:1b'); | |
| } | |
| else if (cmd === 'convert') { | |
| const repo = argv[1], file = argv[2]; | |
| if (!repo || !file) { console.error('forge convert <hf-repo> <file.gguf>'); process.exit(1); } | |
| const name = flag('name', file.replace(/\.gguf$/i, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 30)); | |
| const donorTag = flag('donor-tag', 'llama3.2:1b'); | |
| const outName = 'forge-' + name; | |
| console.log(`forge convert: ${repo}/${file} -> NPU as ${name}-forge`); | |
| if (!run('modal', ['run', path.join(FORGE, 'modal', 'convert_q4nx.py'), | |
| '--repo-id', repo, '--filename', file, '--out-name', outName])) { | |
| console.error('convert job failed'); process.exit(1); | |
| } | |
| const size = donorTag.split(':')[1] || '1b'; | |
| if (!assemble(outName, name + '-forge:' + size, donorTag, donorFolderFor(donorTag))) process.exit(1); | |
| console.log('\nDone. Run: forge register then: flm run ' + name + '-forge:' + size); | |
| } | |
| else if (cmd === 'register') { | |
| const r = spawnSync('node', [path.join(FORGE, 'bin', 'register.js')], { encoding: 'utf8', env: ENV, shell: true }); | |
| process.stdout.write(r.stdout || ''); | |
| if (r.status !== 0) { | |
| console.log('(needs admin β launching UAC prompt via register-admin.bat)'); | |
| spawn('cmd', ['/c', 'start', '', path.join(FORGE, 'register-admin.bat')], { detached: true, shell: false }); | |
| } | |
| } | |
| else if (cmd === 'list') { | |
| run('flm', ['list', '--filter', 'installed']); | |
| const reg = readJson(path.join(FORGE, 'registry.json')) || { models: {} }; | |
| const ml = readJson(FLM_LIST) || { models: {} }; | |
| console.log('\nforge customs:'); | |
| for (const [k, sizes] of Object.entries(reg.models)) { | |
| for (const [s, e] of Object.entries(sizes)) { | |
| const registered = !!(ml.models[k] && ml.models[k][s]); | |
| const folder = fs.existsSync(path.join(FLM_MODELS, e.name)); | |
| console.log(` ${k}:${s} files:${folder ? 'OK' : 'MISSING'} registered:${registered ? 'YES' : 'no β run forge register'}`); | |
| } | |
| } | |
| } | |
| else if (cmd === 'doctor') { | |
| console.log('β NPU device β'); | |
| run('powershell', ['-NoProfile', '-Command', '"Get-PnpDevice -Class ComputeAccelerator | Select-Object Status, FriendlyName | Format-Table -HideTableHeaders"']); | |
| console.log('β NPU driver version (FLM needs >= 32.0.203.304, recommends .311) β'); | |
| const dv = spawnSync('powershell', ['-NoProfile', '-Command', '"Get-PnpDevice -Class ComputeAccelerator | ForEach-Object { (Get-PnpDeviceProperty -InstanceId $_.InstanceId -KeyName DEVPKEY_Device_DriverVersion).Data }"'], { encoding: 'utf8', shell: true }); | |
| const ver = (dv.stdout || '').trim().split('\n')[0] || 'unknown'; | |
| const last = parseInt((ver.match(/(\d+)$/) || [])[1] || '0', 10); | |
| console.log(` installed: ${ver}` + (last && last < 304 ? ' -> TOO OLD: update the AMD NPU driver (AMD Software / amd.com Ryzen AI driver), then re-run doctor' : last ? ' -> OK' : '')); | |
| console.log('β flm validate β'); | |
| run('flm', ['validate']); | |
| console.log('β registry drift (customs lost to an flm update?) β'); | |
| const reg = readJson(path.join(FORGE, 'registry.json')) || { models: {} }; | |
| const ml = readJson(FLM_LIST) || { models: {} }; | |
| let drift = 0; | |
| for (const [k, sizes] of Object.entries(reg.models)) for (const s of Object.keys(sizes)) { | |
| if (!(ml.models[k] && ml.models[k][s])) { console.log(` LOST: ${k}:${s} β run forge register`); drift++; } | |
| } | |
| if (!drift) console.log(' all forge customs registered'); | |
| console.log('β port 52625 β'); | |
| const ns = spawnSync('netstat', ['-ano'], { encoding: 'utf8' }); | |
| console.log((ns.stdout || '').split('\n').some(l => l.includes(':52625') && l.includes('LISTENING')) ? ' flm serve is UP' : ' free (flm not serving)'); | |
| } | |
| else if (cmd === 'serve') { | |
| const tag = argv[1]; | |
| if (!tag) { console.error('forge serve <tag>'); process.exit(1); } | |
| run('flm', ['serve', tag, '--pmode', 'performance']); | |
| } | |
| else { | |
| console.log(fs.readFileSync(__filename, 'utf8').split('\n').slice(1, 11).map(l => l.replace(/^\/\/ ?/, '')).join('\n')); | |
| } | |