Automatic Speech Recognition
Safetensors
Chinese
whisper

Adding Inference Compatibility

#1
Files changed (3) hide show
  1. README.md +1 -32
  2. handler.py +2 -23
  3. tests/test_handler.py +0 -83
README.md CHANGED
@@ -80,37 +80,6 @@ print(f"Transcription: {transcription}")
80
 
81
  -----
82
 
83
- ### Hosted inference endpoint: selecting a language
84
-
85
- For an endpoint deployed with `handler.py`, send a JSON request with the
86
- language in `parameters.language`:
87
-
88
- ```json
89
- {
90
- "inputs": "<base64-encoded audio file>",
91
- "parameters": {"language": "en"}
92
- }
93
- ```
94
-
95
- Whisper accepts language codes, names, or language tokens, for example `"en"`,
96
- `"english"`, or `"<|en|>"` for English and `"zh"` for Mandarin Chinese.
97
- The endpoint passes the selected language to Whisper with `task="transcribe"`
98
- so the output stays in the source language rather than translating it.
99
- The same `parameters` object works with `{"inputs": {"audio": "..."}}`
100
- and `{"audio": "..."}` requests. Use JSON to select a language; raw binary
101
- requests do not carry parameters.
102
-
103
- Omitting `language` or setting it to `null` preserves the model's default
104
- generation behavior. The checked-in generation configuration leaves the
105
- language unspecified, allowing Whisper to detect it automatically; the handler
106
- does not force Mandarin. A language override applies only to its own request.
107
- Unsupported language strings are rejected by Whisper using the handler's
108
- existing error response format.
109
-
110
- Language selection enables cross-language benchmarking, but this model was
111
- fine-tuned on Mandarin and improvements in other languages have not been
112
- established. Redeploy the endpoint after merging handler changes to use this option.
113
-
114
  ## Training Data
115
 
116
  The model was fine-tuned on the **[AImpower/MandarinStutteredSpeech](https://huggingface.co/datasets/AImpower/MandarinStutteredSpeech)** dataset.
@@ -165,4 +134,4 @@ If you use this model, please cite the original paper:
165
  location = {Athens, Greece},
166
  series = {FAccT '25}
167
  }
168
- ```
 
80
 
81
  -----
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  ## Training Data
84
 
85
  The model was fine-tuned on the **[AImpower/MandarinStutteredSpeech](https://huggingface.co/datasets/AImpower/MandarinStutteredSpeech)** dataset.
 
134
  location = {Athens, Greece},
135
  series = {FAccT '25}
136
  }
137
+ ```
handler.py CHANGED
@@ -9,7 +9,6 @@ import numpy as np
9
  import base64
10
  import io
11
  import logging
12
- from copy import deepcopy
13
  from typing import Dict, Any
14
  from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor
15
 
@@ -102,9 +101,6 @@ class EndpointHandler:
102
  1. {"inputs": "base64_encoded_audio_string"}
103
  2. {"inputs": {"audio": "base64_encoded_audio_string"}}
104
  3. Binary audio data in request body
105
-
106
- Dictionary requests may include {"parameters": {"language": "en"}}.
107
- Omit language (or use null) to keep the model's default behavior.
108
 
109
  Args:
110
  data: Input data dictionary
@@ -114,23 +110,6 @@ class EndpointHandler:
114
  """
115
  try:
116
  logger.info("Processing inference request...")
117
-
118
- generate_kwargs = {}
119
- if isinstance(data, dict):
120
- parameters = data.get("parameters", {})
121
- if not isinstance(parameters, dict):
122
- raise ValueError("'parameters' must be a dictionary")
123
- language = parameters.get("language")
124
- if language is not None:
125
- if not isinstance(language, str) or not language.strip():
126
- raise ValueError("'language' must be a non-empty string or null")
127
- # Keep language overrides local to this request, including on
128
- # Transformers versions that modify the generation config.
129
- generate_kwargs = {
130
- "generation_config": deepcopy(self.model.generation_config),
131
- "language": language.strip(),
132
- "task": "transcribe",
133
- }
134
 
135
  # Extract audio data from various input formats
136
  waveform = None
@@ -191,9 +170,9 @@ class EndpointHandler:
191
  # Move to device
192
  input_features = input_features.to(self.device, dtype=self.torch_dtype)
193
 
194
- # Use the requested language, or the model's defaults when omitted.
195
  with torch.no_grad():
196
- predicted_ids = self.model.generate(input_features, **generate_kwargs)
197
 
198
  # Decode transcription
199
  transcription = self.processor.batch_decode(
 
9
  import base64
10
  import io
11
  import logging
 
12
  from typing import Dict, Any
13
  from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor
14
 
 
101
  1. {"inputs": "base64_encoded_audio_string"}
102
  2. {"inputs": {"audio": "base64_encoded_audio_string"}}
103
  3. Binary audio data in request body
 
 
 
104
 
105
  Args:
106
  data: Input data dictionary
 
110
  """
111
  try:
112
  logger.info("Processing inference request...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  # Extract audio data from various input formats
115
  waveform = None
 
170
  # Move to device
171
  input_features = input_features.to(self.device, dtype=self.torch_dtype)
172
 
173
+ # Run inference with forced Mandarin Chinese language
174
  with torch.no_grad():
175
+ predicted_ids = self.model.generate(input_features)
176
 
177
  # Decode transcription
178
  transcription = self.processor.batch_decode(
tests/test_handler.py DELETED
@@ -1,83 +0,0 @@
1
- """Handler regression tests; no model weights or endpoint required."""
2
-
3
- import unittest
4
- from types import SimpleNamespace
5
- from unittest.mock import Mock
6
-
7
- from handler import EndpointHandler
8
-
9
-
10
- class LanguageTests(unittest.TestCase):
11
- def setUp(self):
12
- self.handler = EndpointHandler.__new__(EndpointHandler)
13
- self.handler.device = "cpu"
14
- self.handler.torch_dtype = None
15
- self.handler.model = Mock()
16
- self.handler.model.generation_config = SimpleNamespace(language=None)
17
- self.features = Mock()
18
- self.features.to.return_value = self.features
19
- self.handler.processor = Mock(return_value=SimpleNamespace(input_features=self.features))
20
- self.handler.processor.batch_decode.return_value = [" test transcription "]
21
- self.handler._load_audio_from_base64 = Mock(return_value=[0.0] * 16000)
22
- self.handler._load_audio_from_bytes = Mock(return_value=[0.0] * 16000)
23
-
24
- def assert_success(self, request):
25
- self.assertEqual(self.handler(request), {
26
- "transcription": "test transcription",
27
- "audio_duration_seconds": 1.0,
28
- "model": "AImpower/StutteredSpeechASR",
29
- })
30
-
31
- def test_languages_and_json_audio_formats(self):
32
- for language in ("en", "english", "<|en|>", "zh", " en "):
33
- for audio in ({"inputs": "audio"}, {"inputs": {"audio": "audio"}},
34
- {"audio": "audio"}, {"inputs": b"audio"}):
35
- with self.subTest(language=language, audio=audio):
36
- self.assert_success({**audio, "parameters": {"language": language}})
37
- args, kwargs = self.handler.model.generate.call_args
38
- self.assertEqual(args, (self.features,))
39
- self.assertEqual(kwargs["language"], language.strip())
40
- self.assertEqual(kwargs["task"], "transcribe")
41
- self.assertIsNot(kwargs["generation_config"], self.handler.model.generation_config)
42
-
43
- def test_default_requests_keep_existing_generation_call(self):
44
- for request in ({"inputs": "audio"}, {"inputs": "audio", "parameters": {}},
45
- {"inputs": "audio", "parameters": {"language": None}},
46
- b"audio", bytearray(b"audio")):
47
- with self.subTest(request=request):
48
- self.assert_success(request)
49
- self.handler.model.generate.assert_called_with(self.features)
50
-
51
- def test_language_does_not_leak_between_requests(self):
52
- def generate(features, **kwargs):
53
- if "generation_config" in kwargs:
54
- kwargs["generation_config"].language = kwargs["language"]
55
- return [[1]]
56
- self.handler.model.generate.side_effect = generate
57
- for language in ("en", "zh"):
58
- self.assert_success({"inputs": "audio", "parameters": {"language": language}})
59
- self.assertIsNone(self.handler.model.generation_config.language)
60
- self.assert_success({"inputs": "audio"})
61
- self.handler.model.generate.assert_called_with(self.features)
62
-
63
- def test_invalid_parameter_shapes_return_existing_error_format(self):
64
- for parameters in (None, [], "en", {"language": ""}, {"language": " "},
65
- {"language": 123}, {"language": ["en"]}):
66
- with self.subTest(parameters=parameters):
67
- result = self.handler({"inputs": "audio", "parameters": parameters})
68
- self.assertIsNone(result["transcription"])
69
- self.assertIn("error", result)
70
- self.handler.model.generate.assert_not_called()
71
-
72
- def test_unsupported_language_error_is_returned(self):
73
- self.handler.model.generate.side_effect = ValueError("Unsupported language: invalid")
74
- result = self.handler({"inputs": "audio", "parameters": {"language": "invalid"}})
75
- self.assertEqual(result, {"error": "Unsupported language: invalid", "transcription": None})
76
-
77
- def test_unrelated_parameters_are_not_forwarded(self):
78
- self.assert_success({"inputs": "audio", "parameters": {"max_new_tokens": 1}})
79
- self.handler.model.generate.assert_called_with(self.features)
80
-
81
-
82
- if __name__ == "__main__":
83
- unittest.main()