spike(llm): T18 端侧小模型——MNN native 骨架 + 局域网模型服务

- cpp/:liblocalllm.so NAPI 骨架(loadModel/chat/benchmark/release),预置 libMNN.so 3.6.1 鸿蒙包(已验证含 createLLM 符号;.gitignore 挡预编译库与 models/,获取方式见任务文档)
- LocalLlm.ets:native 调用的 ets 封装;LlmTestPage:模型从局域网服务器下载→加载→首 token/速度 benchmark
- scripts/serve-models.mjs:开发期局域网模型文件服务器(Qwen3-0.6B-MNN 456MB,SHA256 与 ModelScope 官方清单逐文件核对)
- build-profile.json5 接 externalNativeOptions(arm64-v8a);ProfileContent 加「端侧LLM调试」入口(内部调试用)
- docs/开发任务包/T18-端侧小模型SPIKE.md:目标/验证项/回退线;真机 benchmark 数据回来后写 T18-REPORT 三问三答
This commit is contained in:
xsl
2026-08-30 13:18:03 +08:00
parent 120bbeceff
commit b31c7490f2
36 changed files with 6789 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env node
/**
* T18 调试期局域网模型服务器(零依赖,Node 18+)。
*
* 用法:node scripts/serve-models.mjs [端口=8080]
* 手机与电脑连同一 Wi-Fi,App 内模型地址填 http://<电脑局域网IP>:<端口>
*
* 服务内容:models/ 目录下的 manifest.json 与模型文件。
* 调试阶段一次性整包下载即可;断点续传(Range)留待商用阶段接 CDN 时再做。
*/
import { createServer } from 'node:http';
import { createReadStream, existsSync, statSync } from 'node:fs';
import { networkInterfaces } from 'node:os';
import { extname, join, normalize } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = normalize(join(fileURLToPath(import.meta.url), '..', '..', 'models'));
const port = Number(process.argv[2] ?? 8080);
const MIME = {
'.json': 'application/json; charset=utf-8',
'.txt': 'text/plain; charset=utf-8',
'.mnn': 'application/octet-stream',
'.weight': 'application/octet-stream',
};
const server = createServer((req, res) => {
const urlPath = decodeURIComponent((req.url ?? '/').split('?')[0]);
// 防目录穿越:规范化后必须仍落在 root 内
const filePath = normalize(join(root, urlPath));
if (!filePath.startsWith(root) || !existsSync(filePath) || !statSync(filePath).isFile()) {
res.writeHead(404).end('not found');
return;
}
res.writeHead(200, {
'Content-Type': MIME[extname(filePath)] ?? 'application/octet-stream',
'Content-Length': statSync(filePath).size,
});
createReadStream(filePath).pipe(res);
});
server.listen(port, '0.0.0.0', () => {
console.log(`serving ${root}`);
for (const [name, addrs] of Object.entries(networkInterfaces())) {
for (const a of addrs ?? []) {
if (a.family === 'IPv4' && !a.internal) {
console.log(` http://${a.address}:${port}/ (${name})`);
}
}
}
});