试用
$:~/ggml/ggml$cd examples/mnist
$:~/ggml/ggml/examples/mnist$ emcc -I../../include -I../../include/ggml -I../../examples ../../src/ggml.c main.cpp -o web/mnist.js -s EXPORTED_FUNCTIONS='["_wasm_eval","_wasm_random_digit","_malloc","_free"]' -s EXPORTED_RUNTIME_METHODS='["ccall"]' -s ALLOW_MEMORY_GROWTH=1 --preload-file models/mnist
cache:INFO: generating system asset: symbol_lists/a262e86b2699be8569b00ee0af7e34a0a05c5b5e.json... (this will be cached in "/home/pdd/Downloads/emsdk/upstream/emscripten/cache/symbol_lists/a262e86b2699be8569b00ee0af7e34a0a05c5b5e.json" for subsequent builds)
cache:INFO: - ok
- 启动一个服务
$:~/ggml/ggml/examples/mnist/web$ python -m http.server
与js代码交互
使用ccall 从JavaScript调用已编译的C函数
- 从JavaScript调用已编译的C函数的最简单方法是使用ccall()或cwrap()。ccall()用指定的参数调用一个编译后的C函数并返回结果,而cwrap()则“包装”一个编译过的C函数,并返回一个可以正常调用的JavaScript函数。因此,如果您计划多次调用已编译的函数,cwrap()会更有用。
函数的实现代码
#ifdef __cplusplus
extern "C" {
#endif
int wasm_eval(uint8_t * digitPtr) {
mnist_model model;
if (!mnist_model_load("models/mnist/ggml-model-f32.bin", model)) {
fprintf(stderr, "error loading model\n");
return -1;
}
std::vector<float> digit(digitPtr, digitPtr + 784);
int result = mnist_eval(model, 1, digit, nullptr);
ggml_free(model.ctx);
return result;
}
int wasm_random_digit(char * digitPtr) {
auto fin = std::ifstream("models/mnist/t10k-images.idx3-ubyte", std::ios::binary);
if (!fin) {
fprintf(stderr, "failed to open digits file\n");
return 0;
}
srand(time(NULL));
// Seek to a random digit: 16-byte header + 28*28 * (random 0 - 10000)
fin.seekg(16 + 784 * (rand() % 10000));
fin.read(digitPtr, 784);
return 1;
}
#ifdef __cplusplus
}
#endif
js中调用代码
// Call C from JavaScript
var result = Module.ccall('int_sqrt', // name of C function
'number', // return type
['number'], // argument types
[28]); // arguments
调试
// 在chrome edge运行都没有问题,但是在火狐运行会报错(官方的网页在火狐能够正常运行)
Uncaught RuntimeError: index out of bounds
createExportWrapper http://127.0.0.1:8000/mnist.js:876
ccall http://127.0.0.1:8000/mnist.js:4875
predict http://127.0.0.1:8000/:46 # predict函数报错
onRandom http://127.0.0.1:8000/:69
onclick http://127.0.0.1:8000/:1
mnist.wasm:360562:1
- 生成的minist.js
/**
* @param {string|null=} returnType
* @param {Array=} argTypes
* @param {Arguments|Array=} args
* @param {Object=} opts
*/
var ccall = function(ident, returnType, argTypes, args, opts) {
// For fast lookup of conversion functions
var toC = {
'string': (str) => {
var ret = 0;
if (str !== null && str !== undefined && str !== 0) { // null string
// at most 4 bytes per UTF-8 code point, +1 for the trailing '\0'
ret = stringToUTF8OnStack(str);
}
return ret;
},
'array': (arr) => {
var ret = stackAlloc(arr.length);
writeArrayToMemory(arr, ret);
return ret;
}
};
function convertReturnValue(ret) {
if (returnType === 'string') {
return UTF8ToString(ret);
}
if (returnType === 'boolean') return Boolean(ret);
return ret;
}
var func = getCFunc(ident);
var cArgs = [];
var stack = 0;
assert(returnType !== 'array', 'Return type should not be "array".');
if (args) {
for (var i = 0; i < args.length; i++) {
var converter = toC[argTypes[i]];
if (converter) {
if (stack === 0) stack = stackSave();
cArgs[i] = converter(args[i]);
} else {
cArgs[i] = args[i];
}
}
}
var ret = func.apply(null, cArgs);
function onDone(ret) {
if (stack !== 0) stackRestore(stack);
return convertReturnValue(ret);
}
ret = onDone(ret);
return ret;
};
- 可以打断点
- 然后运行到wasm代码
同一段代码在chrome , firefox对比
我在minist.js加入了一个输出,firefox好像给我优化掉了,并且对于同一个wasm的解释方式也有不同,截图如下
- chrome
- firefox
CG
- WASM throws memory access out of bounds in Chrome but not Firefox/Edge