Step down when losing ground, not when slower than a constant
Build 117. A campus M4 Pro sat in the red with nobody listening and no language being translated — so transcription alone. Measured over SSH: sentences 4.2-4.7 s against roughly 4-5 s of audio, queue 46 deep, lag 152 s and still climbing. The mechanism built to catch exactly this could not fire. It triggers past 8 s, on the reasoning that 8 s means slower than people talk. That held when a chunk could be 8 s long; chunks are capped at HARD_CUT_MAX, 5.5 s. A 4.4 s sentence is already losing and never comes near 8 s, so the ladder never stepped down and the backlog grew without bound. It now compares against the audio the chunk actually held. Two details are measured rather than chosen: the margin is 0.85, because the Mini's 4.4 s against 4.5 s was NOMINALLY faster than real time while still losing — the work outside that timer (segmenting, the preview model sharing the GPU, the scheduler) loses on its own. And it requires a backlog of five, so one slow sentence on a healthy machine touches nothing. Checked against eight cases including the Mini's real numbers and a healthy M2 Max. The old absolute rule stays for a model that is genuinely hung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
73ec7f1436
commit
92e86e415f
+38
-2
@@ -1794,6 +1794,7 @@ async function pump() {
|
||||
|
||||
state.busy = true;
|
||||
const started = Date.now();
|
||||
let jobAudioMs = 0;
|
||||
try {
|
||||
/* job.at is when the audio ENDED. Live translation of stale audio is
|
||||
worthless, but the WORDS are not: however old the job, it still gets
|
||||
@@ -1805,6 +1806,9 @@ async function pump() {
|
||||
/* Near-silence telemetry (log-only this release): a chunk whose average
|
||||
level barely clears the noise floor is hallucination fuel. */
|
||||
const audio = flatten(job.frames);
|
||||
/* How much speech this chunk actually holds. The step-down below compares
|
||||
against it, because "slow" only means anything relative to the audio. */
|
||||
jobAudioMs = (audio.length / SAMPLE_RATE) * 1000;
|
||||
let sum = 0;
|
||||
for (let i = 0; i < audio.length; i += 4) sum += audio[i] * audio[i];
|
||||
const meanRms = Math.sqrt(sum / Math.ceil(audio.length / 4));
|
||||
@@ -1908,11 +1912,43 @@ async function pump() {
|
||||
* talk, and the backlog only grows — step down before the service is lost
|
||||
* rather than after. Measured on the Intel laptop: 12-25 s, every sentence.
|
||||
*/
|
||||
if (took > 8000) {
|
||||
/*
|
||||
* SLOWER THAN THE AUDIO IT COVERS — not slower than some constant.
|
||||
*
|
||||
* This used to fire only past 8 s, on the reasoning that 8 s meant slower
|
||||
* than people talk. That was true when a chunk could be 8 s long. Chunks
|
||||
* are capped at HARD_CUT_MAX, 5.5 s, so a 4.5 s chunk taking 4.3 s is
|
||||
* already losing ground and never came near the old threshold.
|
||||
*
|
||||
* MEASURED on a campus M4 Pro: every sentence 4.2-4.7 s against roughly
|
||||
* 4-5 s of audio, a queue 46 deep and a lag of 152 s still climbing — with
|
||||
* nobody listening and no language being translated, so this was
|
||||
* transcription alone. The one mechanism built to catch exactly that could
|
||||
* not fire, because 4.4 s is not 8 s.
|
||||
*
|
||||
* The queue test is what separates "this machine cannot keep up" from one
|
||||
* slow sentence: a backlog that is three deep and being fed faster than it
|
||||
* drains does not recover on its own.
|
||||
*/
|
||||
/*
|
||||
* 0.85 and a backlog of five, both measured rather than chosen.
|
||||
*
|
||||
* The Mini's sentences ran 4.4 s against 4.5 s of audio — nominally FASTER
|
||||
* than real time, while the queue sat at 46 and the lag climbed past 150 s.
|
||||
* Per-sentence time alone therefore misses it: the work outside this timer
|
||||
* (segmenting, the preview model sharing the GPU, the scheduler) is enough
|
||||
* to lose on its own. The margin catches "near enough to real time to be
|
||||
* losing"; the queue depth is the proof that it actually is, and keeps one
|
||||
* slow sentence on a healthy machine from touching anything.
|
||||
*/
|
||||
const losingGround = jobAudioMs > 0 && took > jobAudioMs * 0.85 && queue.length >= 5;
|
||||
if (took > 8000 || losingGround) {
|
||||
state.slowRun = (state.slowRun || 0) + 1;
|
||||
if (state.slowRun >= 3 && !respawning && lighterAsr(state.asrModelInUse || state.cfg.asrModel)) {
|
||||
state.slowRun = 0;
|
||||
log('transcription is slower than people are speaking — stepping down to a lighter model', 'err');
|
||||
log('transcription is slower than people are speaking (' + took + ' ms for ' +
|
||||
Math.round(jobAudioMs) + ' ms of audio, ' + queue.length + ' waiting) — ' +
|
||||
'stepping down to a lighter model', 'err');
|
||||
respawnWorker(state.asrWorker);
|
||||
}
|
||||
} else state.slowRun = 0;
|
||||
|
||||
Reference in New Issue
Block a user