Skip to content

feat(videos): reference conditioning over /v1/videos, image and clip and voice - #92

Merged
localai-bot merged 1 commit into
mainfrom
row/SERVE-VIDEOS-REFS
Aug 7, 2026
Merged

feat(videos): reference conditioning over /v1/videos, image and clip and voice#92
localai-bot merged 1 commit into
mainfrom
row/SERVE-VIDEOS-REFS

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Row SERVE-VIDEOS-REFS, stacked on SERVE-VIDEOS-OAI (#71, merged). That row made an OpenAI client's body parse; this one makes its references do something. Before it, no reference modality was reachable over HTTP at all: an image-to-video request generated silently from the prompt alone.

This is the second half of the #71 split (the combined change was 1241 non-exempt lines, over the 900 check-pr-size.py cap). 552 non-exempt lines here.

input_reference maps to fl2va, not ref2va

OpenAI documents it as the image the generated video starts from, which is exactly what fl2va expresses: MiniMaxH3EncodeKeyframeCondRows pins frame 0 of the output to the supplied image. MiniMaxH3EncodeReferenceImages means something else entirely, whole reference images prepended as their own blocks (subject or style guidance that never becomes a frame), so mapping input_reference there would have silently changed what the API promises.

The source is a filesystem path or an RFC 2397 data: URL, decoded by the same DecodeDataUri the chat multimodal parts use rather than a second, subtly different decoder. An http(s) URL is refused by name rather than stat-ed as a path and failing later with a confusing message.

The two modalities OpenAI has no slot for ride metadata

H3 supports three (image, silent video, audio) and the Sora schema carries one, so the other two enter through the standard free-form string map every strict client already tolerates, rather than invented top-level fields that would fail a client's schema validation. The whole map is kept verbatim; two keys are lifted into typed fields.

  • metadata.input_reference_video is a directory of frame_%06d.ppm, the exact layout minimax-h3-gen and this server write, so one run's frames chain straight into the next request. It is not a container: no demuxer is vendored, and a data: URL cannot name a directory.
  • metadata.input_reference_audio is a 16-bit PCM WAV path or data: URL.

A video reference is silent, by construction and stated. MiniMaxH3EncodeReferenceVideo emits ref_audio_t == 0. An audio reference supplied alongside attaches to that same kVideoAudio block, the layout packed_sequence.py builds; alone it is its own block.

Legality is enforced in the parser

The rule is the pipeline's own (minimax_h3_pipeline.cpp:251: fl2va keyframes and ref2va blocks are exclusive), so an illegal pair is a 400 naming it rather than a failed job, and never a silently dropped reference, which is the failure that looks like it worked.

Two refusals up front rather than deep in the denoise: a reference image must be a binary PPM (P6), since no PNG/JPEG codec is vendored (the same named residual the chat multimodal path carries), and it must already be at the resolved output geometry, since no resampler is vendored. Both VAE encoder halves load lazily and once each under one mutex.

Gate (CPU, foreground)

  • test_video_api 14/14, 167 assertions
  • test_openai_api_server 41/41, 525 assertions
  • server builds clean
  • Each modality is gated as arriving at the runner (a reference that parsed and then never reached the pipeline is the failure that looks like it worked), and an illegal pair is a 400 that generates nothing (calls == 0).

FOLLOWING_AGENTS_PROTOCOL

…and voice

Row `SERVE-VIDEOS-REFS`, branch `row/SERVE-VIDEOS-REFS`, stacked on
`row/SERVE-VIDEOS-OAI`. That row made an OpenAI client's body PARSE; this one
makes its REFERENCES do something. Before it, no reference modality was
reachable over HTTP at all: an image-to-video request generated silently from
the prompt alone.

`input_reference` -> fl2va, NOT ref2va. OpenAI documents it as the image the
generated video STARTS FROM, which is exactly what fl2va expresses:
MiniMaxH3EncodeKeyframeCondRows pins frame 0 OF THE OUTPUT to the supplied
image. MiniMaxH3EncodeReferenceImages means something else entirely, whole
reference images PREPENDED as their own blocks (subject or style guidance that
never becomes a frame), so mapping input_reference there would have silently
changed what the API promises. The source is a filesystem path or an RFC 2397
data: URL, decoded by the SAME DecodeDataUri the chat multimodal parts use
rather than a second, subtly different decoder; an http(s) URL is refused BY
NAME rather than stat-ed as a path and failing later with a confusing message.

The two modalities OpenAI has no slot for ride `metadata`. H3 supports three
(image, silent video, audio) and the Sora schema carries one, so the other two
enter through the standard free-form string map every strict client already
tolerates, rather than invented top-level fields that would fail a client's
schema validation. metadata.input_reference_video is a DIRECTORY of
frame_%06d.ppm (the exact layout minimax-h3-gen and this server write, so clips
chain) because no demuxer is vendored; metadata.input_reference_audio is a
16-bit PCM WAV path or data: URL.

A video reference is SILENT, by construction and stated:
MiniMaxH3EncodeReferenceVideo emits ref_audio_t == 0. An audio reference
supplied alongside ATTACHES to that same kVideoAudio block, the layout
packed_sequence.py builds; alone it is its own block.

Legality is enforced in the PARSER, not left to the pipeline. The rule is the
pipeline's own (minimax_h3_pipeline.cpp:251: fl2va keyframes and ref2va blocks
are EXCLUSIVE), so an illegal pair is a 400 naming it rather than a failed job,
and never a silently dropped reference, which is the failure that looks like it
worked.

