Skip to content

Commit fcf515d

Browse files
committed
bench.wasm : same as "bench" but runs in the browser (#89)
1 parent 85c9ac1 commit fcf515d

File tree

9 files changed

+383
-3
lines changed

9 files changed

+383
-3
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ Some of the examples are even ported to run in the browser using WebAssembly. Ch
459459
| Example | Web | Description |
460460
| --- | --- | --- |
461461
| [main](examples/main) | [whisper.wasm](examples/whisper.wasm) | Tool for translating and transcribing audio using Whisper |
462-
| [bench](examples/bench) | | Benchmark the performance of Whisper on your machine |
462+
| [bench](examples/bench) | [bench.wasm](examples/bench.wasm) | Benchmark the performance of Whisper on your machine |
463463
| [stream](examples/stream) | [stream.wasm](examples/stream.wasm) | Real-time transcription of raw microphone capture |
464464
| [command](examples/command) | [command.wasm](examples/command.wasm) | Basic voice assistant example for receiving voice commands from the mic |
465465
| [talk](examples/talk) | [talk.wasm](examples/talk.wasm) | Talk with a GPT-2 bot |

bindings/javascript/whisper.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ if (EMSCRIPTEN)
2323
add_subdirectory(stream.wasm)
2424
add_subdirectory(command.wasm)
2525
add_subdirectory(talk.wasm)
26+
add_subdirectory(bench.wasm)
2627
else()
2728
add_subdirectory(main)
2829
add_subdirectory(stream)

examples/bench.wasm/CMakeLists.txt

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#
2+
# libbench
3+
#
4+
5+
set(TARGET libbench)
6+
7+
add_executable(${TARGET}
8+
emscripten.cpp
9+
)
10+
11+
target_link_libraries(${TARGET} PRIVATE
12+
whisper
13+
)
14+
15+
unset(EXTRA_FLAGS)
16+
17+
if (WHISPER_WASM_SINGLE_FILE)
18+
set(EXTRA_FLAGS "-s SINGLE_FILE=1")
19+
message(STATUS "Embedding WASM inside bench.js")
20+
21+
add_custom_command(
22+
TARGET ${TARGET} POST_BUILD
23+
COMMAND ${CMAKE_COMMAND} -E copy
24+
${CMAKE_BINARY_DIR}/bin/libbench.js
25+
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/bench.wasm/bench.js
26+
)
27+
endif()
28+
29+
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
30+
--bind \
31+
-s USE_PTHREADS=1 \
32+
-s PTHREAD_POOL_SIZE=8 \
33+
-s INITIAL_MEMORY=1024MB \
34+
-s TOTAL_MEMORY=1024MB \
35+
-s FORCE_FILESYSTEM=1 \
36+
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \
37+
${EXTRA_FLAGS} \
38+
")
39+
40+
#
41+
# bench.wasm
42+
#
43+
44+
set(TARGET bench.wasm)
45+
46+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
47+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/helpers.js @ONLY)

examples/bench.wasm/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# bench.wasm
2+
3+
Benchmark the performance of whisper.cpp in the browser using WebAssembly
4+
5+
Link: https://whisper.ggerganov.com/bench/
6+
7+
Terminal version: [examples/bench](/examples/bench)
8+
9+
## Build instructions
10+
11+
```bash
12+
# build using Emscripten (v3.1.2)
13+
git clone https://github.com/ggerganov/whisper.cpp
14+
cd whisper.cpp
15+
mkdir build-em && cd build-em
16+
emcmake cmake ..
17+
make -j
18+
19+
# copy the produced page to your HTTP path
20+
cp bin/bench.wasm/* /path/to/html/
21+
cp bin/libbench.worker.js /path/to/html/
22+
```

