Skip to content

feat(multimodal): video input - #60

Merged
lloyal-research merged 19 commits into
mainfrom
feature/native-video
Oct 17, 2026
Merged

lloyal-research merged 19 commits into
mainfrom
feature/native-video

Conversation

@lloyal-research

Copy link
Copy Markdown
Contributor

Adds video as a multimodal input kind:

{ kind: 'video', frames: Uint8Array[], timestampsMs: number[], timestampIntervalMs?: number, notes?: { atMs: number, text: string }[] }

copyMedia copies it into liblloyal's MediaInput::Kind::Video. OwnedMedia::Video owns the frames, times and notes for the length of the call, and the existing _storePrefillMultimodal and _cellsMultimodal carry it. There's no new method. MTMD_VIDEO stays off, so decoded frames come from the caller.

liblloyal: the submodule moves to feature/native-video (dde39da), see lloyal-ai/liblloyal.

Tests: run with LLOYAL_LOCAL=1, against the local build.

  • Images (test/integration.ts), Qwen3.5-4B with its projector: new assertion 8b. A 16-frame video costs 758 bare and lands 775 in a turn, matching llama-server. It answers "right" for the square's direction. Assertion 8 is relabelled as multi-image.
  • Audio (test/audio-integration.ts), Qwen3-ASR: 7/7. 'video' is now a valid kind, and an unknown kind is still refused.
  • Text: passes.

The README's multimodal section now covers video.

…'s video kind

copyMedia accepts { kind: 'video', frames, timestampsMs, timestampIntervalMs?,
notes? } beside the image and audio kinds and owns every frame and note off
the JS thread. liblloyal lays the frames out as llama.cpp's video helper does,
so Qwen-VL fuses adjacent frames in pairs. The addon never decodes a video
file; MTMD_VIDEO stays off.

The image suite's former "frames" assertion is relabelled for what it is:
several still images, which never fuse at this pin (594 == 3 x 198 cells).
The video assertion costs the clip at the helper's own tokenization (758),
lands the quote (775 for the user turn, the prompt tokens llama-server
reports for the same clip and question), answers "right" for the direction of
motion, refuses mixed frame sizes per entry, and rejects a video without
timestamps as a type error.

liblloyal at feature/native-video.
Copilot AI balanced review requested due to automatic review settings October 14, 2026 02:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Video ownership coverage and native contract documentation should be completed, alongside the README grammar correction.

3 open findings
What changed in this PR

Adds caller-decoded video frames as a multimodal input backed by liblloyal video support.

Changes:

  • Marshals video frames, timestamps, intervals, and notes into native inputs.
  • Adds video integration and validation coverage.
  • Documents video input usage.
File Description
src/​SessionContext.cpp Implements native video marshalling and ownership.
test/​integration.ts Tests video tokenization, inference, and validation.
test/​audio-integration.ts Updates invalid-kind tests for video support.
README.md Documents the video input format.
liblloyal Advances to the video-capable kernel revision.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/SessionContext.cpp
Comment thread README.md Outdated
Comment thread src/SessionContext.cpp
The relabelled prompt made the CPU runners stop before a token. The
prompt's words are not what assertion 8 proves; several still images and
text in one prefill are, with the new no-fusion check beside them.
- liblloyal with the video layout review fixes (034ce4a).
- The image suite holds frame ownership: every frame a byte-offset view of
  one backing store, zeroed while measurement and prefill run.
- The prefill declaration documents the video input shape.
- README grammar.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Native memory ownership and behavior dependent on the external liblloyal submodule warrant final human review.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Native memory ownership and GPU video tokenization depend on an external submodule implementation requiring final human verification.

2 open findings

🧠 Review effort: Balanced

@lloyal-research
lloyal-research requested a balanced review from Copilot October 14, 2026 07:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Video marshalling constructs a vector from a null iterator range, introducing undefined behavior.

3 open findings

🧠 Review effort: Balanced

Comment thread src/SessionContext.cpp

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Correctness depends on model-specific native behavior in the updated liblloyal submodule, which was unavailable for full inspection and verification.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

…a reseed is counted

