Skip to content

Commit f88f9ea

Browse files
authored
Merge branch 'main' into titaiwang/olive-int2-qualification
2 parents 4b927b3 + 6b27a3f commit f88f9ea

2 files changed

Lines changed: 275 additions & 56 deletions

File tree

‎src/mobius/integrations/ort_genai/auto_export.py‎

Lines changed: 98 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,12 @@ def _revision_kwargs(revision: str | None) -> dict[str, str]:
9999
"gemma2": "gemma",
100100
"gemma4": "gemma4",
101101
"gemma4_text": "gemma4_text",
102-
# gemma-4-12B "unified" (encoder-free) variant reuses the gemma4 ORT GenAI
103-
# pipelines: the multimodal package (decoder taking inputs_embeds + vision
104-
# embedder + embedding fusion) maps to "gemma4"; the standalone text
105-
# backbone maps to "gemma4_text".
106-
"gemma4_unified": "gemma4",
102+
# gemma-4-12B "unified" (encoder-free) variant. The multimodal package
103+
# (decoder taking inputs_embeds + encoder-free vision/audio embedders +
104+
# embedding fusion) maps to the dedicated "gemma4_unified" ORT GenAI type,
105+
# which drives the unified processor (48px merged patches / raw 640-sample
106+
# audio frames). The standalone text backbone maps to "gemma4_text".
107+
"gemma4_unified": "gemma4_unified",
107108
"gemma4_unified_text": "gemma4_text",
108109
"mistral": "mistral",
109110
"mistral3": "mistral3",
@@ -194,12 +195,12 @@ class _DecoderAbi:
194195
)
195196
# Encoder-free gemma-4-12B "unified" variants. Their image/audio inputs are raw
196197
# merged pixel patches (48px, 6912-dim) / raw waveform frames (640-dim), NOT the
197-
# SigLIP 16px / 128-dim log-mel contract that the ort-extensions
198-
# ``Gemma4ImageTransform`` / ``Gemma4LogMel`` ops implement. There is no
199-
# genai-native transform for the unified contract, so we deliberately do NOT
200-
# emit image_processor.json / audio_processor.json for these models — callers
201-
# must preprocess with the HuggingFace processor and feed tensors via
202-
# ``Generator.set_inputs`` (see examples/gemma4_unified_ort_genai.py).
198+
# SigLIP 16px / 128-dim log-mel contract of the standard gemma4 (E2B/E4B) model.
199+
# These are produced natively by ort-extensions: ``Gemma4ImageTransform`` with
200+
# patch_size=48 / pooling_kernel_size=1 (the 48px merged patch is identical to a
201+
# direct 48px patchify) and the ``Gemma4UnifiedAudioFrames`` raw-framing op. The
202+
# genai ``gemma4_unified`` processor consumes both (see the companion
203+
# onnxruntime-genai / onnxruntime-extensions support).
203204
_GEMMA4_UNIFIED_MODEL_TYPES = frozenset({"gemma4_unified", "gemma4_unified_text"})
204205
_GLMASR_MODEL_TYPES = frozenset({"glmasr"})
205206
_MINICPM_MODEL_TYPES = frozenset({"minicpmv4_6"})
@@ -937,9 +938,10 @@ def _write_vision_processor_config(
937938
938939
- **Gemma4** (``gemma4``, ``gemma4_text``): Writes ``image_processor.json``
939940
with a ``DecodeImage → Gemma4ImageTransform`` pipeline.
940-
- **Gemma4 unified** (``gemma4_unified*``): Returns ``None`` — the
941-
encoder-free model has no matching ort-extensions transform; callers feed
942-
HF-preprocessed pixel_values via ``Generator.set_inputs``.
941+
- **Gemma4 unified** (``gemma4_unified*``): Writes ``image_processor.json``
942+
with a ``DecodeImage → Gemma4ImageTransform`` pipeline configured for the
943+
encoder-free 48px merged-patch contract (patch_size=48,
944+
pooling_kernel_size=1, patch_dim=6912).
943945
- **Gemma3** (``gemma3`` or ``gemma3_text``): Writes
944946
``processor_config.json`` with a 5-step pipeline (DecodeImage →
945947
Resize[fixed] → Rescale → Normalize → Permute3D). Uses a
@@ -971,16 +973,6 @@ def _write_vision_processor_config(
971973
return None
972974

973975
model_type = getattr(config, "model_type", "")
974-
if model_type in _GEMMA4_UNIFIED_MODEL_TYPES:
975-
# Encoder-free unified model: no ort-extensions transform matches its
976-
# raw merged-patch contract. Emit no image_processor.json; callers feed
977-
# HF-preprocessed pixel_values via Generator.set_inputs.
978-
logger.info(
979-
"Skipping image_processor.json for encoder-free %s "
980-
"(no native ort-extensions transform; use HF processor + set_inputs)",
981-
model_type,
982-
)
983-
return None
984976
if model_type in _MINICPM_MODEL_TYPES:
985977
# MiniCPM needs adaptive slicing and NaViT horizontal patch packing.
986978
# ort-extensions has no equivalent transform, so preserving the HF
@@ -1007,7 +999,49 @@ def _write_vision_processor_config(
1007999
vision_model_type = getattr(vision, "model_type", None)
10081000
is_pixtral = vision_model_type == "pixtral" or model_type in _PIXTRAL_MODEL_TYPES
10091001

1010-
if model_type in _GEMMA4_MODEL_TYPES:
1002+
if model_type in _GEMMA4_UNIFIED_MODEL_TYPES:
1003+
# Encoder-free unified model: it consumes 48px MERGED patches
1004+
# (patch_dim = 48*48*3 = 6912) directly, with no SigLIP tower to pool
1005+
# 3x3 teacher patches. HuggingFace produces these via
1006+
# (16px patchify -> 3x3 patches_merge), which is provably identical to a
1007+
# direct 48px patchify. So we reuse the same ort-extensions
1008+
# ``Gemma4ImageTransform`` op with the merged geometry: patch_size =
1009+
# patch_size * pooling_kernel_size (48) and pooling_kernel_size = 1.
1010+
max_soft_tokens = (
1011+
getattr(vision, "mm_tokens_per_image", None)
1012+
or getattr(config, "mm_tokens_per_image", None)
1013+
or 280
1014+
)
1015+
patch_size = getattr(vision, "patch_size", None) or 16
1016+
pooling = getattr(vision, "pooling_kernel_size", None) or 3
1017+
model_patch_size = patch_size * pooling # 16 * 3 = 48
1018+
processor_config: dict[str, Any] = {
1019+
"processor": {
1020+
"name": "gemma_4_unified_image_processing",
1021+
"transforms": [
1022+
{
1023+
"operation": {
1024+
"name": "decode_image",
1025+
"type": "DecodeImage",
1026+
"attrs": {"color_space": "RGB"},
1027+
}
1028+
},
1029+
{
1030+
"operation": {
1031+
"name": "gemma4_image_transform",
1032+
"type": "Gemma4ImageTransform",
1033+
"attrs": {
1034+
"patch_size": model_patch_size,
1035+
"max_soft_tokens": max_soft_tokens,
1036+
"pooling_kernel_size": 1,
1037+
},
1038+
}
1039+
},
1040+
],
1041+
}
1042+
}
1043+
path = os.path.join(output_dir, "image_processor.json")
1044+
elif model_type in _GEMMA4_MODEL_TYPES:
10111045
# Gemma4 needs an onnxruntime-extensions format processor config
10121046
# with a transforms pipeline (DecodeImage -> Gemma4ImageTransform).
10131047
max_soft_tokens = (
@@ -1017,7 +1051,7 @@ def _write_vision_processor_config(
10171051
)
10181052
patch_size = getattr(vision, "patch_size", None) or 16
10191053
pooling_kernel_size = getattr(vision, "pooling_kernel_size", None) or 3
1020-
processor_config: dict[str, Any] = {
1054+
processor_config = {
10211055
"processor": {
10221056
"name": "gemma_4_image_processing",
10231057
"transforms": [
@@ -1306,7 +1340,7 @@ def _write_audio_processor_config(
13061340
config: Any,
13071341
output_dir: str,
13081342
) -> str | None:
1309-
"""Write audio_processor.json for models with audio encoders.
1343+
"""Write the native audio processor config for models with audio encoders.
13101344
13111345
Returns the path if written, None otherwise.
13121346
"""
@@ -1317,18 +1351,40 @@ def _write_audio_processor_config(
13171351
model_type = getattr(config, "model_type", "")
13181352

13191353
if model_type in _GEMMA4_UNIFIED_MODEL_TYPES:
1320-
# Encoder-free unified model: raw 640-dim waveform frames, not the
1321-
# 128-dim log-mel Gemma4LogMel contract. Emit no audio_processor.json;
1322-
# callers feed HF-preprocessed input_features via Generator.set_inputs.
1323-
logger.info(
1324-
"Skipping audio_processor.json for encoder-free %s "
1325-
"(no native ort-extensions transform; use HF processor + set_inputs)",
1326-
model_type,
1327-
)
1328-
return None
1329-
1330-
if model_type in _GEMMA4_MODEL_TYPES:
1331-
# Gemma4 USM-style 128-dim log-mel spectrogram.
1354+
# Encoder-free unified model: each audio soft token is a raw chunk of the
1355+
# 16 kHz waveform (audio_samples_per_token = audio_embed_dim, 640), not a
1356+
# 128-dim log-mel frame. Reproduced natively by the ort-extensions
1357+
# ``Gemma4Audio`` op with ``type="raw_frames"`` (pad to a whole number of
1358+
# frames, reshape to (num_tokens, 640)).
1359+
samples_per_token = getattr(audio, "hidden_size", None) or 640
1360+
processor = {
1361+
"feature_extraction": {
1362+
"sequence": [
1363+
{
1364+
"operation": {
1365+
"name": "audio_decoder",
1366+
"type": "AudioDecoder",
1367+
}
1368+
},
1369+
{
1370+
"operation": {
1371+
"name": "gemma4_audio",
1372+
"type": "Gemma4Audio",
1373+
"attrs": {
1374+
"type": "raw_frames",
1375+
"audio_samples_per_token": samples_per_token,
1376+
"sampling_rate": 16000,
1377+
"padding_value": 0.0,
1378+
},
1379+
}
1380+
},
1381+
]
1382+
}
1383+
}
1384+
proc_filename = "audio_feature_extraction.json"
1385+
elif model_type in _GEMMA4_MODEL_TYPES:
1386+
# Gemma4 USM-style 128-dim log-mel spectrogram via the ort-extensions
1387+
# ``Gemma4Audio`` op with ``type="log_mel"``.
13321388
# OrtxCreateSpeechFeatureExtractor requires the feature_extraction.sequence format.
13331389
processor = {
13341390
"feature_extraction": {
@@ -1341,9 +1397,10 @@ def _write_audio_processor_config(
13411397
},
13421398
{
13431399
"operation": {
1344-
"name": "gemma4_log_mel",
1345-
"type": "Gemma4LogMel",
1400+
"name": "gemma4_audio",
1401+
"type": "Gemma4Audio",
13461402
"attrs": {
1403+
"type": "log_mel",
13471404
"feature_size": 128,
13481405
"sampling_rate": 16000,
13491406
"frame_length_ms": 20.0,

0 commit comments

Comments
 (0)