examples/bench.wasm/emscripten.cpp

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "whisper.h"
2+
3+
#include <emscripten.h>
4+
#include <emscripten/bind.h>
5+
6+
#include <cmath>
7+
#include <string>
8+
#include <thread>
9+
#include <vector>
10+
11+
constexpr int N_THREAD = 8;
12+
13+
// TODO: get rid of this vector of contexts - bad idea in the first place
14+
std::vector<struct whisper_context *> g_contexts(4, nullptr);
15+
16+
std::thread g_worker;
17+
18+
void bench_main(size_t index) {
19+
const int n_threads = std::min(N_THREAD, (int) std::thread::hardware_concurrency());
20+
21+
// whisper context
22+
auto & ctx = g_contexts[index];
23+
24+
fprintf(stderr, "%s: running benchmark with %d threads - please wait...\n", __func__, n_threads);
25+
26+
if (int ret = whisper_set_mel(ctx, nullptr, 0, WHISPER_N_MEL)) {
27+
fprintf(stderr, "error: failed to set mel: %d\n", ret);
28+
return;
29+
}
30+
31+
if (int ret = whisper_encode(ctx, 0, n_threads) != 0) {
32+
fprintf(stderr, "error: failed to encode model: %d\n", ret);
33+
return;
34+
}
35+
36+
whisper_print_timings(ctx);
37+
38+
fprintf(stderr, "\n");
39+
fprintf(stderr, "If you wish, you can submit these results here:\n");
40+
fprintf(stderr, "\n");
41+
fprintf(stderr, " https://github.com/ggerganov/whisper.cpp/issues/89\n");
42+
fprintf(stderr, "\n");
43+
fprintf(stderr, "Please include the following information:\n");
44+
fprintf(stderr, "\n");
45+
fprintf(stderr, " - CPU model\n");
46+
fprintf(stderr, " - Operating system\n");
47+
fprintf(stderr, " - Browser\n");
48+
fprintf(stderr, "\n");
49+
}
50+
51+
EMSCRIPTEN_BINDINGS(bench) {
52+
emscripten::function("init", emscripten::optional_override([](const std::string & path_model) {
53+
for (size_t i = 0; i < g_contexts.size(); ++i) {
54+
if (g_contexts[i] == nullptr) {
55+
g_contexts[i] = whisper_init(path_model.c_str());
56+
if (g_contexts[i] != nullptr) {
57+
if (g_worker.joinable()) {
58+
g_worker.join();
59+
}
60+
g_worker = std::thread([i]() {
61+
bench_main(i);
62+
});
63+
64+
return i + 1;
65+
} else {
66+
return (size_t) 0;
67+
}
68+
}
69+
}
70+
71+
return (size_t) 0;
72+
}));
73+
74+
emscripten::function("free", emscripten::optional_override([](size_t index) {
75+
if (index < g_contexts.size()) {
76+
whisper_free(g_contexts[index]);
77+
g_contexts[index] = nullptr;
78+
}
79+
}));
80+
}

