Skip to content

transcribe phase E: vibevoice behind an adapter, and the only truncation story in the tool #23

Description

@fyang0507

Context

Decomposes #12, phase E of
TRANSCRIBE_IMPLEMENTATION_PLAN.md.

vibevoice is last because it is the slowest to iterate against and the least recoverable. It is
also the stack whose adapter has the two sharpest absence rules and the only truncation story in
the tool.

Scope

The vibevoice stage script and adapter. Native diarization and segment_timestamps, so no
diarizer; word_timestamps still adds the aligner, in mlx, which is why this request spans two
environments and still loads one model at a time.

Two forms of "no speaker", and the docs name one

The floors name Speaker: "N/A". The artifacts show both forms:

  • model_tests/vibevoice/VibeVoice/result_vibevoice_multispeaker.json"Speaker": "N/A" on both
    [Environmental Sounds] segments, on the 139.284 s clip.
  • vibe_mps_bf16_logitskeep_spice30m_participant.json — the Speaker key is absent on
    [Silence] segments in the model's own raw_text, and "N/A" appears nowhere in any tracked
    artifact.

Both are the absence of a label and both become an absent key. Only the first is reachable for a
test, and it is reachable, so the test is not vacuous.

Non-speech event tags

[Environmental Sounds], [Silence], [Human Sounds] arrive as segment text with real bounds
and no words: the aligner is not run on a segment with no speech to align. That is neither a failure
nor an abstention. words is therefore absent on some segments while word_timestamps reports
produced, and observed.segments_without_words records how many.

Truncation loses everything upstream

post_process_transcription finds the closing bracket by counting
(vibevoice/processor/vibevoice_asr_processor.py:514-524, commit 94da20d). On an unterminated
array the count never returns to zero, json_str becomes empty, json.loads raises, and the
handler logs a warning and returns [] (:559-562). So a truncated forty-minute decode yields
nothing through the upstream parser — the transcript is lost, not merely trimmed.

The adapter therefore owns a salvage parse: take complete objects up to the truncation, discard the
trailing partial, and take the coverage watermark from the last complete segment's end. The detector
is hit_max_new_tokens (generated == cap with no EOS), and the observable symptom is an empty
segment list beside a non-empty raw_text.

The cap is reachable on the target use case: the 30-minute run generated 11,345 tokens for 1800 s =
6.30 tok/s, so the recorded 16,384-token cap exhausts near 43 minutes of comparable audio.
That is a rate extrapolation, not an observed truncation, and the catalog says so. max_new_tokens
is declared configuration.

failure_recovery

partial_results becomes prefix_only (added in phase 0, replacing none, which is retired
because vibevoice was its only holder). The note carries what each failure yields: a truncation
leaves a prefix and --range addresses the rest; an out-of-memory at model load leaves nothing. The
field says what the partition permits, not what every failure produces — the same reading per_unit
already has.

Memory

Measured 20.28 GiB live MPS on the 30-minute fixture; a strict 16 GiB cap OOMs at model load on
a 27.8 s probe while an 18 GiB cap passes it. The plan warns from
measured_peak_exceeds_target and does not block — an accepted product decision — and no 16 GiB
claim is made for this route. VibeVoice and the aligner have never been measured co-resident and
this plan does not make them so. Its acoustic tokenizer samples a Gaussian latent, so the seed is
fixed at 1234 and recorded in provenance.

Out of scope

export. The MLX 8-bit alternative is
#13 — adopting it replaces every
recorded VibeVoice figure, so it is a re-measurement decision and not this issue's business.

Acceptance

  • A recorded segment carrying Speaker: "N/A" produces no speaker key, and the test fails if
    that string reaches the document — asserted anywhere a speaker label can appear, including turns.
  • An absent Speaker key produces the same absent key, from the tracked 30-minute artifact.
  • observed.segments_without_words counts the event-tag segments while word_timestamps
    reports produced.
  • A real raw_text truncated at an arbitrary offset yields every complete segment before the
    cut, exit 4, and a watermark at the last complete segment's end — and yields [] through
    post_process_transcription, which is the reason the salvage exists. Both halves asserted.
  • diarization and segment_timestamps resolve native with no diarizer role and no add-on
    package; --diarizer fluidaudio is refused with pin_conflicts_with_native_capability.
  • uv run --extra dev pytest green.

Depends on

  • #21 — the transport and the aligner stage, which this stack reuses unchanged.
  • #18prefix_only replaces none there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions