[Chore] Use upstream Transformers InklingProcessor (#61883)

Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Harry Mellor
2026-10-17 01:04:54 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent f019e3e105
commit f8112704d5
3 changed files with 61 additions and 569 deletions
+61 -59
View File
@@ -4,12 +4,15 @@
from __future__ import annotations
import math
from collections.abc import Iterable, Mapping, Sequence
from typing import Any, cast
import numpy as np
import regex as re
import torch
from PIL import Image
from transformers import InklingProcessor
from transformers.feature_extraction_utils import BatchFeature
from vllm.config.multimodal import (
@@ -29,18 +32,19 @@ from vllm.multimodal.processing import (
PromptUpdate,
PromptUpdateDetails,
)
from vllm.transformers_utils.processors.inkling import (
AUDIO_MARKER_ID,
AUDIO_TOKEN_ID,
IMAGE_MARKER_ID,
IMAGE_TOKEN_ID,
InklingAudioFeatureExtractor,
InklingImageProcessor,
InklingProcessor,
)
from ..configs import InklingMMConfig
# Block-start markers (<|content_image|>, <|content_audio_input|>), kept verbatim
IMAGE_MARKER_ID = 200005
AUDIO_MARKER_ID = 200020
# Per-patch / per-frame placeholders (<|unused_200054|>, <|unused_200053|>)
IMAGE_TOKEN_ID = 200054
AUDIO_TOKEN_ID = 200053
# Long-edge upscale applied before patchifying (upstream defaults to no rescale)
DEFAULT_RESCALE_IMAGE_FRAC = 2.0
# Maximum audio tokens accepted per clip. At the dMel rate of 20 tokens/s
# (50 ms hop) this is ~10 minutes of audio. It bounds the persistent per-request
# buffers and the encoder/memory budget; longer clips are rejected up front.
@@ -57,6 +61,24 @@ class InklingMultiModalDataParser(MultiModalDataParser):
return super()._parse_audio_data(data)
def _rescale_image(
image: Image.Image, frac: float | None, max_upscaled_long_edge: int | None
) -> Image.Image:
"""Scale the long edge by `frac` with PIL LANCZOS, as the reference does."""
image = image.convert("RGB")
if frac is None:
return image
long_edge = max(image.size)
target_long_edge = long_edge * frac
if max_upscaled_long_edge is not None:
target_long_edge = min(target_long_edge, max(max_upscaled_long_edge, long_edge))
ratio = target_long_edge / long_edge
if ratio == 1.0:
return image
size = tuple(max(1, math.floor(d * ratio + 0.5)) for d in image.size)
return image.resize(size, resample=Image.Resampling.LANCZOS)
def inkling_vision_enabled(config: InklingMMConfig) -> bool:
return getattr(config.vision_config, "decoder_dmodel", None) is not None
@@ -70,38 +92,13 @@ class InklingProcessingInfo(BaseProcessingInfo):
return self.ctx.get_hf_config(InklingMMConfig)
def get_hf_processor(self, **kwargs: object) -> InklingProcessor:
config = self.get_hf_config()
vision_config = config.vision_config
audio_config = config.audio_config
image_processor = InklingImageProcessor(
patch_size=getattr(vision_config, "patch_size", None) or 40,
)
if inkling_audio_enabled(config):
audio_params = {
"n_mels": audio_config.n_mel_bins,
"num_dmel_bins": audio_config.mel_vocab_size,
"dmel_min_value": audio_config.dmel_min_value,
"dmel_max_value": audio_config.dmel_max_value,
}
else:
audio_params = {}
audio_extractor = InklingAudioFeatureExtractor(params=audio_params)
return InklingProcessor(
image_processor=image_processor,
audio_feature_extractor=audio_extractor,
tokenizer=self.get_tokenizer(),
)
return self.ctx.get_hf_processor(InklingProcessor, **kwargs)
def get_data_parser(self) -> MultiModalDataParser:
# Audio inputs must be resampled to the dMel feature extractor's rate
# before `process_audios` (see InklingAudioFeatureExtractor._decode_one).
# Without a target_sr the default parser raises on any audio input.
extractor = self.get_hf_processor().audio_feature_extractor
# The feature extractor requires audio at its sampling rate
feature_extractor = self.get_hf_processor().feature_extractor
return InklingMultiModalDataParser(
target_sr=extractor.params.sample_rate,
target_sr=feature_extractor.sampling_rate,
target_channels=1,
expected_hidden_size=self._get_expected_hidden_size(),
)
@@ -163,9 +160,8 @@ class InklingDummyInputsBuilder(BaseDummyInputsBuilder[InklingProcessingInfo]):
if num_audios:
# Size the dummy at the maximum allowed audio so memory/encoder
# budgeting reflects the largest clip we accept (MAX_AUDIO_TOKENS).
params = self.info.get_hf_processor().audio_feature_extractor.params
hop = int(round(params.audio_token_duration_s * params.sample_rate))
audio_len = MAX_AUDIO_TOKENS * hop
feature_extractor = self.info.get_hf_processor().feature_extractor
audio_len = MAX_AUDIO_TOKENS * feature_extractor.hop_length
mm_data["audio"] = self._get_dummy_audios(
length=audio_len,
num_audios=num_audios,
@@ -203,31 +199,37 @@ class InklingMultiModalProcessor(BaseMultiModalProcessor[InklingProcessingInfo])
data: dict[str, Any] = {"input_ids": [prompt_ids]}
if images:
img_feat = processor.process_images(images)
data["pixel_values"] = img_feat["vision_patches_bthwc"]
data["num_patches"] = torch.tensor(
img_feat["num_patches"], dtype=torch.int64
image_processor = processor.image_processor
frac = image_processor.rescale_image_frac or DEFAULT_RESCALE_IMAGE_FRAC
max_long_edge = image_processor.rescale_image_max_upscaled_long_edge
# Resize with PIL: torchvision LANCZOS does not match the reference
images = [_rescale_image(img, frac, max_long_edge) for img in images]
img_feat = image_processor(
images, rescale_image_frac=None, return_tensors="pt"
)
data["pixel_values"] = img_feat["pixel_values"].to(torch.bfloat16)
data["num_patches"] = img_feat["num_patches"].to(torch.int64)
if audios:
aud_feat = processor.process_audios(audios)
per_clip = aud_feat["dmel_bins"]
num_audio_tokens = aud_feat["num_audio_tokens"]
for i, n in enumerate(num_audio_tokens):
if int(n) > MAX_AUDIO_TOKENS:
feature_extractor = processor.feature_extractor
aud_feat = feature_extractor(
audios,
sampling_rate=feature_extractor.sampling_rate,
return_tensors="pt",
)
audio_input_ids = processor._extract_dmel_bins(aud_feat["input_features"])
num_audio_tokens = aud_feat["input_features_mask"].sum(-1)
for i, n in enumerate(num_audio_tokens.tolist()):
if n > MAX_AUDIO_TOKENS:
raise ValueError(
f"Audio clip {i} produces {int(n)} tokens, exceeding the "
f"Audio clip {i} produces {n} tokens, exceeding the "
f"maximum of {MAX_AUDIO_TOKENS} (~10 min at 20 tokens/s). "
"Provide a shorter clip."
)
if per_clip:
input_audio_features = torch.cat(
[torch.as_tensor(c) for c in per_clip], dim=0
)
else:
input_audio_features = torch.empty(0)
data["input_audio_features"] = input_audio_features
data["num_audio_tokens"] = torch.tensor(num_audio_tokens, dtype=torch.int64)
data["input_audio_features"] = torch.cat(
[ids[:n] for ids, n in zip(audio_input_ids, num_audio_tokens)]
)
data["num_audio_tokens"] = num_audio_tokens.to(torch.int64)
processed_data = BatchFeature(data=data, tensor_type=None)
return self._finalize_hf_mm_data(
@@ -39,9 +39,6 @@ __all__ = [
"OvisProcessor",
"Ovis2_5Processor",
"Step3VLProcessor",
"InklingProcessor",
"InklingImageProcessor",
"InklingAudioFeatureExtractor",
]
_CLASS_TO_MODULE: dict[str, str] = {
@@ -74,9 +71,6 @@ _CLASS_TO_MODULE: dict[str, str] = {
"OvisProcessor": "vllm.transformers_utils.processors.ovis",
"Ovis2_5Processor": "vllm.transformers_utils.processors.ovis2_5",
"Step3VLProcessor": "vllm.transformers_utils.processors.step3_vl",
"InklingProcessor": "vllm.transformers_utils.processors.inkling",
"InklingImageProcessor": "vllm.transformers_utils.processors.inkling",
"InklingAudioFeatureExtractor": "vllm.transformers_utils.processors.inkling",
}
@@ -1,504 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Vendored HuggingFace-convention processors for the Inkling Titan model.
Implements the image processor (numba patchifier), the audio feature extractor
(STFT/dMel path), the composite processor, and the MM token-id constants.
Besides raw bytes / file paths, the extractors also accept the dummy inputs
vLLM generates during profiling (PIL images / numpy audio arrays).
"""
from __future__ import annotations
import io
import math
from collections.abc import Sequence
from dataclasses import dataclass
import numpy as np
import torch
import torch.nn.functional as F
from numba import njit
from transformers.feature_extraction_utils import (
BatchFeature,
FeatureExtractionMixin,
)
from transformers.image_processing_utils import BaseImageProcessor
from transformers.image_utils import ImageInput
# ---------------------------------------------------------------------------
# MM token-id constants
# ---------------------------------------------------------------------------
# Block-start marker token ids. These are real tokens the model was trained on
# (``<|content_image|>`` / ``<|content_audio_input|>``) that mark the start of an
# image/audio embedding block; they are kept verbatim in ``input_ids``.
IMAGE_MARKER_ID = 200005 # <|content_image|>
AUDIO_MARKER_ID = 200020 # <|content_audio_input|>
# Per-patch / per-frame placeholder ids marking where tower embeddings are
# scattered in. These are unused slots in the padded vocabulary and the
# corresponding positions are always overwritten by tower embeddings.
IMAGE_TOKEN_ID = 200054 # <|unused_200054|>
AUDIO_TOKEN_ID = 200053 # <|unused_200053|>
# ---------------------------------------------------------------------------
# Image processing
# ---------------------------------------------------------------------------
IMAGE_MEAN = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32)
IMAGE_STD = np.array([0.26862954, 0.2613026, 0.2757771], dtype=np.float32)
PAD_RAW_VALUE = np.float32(-1.0 / 255.0)
PAD_NORM = (np.full((3,), PAD_RAW_VALUE, dtype=np.float32) - IMAGE_MEAN) / IMAGE_STD
def _validate_image_rescale(
rescale_image_frac: float | None,
rescale_image_max_upscaled_long_edge: int | None,
) -> None:
if rescale_image_frac is not None and (
not math.isfinite(rescale_image_frac) or rescale_image_frac <= 0
):
raise ValueError(
"rescale_image_frac must be positive and finite or None, "
f"got {rescale_image_frac}"
)
if rescale_image_max_upscaled_long_edge is None:
return
if rescale_image_max_upscaled_long_edge <= 0:
raise ValueError(
"rescale_image_max_upscaled_long_edge must be positive or None, "
f"got {rescale_image_max_upscaled_long_edge}"
)
if rescale_image_frac is None or rescale_image_frac <= 1.0:
raise ValueError(
"rescale_image_max_upscaled_long_edge requires rescale_image_frac > 1, "
f"got {rescale_image_frac}"
)
def _scaled_image_dimensions(
width: int,
height: int,
rescale_image_frac: float | None,
rescale_image_max_upscaled_long_edge: int | None,
) -> tuple[int, int]:
"""Return the long-edge-scaled ``(width, height)``."""
if rescale_image_frac is None:
return width, height
long_edge = max(width, height)
if long_edge == 0:
return width, height
target_long_edge = float(long_edge) * rescale_image_frac
if rescale_image_max_upscaled_long_edge is not None:
effective_cap = max(rescale_image_max_upscaled_long_edge, long_edge)
target_long_edge = min(target_long_edge, float(effective_cap))
ratio = target_long_edge / float(long_edge)
if ratio == 1.0:
return width, height
def scale(value: int) -> int:
return max(1, math.floor(float(value) * ratio + 0.5))
return scale(width), scale(height)
def _load_image_bytes(image) -> bytes:
"""Encode a PIL image as raw PNG bytes for preprocessing.
The HF processor is always handed ``PIL.Image`` instances by vLLM, so no
other input types need to be supported here.
"""
if image.mode != "RGB":
image = image.convert("RGB")
buf = io.BytesIO()
image.save(buf, format="PNG")
return buf.getvalue()
@njit(cache=True)
def _fill_patches_numba(
arr: np.ndarray,
patch_size: int,
patches: np.ndarray,
mean: np.ndarray,
std: np.ndarray,
pad_norm: np.ndarray,
) -> None:
h = arr.shape[0]
w = arr.shape[1]
nph = (h + patch_size - 1) // patch_size
npw = w // patch_size + 1
inv255 = np.float32(1.0 / 255.0)
for k in range(nph * npw):
i = k // npw
j = k - i * npw
y_base = i * patch_size
x_base = j * patch_size
for y in range(patch_size):
iy = y_base + y
for x in range(patch_size):
ix = x_base + x
if iy < h and ix < w:
for c in range(3):
raw = np.float32(arr[iy, ix, c]) * inv255
patches[k, y, x, c] = (raw - mean[c]) / std[c]
else:
for c in range(3):
patches[k, y, x, c] = pad_norm[c]
def _encode_image_bytes(
image_bytes: bytes,
*,
patch_size: int,
rescale_image_frac: float | None,
rescale_image_max_upscaled_long_edge: int | None,
) -> torch.Tensor:
if patch_size <= 0:
raise ValueError("patch_size must be greater than zero")
_validate_image_rescale(
rescale_image_frac,
rescale_image_max_upscaled_long_edge,
)
from PIL import Image
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
scaled_size = _scaled_image_dimensions(
image.width,
image.height,
rescale_image_frac=rescale_image_frac,
rescale_image_max_upscaled_long_edge=rescale_image_max_upscaled_long_edge,
)
if scaled_size != image.size:
image = image.resize(scaled_size, resample=Image.Resampling.LANCZOS)
arr = np.array(image, dtype=np.uint8, copy=True)
height, width, _ = arr.shape
nph = (height + patch_size - 1) // patch_size
npw = width // patch_size + 1
num_patches = nph * npw
patches = np.empty((num_patches, patch_size, patch_size, 3), dtype=np.float32)
_fill_patches_numba(arr, patch_size, patches, IMAGE_MEAN, IMAGE_STD, PAD_NORM)
return (
torch.from_numpy(patches)
.to(torch.bfloat16)
.view(num_patches, 1, patch_size, patch_size, 3)
.expand(num_patches, 2, patch_size, patch_size, 3)
)
class InklingImageProcessor(BaseImageProcessor):
r"""Turn raw images into ``vision_patches_bthwc`` for Inkling hMLP.
``rescale_image_frac`` scales the long edge while preserving aspect ratio.
``rescale_image_max_upscaled_long_edge`` optionally caps only upscaling and
therefore requires a scale factor greater than one. The defaults, ``2.0`` and
``2048``, grow images toward a 2048-pixel long edge by at most 2x, while leaving
images already at or above 2048 unchanged.
"""
model_input_names = ["vision_patches_bthwc"]
def __init__(
self,
patch_size: int = 40,
rescale_image_frac: float | None = 2.0,
rescale_image_max_upscaled_long_edge: int | None = 2048,
**kwargs,
):
if patch_size <= 0:
raise ValueError("patch_size must be greater than zero")
_validate_image_rescale(
rescale_image_frac,
rescale_image_max_upscaled_long_edge,
)
super().__init__(**kwargs)
self.patch_size = patch_size
self.rescale_image_frac = rescale_image_frac
self.rescale_image_max_upscaled_long_edge = rescale_image_max_upscaled_long_edge
def _encode_one(self, image) -> torch.Tensor:
return _encode_image_bytes(
_load_image_bytes(image),
patch_size=self.patch_size,
rescale_image_frac=self.rescale_image_frac,
rescale_image_max_upscaled_long_edge=self.rescale_image_max_upscaled_long_edge,
)
def preprocess(
self,
images: ImageInput | list,
return_tensors: str | None = "pt",
**kwargs,
) -> BatchFeature:
del return_tensors, kwargs
if not isinstance(images, (list, tuple)):
images = [images]
per_image_patches: list[torch.Tensor] = []
num_patches: list[int] = []
num_tokens: list[int] = []
for img in images:
vp = self._encode_one(img)
n_patches = int(vp.shape[0])
per_image_patches.append(vp)
num_patches.append(n_patches)
num_tokens.append(n_patches)
if len(per_image_patches) == 1:
vision_patches_bthwc = per_image_patches[0]
elif per_image_patches:
vision_patches_bthwc = torch.cat(per_image_patches, dim=0)
else:
vision_patches_bthwc = torch.empty(0)
data = {
"vision_patches_bthwc": vision_patches_bthwc,
"num_patches": num_patches,
"num_tokens": num_tokens,
}
return BatchFeature(data=data, tensor_type=None)
# ---------------------------------------------------------------------------
# Audio feature extraction
# ---------------------------------------------------------------------------
@dataclass
class InklingAudioEncoderParams:
"""Audio preprocessing parameters used to convert raw audio into dMel bins."""
sample_rate: int = 16_000
window_size_multiplier: float = 2.0
n_fft: int | None = None
n_mels: int = 80
num_dmel_bins: int = 16
dmel_min_value: float = -7.0
dmel_max_value: float = 2.0
audio_token_duration_s: float = 0.05
def _to_exact_int(value: float, name: str, tolerance: float = 1e-6) -> int:
rounded = round(value)
if abs(value - rounded) > tolerance:
raise ValueError(f"{name} must resolve to an integer sample count, got {value}")
return int(rounded)
def _hz_to_mel(frequencies: np.ndarray) -> np.ndarray:
"""Slaney mel scale, matching the librosa/torchaudio convention."""
frequencies = np.asarray(frequencies, dtype=np.float64)
f_sp = 200.0 / 3.0
min_log_hz = 1000.0
min_log_mel = min_log_hz / f_sp
logstep = np.log(6.4) / 27.0
linear = frequencies / f_sp
log = (
min_log_mel + np.log(np.maximum(frequencies, min_log_hz) / min_log_hz) / logstep
)
return np.where(frequencies >= min_log_hz, log, linear)
def _mel_to_hz(mels: np.ndarray) -> np.ndarray:
mels = np.asarray(mels, dtype=np.float64)
f_sp = 200.0 / 3.0
min_log_hz = 1000.0
min_log_mel = min_log_hz / f_sp
logstep = np.log(6.4) / 27.0
linear = mels * f_sp
log = min_log_hz * np.exp(logstep * (mels - min_log_mel))
return np.where(mels >= min_log_mel, log, linear)
_MEL_BASIS_CACHE: dict[tuple[int, int, int], torch.Tensor] = {}
def _mel_basis(sample_rate: int, n_fft: int, n_mels: int) -> torch.Tensor:
key = (sample_rate, n_fft, n_mels)
cached = _MEL_BASIS_CACHE.get(key)
if cached is not None:
return cached
fft_bins = n_fft // 2 + 1
fft_freqs = np.arange(fft_bins, dtype=np.float64) * sample_rate / n_fft
mel_edges = _mel_to_hz(
np.linspace(
_hz_to_mel(np.array([0.0]))[0],
_hz_to_mel(np.array([sample_rate / 2.0]))[0],
n_mels + 2,
dtype=np.float64,
)
)
mel_widths = np.diff(mel_edges)
lower = (fft_freqs[None, :] - mel_edges[:-2, None]) / mel_widths[:-1, None]
upper = (mel_edges[2:, None] - fft_freqs[None, :]) / mel_widths[1:, None]
weights = np.maximum(0.0, np.minimum(lower, upper))
# Slaney area normalization.
weights *= (2.0 / (mel_edges[2:] - mel_edges[:-2]))[:, None]
basis = torch.from_numpy(weights.astype(np.float32, copy=False)).contiguous()
_MEL_BASIS_CACHE[key] = basis
return basis
def _dmel_bins(audio: torch.Tensor, params: InklingAudioEncoderParams) -> torch.Tensor:
hop_length = _to_exact_int(
params.audio_token_duration_s * params.sample_rate,
"audio_token_duration_s * sample_rate",
)
window_size = _to_exact_int(
params.audio_token_duration_s
* params.window_size_multiplier
* params.sample_rate,
"audio_token_duration_s * window_size_multiplier * sample_rate",
)
n_fft = params.n_fft or window_size
if hop_length <= 0 or window_size <= 0 or n_fft <= 0:
raise ValueError("audio hop length, window size, and n_fft must be positive")
if audio.numel() == 0:
return torch.empty((0, params.n_mels), dtype=torch.int32)
right_pad = math.ceil(audio.numel() / hop_length) * hop_length - audio.numel()
left_pad = max(n_fft - hop_length, 0)
audio = F.pad(audio, (left_pad, right_pad))
window = torch.hann_window(window_size, periodic=True, dtype=torch.float32)
spec = torch.stft(
audio.unsqueeze(0),
n_fft=n_fft,
hop_length=hop_length,
win_length=window_size,
window=window,
center=False,
normalized=False,
onesided=True,
return_complex=True,
)
spec_ri = torch.view_as_real(spec)
magnitude = (
(spec_ri[..., 0].square() + spec_ri[..., 1].square())
.clamp_min(1e-10)
.sqrt()
.squeeze(0)
)
mel = (
_mel_basis(params.sample_rate, n_fft, params.n_mels)
.matmul(magnitude)
.clamp_min(1e-10)
.log10()
)
mel = mel.to(torch.float64).clamp(
min=params.dmel_min_value, max=params.dmel_max_value
)
bin_centers = torch.linspace(
params.dmel_min_value,
params.dmel_max_value,
params.num_dmel_bins,
dtype=torch.float64,
)
dmel_bins = (mel.unsqueeze(-1) - bin_centers).abs().argmin(dim=-1)
return dmel_bins.to(torch.int32).T.contiguous()
class InklingAudioFeatureExtractor(FeatureExtractionMixin):
"""Convert raw audio into Inkling dMel bins in the HF feature-extractor API."""
model_input_names = ["dmel_bins"]
def __init__(self, params: dict | None = None, **kwargs):
super().__init__(**kwargs)
merged = InklingAudioEncoderParams()
if params:
for k, v in params.items():
if hasattr(merged, k):
setattr(merged, k, v)
# also accept flat kwargs (HF config style)
for k in list(kwargs.keys()):
if hasattr(merged, k):
setattr(merged, k, kwargs[k])
self.params = merged
def _decode_one(self, audio) -> torch.Tensor:
# vLLM hands the feature extractor numpy arrays (the dummy-input builder
# during profiling, and MultiModalDataParser after resampling to the
# target sample rate), so no other input types need to be supported.
return torch.from_numpy(
np.ascontiguousarray(audio.astype(np.float32, copy=False))
).flatten()
def _encode_one(self, audio) -> torch.Tensor:
return _dmel_bins(self._decode_one(audio), self.params)
def __call__(
self,
audios: Sequence | None,
return_tensors: str | None = None,
**kwargs,
) -> BatchFeature:
del return_tensors, kwargs
if audios is None:
audios = []
if not isinstance(audios, (list, tuple)):
audios = [audios]
dmel_bins = [self._encode_one(a) for a in audios]
data = {
# per-clip feature: dmel bins as float32 [T, n_mels]
"dmel_bins": [bins.to(torch.float32) for bins in dmel_bins],
"num_audio_tokens": [int(bins.shape[0]) for bins in dmel_bins],
}
# return_tensors intentionally ignored: per-clip features have ragged T.
return BatchFeature(data=data, tensor_type=None)
# ---------------------------------------------------------------------------
# Composite processor
# ---------------------------------------------------------------------------
class InklingProcessor:
"""Bundle Inkling image + audio preprocessing with the MM token ids."""
def __init__(
self,
image_processor: InklingImageProcessor | None = None,
audio_feature_extractor: InklingAudioFeatureExtractor | None = None,
tokenizer=None,
):
self.image_processor = image_processor or InklingImageProcessor()
self.audio_feature_extractor = (
audio_feature_extractor or InklingAudioFeatureExtractor()
)
self.tokenizer = tokenizer
def process_images(self, images: list):
"""Raw images -> BatchFeature(vision_patches_bthwc, num_patches, num_tokens)."""
return self.image_processor.preprocess(images, return_tensors="pt")
def process_audios(self, audios: list):
"""Raw audios -> BatchFeature(dmel_bins, num_audio_tokens)."""
return self.audio_feature_extractor(audios)
__all__ = [
"InklingImageProcessor",
"InklingAudioFeatureExtractor",
"InklingAudioEncoderParams",
"InklingProcessor",
"IMAGE_MARKER_ID",
"AUDIO_MARKER_ID",
"IMAGE_TOKEN_ID",
"AUDIO_TOKEN_ID",
]