Skip to content

Consolidate VLM sequential engine: factory entry point, delete CLI fork (C1+C2) - #4

Draft
stikves wants to merge 6 commits into
sukru/vlm-consolidation-b3b4from
sukru/vlm-consolidation-c1c2
Draft

stikves wants to merge 6 commits into
sukru/vlm-consolidation-b3b4from
sukru/vlm-consolidation-c1c2

Conversation

@stikves

@stikves stikves commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

Final step of the VLM sequential-engine consolidation (steps C1 and C2). Stacked on #3. Routes VLM
construction through EngineFactory and removes the CLI if isVLM { ... } else { ... } construction
fork, the review-feedback item that motivated this work.

Targets sukru/vlm-consolidation-b3b4 so the diff is limited to the C1/C2 changes. Will be
retargeted up the stack as the lower PRs merge.

Changes

C1: Add EngineFactory.createEngine(bundle:options:), routing by bundle kind (.vlm goes to a
private makeVLMEngine). Routing by kind avoids variant auto-detection, which cannot distinguish a
VLM main graph from a plain dynamic LLM. Non-VLM kinds resolve main and use the existing
single-asset path. The factory owns makeVLMConfig (base config plus chunking overrides) and
prepares the three VLM components sequentially, since concurrent preparation can trip Core AI
specialization. Adds asset-free unit tests (EngineFactoryVLMTests).

C2: Migrate call sites and delete the fork. LLMRunnerMain replaces its construction fork with a
single createEngine(bundle:) call. The FoundationModels adapter (CoreAIVisionLanguageModel)
routes through the factory, which also removes a latent bug where it prepared the three components
concurrently. CoreAISequentialVLMEngine.init drops its resolvedBase config-rebuild block, since
the factory applies overrides before construction.

Validation

Bit-exact against the pre-refactor baseline at this tip: text greedy 128/128, T2 PPL exact, VLM
greedy 115/115 and 400/400.

xcodebuild build and xcodebuild test pass; EngineFactoryVLMTests and the B1/B2 unit suites are
green.

@stikves
stikves force-pushed the sukru/vlm-consolidation-b3b4 branch from b065d86 to 9e90ffc Compare September 14, 2026 20:34
@stikves
stikves force-pushed the sukru/vlm-consolidation-c1c2 branch from e947c89 to 2891131 Compare September 14, 2026 20:34
@stikves
stikves force-pushed the sukru/vlm-consolidation-b3b4 branch from 9e90ffc to 51d4c22 Compare September 14, 2026 21:09
@stikves
stikves force-pushed the sukru/vlm-consolidation-c1c2 branch from 2891131 to 67d3be5 Compare September 14, 2026 21:10
…d prefill (B1+B2)

B1: Replace the VLM engine's hand-rolled KV cache (keyCache/valueCache/currentKVCapacity
plus ensureKVCapacity/copyCache/zeroFill) with the shared StateHandlerFactory and
runWithStates path used by the text engine. Growth semantics unchanged (initial 256, 2x).
Restore the per-realloc "KV cache grew" log in GrowingNDArrayState. Read back
hasNonTruncatableStates and guard reset(to:) against partial reset for recurrent-state
models, matching the text engine.

B2: Add GenerationTokenBox for the active-token busy/cancel/install bookkeeping both engines
duplicated, and runChunkedPrefill() to replace both processChunkedPrompt implementations.
The text engine passes a non-zero heldBack and routes non-held-back chunks through the
prefill graph; the VLM engine passes heldBack = 0.

Adds GenerationTokenBox and chunked-prefill unit tests.
@stikves
stikves force-pushed the sukru/vlm-consolidation-b3b4 branch from 51d4c22 to 9f083c2 Compare September 14, 2026 21:26
@stikves
stikves force-pushed the sukru/vlm-consolidation-c1c2 branch from 67d3be5 to 7c19bcc Compare September 14, 2026 21:26
Both sequential engines' generation iterators computed the maxTokens clamp and
the forced-vs-sampled next token with identical logic. Extract them into a
SequentialIterator helper namespace (clampMaxTokens, nextToken) that both call.

Kept intentionally minimal and pure value-semantics: each engine retains its own
next() control flow (the text engine's batched forced-continuation PPL path and
prefix-cache bookkeeping; the VLM's image prefill), so the shared helpers don't
touch the engines' lifetime/MutableViews machinery. Behavior-preserving: the
clamp arithmetic and the copy-on-write sampling semantics are unchanged.
With KV, token box, chunked prefill, and iterator helpers now shared with
CoreAISequentialEngine, remove the '// TODO: Refactor to re-use common
components' marker and update the class doc to describe the shared decode-loop
machinery. Fold the repeated processedTokenCount/kvCache.reset/additionalStates
zeroing (reset and warmup) into a private clearGenerationState() helper. The
pipelined-variant TODO stays — that remains a separate future effort.
Add EngineFactory.createEngine(bundle:options:), a multi-component entry point
alongside the single-asset createEngine(config:modelURL:options:). VLM bundles
(kind == .vlm) carry three assets (vision, embedding, main) that the single-asset
API can't express; they route to the sequential VLM engine, selected by bundle
kind rather than variant auto-detection (a VLM main graph is indistinguishable
from a plain dynamic LLM at the graph level). Other kinds resolve their main
asset and fall through to the single-asset path.

The factory now owns VLM config assembly (makeVLMConfig) and applies chunking
overrides to the base config in one place, mirroring selectEngine for the text
path. Sequential component preparation matches the runner's existing choice.
Adds unit tests for config assembly and override application (asset-free).
Migrate both VLM construction sites to EngineFactory.createEngine(bundle:options:):
- LLMRunnerMain: replace the if-isVLM/else fork with a single factory call.
- CoreAIVisionLanguageModel (FM adapter): call the factory and downcast to the
  concrete engine for the executor. This also moves the adapter's three component
  preparations from concurrent to sequential, matching the runner (concurrent
  preparation could trip Core AI specialization).
- CoreAISequentialVLMEngine.init: drop the resolvedBase chunking-override block;
  the factory (makeVLMConfig) now applies overrides before construction, so the
  engine takes an already-resolved config and no longer copies/rebuilds it.

No caller constructs CoreAISequentialVLMEngine directly anymore; chunking-override
application lives in exactly one place (the factory).
@stikves
stikves force-pushed the sukru/vlm-consolidation-b3b4 branch from 9f083c2 to 8d200cb Compare September 14, 2026 21:30
@stikves
stikves force-pushed the sukru/vlm-consolidation-c1c2 branch from 7c19bcc to 927341a Compare September 14, 2026 21:30
@stikves
stikves force-pushed the sukru/vlm-consolidation-b3b4 branch 2 times, most recently from 3d6e3f3 to b50e30a Compare September 15, 2026 01:52
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.

1 participant