examples/bench.wasm/index-tmpl.html

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<!doctype html>
2+
<html lang="en-us">
3+
<head>
4+
<title>bench : Benchmark whisper.cpp performance in the browser</title>
5+
6+
<style>
7+
#output {
8+
width: 100%;
9+
height: 100%;
10+
margin: 0 auto;
11+
margin-top: 10px;
12+
border-left: 0px;
13+
border-right: 0px;
14+
padding-left: 0px;
15+
padding-right: 0px;
16+
display: block;
17+
background-color: black;
18+
color: white;
19+
font-size: 10px;
20+
font-family: 'Lucida Console', Monaco, monospace;
21+
outline: none;
22+
white-space: pre;
23+
overflow-wrap: normal;
24+
overflow-x: scroll;
25+
}
26+
</style>
27+
</head>
28+
<body>
29+
<div id="main-container">
30+
<b>bench : Benchmark whisper.cpp performance in the browser</b>
31+
32+
<br><br>
33+
34+
You can find more about this project on <a href="https://github.com/ggerganov/whisper.cpp/tree/master/examples/bench.wasm">GitHub</a>.
35+
36+
<br><br>
37+
38+
<hr>
39+
40+
Select the model you would like to use and click the "Bench" button.<br>
41+
The results will be displayed in the textarea below.
42+
43+
<br><br>
44+
45+
<div id="model-whisper">
46+
Whisper model: <span id="model-whisper-status"></span>
47+
<button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">tiny.en (75 MB)</button>
48+
<button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">base.en (142 MB)</button>
49+
<span id="fetch-whisper-progress"></span>
50+
51+
<input type="file" id="whisper-file" name="file" onchange="loadFile(event, 'whisper.bin')" />
52+
</div>
53+
54+
<br>
55+
56+
<div id="input">
57+
<button id="bench" onclick="onBench()" disabled>Bench</button>
58+
<button id="clear" onclick="clearCache()">Clear Cache</button>
59+
</div>
60+
61+
<hr>
62+
63+
Debug output:
64+
<textarea id="output" rows="20"></textarea>
65+
66+
<br>
67+
68+
<b>Troubleshooting</b>
69+
70+
<br><br>
71+
72+
The page does some heavy computations, so make sure:
73+
74+
<ul>
75+
<li>To use a modern web browser (e.g. Chrome, Firefox)</li>
76+
<li>To use a fast desktop or laptop computer (i.e. not a mobile phone)</li>
77+
<li>Your browser supports WASM <a href="https://webassembly.org/roadmap/">Fixed-width SIMD</a></li>
78+
</ul>
79+
80+
<div class="cell-version">
81+
<span>
82+
|
83+
Build time: <span class="nav-link">@GIT_DATE@</span> |
84+
Commit hash: <a class="nav-link" href="https://github.com/ggerganov/whisper.cpp/commit/@GIT_SHA1@">@GIT_SHA1@</a> |
85+
Commit subject: <span class="nav-link">@GIT_COMMIT_SUBJECT@</span> |
86+
<a class="nav-link" href="https://github.com/ggerganov/whisper.cpp/tree/master/examples/bench.wasm">Source Code</a> |
87+
</span>
88+
</div>
89+
</div>
90+
91+
<script type="text/javascript" src="helpers.js"></script>
92+
<script type='text/javascript'>
93+
// the bench instance
94+
var instance = null;
95+
96+
// model name
97+
var model_whisper = null;
98+
99+
var Module = {
100+
print: printTextarea,
101+
printErr: printTextarea,
102+
setStatus: function(text) {
103+
printTextarea('js: ' + text);
104+
},
105+
monitorRunDependencies: function(left) {
106+
},
107+
preRun: function() {
108+
printTextarea('js: Preparing ...');
109+
},
110+
postRun: function() {
111+
printTextarea('js: Initialized successfully!');
112+
}
113+
};
114+
115+
//
116+
// fetch models
117+
//
118+
119+
let dbVersion = 1
120+
let dbName = 'whisper.ggerganov.com';
121+
let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
122+
123+
function storeFS(fname, buf) {
124+
// write to WASM file using FS_createDataFile
125+
// if the file exists, delete it
126+
try {
127+
Module.FS_unlink(fname);
128+
} catch (e) {
129+
// ignore
130+
}
131+
132+
Module.FS_createDataFile("/", fname, buf, true, true);
133+
134+
printTextarea('storeFS: stored model: ' + fname + ' size: ' + buf.length);
135+
136+
model_whisper = fname;
137+
138+
document.getElementById('model-whisper-status').innerHTML = 'loaded "' + model_whisper + '"!';
139+
140+
if (model_whisper != null) {
141+
document.getElementById('bench').disabled = false;
142+
}
143+
}
144+
145+
function loadFile(event, fname) {
146+
var file = event.target.files[0] || null;
147+
if (file == null) {
148+
return;
149+
}
150+
151+
printTextarea("loadFile: loading model: " + file.name + ", size: " + file.size + " bytes");
152+
printTextarea('loadFile: please wait ...');
153+
154+
var reader = new FileReader();
155+
reader.onload = function(event) {
156+
var buf = new Uint8Array(reader.result);
157+
storeFS(fname, buf);
158+
}
159+
reader.readAsArrayBuffer(file);
160+
161+
document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
162+
document.getElementById('fetch-whisper-base-en').style.display = 'none';
163+
document.getElementById('whisper-file' ).style.display = 'none';
164+
document.getElementById('model-whisper-status' ).innerHTML = 'loaded model: ' + file.name;
165+
}
166+
167+
function loadWhisper(model) {
168+
let urls = {
169+
'tiny.en': 'https://whisper.ggerganov.com/ggml-model-whisper-tiny.en.bin',
170+
'base.en': 'https://whisper.ggerganov.com/ggml-model-whisper-base.en.bin',
171+
};
172+
173+
let sizes = {
174+
'tiny.en': 75,
175+
'base.en': 142,
176+
};
177+
178+
let url = urls[model];
179+
let dst = 'whisper.bin';
180+
let size_mb = sizes[model];
181+
182+
document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
183+
document.getElementById('fetch-whisper-base-en').style.display = 'none';
184+
document.getElementById('model-whisper-status').innerHTML = 'loading "' + model + '" ... ';
185+
186+
cbProgress = function(p) {
187+
let el = document.getElementById('fetch-whisper-progress');
188+
el.innerHTML = Math.round(100*p) + '%';
189+
};
190+
191+
cbCancel = function() {
192+
var el;
193+
el = document.getElementById('fetch-whisper-tiny-en'); if (el) el.style.display = 'inline-block';
194+
el = document.getElementById('fetch-whisper-base-en'); if (el) el.style.display = 'inline-block';
195+
el = document.getElementById('model-whisper-status'); if (el) el.innerHTML = '';
196+
};
197+
198+
loadRemote(url, dst, size_mb, cbProgress, storeFS, cbCancel, printTextarea);
199+
}
200+
201+
//
202+
// main
203+
//
204+
205+
function onBench() {
206+
if (instance) {
207+
Module.free(instance);
208+
}
209+
210+
instance = Module.init('whisper.bin');
211+
212+
if (instance) {
213+
printTextarea("js: whisper initialized, instance: " + instance);
214+
}
215+
216+
document.getElementById('bench').disabled = true;
217+
218+
if (!instance) {
219+
printTextarea("js: failed to initialize whisper");
220+
return;
221+
}
222+
}
223+
224+
</script>
225+
<script type="text/javascript" src="bench.js"></script>
226+
</body>
227+
</html>

0 commit comments

Comments
 (0)