_branchFastForward(parent, child) marshals liblloyal's fast_forward and turns
every refusal into a JS error. _branchSamplerChainReseed now reseeds through
the branch (reseed_sampler), so a parent reseeded after a fork has changed
since it and cannot be fast-forwarded over.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Core video and fast-forward behavior depends on unavailable liblloyal implementation and requires human verification.

2 open findings

🧠 Review effort: Balanced

@lloyal-research
lloyal-research requested a balanced review from Copilot October 15, 2026 04:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The underlying native contracts remain unverified, and the reseed-triggered fast-forward safeguard lacks integration coverage.

1 open finding
2 resolved since last review

🧠 Review effort: Balanced

Comment thread src/SessionContext.cpp
… the binding

The reseed goes through liblloyal's reseed_sampler, which counts it as a
change: a fork taken before it is refused with nothing moved. A greedy chain
has no PRNG to reseed, so it does not refuse. The test fails against the old
reseed body that bypassed the branch.
@lloyal-research
lloyal-research requested a balanced review from Copilot October 15, 2026 09:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

StoreCommitWorker accepts every token, then decodes, and on a failure
swaps the sampler, grammar and metrics back. The revision each accept
bumped stayed, so a fork taken before a refused commit could no longer
fast-forward although its parent was as it had been. The rollback now
restores the revision of every branch the failure left intact; a branch
the failure poisoned keeps the bump poisoning gave it. Duplicate handles
are refused before anything is accepted.

The integration suite gains the case, red before the change on SmolLM2:
a batch refused with rc 1 leaves its parent fast-forwardable, and a
duplicate commit is refused before any accept. A `branch` suite runs the
branch-state cases on their own, so they also run on Qwen3.5-4B apart
from the rest of the text suite.
The range ">=3.0.0 || >=4.0.0-0 <5.0.0" had no upper bound, so npm could
pair this binding with any later SDK major and its native contract. It
is now ">=5.0.0-0 <6.0.0": sdk 5, its prereleases, nothing else. The
peer test checks what the range refuses as well as what it admits, red
before the change.
… into

- README: audio has been admitted since 3.3.0 when the loaded projector
  has an audio encoder, and refused before decoding otherwise; the
  multimodal prefill takes images, audio and video.
- SessionContext.hpp: MtmdSource yields text and media segments; a video
  expands into image and text chunks.
- liblloyal at 37bbc39 (docs: retainOnly, seq_id ownership, partial and
  poisoned, fast_forward's in-flight rule).
@lloyal-research
lloyal-research requested a balanced review from Copilot October 15, 2026 14:28
@lloyal-research

Copy link
Copy Markdown
Contributor Author

This push:

  • A commit the decode refused with every branch intact (rc 1) restores each branch's revision with its sampler, grammar and metrics, so it is no change to fast_forward; duplicate handles are refused before anything is accepted. Red first in the integration suite.
  • The SDK peer range is >=5.0.0-0 <6.0.0: the one SDK major this binding implements. The peer test checks what it refuses as well as what it admits.
  • A --suite=branch runs the branch-state cases on their own, so they also run on a hybrid recurrent model (Qwen3.5-4B).
  • liblloyal pinned at 37bbc39; README and header comments corrected (audio is admitted with an audio projector; what a video expands into).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Development dependencies still target SDK 3, leaving the required SDK 5 pairing unvalidated by the standard build and integration setup.

2 open findings

🧠 Review effort: Balanced

Comment thread package.json
The peer range names SDK 5 only, but the dev dependency still asked for
^3.0.0, so the integration suites, which use the SDK's Branch and
BranchStore through this package's re-exports, ran against a major the
binding no longer supports. The dev dependency is ^5.0.0 and the lockfile
resolves 5.0.0; its root now mirrors the manifest. The platform packages
stay out of the lockfile, as the release workflow expects.

Verified on SDK 5 with the local build: the text suite on SmolLM2, 321
passing; the branch suite on Qwen3.5-4B, 44 passing; the unit checks.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The unavailable liblloyal implementation leaves native video and fast-forward contracts unverified and requiring human review.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Native video and branch-state correctness depend on the unavailable liblloyal implementation and require human verification.

0 open findings

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Video lifetime and branch-state correctness depend on unavailable liblloyal implementations that require human verification.

No open issues 🎉

Review effort: Balanced

@lloyal-research
lloyal-research merged commit 3b27ec8 into main Oct 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants