112 lines
4.5 KiB
Kotlin
112 lines
4.5 KiB
Kotlin
// Mini-harness Android pour mesurer le gain QNN sans rien casser.
|
|
// À placer temporairement dans com.kazeia.stt.BenchQnnHarness ou similaire,
|
|
// déclenchable via un point d'entrée (settings dev, ou bouton caché).
|
|
//
|
|
// Hypothèse : l'app capture déjà des PCM16 16kHz mono lors des sessions
|
|
// thérapeutiques. Si oui, on charge X audios sauvegardés et on bench
|
|
// avec et sans patch QNN. Sinon : utiliser des audios fixture FR de 1-3s
|
|
// (Pierre disponibles ? ou damien_5s_16k.wav du repo Kazeia-Engine/voix).
|
|
//
|
|
// Avant le patch : compiler/déployer l'app actuelle, exécuter cette fonction,
|
|
// noter les temps.
|
|
// Après le patch : appliquer whisper_hybrid_engine_qnn.diff, recompiler,
|
|
// re-exécuter cette même fonction, comparer.
|
|
|
|
package com.kazeia.stt
|
|
|
|
import android.content.Context
|
|
import android.util.Log
|
|
import kotlinx.coroutines.runBlocking
|
|
import java.io.File
|
|
|
|
object BenchQnnHarness {
|
|
private const val TAG = "BenchQnn"
|
|
|
|
/**
|
|
* @param audioDir directory containing test WAVs mono 16-bit 16kHz (PCM16).
|
|
* @param modelDir whisper-small-sm8750 dir (= WhisperHybridEngine.WHISPER_DIR).
|
|
*/
|
|
fun runBench(ctx: Context, audioDir: File, modelDir: String) {
|
|
val engine = WhisperHybridEngine(nativeLibDir = ctx.applicationInfo.nativeLibraryDir) { msg ->
|
|
Log.i(TAG, msg)
|
|
}
|
|
runBlocking { engine.load(modelPath = modelDir) }
|
|
if (!engine.isLoaded()) {
|
|
Log.e(TAG, "engine load FAILED — cannot bench")
|
|
return
|
|
}
|
|
|
|
val wavs = audioDir.listFiles { _, name -> name.endsWith(".wav") }?.sortedBy { it.name }
|
|
?: run { Log.e(TAG, "no WAVs in $audioDir"); return }
|
|
|
|
Log.i(TAG, "=== Bench start, ${wavs.size} audios ===")
|
|
// Warm-up : 1 transcribe à blanc (le 1er est cold)
|
|
wavs.firstOrNull()?.let { f ->
|
|
val pcm = readWavPcm16(f) ?: return@let
|
|
runBlocking { engine.transcribe(pcm, "fr") }
|
|
}
|
|
|
|
// Bench reproductible : 3 runs par audio, garder la médiane
|
|
val results = mutableListOf<Triple<String, Long, String>>()
|
|
for (f in wavs) {
|
|
val pcm = readWavPcm16(f) ?: continue
|
|
val durMs = (pcm.size * 1000L) / 16000
|
|
val times = mutableListOf<Long>()
|
|
var text = ""
|
|
repeat(3) {
|
|
val t0 = System.currentTimeMillis()
|
|
val r = runBlocking { engine.transcribe(pcm, "fr") }
|
|
val dt = System.currentTimeMillis() - t0
|
|
times.add(dt)
|
|
text = r.text
|
|
}
|
|
times.sort()
|
|
val median = times[1]
|
|
results.add(Triple(f.name, median, text))
|
|
Log.i(TAG, "${f.name} : ${durMs}ms audio, median=${median}ms (${times.joinToString()}), text='${text}'")
|
|
}
|
|
|
|
// Récap
|
|
val totalAudio = results.sumOf {
|
|
val w = readWavPcm16(audioDir.resolve(it.first)) ?: ShortArray(0)
|
|
(w.size * 1000L) / 16000
|
|
}
|
|
val totalTime = results.sumOf { it.second }
|
|
Log.i(TAG, "=== Bench end ===")
|
|
Log.i(TAG, "total audio = ${totalAudio} ms, total transcribe (median sum) = ${totalTime} ms")
|
|
Log.i(TAG, "weighted RTF = ${totalTime.toDouble() / totalAudio}")
|
|
|
|
engine.release()
|
|
}
|
|
|
|
private fun readWavPcm16(f: File): ShortArray? = try {
|
|
val bytes = f.readBytes()
|
|
// Skip the 44-byte WAV header (or parse properly if ffmpeg may have inserted extra chunks)
|
|
var dataOff = 44
|
|
// Look for "data" chunk to be safe
|
|
for (i in 12 until bytes.size - 8) {
|
|
if (bytes[i] == 'd'.code.toByte() && bytes[i+1] == 'a'.code.toByte() &&
|
|
bytes[i+2] == 't'.code.toByte() && bytes[i+3] == 'a'.code.toByte()) {
|
|
dataOff = i + 8; break
|
|
}
|
|
}
|
|
val n = (bytes.size - dataOff) / 2
|
|
ShortArray(n) { i ->
|
|
((bytes[dataOff + 2*i].toInt() and 0xFF) or (bytes[dataOff + 2*i + 1].toInt() shl 8)).toShort()
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "readWavPcm16 fail ${f.name}: ${e.message}"); null
|
|
}
|
|
}
|
|
|
|
/* USAGE :
|
|
BenchQnnHarness.runBench(
|
|
ctx = applicationContext,
|
|
audioDir = File("/sdcard/Android/data/com.kazeia/files/bench_audio"), // ou autre
|
|
modelDir = "/data/local/tmp/kazeia/models/whisper-small-sm8750"
|
|
)
|
|
Mettre 10-20 audios FR Pierre dans audioDir au préalable.
|
|
Pas besoin de modifier le runner pour le A/B QNN : on compare juste 2 runs
|
|
d'app (avant patch / après patch) sur le même corpus.
|
|
*/
|