Greptile on #2109: a flat 600s default still undercuts the budget the job
was granted. generate_timeout_s adds 1s per 40 characters past a 1200-char
allowance and can be raised by env, so a long passage is granted more than
600s and a constant deadline only moves the cliff to longer inputs rather
than removing it. The comment on GENERATE_RECV_TIMEOUT_S claimed the inner
deadline "can never fire before the outer budget", which was not true for
those inputs.
_effective_recv_timeout_s(text) now takes the larger of the engine's own
value and the budget this specific request was granted, and generate()
uses it for both the first frame and the progress loop.
Only for engines that expressed no opinion. An override is a deliberate
statement about that model, and #2103 asks for "fast engines opt down" to
keep working, so a subclass that sets recv_timeout_s keeps exactly that
value — including a smaller one. Detected by walking __mro__ for a
recv_timeout_s in a subclass __dict__ rather than comparing values, so an
engine that happens to pick the same number as the floor is still honoured.
Budget probing is advisory: any failure falls back to the class floor
rather than turning a working generate into an error.
Tests: a long passage raises the deadline above the flat floor and tracks
generate_timeout_s; an opt-down engine keeps its value at any text length;
a failing probe falls back. Verified load-bearing by making the derivation
return the engine value unconditionally (the long-passage test fails on
600.0 > 600.0).
SubprocessBackend.recv_timeout_s defaulted to RECV_TIMEOUT_S (60s), the
budget for a health_check ping. Four engines never overrode it and so used
a ping's deadline as their generation deadline: confucius4-tts, dots-tts,
moss-tts-v15 and supertonic3. All four fall back to CPU off CUDA, where a
normal sentence does not finish in 60s, and the watchdog killed them
mid-synthesis.
That deadline sat 5-10x below the wall-clock budget the same job was
granted by model_manager.generate_timeout_s (300s accelerated, 600s CPU),
so the sidecar was reclaimed while its caller still considered it well
inside budget. Every engine that did override the hook chose 300s..900s,
at or above the accelerated budget; omnivoice_subprocess documents the
intent as "aligns the kill deadline with the generate budget". The 60s
default contradicted that intent for anyone who did not opt out of it.
which left the class default and the four silent engines untouched.
Default to GENERATE_RECV_TIMEOUT_S (600s, the CPU budget floor) so an
engine that expresses no opinion can no longer be cut off before its own
job budget. RECV_TIMEOUT_S stays 60s for health_check, where a ping must
stay fast. Overriding is still how an engine asks for more.
Second half: the watchdog logged "exceeded recv timeout; killing" but
generate() raised "sidecar closed pipe mid-generate", so the one fact that
explained the failure never left the backend log and reporters read it as
a crash. _last_recv_timed_out already distinguished the two for the spawn
handshake (#2026); use it here too and name the deadline, the elapsed
time and the sidecar's last stderr. Crash wording is unchanged.
Tests: a registry invariant that no SubprocessBackend can be dispatched
with a deadline under the accelerated budget, so a new engine cannot
inherit this by omission the way these four did; lockstep with
model_manager's budgets; and a wedging sidecar asserting the timeout
message. Verified fail-before/pass-after by reverting the default to 60s
(all 7 fail, naming exactly the four engines) and by forcing the crash
wording (the message test fails).
Fixes#2103
An imported or pasted subtitle whose line is just a number — a year, a
score, a street number, a "3 / 2 / 1" countdown — was silently deleted,
and when that line was the cue's only text the whole cue disappeared.
Cue bodies are sliced from a timing line up to the next one, which
swallows the next cue's index. The parser clawed that back by popping
every trailing digit-only line, a rule that cannot tell an index from
numeric dialogue. Three shapes fell out of it:
- the last cue of a file has no next index to pop, so a closing "1999"
was taken for one and the cue was dropped as empty;
- an index-less export has no indices at all, yet still lost its
closing numeric line — and lost it without counting a skip, so the
import reported itself lossless while dropping a line;
- the `while` loop popped digit lines until it hit a non-digit, eating
a multi-line countdown cue whole.
Give back exactly the one line that was swallowed: a single line, only
when a next cue exists to own it, and only in a file that indexes its
cues at all — decided once from the preamble before the first timing
line, since a body of "42" is indistinguishable from an index on its
own. Index detection is ASCII-only, because `str.isdigit()` is also
true for Arabic-Indic and Devanagari numerals, which in a 646-language
dubbing app are dialogue rather than SubRip indices.
Affects both subtitle entry points: POST /dub/import-srt/{job_id} and
POST /dub/parse-subtitle-text.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On a Tesla T4 the backend exited during the first /generate with no
traceback and no HTTP response, leaving the client with
RemoteDisconnected and every later call with ConnectionRefused. Three
separate defects combined, which is why none of the reporter's
workarounds helped.
1. torch.compile(mode="reduce-overhead") captures CUDA graphs. T4
(sm_75) passed the existing arch gate, so capture was attempted and
aborted the process from inside the native CUDA library — below the
interpreter, where neither the #278 eager-fallback wrapper nor any
except clause can see it. The compile mode is now resolved per GPU:
Ampere (sm_80) and newer keep the cudagraph mode, older cards drop to
the non-cudagraph "default" mode and keep their compiled Inductor
kernels. Fails open on any probe error, so no GPU that works today
loses the optimization. OMNIVOICE_FORCE_CUDAGRAPH=1 restores it.
2. should_torch_compile() never read TORCH_COMPILE_DISABLE. main.py sets
it on win32, build_engine_env injected it into subprocesses, and
docs/install/windows.md tells users to export it — but the in-process
gate ignored it, so the reporter exported the documented variable and
still got "torch.compile applied". The gate now honours
TORCH_COMPILE_DISABLE / TORCHDYNAMO_DISABLE / TORCHINDUCTOR_DISABLE on
every platform, and an env opt-out on the parent propagates to engine
subprocesses. The settings DB path is logged alongside the toggle:
the reporter had three omnivoice.db files and edited one the backend
never opened.
3. Settings -> Performance -> "Disable torch.compile" was rendered
disabled outside Windows in both the Tauri and Electron UIs, so the
one control that would have stopped this was unreachable for the
affected Linux user. The toggle is now live on every platform, and
build_engine_env honours it everywhere rather than only on win32.
Also arms faulthandler before torch is imported, so a fatal native
signal writes the faulting thread's Python stack to backend_err.log
instead of the process vanishing silently. This does not prevent a
crash; it makes one diagnosable. OMNIVOICE_DISABLE_FAULTHANDLER=1 skips
it.
Tests fail before / pass after, verified by stashing the source and
running the new tests against unfixed code. The crash test kills a real
child interpreter with a real SIGSEGV and requires a named Python frame
in the output. test_torch_compile_path_gate's fixture now clears the
compile-disable env vars: main.py setdefaults them on win32, so on a
Windows runner they leaked into os.environ and decided those tests.
Not verified on real hardware — no Turing GPU available. The sm_80 floor
is inferred from the crash report and from docs/hardware-notes-tesla-t4.md,
which already flagged cudagraphs on T4 as attempted by default and never
evaluated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Argos Translate ships pairs as zh, not Chinese. Passing the UI
label or a tag like cmn-Hans made get_translation_from_codes
return None and every subtitle stayed in the source language.
Fixes#2140
- _ping_while retrieves failures that occur after the consumer disconnects,
preventing "exception was never retrieved" at GC; the early-exit test
covers this case
- stream_cut_backend_alive_local now builds the Settings → Logs → Backend
path from each locale's actual UI labels (`settings.title`,
`settings.logs`, `common.backend`)
- Fix incorrect section names in 15 locales, including ru, de, and zh-TW
Addresses CodeRabbit review on #2138
`_ping_while` runs awaited work as its own task, so a client disconnect
previously cancelled only the ping loop. Reference-text refinement could
continue running while `run_transcribe_guarded` skipped its abandon path
and the stream finalizer unloaded the ASR model underneath it.
Cancel unfinished work on early exit, matching the cancellation behavior
of the bare `await` it replaced. Do not await it in `finally`, which can
also run under `GeneratorExit`.
- **dub_core**: `_ping_while` cancels an unfinished future in `finally`,
covering all seven call sites
- **test**: verify that `_ping_while` cancels work on `aclose()` while
leaving completed work untouched (fail-before / pass-after)
- **CHANGELOG**: add a Highlights entry for the user-visible fix; keep
Fixed entries under `### Fixed` per `CLAUDE.md`
Addresses Greptile P1/P2 on #2138
After transcription, several minutes of backend work could produce no
SSE bytes, causing the desktop webview to close the idle connection while
the job continued and eventually completed. This also led to a misleading
reverse-proxy error on local connections.
- dub_core: keep post-transcript awaits alive with `_ping_while` (5s pings)
- dub_export: send SSE comments after 15s of stream silence
- backendCrash.ts: show deployment-aware connection-loss guidance
- Add regression tests for post-transcript pings, task-stream keepalive,
and local-mode error messaging
Fixes#2108
- Drops experimental flag (continue-on-error) on macos-14 in build-omnivoice-tts.yml now that omnivoice.cpp builds cleanly with -DGGML_METAL=ON at the pinned SHA.
- Updates bin/README.md, backend/engines/omnivoice_gguf/README.md, and SPIKE-01 ADR to document verified Apple Silicon Metal acceleration.
- Corrects workflow reference in bin/README.md.
- Adds changelog credit for @martinezpl.
Closes#2105
- SubprocessBackend and SubprocessASRBackend now check _last_recv_timed_out
when a sidecar stream closes unexpectedly, reporting an actionable timeout
error with the deadline rather than describing a generic pipe-closed crash.
- Confucius4Backend, DotsTTSBackend, MossTTSV15Backend, and Supertonic3Backend
now define generous recv_timeout_s properties with OMNIVOICE_* env overrides,
preventing mid-generation termination on slower CPU/MPS devices.
- Adds regression tests in backend/tests/test_omnivoice_subprocess.py.
Closes#2103
Three comments name Blackwell sm_120 as an architecture Triton/Inductor does
not support. Measured on an sm_120 device with the pinned torch 2.8.0+cu128
and triton 3.4.0: torch.compile default and reduce-overhead (the
cudagraph_trees path #278 names), an attention module over growing sequence
lengths, and a raw Triton kernel all run, with compiled output matching eager
(maxdiff 1.19e-06 and 0.0). The app's own probe agrees — arch_unsupported
returns None, so compile is already attempted there.
The error text #278 quotes is also misattributed. "Detected that you are using
FX to symbolically trace a dynamo-optimized function" reproduces with CUDA
unavailable: Dynamo raises it whenever FX traces a compiled function,
regardless of device. It belongs in the compile-stack classifier, not in the
evidence for a missing-architecture failure.
Comments only. The fallback contract and the arch-list gate are unchanged and
still correct: the gate is generic rather than a Blackwell blocklist, and on a
build whose arch list lacks the device the described mechanism holds. Only the
example and the FX attribution are stale.
Signed-off-by: Moep90 <volleyballlive@googlemail.com>
Signed-off-by: moep90 <volleyballlive@googlemail.com>
Review catch: the code comments were corrected while
docs/install/troubleshooting.md still gave users the false diagnosis in two
places, which is the docs-sync rule exactly. The user-facing wording was the
stronger of the two:
5b: "the wheel does not contain code for the GPU" and "no setting works
around it"
1052: "This is a property of the pinned build, not of your driver or your
install"
The second is actively misleading. Since torch 2.8.0+cu128 does list sm_120 in
get_arch_list(), the driver, the platform and the native init path are exactly
where the cause plausibly is, and the doc steered readers away from them.
Both sections now state that the cause is not established, that the pinned
build does contain Blackwell code, and that moving the trio to 2.9.x is the
known workaround for the users who hit it. 5b also gains a get_arch_list()
command so a reader can check their own build instead of trusting a blanket
claim. The fix steps are unchanged.
Also softens the CU128_ARCHS citation in the three comments. That list is
fixture data fed to a mocked torch, captured verbatim from the #1285 report,
so it corroborates the arch list but does not assert what the installed wheel
contains. Saying it "already asserts" overstated it.
Signed-off-by: Moep90 <volleyballlive@googlemail.com>
Signed-off-by: moep90 <volleyballlive@googlemail.com>
Three comments justify the set_audio_backend() hasattr guard by asserting
that the pinned torch 2.8.0 carries no sm_120 kernels, so Blackwell owners
have no choice but to upgrade. The pinned build does carry them.
On a Blackwell GPU (capability (12, 0)) running this repo's own resolved
environment, torch 2.8.0+cu128 reports:
arch_list ['sm_70', 'sm_75', 'sm_80', 'sm_86', 'sm_90', 'sm_100', 'sm_120']
capability (12, 0)
cuda_matmul OK
import torch succeeds and CUDA runs. pyproject.toml:282 resolves torch from
the cu128 index, and tests/test_cuda_arch_compat.py:39 already lists sm_120
in CU128_ARCHS, so the repo asserted the opposite of these comments in two
places at once.
#1931 itself reported an `import torch` access violation on Windows with an
sm_120 card, and attributed it to missing kernel support. The symptom was
real and the reporter did resolve it by moving to torch 2.9.1; the stated
mechanism is what got copied into the comments.
The guard's justification survives intact: sm_120 users did land on torch
2.9.x, which brings torchaudio 2.9, which is what removed set_audio_backend().
Only the reason changes. Also drops two related over-attributions in the same
files, where the crash was described as hitting Blackwell or RTX 50-series
machines rather than any machine on torchaudio 2.9.
Comments and one assertion message only. No behaviour change, no test logic
touched. Not re-litigating #1931's fix.
Signed-off-by: Moep90 <volleyballlive@googlemail.com>
Signed-off-by: moep90 <volleyballlive@googlemail.com>
Both comments said #1931's cohort has no choice but to leave the torch 2.8.0
pin because it carries no sm_120 kernels. That is wrong for this repo's pin:
pyproject resolves torch==2.8.0 from the cu128 index, whose arch_list
includes sm_120 (confirmed on an sm_120 device, and already asserted by
CU128_ARCHS in tests/test_cuda_arch_compat.py).
State the forcing function that is actually verifiable instead: torch 2.8.0
publishes no aarch64 wheel, so arm64 CUDA hosts land on 2.9 with no 2.8.0
option at all. Comments only, no behaviour change.
Signed-off-by: Moep90 <volleyballlive@googlemail.com>
Signed-off-by: moep90 <volleyballlive@googlemail.com>
torchaudio >= 2.9 sends save()/load() through TorchCodec, which requires
FFmpeg shared libraries. Without them every generation returns 500 and every
reference-audio read (cloning, watermark, dub) raises ImportError.
Follow-up to #1931: that issue's fix guarded set_audio_backend() but left
load()/save() unprotected, so the RTX 50-series users it identified as having
no choice but to leave the torch 2.8.0 pin still hit a hard failure.
- _safe_torchaudio_save: fall back to the audited _safe_soundfile_write
- _safe_soundfile_write: optional format= passthrough (BytesIO needs it)
- load_audio: catch ImportError so its existing pydub fallback actually runs
backend="soundfile" does not avoid this; 2.9 accepts and ignores it.
Signed-off-by: Moep90 <volleyballlive@googlemail.com>
Dub -> Paste translation -> Load file accepts .vtt and sends timestamped
text to the lenient SRT parser, which is meant to take VTT too. Two
ordinary WebVTT files broke it:
- Cues without an hours field (00:01.000 --> 00:04.500) matched neither
the frontend's timing detector nor the backend pattern, so the dialog
mapped WEBVTT, the timing lines and the dialogue as plain translations,
and the endpoint itself answered "No timed cues found".
- A cue identifier or NOTE block after a cue became part of that cue's
text, because only digit-only index lines were trimmed.
The hours are now optional in both patterns, as dub_pipeline's yt-dlp
caption parser already allows. For WebVTT input a cue's text ends at its
first blank line, as the format specifies; SRT keeps its lenient
blank-line handling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The SRT/VTT formatters in dub_export and openai_compat truncated
(seconds % 1) * 1000. Most decimal times are not exact in binary (2.3 is
2.29999...), so a cue imported as 00:00:02,300 exported as 00:00:02,299:
every such cue moved a millisecond early in the /dub/srt and /dub/vtt
downloads, burned-in subtitles, and /v1/audio/transcriptions srt/vtt.
All four now call srt_parser.format_cue_timestamp, which rounds the whole
value to milliseconds once and splits it, so 59.9996 carries to
00:01:00,000 rather than printing ",1000" -- the same round-then-divmod
shape karaoke_ass._ass_time already uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-up. A Windows-1252 upload with one of the five bytes cp1252
leaves undefined fell back to decoding the entire file as Latin-1, so its
curly quotes, dashes and euro signs became C1 control characters. Only
the undefined bytes now take their Latin-1 code point, which is what the
browser's windows-1252 decoder does, so readTextFile and
decode_text_upload agree byte for byte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/dub/import-srt fell back to Latin-1 when UTF-8 failed, so a UTF-16 .srt
(Notepad's "Unicode", many subtitle editors) decoded with a NUL between
every character and was rejected as having no cues, and a Windows-1252
one turned curly quotes and dashes into C1 control characters.
/audiobook/import decoded .txt/.md with errors="ignore", silently dropping
every accent, dash and curly quote from a Windows-1252 manuscript,
returning NUL-interleaved text for a UTF-16 one, and keeping a UTF-8 BOM
at the start of the editor text.
Both now use decode_text_upload: a BOM names the encoding, valid UTF-8
stays UTF-8, and anything else is read as Windows-1252, with Latin-1 for
the bytes cp1252 leaves undefined so the decode never raises.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-up. The budget matched model names by substring, so a custom
repo whose name contains "turbo", "small" or "base" (or a word such as
"database") got a reduced budget and could be admitted to CUDA without
enough memory. Reduced budgets now apply only to the exact OpenAI
checkpoint ids, .en variants included. Any other repository, fine-tunes
included, keeps the conservative 5.0 GB, as the engine doc says.
Review follow-up. A generation first waits in the GPU pool's queue, on its
own clock (GPU_QUEUE_TIMEOUT_S, 1800 s by default), before its execution
budget starts, so a 630 s wait could still give up before the backend
returned its queue error. The generate wait now adds the queue budget.
Transcription is unchanged: run_transcribe_guarded starts its 300 s clock
at submission, so queue time already counts against it.
Parity tests pin each tool's wait above the backend's own worst case,
from ASR_TRANSCRIBE_TIMEOUT_S, GPU_QUEUE_TIMEOUT_S and generate_timeout_s
on cpu, cuda and mps. A later backend change that outgrows the MCP wait
now fails a test. A new case covers a CPU budget larger than the GPU one.