Skip to content

Add video input support for vision-language models - #140

Open
stikves wants to merge 7 commits into
apple:mainfrom
stikves:sukru/video-input
Open

Add video input support for vision-language models#140
stikves wants to merge 7 commits into
apple:mainfrom
stikves:sukru/video-input

Conversation

@stikves

@stikves stikves commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Video frame extraction pipeline using AVAssetImageGenerator with uniform and FPS-based sampling strategies
  • VideoInput delivers frames lazily via a concrete VideoFrameSequence (AsyncSequence + Sendable) to keep peak memory at 1–2 decoded frames
  • VLM engine encodes each frame through the existing vision encoder and concatenates embeddings along the sequence dimension
  • --video, --video-frames, --video-sampling flags on llm-runner
  • VisionConfig extended with optional max_video_frames and tokens_per_frame fields (backwards compatible)
  • VLM export gains --num-frames for native multi-frame temporal encoding (grid_t > 1)
  • Vectorized RGBA→CHW transpose in ImagePreprocessor using vDSP_vsadd stride-4 gather

New types

Type Module Role
FrameSamplingStrategy CoreAIShared .uniform(count:) / .fps(rate:, maxFrames:)
VideoInput CoreAIShared Frame source with lazy VideoFrameSequence
VideoFrameSequence CoreAIShared Concrete AsyncSequence & Sendable over VideoFrame
VideoFrame CoreAIShared CGImage + timestamp

Usage

swift run -c release llm-runner \
    --model exports/qwen3_vl_2b.llmasset \
    --video path/to/video.mp4 \
    --video-frames 8 \
    --prompt "Describe what happens in this video"

Test plan

  • FrameSamplingStrategy: uniform/fps math, edge cases (single frame, clamp, zero duration, negative duration)
  • VideoInput.fromFrames: wraps [CGImage], empty array
  • VisionConfig: decodes with/without video fields (backwards compat)
  • ImagePreprocessor: existing tests pass with vectorized transpose
  • End-to-end: Qwen3-VL-2B + screen recording (4 frames, 784 visual tokens)

@stikves
stikves force-pushed the sukru/video-input branch 2 times, most recently from fbb72c2 to 6b16086 Compare August 3, 2026 21:18
Comment thread swift/Sources/CoreAILanguageModels/InferenceEngines/CoreAISequentialEngine.swift Outdated
@stikves
stikves requested review from carinapeng, kevchengcodes and tjia1818 and removed request for carinapeng and tjia1818 August 3, 2026 21:53
@stikves stikves self-assigned this Aug 3, 2026
@stikves
stikves requested a review from alejandro-isaza August 3, 2026 21:53
@stikves
stikves marked this pull request as ready for review August 3, 2026 21:53
@stikves
stikves requested a review from carinapeng August 3, 2026 21:54
Comment thread swift/Sources/CoreAILanguageModels/InferenceEngines/CoreAIPipelinedEngine.swift Outdated
Comment thread swift/Sources/CoreAIShared/Video/FrameSamplingStrategy.swift Outdated
Comment thread swift/Sources/CoreAIShared/Video/VideoInput.swift
Comment thread swift/Sources/CoreAIShared/Video/VideoInput.swift
Comment thread swift/Sources/CoreAIShared/Video/VideoInput.swift Outdated
Comment thread swift/Sources/CoreAIShared/Video/VideoInput.swift
Comment thread swift/Sources/Tools/llm-runner/LLMRunnerMain.swift
Comment thread swift/Sources/Tools/llm-runner/LLMRunnerMain.swift Outdated
Comment thread swift/Sources/Tools/llm-runner/LLMRunnerMain.swift
@stikves
stikves force-pushed the sukru/video-input branch from 9c5db2b to a373446 Compare August 4, 2026 18:16
@stikves stikves added the enhancement New feature or request label Aug 5, 2026
stikves added 5 commits August 5, 2026 22:32
Frame extraction pipeline using AVAssetImageGenerator with two sampling
strategies (uniform and FPS-based). Frames are delivered lazily via a
concrete VideoFrameSequence (AsyncSequence + Sendable) so peak memory
stays at 1-2 decoded frames.

VLM engine encodes each frame independently through the existing
single-image vision encoder, concatenates embeddings along the sequence
dimension, and scatter-merges into the token stream. Zero changes to
exported models required.

New types: FrameSamplingStrategy, VideoInput, VideoFrameSequence,
VideoFrame, VideoFrameExtractor.

VisionConfig extended with optional max_video_frames and
tokens_per_frame fields (backwards compatible).

CLI: --video, --video-frames, --video-sampling flags on llm-runner.

Tested end-to-end with Qwen3-VL-2B on a screen recording (4 frames,
784 visual tokens, correct output).
M4: VLM export --num-frames flag for native temporal video encoding.
StaticVisionEncoder now accepts num_frames parameter (default 1 for
backwards compatibility). Multi-frame input: [1, 3*N, H, W] is
reshaped into real temporal patches instead of duplicating a single
frame. Position and rotary embeddings are computed with grid_t > 1.
metadata.json gains max_video_frames, tokens_per_frame, and
temporal_patch_size fields when exporting with --num-frames > 1.

M5: Vectorize the RGBA-to-planar CHW transpose in ImagePreprocessor
using vDSP_vsadd with stride-4 gather. Replaces the O(3*H*W) scalar
loop with three vectorized passes.
- Remove unused num_visual_tokens_for_frames property from VLMSpec
- Handle BFloat16 in video embedding concatenation (switch on scalarType)
- Guard pixelCount > 0 in preprocessCHW to avoid force-unwrap on empty buffer
- Respect maxFrames: 0 in FPS sampling (return empty instead of 1 frame)
- Keep underlying Error in VideoInputError.frameExtractionFailed
- Replace videoSampling String with VideoSamplingMode enum
- Extract shared runVLMGeneration helper from image/video inference paths
@stikves
stikves force-pushed the sukru/video-input branch from 6a23617 to 065afaf Compare August 6, 2026 05:34
stikves added 2 commits August 7, 2026 11:50
… video config

- Rename FrameSamplingStrategy.timestamps() to sampleTimes() for clarity
- Add withFrameCount() to FrameSamplingStrategy, make it ExpressibleByArgument
- Remove VideoSamplingMode wrapper enum (de-dupe with FrameSamplingStrategy)
- Add VisionConfig.supportsVideo computed property to clarify maxVideoFrames semantics
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants