Skip to content

Commit 8c567f8

Browse files
authored
whisper.android : address ARM's big.LITTLE arch by checking cpu info (ggml-org#1254)
Addresses ggml-org#1248
1 parent cd08892 commit 8c567f8

File tree

3 files changed

+79
-8
lines changed

3 files changed

+79
-8
lines changed

examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/LibWhisper.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class WhisperContext private constructor(private var ptr: Long) {
1818

1919
suspend fun transcribeData(data: FloatArray): String = withContext(scope.coroutineContext) {
2020
require(ptr != 0L)
21-
WhisperLib.fullTranscribe(ptr, data)
21+
val numThreads = WhisperCpuConfig.preferredThreadCount
22+
Log.d(LOG_TAG, "Selecting $numThreads threads")
23+
WhisperLib.fullTranscribe(ptr, numThreads, data)
2224
val textCount = WhisperLib.getTextSegmentCount(ptr)
2325
return@withContext buildString {
2426
for (i in 0 until textCount) {
@@ -126,7 +128,7 @@ private class WhisperLib {
126128
external fun initContextFromAsset(assetManager: AssetManager, assetPath: String): Long
127129
external fun initContext(modelPath: String): Long
128130
external fun freeContext(contextPtr: Long)
129-
external fun fullTranscribe(contextPtr: Long, audioData: FloatArray)
131+
external fun fullTranscribe(contextPtr: Long, numThreads: Int, audioData: FloatArray)
130132
external fun getTextSegmentCount(contextPtr: Long): Int
131133
external fun getTextSegment(contextPtr: Long, index: Int): String
132134
external fun getSystemInfo(): String
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.whispercppdemo.whisper
2+
3+
import android.util.Log
4+
import java.io.BufferedReader
5+
import java.io.FileReader
6+
7+
object WhisperCpuConfig {
8+
val preferredThreadCount: Int
9+
// Always use at least 2 threads:
10+
get() = CpuInfo.getHighPerfCpuCount().coerceAtLeast(2)
11+
}
12+
13+
private class CpuInfo(private val lines: List<String>) {
14+
private fun getHighPerfCpuCount(): Int = try {
15+
getHighPerfCpuCountByFrequencies()
16+
} catch (e: Exception) {
17+
Log.d(LOG_TAG, "Couldn't read CPU frequencies", e)
18+
getHighPerfCpuCountByVariant()
19+
}
20+
21+
private fun getHighPerfCpuCountByFrequencies(): Int =
22+
getCpuValues(property = "processor") { getMaxCpuFrequency(it.toInt()) }
23+
.also { Log.d(LOG_TAG, "Binned cpu frequencies (frequency, count): ${it.binnedValues()}") }
24+
.countDroppingMin()
25+
26+
private fun getHighPerfCpuCountByVariant(): Int =
27+
getCpuValues(property = "CPU variant") { it.substringAfter("0x").toInt(radix = 16) }
28+
.also { Log.d(LOG_TAG, "Binned cpu variants (variant, count): ${it.binnedValues()}") }
29+
.countKeepingMin()
30+
31+
private fun List<Int>.binnedValues() = groupingBy { it }.eachCount()
32+
33+
private fun getCpuValues(property: String, mapper: (String) -> Int) = lines
34+
.asSequence()
35+
.filter { it.startsWith(property) }
36+
.map { mapper(it.substringAfter(':').trim()) }
37+
.sorted()
38+
.toList()
39+
40+
41+
private fun List<Int>.countDroppingMin(): Int {
42+
val min = min()
43+
return count { it > min }
44+
}
45+
46+
private fun List<Int>.countKeepingMin(): Int {
47+
val min = min()
48+
return count { it == min }
49+
}
50+
51+
companion object {
52+
private const val LOG_TAG = "WhisperCpuConfig"
53+
54+
fun getHighPerfCpuCount(): Int = try {
55+
readCpuInfo().getHighPerfCpuCount()
56+
} catch (e: Exception) {
57+
Log.d(LOG_TAG, "Couldn't read CPU info", e)
58+
// Our best guess -- just return the # of CPUs minus 4.
59+
(Runtime.getRuntime().availableProcessors() - 4).coerceAtLeast(0)
60+
}
61+
62+
private fun readCpuInfo() = CpuInfo(
63+
BufferedReader(FileReader("/proc/cpuinfo"))
64+
.useLines { it.toList() }
65+
)
66+
67+
private fun getMaxCpuFrequency(cpuIndex: Int): Int {
68+
val path = "/sys/devices/system/cpu/cpu${cpuIndex}/cpufreq/cpuinfo_max_freq"
69+
val maxFreq = BufferedReader(FileReader(path)).use { it.readLine() }
70+
return maxFreq.toInt()
71+
}
72+
}
73+
}

examples/whisper.android/app/src/main/jni/whisper/jni.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,12 @@ Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_freeContext(
163163

164164
JNIEXPORT void JNICALL
165165
Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_fullTranscribe(
166-
JNIEnv *env, jobject thiz, jlong context_ptr, jfloatArray audio_data) {
166+
JNIEnv *env, jobject thiz, jlong context_ptr, jint num_threads, jfloatArray audio_data) {
167167
UNUSED(thiz);
168168
struct whisper_context *context = (struct whisper_context *) context_ptr;
169169
jfloat *audio_data_arr = (*env)->GetFloatArrayElements(env, audio_data, NULL);
170170
const jsize audio_data_length = (*env)->GetArrayLength(env, audio_data);
171171

172-
// Leave 2 processors free (i.e. the high-efficiency cores).
173-
int max_threads = max(1, min(8, get_nprocs() - 2));
174-
LOGI("Selecting %d threads", max_threads);
175-
176172
// The below adapted from the Objective-C iOS sample
177173
struct whisper_full_params params = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
178174
params.print_realtime = true;
@@ -181,7 +177,7 @@ Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_fullTranscribe(
181177
params.print_special = false;
182178
params.translate = false;
183179
params.language = "en";
184-
params.n_threads = max_threads;
180+
params.n_threads = num_threads;
185181
params.offset_ms = 0;
186182
params.no_context = true;
187183
params.single_segment = false;

0 commit comments

Comments
 (0)