Two refusals up front rather than deep in the denoise: a reference image must be
a binary PPM (P6), since no PNG/JPEG codec is vendored (the same NAMED residual
the chat multimodal path carries), and it must already be at the resolved output
geometry, since no resampler is vendored. Both VAE encoder halves load LAZILY
and ONCE each under one mutex.

GATE (CPU, foreground): test_video_api 14/14 (167 assertions),
test_openai_api_server 41/41 (525), `server` builds clean. Each modality is
gated as ARRIVING at the runner, and an illegal pair is a 400 that generates
nothing.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Opus 5 (1M context)
@localai-bot
localai-bot merged commit 548b000 into main Aug 7, 2026
11 checks passed
mudler added a commit that referenced this pull request Aug 7, 2026
…user docs

PR #68. A real bug found by RENDERING, not by the suite, plus the user-facing
documentation the H3 lane never had.

THE BUG. A 124-frame render silently muxed as 61 frames. `audio_t` is the
PER-CHANNEL latent length (the planner sets it from 40 Hz * duration) and the
packed layout carries `audio_t * audio_channel` ROWS, one per (channel, step).
The denormalize step divided by the channel count, so the decoded audio ran half
the video's duration; because the muxer passes `-shortest`, that silently
truncated the VIDEO to half its frames too. The fix is one line, and it now
agrees with the same function's own `target_audio_rows = request.audio_t *
request.audio_channel` a few lines above.

WHY THE SUITE WAS BLIND, which is the part worth keeping. Every existing gate
asserts shape SELF-CONSISTENCY, and a uniformly halved pipeline is perfectly
self-consistent: the shapes agreed with each other, they were just half as long
as the request asked for. ffprobe on the artifact exposed it. The gate added is
the one that is NOT self-consistency: the structural t2va e2e case now decodes
`audio_t` latent steps INDEPENDENTLY and requires the pipeline to have produced
exactly that many samples. RED-first: restoring the divide turns that case RED
(1 failed assertion), removing it goes GREEN. Suite 69/69, 47416 assertions.

A SECOND BUG FROM THE SAME SESSION, DROPPED RATHER THAN LANDED TWICE.
Conditioning PREPENDS rows to img_pos, so the denoise loop returns condition rows
followed by the targets, and unpatchify was handed the whole set. This branch
fixed it by carrying the layout's own `update_mask` out of the loop; main fixed
the SAME bug independently in row/H3-RENDER-CLOSE by taking the TRAILING
target_video_rows with a VT_CHECK on the row accounting. On rebase the
update_mask mechanism and its test were DROPPED: landing a second solution to a
fixed bug is churn, and the positional form already ships gated. The alternative
is recorded in .agents/state.md rather than silently lost.

DOCS. The video+audio generation path had no user-facing documentation at all,
and the multimodal INPUT interface was documented WRONG: `vllm-cli
--image/--video/--audio` does not exist, the CLI is text-only. Multimodal input
is served over the OpenAI API as `image_url`, `video_url` and
`input_audio`/`audio_url` content parts on /v1/chat/completions
(src/vllm/entrypoints/openai/chat_mm.cpp), which was documented NOWHERE, so
docs/USAGE.md now states it rather than the wrong claim merely being removed.
README gains a News section led by video+audio generation, paid for INSIDE the
30,000-char landing-page budget rather than by raising it.

PUBLIC DOCS. The 19-line narrative this branch originally appended to
docs/BENCHMARKS.md was a non-canonical H2 section and pushed the page over its
prose budget: converted into three keyed ROWS (Thor render speed, render
duration, quantization floor), with the forensics moved verbatim into
.agents/benchmark-record.md, which is what that page's own checker instructs.

ALSO REPAIRS RECORD DAMAGE THAT LEFT check-agent-record RED ON MAIN. #93's
squash-merge removed 130 lines from specs/minimax-h3.md: its branch predated
#71/#92, so landing it DELETED sections 9 and 10 (the /v1/videos rows) and left
two orphaned table fragments with no heading and no separator. The checker
reports that as "table has 4 pipes; expected 3" plus "no linked spec names exact
stable token SERVE-VIDEOS-REFS", the engine-matrix row pointing at a section that
no longer existed. Restored sections 9 and 10 VERBATIM from 548b000 and cut the
orphans; #93's own content is preserved byte-for-byte.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Opus 5 (1M context)
localai-bot added a commit that referenced this pull request Aug 7, 2026
…user docs (#68)

PR #68. A real bug found by RENDERING, not by the suite, plus the user-facing
documentation the H3 lane never had.

THE BUG. A 124-frame render silently muxed as 61 frames. `audio_t` is the
PER-CHANNEL latent length (the planner sets it from 40 Hz * duration) and the
packed layout carries `audio_t * audio_channel` ROWS, one per (channel, step).
The denormalize step divided by the channel count, so the decoded audio ran half
the video's duration; because the muxer passes `-shortest`, that silently
truncated the VIDEO to half its frames too. The fix is one line, and it now
agrees with the same function's own `target_audio_rows = request.audio_t *
request.audio_channel` a few lines above.

WHY THE SUITE WAS BLIND, which is the part worth keeping. Every existing gate
asserts shape SELF-CONSISTENCY, and a uniformly halved pipeline is perfectly
self-consistent: the shapes agreed with each other, they were just half as long
as the request asked for. ffprobe on the artifact exposed it. The gate added is
the one that is NOT self-consistency: the structural t2va e2e case now decodes
`audio_t` latent steps INDEPENDENTLY and requires the pipeline to have produced
exactly that many samples. RED-first: restoring the divide turns that case RED
(1 failed assertion), removing it goes GREEN. Suite 69/69, 47416 assertions.

A SECOND BUG FROM THE SAME SESSION, DROPPED RATHER THAN LANDED TWICE.
Conditioning PREPENDS rows to img_pos, so the denoise loop returns condition rows
followed by the targets, and unpatchify was handed the whole set. This branch
fixed it by carrying the layout's own `update_mask` out of the loop; main fixed
the SAME bug independently in row/H3-RENDER-CLOSE by taking the TRAILING
target_video_rows with a VT_CHECK on the row accounting. On rebase the
update_mask mechanism and its test were DROPPED: landing a second solution to a
fixed bug is churn, and the positional form already ships gated. The alternative
is recorded in .agents/state.md rather than silently lost.

DOCS. The video+audio generation path had no user-facing documentation at all,
and the multimodal INPUT interface was documented WRONG: `vllm-cli
--image/--video/--audio` does not exist, the CLI is text-only. Multimodal input
is served over the OpenAI API as `image_url`, `video_url` and
`input_audio`/`audio_url` content parts on /v1/chat/completions
(src/vllm/entrypoints/openai/chat_mm.cpp), which was documented NOWHERE, so
docs/USAGE.md now states it rather than the wrong claim merely being removed.
README gains a News section led by video+audio generation, paid for INSIDE the
30,000-char landing-page budget rather than by raising it.

PUBLIC DOCS. The 19-line narrative this branch originally appended to
docs/BENCHMARKS.md was a non-canonical H2 section and pushed the page over its
prose budget: converted into three keyed ROWS (Thor render speed, render
duration, quantization floor), with the forensics moved verbatim into
.agents/benchmark-record.md, which is what that page's own checker instructs.

ALSO REPAIRS RECORD DAMAGE THAT LEFT check-agent-record RED ON MAIN. #93's
squash-merge removed 130 lines from specs/minimax-h3.md: its branch predated
#71/#92, so landing it DELETED sections 9 and 10 (the /v1/videos rows) and left
two orphaned table fragments with no heading and no separator. The checker
reports that as "table has 4 pipes; expected 3" plus "no linked spec names exact
stable token SERVE-VIDEOS-REFS", the engine-matrix row pointing at a section that
no longer existed. Restored sections 9 and 10 VERBATIM from 548b000 and cut the
orphans; #93's own content is preserved byte-for-byte.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Opus 5 (1M context)

Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants