Skip to content

Add Gemma3 allHiddenStates for frozen-text-encoder use#387

Open
xocialize wants to merge 2 commits into
ml-explore:mainfrom
xocialize:gemma3-all-hidden-states
Open

Add Gemma3 allHiddenStates for frozen-text-encoder use#387
xocialize wants to merge 2 commits into
ml-explore:mainfrom
xocialize:gemma3-all-hidden-states

Conversation

@xocialize

Copy link
Copy Markdown

Motivation

Multimodal generators increasingly use a frozen LLM as a text encoder, conditioning on the hidden states of every layer rather than on generated tokens. A concrete example: Lightricks LTX-2 conditions its video DiT on all 49 hidden states of Gemma-3-12B-it. We've been carrying this as a small fork while building a Swift port of LTX-2 — upstreaming so the next consumer of Gemma-3-as-encoder doesn't have to.

API

extension Gemma3Model {
    /// Embedding output plus each transformer layer's output —
    /// numHiddenLayers + 1 states, each (B, T, hiddenSize).
    public func allHiddenStates(
        _ inputs: MLXArray,
        mask: MLXFast.ScaledDotProductAttentionMaskMode
    ) -> [MLXArray]
}
// + forwarding convenience on Gemma3TextModel

Design notes:

  • A single uniform caller-supplied mask is applied to every layer (the encoder use supplies a combined causal+padding mask); the per-layer sliding-window/global mask selection of the generation path is intentionally bypassed. This matches the reference LTX-2 encoder implementation.
  • Per-layer eval keeps each Metal command buffer small so long-sequence (1024-token) encodes on 12B-class checkpoints stay under the macOS GPU watchdog — without it all 48 layers fuse into one dispatch. Measured as load-bearing in production use.
  • Purely additive — no existing code paths change.

Testing

  • New Gemma3AllHiddenStatesTests (tiny randomly-initialized model, no downloads, follows the existing ChatSessionTests pattern): state count/shapes, first state == scaled embedding, layers transform the state, determinism. 3/3 pass via xcodebuild test.
  • Field validation: numerically parity-checked against the Python LTX-2 reference across all 49 layers of Gemma-3-12B-it-4bit at T=1024 (mean cosine 0.999866 vs reference goldens).

🤖 Generated with Claude Code

xocialize and others added 2 commits July 2, 2026 07:50
Encoder-style multi-layer hidden-state extraction on Gemma3Model /
Gemma3TextModel: returns the embedding output plus every transformer
layer's output (numHiddenLayers + 1 states), under a single caller-
supplied uniform mask (combined causal+padding), intentionally bypassing
the per-layer sliding/global mask selection of the generation path.

Motivation: multimodal generators increasingly condition on ALL hidden
states of a frozen LLM text encoder rather than on generated tokens —
e.g. Lightricks LTX-2 conditions its video DiT on all 49 hidden states
of Gemma-3-12B. Per-layer materialization keeps command buffers small so
long-sequence encodes on large checkpoints stay under the macOS GPU
watchdog.

Unit tests run on a tiny randomly-initialized model (no downloads):
state count/shapes, first-state == scaled embedding, layers transform
the state, determinism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move @_spi(Testing) @testable import below the plain imports to satisfy
the swift-format pre-commit hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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