* whisper.android: Support benchmark for Android example.
* whisper.android: update screenshot in README.
* update: Make text selectable for copy & paste.
* Update whisper.h to restore API name
Co-authored-by: Georgi Gerganov <redacted>
* whisper.android: Restore original API names.
---------
Co-authored-by: tinoue <redacted>
Co-authored-by: Georgi Gerganov <redacted>
5. Select the "release" active build variant, and use Android Studio to run and deploy to your device.
[^1]: I recommend the tiny or base models for running on an Android device.
-<img width="300" alt="image" src="https://user-images.githubusercontent.com/1991296/208154256-82d972dc-221b-48c4-bfcb-36ce68602f93.png">
+<img width="300" alt="image" src="https://user-images.githubusercontent.com/1670775/221613663-a17bf770-27ef-45ab-9a46-a5f99ba65d2a.jpg">
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
canTranscribe = viewModel.canTranscribe,
isRecording = viewModel.isRecording,
messageLog = viewModel.dataLog,
+ onBenchmarkTapped = viewModel::benchmark,
onTranscribeSampleTapped = viewModel::transcribeSample,
onRecordTapped = viewModel::toggleRecord
)
canTranscribe: Boolean,
isRecording: Boolean,
messageLog: String,
+ onBenchmarkTapped: () -> Unit,
onTranscribeSampleTapped: () -> Unit,
onRecordTapped: () -> Unit
) {
.padding(innerPadding)
.padding(16.dp)
) {
- Row(horizontalArrangement = Arrangement.SpaceBetween) {
- TranscribeSampleButton(enabled = canTranscribe, onClick = onTranscribeSampleTapped)
+ Column(verticalArrangement = Arrangement.SpaceBetween) {
+ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
+ BenchmarkButton(enabled = canTranscribe, onClick = onBenchmarkTapped)
+ TranscribeSampleButton(enabled = canTranscribe, onClick = onTranscribeSampleTapped)
+ }
RecordButton(
enabled = canTranscribe,
isRecording = isRecording,
@Composable
private fun MessageLog(log: String) {
- Text(modifier = Modifier.verticalScroll(rememberScrollState()), text = log)
+ SelectionContainer() {
+ Text(modifier = Modifier.verticalScroll(rememberScrollState()), text = log)
+ }
+}
+
+@Composable
+private fun BenchmarkButton(enabled: Boolean, onClick: () -> Unit) {
+ Button(onClick = onClick, enabled = enabled) {
+ Text("Benchmark")
+ }
}
@Composable
init {
viewModelScope.launch {
+ printSystemInfo()
loadData()
}
}
+ private suspend fun printSystemInfo() {
+ printMessage(String.format("System Info: %s\n", WhisperContext.getSystemInfo()));
+ }
+
private suspend fun loadData() {
printMessage("Loading data...\n")
try {
//whisperContext = WhisperContext.createContextFromFile(firstModel.absolutePath)
}
+ fun benchmark() = viewModelScope.launch {
+ runBenchmark(6)
+ }
+
fun transcribeSample() = viewModelScope.launch {
transcribeAudio(getFirstSample())
}
+ private suspend fun runBenchmark(nthreads: Int) {
+ if (!canTranscribe) {
+ return
+ }
+
+ canTranscribe = false
+
+ printMessage("Running benchmark. This will take minutes...\n")
+ whisperContext?.benchMemory(nthreads)?.let{ printMessage(it) }
+ printMessage("\n")
+ whisperContext?.benchGgmlMulMat(nthreads)?.let{ printMessage(it) }
+
+ canTranscribe = true
+ }
+
private suspend fun getFirstSample(): File = withContext(Dispatchers.IO) {
samplesPath.listFiles()!!.first()
}
canTranscribe = false
try {
- printMessage("Reading wave samples...\n")
+ printMessage("Reading wave samples... ")
val data = readAudioSamples(file)
+ printMessage("${data.size / (16000 / 1000)} ms\n")
printMessage("Transcribing data...\n")
+ val start = System.currentTimeMillis()
val text = whisperContext?.transcribeData(data)
- printMessage("Done: $text\n")
+ val elapsed = System.currentTimeMillis() - start
+ printMessage("Done ($elapsed ms): $text\n")
} catch (e: Exception) {
Log.w(LOG_TAG, e)
printMessage("${e.localizedMessage}\n")
}
}
+ suspend fun benchMemory(nthreads: Int): String = withContext(scope.coroutineContext) {
+ return@withContext WhisperLib.benchMemcpy(nthreads)
+ }
+
+ suspend fun benchGgmlMulMat(nthreads: Int): String = withContext(scope.coroutineContext) {
+ return@withContext WhisperLib.benchGgmlMulMat(nthreads)
+ }
+
suspend fun release() = withContext(scope.coroutineContext) {
if (ptr != 0L) {
WhisperLib.freeContext(ptr)
}
return WhisperContext(ptr)
}
+
+ fun getSystemInfo(): String {
+ return WhisperLib.getSystemInfo()
+ }
}
}
external fun fullTranscribe(contextPtr: Long, audioData: FloatArray)
external fun getTextSegmentCount(contextPtr: Long): Int
external fun getTextSegment(contextPtr: Long, index: Int): String
+ external fun getSystemInfo(): String
+ external fun benchMemcpy(nthread: Int): String
+ external fun benchGgmlMulMat(nthread: Int): String
}
}
#include <sys/sysinfo.h>
#include <string.h>
#include "whisper.h"
+#include "ggml.h"
#define UNUSED(x) (void)(x)
#define TAG "JNI"
const char *text = whisper_full_get_segment_text(context, index);
jstring string = (*env)->NewStringUTF(env, text);
return string;
-}
\ No newline at end of file
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_getSystemInfo(
+ JNIEnv *env, jobject thiz
+) {
+ UNUSED(thiz);
+ const char *sysinfo = whisper_print_system_info();
+ jstring string = (*env)->NewStringUTF(env, sysinfo);
+ return string;
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_benchMemcpy(JNIEnv *env, jobject thiz,
+ jint n_threads) {
+ UNUSED(thiz);
+ const char *bench_ggml_memcpy = whisper_bench_memcpy_str(n_threads);
+ jstring string = (*env)->NewStringUTF(env, bench_ggml_memcpy);
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_benchGgmlMulMat(JNIEnv *env, jobject thiz,
+ jint n_threads) {
+ UNUSED(thiz);
+ const char *bench_ggml_mul_mat = whisper_bench_ggml_mul_mat_str(n_threads);
+ jstring string = (*env)->NewStringUTF(env, bench_ggml_mul_mat);
+}
//
WHISPER_API int whisper_bench_memcpy(int n_threads) {
+ fputs(whisper_bench_memcpy_str(n_threads), stderr);
+ return 0;
+}
+
+WHISPER_API const char * whisper_bench_memcpy_str(int n_threads) {
+ static std::string s;
+ s = "";
+ char strbuf[256];
+
ggml_time_init();
size_t n = 50;
src[0] = rand();
}
- fprintf(stderr, "memcpy: %.2f GB/s\n", (double) (n*size)/(tsum*1024llu*1024llu*1024llu));
+ snprintf(strbuf, sizeof(strbuf), "memcpy: %.2f GB/s\n", (double) (n*size)/(tsum*1024llu*1024llu*1024llu));
+ s += strbuf;
// needed to prevent the compile from optimizing the memcpy away
{
for (size_t i = 0; i < size; i++) sum += dst[i];
- fprintf(stderr, "sum: %s %f\n", sum == -536870910.00 ? "ok" : "error", sum);
+ snprintf(strbuf, sizeof(strbuf), "sum: %s %f\n", sum == -536870910.00 ? "ok" : "error", sum);
+ s += strbuf;
}
free(src);
free(dst);
- return 0;
+ return s.c_str();
}
WHISPER_API int whisper_bench_ggml_mul_mat(int n_threads) {
+ fputs(whisper_bench_ggml_mul_mat_str(n_threads), stderr);
+ return 0;
+}
+
+WHISPER_API const char * whisper_bench_ggml_mul_mat_str(int n_threads) {
+ static std::string s;
+ s = "";
+ char strbuf[256];
+
ggml_time_init();
const int n_max = 128;
s = ((2.0*N*N*N*n)/tsum)*1e-9;
}
- fprintf(stderr, "ggml_mul_mat: %5zu x %5zu: F16 %8.1f GFLOPS (%3d runs) / F32 %8.1f GFLOPS (%3d runs)\n",
+ snprintf(strbuf, sizeof(strbuf), "ggml_mul_mat: %5zu x %5zu: F16 %8.1f GFLOPS (%3d runs) / F32 %8.1f GFLOPS (%3d runs)\n",
N, N, s_fp16, n_fp16, s_fp32, n_fp32);
+ s += strbuf;
}
- return 0;
+ return s.c_str();
}
// =================================================================================================
// Temporary helpers needed for exposing ggml interface
WHISPER_API int whisper_bench_memcpy(int n_threads);
+ WHISPER_API const char * whisper_bench_memcpy_str(int n_threads);
WHISPER_API int whisper_bench_ggml_mul_mat(int n_threads);
+ WHISPER_API const char * whisper_bench_ggml_mul_mat_str(int n_threads);
#ifdef __cplusplus
}