Model name
qwen3_vl and any other VLM whose text decoder mixes linear attention with full attention
(gated-delta / Mamba-style hybrids). The text-only engine already accepts these; the VLM engine
does not.
swift/Sources/CoreAILanguageModels/InferenceEngines/CoreAISequentialVLMEngine.swift
Command run
No command — this is a construction-time guard, and it is unconditional. Loading any
embeddings-input VLM bundle whose LLM function declares more than two states throws from
CoreAISequentialVLMEngine.init, before any inference runs.
All line numbers below are on 475c585.
macOS / iOS target
Reported against the source on 475c585. I could not run the Swift package here — it declares
platforms: [.macOS("27.0"), .iOS("27.0")] and this machine is on macOS 26.7 — so this report is
a code-level one: the guard and the machinery it blocks are quoted with line numbers rather than
with a runtime log.
Xcode version
Xcode 27.0 (27A266a)
Python / uv version
Not applicable — Swift runtime only.
Full error output
CoreAISequentialVLMEngine.swift, lines 235–239:
guard llmDesc.stateNames.count == 2 else {
throw InferenceRuntimeError.invalidOutputType(
"VLM LLM function expected 2 states (KV cache), "
+ "got \(llmDesc.stateNames.count): \(llmDesc.stateNames)")
}
A hybrid decoder declares four states — a key/value pair plus a convolution state and a recurrent
state — so it is refused at init with
VLM LLM function expected 2 states (KV cache), got 4: [keyCache, valueCache, convState, recurrentState]
Anything else?
The rest of the engine already handles this. The guard is the only thing in the way; every
piece of four-state support is present and, on the VLM path, currently dead:
-
Lines 277–284 build the handlers through the shared factory and keep both extra results:
let stateHandlers = try StateHandlerFactory.createSyncHandlers(
descriptor: llmDesc,
maxContextLength: config.maxContextLength,
options: options
)
self.kvCache = stateHandlers.kvCache
self.additionalStates = stateHandlers.additionalStates
self.hasNonTruncatableStates = stateHandlers.hasNonTruncatableStates
-
StateHandlerFactory.createSyncHandlers
(swift/Sources/CoreAILanguageModels/Handlers/StateHandlerFactory.swift, lines 100–174) already
splits the growing KV pair from everything else, allocates the remainder as a
FixedNDArrayState, and sets hasNonTruncatableStates for .fixed states (lines 131–134).
-
Line 711 binds additionalStates on every call as runWithStates(secondary:).
-
Line 912 resets them alongside the KV cache in clearGenerationState().
-
Lines 875–879 already refuse a partial rewind when a non-truncatable state is present:
if tokenIndex != 0 && hasNonTruncatableStates {
throw InferenceRuntimeError.invalidState(
"Partial reset is not supported for hybrid models with recurrent state. "
+ "Use reset(to: 0) and replay the prefix.")
}
The engine's own property comment says as much — line 113:
/// Additional non-KV states (nil for the VLM's two-state KV contract; carried for symmetry).
It is nil only because the guard above makes it impossible for it to be anything else.
The text-only engine takes the same contract. CoreAISequentialEngine.swift, lines 117–121:
guard descriptor.stateNames.count >= 2 && descriptor.stateNames.count <= 4 else {
throw InferenceRuntimeError.invalidOutputType(
"Expected 2–4 states (KV cache + optional persistent states), got \(descriptor.stateNames.count): "
+ "states=\(descriptor.stateNames), outputs=\(descriptor.outputNames)")
}
That has been the case since "Add state handlers for hybrid model support (>2 states)" (#132). The
VLM engine was consolidated onto the same shared handlers afterwards (#249, #251, #262) but kept
the == 2 check. The result is that a hybrid decoder can be exported, can run under the text-only
engine, and is refused by the VLM engine for a reason that no longer holds.
Suggested fix. Relax the guard to >= 2, matching CoreAISequentialEngine. Reference patch:
baozzz1@91265fd
(swift/Sources/CoreAILanguageModels/InferenceEngines/CoreAISequentialVLMEngine.swift).
A related documentation gap, while you are in this file. StateHandlerFactory.inferKind
(StateHandlerFactory.swift, lines 84–97) classifies states by shape and then by name:
let hasDynamicDim = desc.shape.contains(where: { $0 < 0 })
if hasDynamicDim {
return .kvCache
}
let lower = name.lowercased()
if lower.contains("cache") || lower.contains("kv") {
return .slidingCache
}
return .fixed
So a statically shaped state whose name happens to contain cache or kv is treated as a
truncatable sliding-window cache. A conv or recurrent state is neither — it advances with every
token and cannot be rewound to a midpoint. If such a state is named, say, conv_cache, it is
classified as .slidingCache, hasNonTruncatableStates stays false, and the partial-reset guard
at line 875 never fires: reset(to:) silently accepts a rewind that corrupts the state, with no
error and only wrong output to show for it.
That naming rule is a real part of the export contract and I could not find it written down
anywhere. Either state it in the export docs (states that must not be truncated must not have
cache or kv in their name), or lean on the explicit "states" field in metadata.json that
classifyStates already prefers — the heuristic's own log line points at it, but nothing in the
model-authoring docs tells an author to populate it.
Model name
qwen3_vland any other VLM whose text decoder mixes linear attention with full attention(gated-delta / Mamba-style hybrids). The text-only engine already accepts these; the VLM engine
does not.
swift/Sources/CoreAILanguageModels/InferenceEngines/CoreAISequentialVLMEngine.swiftCommand run
No command — this is a construction-time guard, and it is unconditional. Loading any
embeddings-input VLM bundle whose LLM function declares more than two states throws from
CoreAISequentialVLMEngine.init, before any inference runs.All line numbers below are on
475c585.macOS / iOS target
Reported against the source on
475c585. I could not run the Swift package here — it declaresplatforms: [.macOS("27.0"), .iOS("27.0")]and this machine is on macOS 26.7 — so this report isa code-level one: the guard and the machinery it blocks are quoted with line numbers rather than
with a runtime log.
Xcode version
Xcode 27.0 (27A266a)
Python / uv version
Not applicable — Swift runtime only.
Full error output
CoreAISequentialVLMEngine.swift, lines 235–239:A hybrid decoder declares four states — a key/value pair plus a convolution state and a recurrent
state — so it is refused at init with
Anything else?
The rest of the engine already handles this. The guard is the only thing in the way; every
piece of four-state support is present and, on the VLM path, currently dead:
Lines 277–284 build the handlers through the shared factory and keep both extra results:
StateHandlerFactory.createSyncHandlers(
swift/Sources/CoreAILanguageModels/Handlers/StateHandlerFactory.swift, lines 100–174) alreadysplits the growing KV pair from everything else, allocates the remainder as a
FixedNDArrayState, and setshasNonTruncatableStatesfor.fixedstates (lines 131–134).Line 711 binds
additionalStateson every call asrunWithStates(secondary:).Line 912 resets them alongside the KV cache in
clearGenerationState().Lines 875–879 already refuse a partial rewind when a non-truncatable state is present:
The engine's own property comment says as much — line 113:
It is nil only because the guard above makes it impossible for it to be anything else.
The text-only engine takes the same contract.
CoreAISequentialEngine.swift, lines 117–121:That has been the case since "Add state handlers for hybrid model support (>2 states)" (#132). The
VLM engine was consolidated onto the same shared handlers afterwards (#249, #251, #262) but kept
the
== 2check. The result is that a hybrid decoder can be exported, can run under the text-onlyengine, and is refused by the VLM engine for a reason that no longer holds.
Suggested fix. Relax the guard to
>= 2, matchingCoreAISequentialEngine. Reference patch:baozzz1@91265fd
(
swift/Sources/CoreAILanguageModels/InferenceEngines/CoreAISequentialVLMEngine.swift).A related documentation gap, while you are in this file.
StateHandlerFactory.inferKind(
StateHandlerFactory.swift, lines 84–97) classifies states by shape and then by name:So a statically shaped state whose name happens to contain
cacheorkvis treated as atruncatable sliding-window cache. A conv or recurrent state is neither — it advances with every
token and cannot be rewound to a midpoint. If such a state is named, say,
conv_cache, it isclassified as
.slidingCache,hasNonTruncatableStatesstays false, and the partial-reset guardat line 875 never fires:
reset(to:)silently accepts a rewind that corrupts the state, with noerror and only wrong output to show for it.
That naming rule is a real part of the export contract and I could not find it written down
anywhere. Either state it in the export docs (states that must not be truncated must not have
cacheorkvin their name), or lean on the explicit"states"field inmetadata.jsonthatclassifyStatesalready prefers — the heuristic's own log line points at it, but nothing in themodel-authoring docs tells an author to populate it.