main
py 105 lines 3.61 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2026 The Moonshot AI team and the HuggingFace Inc. team.
5 # All rights reserved.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 """Processor for Kimi-Audio ASR model."""
19
20 import numpy as np
21 from transformers import BatchFeature, ProcessorMixin
22 from transformers.audio_utils import AudioInput
23 from transformers.tokenization_utils_base import PreTokenizedInput, TextInput
24
25
26 class KimiAudioProcessor(ProcessorMixin):
27 # Required for ProcessorMixin
28 attributes = ["feature_extractor", "tokenizer"]
29 feature_extractor_class = "AutoFeatureExtractor"
30 tokenizer_class = "AutoTokenizer"
31
32 # Special token IDs
33 KIMIA_MEDIA_BEGIN: int = 151661
34 KIMIA_MEDIA_END: int = 151663
35 KIMIA_TEXT_BLANK: int = 151666
36
37 # Audio processing constants
38 AUDIO_SEQ_LEN: int = 376
39
40 def __init__(self, feature_extractor=None, tokenizer=None, **kwargs):
41 self.feature_extractor = feature_extractor
42 self.tokenizer = tokenizer
43
44 def __call__(
45 self,
46 text: TextInput
47 | PreTokenizedInput
48 | list[TextInput]
49 | list[PreTokenizedInput]
50 | None = None,
51 audio: AudioInput | None = None,
52 return_tensors: str = "pt",
53 **kwargs,
54 ) -> BatchFeature:
55 if text is not None:
56 if not isinstance(text, list):
57 text = [text]
58
59 text_inputs = self.tokenizer(
60 text, return_tensors=return_tensors, padding=True
61 )
62 else:
63 text_inputs = {}
64
65 if audio is not None:
66 # Ensure audio is a list
67 if isinstance(audio, np.ndarray):
68 audio = [audio]
69
70 # Pad audio to hop length (required by WhisperFeatureExtractor)
71 hop_length = self.feature_extractor.hop_length
72 padded_audio = []
73 for aud in audio:
74 length = aud.shape[-1]
75 if length % hop_length != 0:
76 pad_length = hop_length - (length % hop_length)
77 aud = np.pad(
78 aud, (0, pad_length), mode="constant", constant_values=0
79 )
80 padded_audio.append(aud)
81
82 # Use feature_extractor directly like Qwen3ASR does
83 audio_inputs = self.feature_extractor(
84 padded_audio,
85 sampling_rate=16000,
86 padding=True,
87 return_attention_mask=True,
88 return_tensors=return_tensors,
89 )
90 # Rename to match Kimi-Audio expectations
91 if "input_features" in audio_inputs:
92 audio_inputs["whisper_input_features"] = audio_inputs.pop(
93 "input_features"
94 )
95 if "attention_mask" in audio_inputs:
96 audio_inputs["feature_attention_mask"] = audio_inputs.pop(
97 "attention_mask"
98 )
99 else:
100 audio_inputs = {}
101
102 return BatchFeature(
103 data={**text_inputs, **audio_inputs},
104 tensor_type=return_tensors,
105 )