OpenSTBench 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {openstbench-0.3.0 → openstbench-0.3.2}/PKG-INFO +4 -4
  2. {openstbench-0.3.0 → openstbench-0.3.2}/pyproject.toml +4 -4
  3. {openstbench-0.3.0 → openstbench-0.3.2}/src/OpenSTBench.egg-info/PKG-INFO +4 -4
  4. {openstbench-0.3.0 → openstbench-0.3.2}/src/OpenSTBench.egg-info/requires.txt +2 -2
  5. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/__init__.py +1 -1
  6. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/cli.py +84 -75
  7. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/instance.py +8 -6
  8. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/metrics.py +40 -28
  9. {openstbench-0.3.0 → openstbench-0.3.2}/README.md +0 -0
  10. {openstbench-0.3.0 → openstbench-0.3.2}/setup.cfg +0 -0
  11. {openstbench-0.3.0 → openstbench-0.3.2}/src/OpenSTBench.egg-info/SOURCES.txt +0 -0
  12. {openstbench-0.3.0 → openstbench-0.3.2}/src/OpenSTBench.egg-info/dependency_links.txt +0 -0
  13. {openstbench-0.3.0 → openstbench-0.3.2}/src/OpenSTBench.egg-info/top_level.txt +0 -0
  14. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/_model_loading.py +0 -0
  15. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/dataset.py +0 -0
  16. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/emotion_evaluator.py +0 -0
  17. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/__init__.py +0 -0
  18. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/agent.py +0 -0
  19. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/basics.py +0 -0
  20. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/latency/utils.py +0 -0
  21. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/paralinguistic_evaluator.py +0 -0
  22. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/speaker_similarity_evaluator.py +0 -0
  23. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/speech_quality_evaluator.py +0 -0
  24. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/temporal_consistency_evaluator.py +0 -0
  25. {openstbench-0.3.0 → openstbench-0.3.2}/src/openstbench/translation_evaluator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenSTBench
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Open speech and translation benchmarking toolkit supporting MT, ASR, TTS, SimulST, VC, and paralinguistics with optimized CJK language support
5
5
  Author-email: Yanjie An <691476922@qq.com>
6
6
  License: MIT
@@ -14,10 +14,10 @@ Classifier: Intended Audience :: Science/Research
14
14
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Programming Language :: Python :: 3
17
- Requires-Python: >=3.8
17
+ Requires-Python: <3.11,>=3.9
18
18
  Description-Content-Type: text/markdown
19
- Requires-Dist: torch>=1.9.0
20
- Requires-Dist: torchaudio>=0.9.0
19
+ Requires-Dist: torch<2.7,>=2.6
20
+ Requires-Dist: torchaudio<2.7,>=2.6
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: sacrebleu>=2.0.0
23
23
  Requires-Dist: pandas
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "OpenSTBench"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Open speech and translation benchmarking toolkit supporting MT, ASR, TTS, SimulST, VC, and paralinguistics with optimized CJK language support"
9
9
  readme = {file = "README.md", content-type = "text/markdown"}
10
- requires-python = ">=3.8"
10
+ requires-python = ">=3.9,<3.11"
11
11
  license = {text = "MIT"}
12
12
  authors = [
13
13
  {name = "Yanjie An", email = "691476922@qq.com"}
@@ -29,8 +29,8 @@ classifiers = [
29
29
  ]
30
30
 
31
31
  dependencies = [
32
- "torch>=1.9.0",
33
- "torchaudio>=0.9.0",
32
+ "torch>=2.6,<2.7",
33
+ "torchaudio>=2.6,<2.7",
34
34
  "numpy",
35
35
  "sacrebleu>=2.0.0",
36
36
  "pandas",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenSTBench
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Open speech and translation benchmarking toolkit supporting MT, ASR, TTS, SimulST, VC, and paralinguistics with optimized CJK language support
5
5
  Author-email: Yanjie An <691476922@qq.com>
6
6
  License: MIT
@@ -14,10 +14,10 @@ Classifier: Intended Audience :: Science/Research
14
14
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Programming Language :: Python :: 3
17
- Requires-Python: >=3.8
17
+ Requires-Python: <3.11,>=3.9
18
18
  Description-Content-Type: text/markdown
19
- Requires-Dist: torch>=1.9.0
20
- Requires-Dist: torchaudio>=0.9.0
19
+ Requires-Dist: torch<2.7,>=2.6
20
+ Requires-Dist: torchaudio<2.7,>=2.6
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: sacrebleu>=2.0.0
23
23
  Requires-Dist: pandas
@@ -1,5 +1,5 @@
1
- torch>=1.9.0
2
- torchaudio>=0.9.0
1
+ torch<2.7,>=2.6
2
+ torchaudio<2.7,>=2.6
3
3
  numpy
4
4
  sacrebleu>=2.0.0
5
5
  pandas
@@ -9,7 +9,7 @@ actually accessed.
9
9
  from importlib import import_module
10
10
  from typing import Dict, Tuple
11
11
 
12
- __version__ = "0.3.0"
12
+ __version__ = "0.3.2"
13
13
 
14
14
  __all__ = [
15
15
  "TranslationEvaluator",
@@ -1,25 +1,26 @@
1
1
  import argparse
2
2
  import importlib.util
3
3
  import sys
4
+ import time
4
5
  from pathlib import Path
5
6
 
6
7
  from tqdm import tqdm
7
8
 
8
9
  from .agent import GenericAgent
9
- from .instance import SpeechToTextInstance, SpeechToSpeechInstance
10
+ from .instance import SpeechToSpeechInstance, SpeechToTextInstance
10
11
  from .metrics import SCORERS
11
12
  from .utils import Visualizer, materialize_s2s_alignment_artifacts, submit_slurm
12
-
13
- # --- 引入兄弟模块 (紧耦合) ---
14
- # 注意:使用相对导入或包绝对导入
15
13
  from ..translation_evaluator import TranslationEvaluator
16
14
 
15
+
17
16
  class LatencyEvaluator:
18
- """同传延迟与质量评测器"""
17
+ """Generic latency evaluator for speech-input streaming tasks."""
18
+
19
19
  def __init__(
20
20
  self,
21
21
  agent: GenericAgent,
22
22
  segment_size=20,
23
+ poll_interval_ms=10.0,
23
24
  latency_unit="word",
24
25
  asr_fallback_for_s2s_alignment=True,
25
26
  asr_model="medium",
@@ -29,6 +30,7 @@ class LatencyEvaluator:
29
30
  ):
30
31
  self.agent = agent
31
32
  self.segment_size = segment_size
33
+ self.poll_interval_ms = max(1.0, float(poll_interval_ms))
32
34
  self.instances = {}
33
35
  self.latency_unit = latency_unit
34
36
  self.asr_fallback_for_s2s_alignment = asr_fallback_for_s2s_alignment
@@ -40,33 +42,62 @@ class LatencyEvaluator:
40
42
  def run(self, source_files, ref_files, task="s2t", output_dir="./output", visualize=False):
41
43
  Path(output_dir).mkdir(parents=True, exist_ok=True)
42
44
  visualizer = Visualizer(output_dir) if visualize else None
43
-
44
- print(f"🚀 Running Latency Evaluation ({task}, {len(source_files)} samples)...")
45
+ poll_interval_s = self.poll_interval_ms / 1000.0
46
+ chunk_interval_s = float(self.segment_size) / 1000.0
47
+ self.instances = {}
48
+
49
+ print(f"Running Latency Evaluation ({task}, {len(source_files)} samples)...")
45
50
  for i, (src, ref) in tqdm(enumerate(zip(source_files, ref_files)), total=len(source_files)):
46
51
  if task == "s2t":
47
52
  ins = SpeechToTextInstance(i, src, ref, output_dir)
48
53
  else:
49
54
  ins = SpeechToSpeechInstance(i, src, ref, output_dir)
50
-
55
+
51
56
  self.agent.reset()
57
+ sample_wall_start = time.perf_counter()
58
+ ins.start_time = sample_wall_start
59
+ send_start_time = None
60
+ sent_chunk_count = 0
61
+ source_finished = False
62
+
52
63
  while not ins.finish_prediction:
53
- s_in = ins.send_source(self.segment_size)
54
- import time
55
- t0 = time.perf_counter()
56
- s_out = self.agent.pushpop(s_in)
57
- t1 = time.perf_counter()
58
- ins.add_inference_time(t1 - t0)
64
+ now = time.monotonic()
65
+ next_due_time = None
66
+
67
+ if not source_finished:
68
+ if send_start_time is None:
69
+ send_start_time = now
70
+ next_due_time = send_start_time + (sent_chunk_count + 1) * chunk_interval_s
71
+ if now >= next_due_time:
72
+ s_in = ins.send_source(self.segment_size)
73
+ self.agent.push(s_in)
74
+ if not getattr(s_in, "is_empty", False):
75
+ sent_chunk_count += 1
76
+ source_finished = bool(getattr(s_in, "finished", False))
77
+
78
+ s_out = self.agent.pop()
59
79
  if hasattr(self.agent, "consume_model_inference_time"):
60
80
  ins.add_model_inference_time(self.agent.consume_model_inference_time())
61
- ins.receive_prediction(s_out)
62
-
81
+
82
+ if not getattr(s_out, "is_empty", False) or getattr(s_out, "finished", False):
83
+ ins.receive_prediction(s_out)
84
+ continue
85
+
86
+ if source_finished:
87
+ time.sleep(poll_interval_s)
88
+ elif next_due_time is not None:
89
+ sleep_time = max(0.0, min(poll_interval_s, next_due_time - time.monotonic()))
90
+ if sleep_time > 0:
91
+ time.sleep(sleep_time)
92
+
93
+ ins.total_inference_time = time.perf_counter() - sample_wall_start
63
94
  self.instances[i] = ins
64
- if visualizer: visualizer.plot(ins.summarize())
65
-
66
- # S2S 准备文本
95
+ if visualizer:
96
+ visualizer.plot(ins.summarize())
97
+
67
98
  if task == "s2s":
68
99
  self._prepare_s2s_transcripts(output_dir)
69
-
100
+
70
101
  return self.instances
71
102
 
72
103
  def _prepare_s2s_transcripts(self, output_dir):
@@ -79,10 +110,8 @@ class LatencyEvaluator:
79
110
  asr_device=self.asr_device,
80
111
  )
81
112
 
82
- # 增加 show_all_metrics 开关,默认为 False
83
113
  def compute_latency(self, computation_aware=False, output_dir="./output", show_all_metrics=False):
84
114
  results = {}
85
- # 1. 扫描并算出所有注册的 Scorer 成绩
86
115
  for name, cls in SCORERS.items():
87
116
  try:
88
117
  if "Align" in name:
@@ -94,44 +123,36 @@ class LatencyEvaluator:
94
123
  )
95
124
  else:
96
125
  scorer = cls(computation_aware=computation_aware)
97
-
126
+
98
127
  score = scorer(self.instances)
99
128
  key = f"{name}_CA" if computation_aware else name
100
129
  results[key] = score
101
- except Exception as e:
130
+ except Exception:
102
131
  pass
103
-
104
- # 2. 智能化筛选最高级/最准确的一个版本展示给用户
132
+
105
133
  cleaned_results = {}
106
-
107
- # [A] 评估: 第一声开口延迟 (StartOffset)
134
+
108
135
  so_key = "StartOffset_CA" if computation_aware else "StartOffset"
109
136
  align_so_key = "StartOffset_SpeechAlign_CA" if computation_aware else "StartOffset_SpeechAlign"
110
-
111
137
  if results.get(align_so_key) is not None and results[align_so_key] > 0:
112
138
  cleaned_results["First_Audio_Delay_(StartOffset_ms)"] = results[align_so_key]
113
139
  else:
114
140
  cleaned_results["First_Audio_Delay_(StartOffset_ms)"] = results.get(so_key, 0)
115
-
116
- # [B] 评估: 整句同传综合延迟 (标准版 ATD: 连同阅读/播放音频时间一起计算)
141
+
117
142
  atd_key = "ATD_CA" if computation_aware else "ATD"
118
143
  align_atd_key = "ATD_SpeechAlign_CA" if computation_aware else "ATD_SpeechAlign"
119
-
120
144
  if results.get(align_atd_key) is not None and results[align_atd_key] > 0:
121
145
  cleaned_results["Overall_Translation_Delay_(ATD_ms)"] = results[align_atd_key]
122
146
  else:
123
147
  cleaned_results["Overall_Translation_Delay_(ATD_ms)"] = results.get(atd_key, 0)
124
148
 
125
- # [C] 评估: 模型结单同传延迟 (纯净版 CustomATD: 剔除音频合成自身的物理时长,只看模型推断何时结束)
126
149
  catd_key = "CustomATD_CA" if computation_aware else "CustomATD"
127
150
  align_catd_key = "CustomATD_SpeechAlign_CA" if computation_aware else "CustomATD_SpeechAlign"
128
-
129
151
  if results.get(align_catd_key) is not None and results[align_catd_key] > 0:
130
152
  cleaned_results["End_Action_Delay_(CustomATD_ms)"] = results[align_catd_key]
131
153
  else:
132
154
  cleaned_results["End_Action_Delay_(CustomATD_ms)"] = results.get(catd_key, 0)
133
155
 
134
- # [D] 评估: 实时率指标 (RTF)
135
156
  if "RTF" in results:
136
157
  cleaned_results["Real_Time_Factor_(RTF)"] = results["RTF"]
137
158
  elif "RTF_CA" in results:
@@ -142,21 +163,19 @@ class LatencyEvaluator:
142
163
  elif "ModelGenerateRTF_CA" in results and results["ModelGenerateRTF_CA"] is not None:
143
164
  cleaned_results["Model_Generate_RTF"] = results["ModelGenerateRTF_CA"]
144
165
 
145
- # ================= 修改开始 =================
146
- # 如果用户显式开启了展示开关,则将原始杂乱数据装入 "detailed_all_metrics"
147
166
  if show_all_metrics:
148
167
  cleaned_results["detailed_all_metrics"] = results
149
- # ================= 修改结束 =================
150
168
 
151
169
  return cleaned_results
152
170
 
171
+
153
172
  def load_agent_from_file(path, class_name):
154
- """动态加载用户 Agent"""
155
173
  spec = importlib.util.spec_from_file_location("user_agent", path)
156
174
  mod = importlib.util.module_from_spec(spec)
157
175
  spec.loader.exec_module(mod)
158
176
  return getattr(mod, class_name)
159
177
 
178
+
160
179
  def main():
161
180
  parser = argparse.ArgumentParser(description="OpenSTBench Latency Evaluator")
162
181
  parser.add_argument("--source", required=True)
@@ -166,6 +185,7 @@ def main():
166
185
  parser.add_argument("--agent-script", required=True, help="Path to python file containing Agent class")
167
186
  parser.add_argument("--agent-class", required=True, help="Name of the Agent class")
168
187
  parser.add_argument("--segment-size", type=int, default=20)
188
+ parser.add_argument("--poll-interval-ms", type=float, default=10.0)
169
189
  parser.add_argument("--latency-unit", choices=["word", "char"], default="word")
170
190
  parser.add_argument("--disable-asr-fallback", action="store_true")
171
191
  parser.add_argument("--asr-model", default="medium")
@@ -179,20 +199,20 @@ def main():
179
199
  if args.slurm:
180
200
  submit_slurm(args, __file__)
181
201
 
182
- # 1. 加载数据
183
- with open(args.source) as f: src = [l.strip() for l in f if l.strip()]
184
- ref = [None]*len(src)
202
+ with open(args.source) as f:
203
+ src = [l.strip() for l in f if l.strip()]
204
+ ref = [None] * len(src)
185
205
  if args.target:
186
- with open(args.target) as f: ref = [l.strip() for l in f if l.strip()]
206
+ with open(args.target) as f:
207
+ ref = [l.strip() for l in f if l.strip()]
187
208
 
188
- # 2. 加载用户 Agent
189
209
  AgentClass = load_agent_from_file(args.agent_script, args.agent_class)
190
210
  agent = AgentClass()
191
-
192
- # 3. 运行 Latency 评测
211
+
193
212
  evaluator = LatencyEvaluator(
194
213
  agent,
195
214
  args.segment_size,
215
+ poll_interval_ms=args.poll_interval_ms,
196
216
  latency_unit=args.latency_unit,
197
217
  asr_fallback_for_s2s_alignment=not args.disable_asr_fallback,
198
218
  asr_model=args.asr_model,
@@ -200,58 +220,47 @@ def main():
200
220
  alignment_dictionary_model=args.alignment_dictionary_model,
201
221
  )
202
222
  instances = evaluator.run(src, ref, args.task, args.output)
203
-
204
- # 4. 计算 Latency 指标
223
+
205
224
  print("\n--- Latency Metrics ---")
206
225
  scores = evaluator.compute_latency(False, args.output)
207
226
  if args.computation_aware:
208
227
  scores.update(evaluator.compute_latency(True, args.output))
209
-
228
+
210
229
  for k, v in scores.items():
211
230
  print(f"{k:<25}: {v:.4f}")
212
231
 
213
- # 5. [紧耦合] 运行 Quality 指标
214
232
  if args.quality and args.target:
215
233
  print("\n--- Quality Metrics (Integrated) ---")
216
-
217
- # 收集预测结果
218
- # 注意: 对于 s2s, instances[i].prediction 应该是音频文件路径
219
234
  predictions = [ins.get_prediction_content() for ins in instances.values()]
220
-
235
+
221
236
  if args.task == "s2t":
222
- # [修改] 调用 TranslationEvaluator 算 BLEU
223
- # 默认只开 BLEU 以保证速度
224
237
  qual_eval = TranslationEvaluator(
225
- use_bleu=True,
226
- use_chrf=False,
227
- use_comet=False,
228
- use_whisper=False
238
+ use_bleu=True,
239
+ use_chrf=False,
240
+ use_comet=False,
241
+ use_whisper=False,
229
242
  )
230
- # 使用新的 evaluate_all 接口
231
243
  q_res = qual_eval.evaluate_all(
232
- target_text=predictions,
233
- reference=ref,
234
- source=src
244
+ target_text=predictions,
245
+ reference=ref,
246
+ source=src,
235
247
  )
236
248
  print(q_res)
237
-
249
+
238
250
  elif args.task == "s2s":
239
- # [修改] 调用 TranslationEvaluator 替代原 SpeechEvaluator
240
- # 开启语音相关功能: use_wer=True, use_whisper=True
241
- # predictions 是 wav 路径列表
242
251
  speech_eval = TranslationEvaluator(
243
- use_wer=True,
244
- use_whisper=True,
245
- whisper_model="tiny", # 用 tiny 快速出 WER
246
- use_utmos=False # 可选开启
252
+ use_wer=True,
253
+ use_whisper=True,
254
+ whisper_model="tiny",
255
+ use_utmos=False,
247
256
  )
248
- # 使用 evaluate_all 接口传入 target_speech
249
257
  s_res = speech_eval.evaluate_all(
250
- target_speech=predictions,
258
+ target_speech=predictions,
251
259
  reference=ref,
252
- source=src
260
+ source=src,
253
261
  )
254
262
  print(s_res)
255
263
 
264
+
256
265
  if __name__ == "__main__":
257
266
  main()
@@ -80,7 +80,8 @@ class SpeechToTextInstance(Instance):
80
80
  def len_sample_to_ms(self, length): return length * 1000 / self.sample_rate
81
81
 
82
82
  def send_source(self, segment_size=10):
83
- if self.step == 0: self.start_time = time.time()
83
+ if self.step == 0 and self.start_time is None:
84
+ self.start_time = time.perf_counter()
84
85
  num_samples = math.ceil(segment_size / 1000 * self.sample_rate)
85
86
 
86
87
  if self.step < len(self.samples):
@@ -105,13 +106,13 @@ class SpeechToTextInstance(Instance):
105
106
  if getattr(seg, "config", None):
106
107
  self.add_model_inference_time(seg.config.get("model_inference_time"))
107
108
 
108
- current_time = time.time()
109
+ current_time = time.perf_counter()
109
110
  content_list = seg.content.strip().split()
110
111
  self.prediction_list += content_list
111
112
  self.append_prediction_text(seg.content, source="native_transcript")
112
113
 
113
114
  curr_delay = self.len_sample_to_ms(self.step)
114
- curr_elapsed = curr_delay + (current_time - self.start_time) * 1000
115
+ curr_elapsed = (current_time - self.start_time) * 1000
115
116
 
116
117
  self.delays += [curr_delay] * len(content_list)
117
118
  self.elapsed += [curr_elapsed] * len(content_list)
@@ -134,7 +135,8 @@ class SpeechToSpeechInstance(SpeechToTextInstance):
134
135
  self.finish_prediction = seg.finished
135
136
  return
136
137
  if not seg.content: return
137
- if not self.start_time: self.start_time = time.time()
138
+ if not self.start_time:
139
+ self.start_time = time.perf_counter()
138
140
  if getattr(seg, "config", None):
139
141
  self.add_model_inference_time(seg.config.get("model_inference_time"))
140
142
  transcript = seg.config.get("transcript")
@@ -142,7 +144,7 @@ class SpeechToSpeechInstance(SpeechToTextInstance):
142
144
  source = seg.config.get("transcript_source", "native_transcript")
143
145
  self.append_prediction_text(transcript, source=source)
144
146
 
145
- current_time = time.time()
147
+ current_time = time.perf_counter()
146
148
  duration_ms = len(seg.content) * 1000 / seg.sample_rate
147
149
  self.target_sample_rate = seg.sample_rate
148
150
 
@@ -150,7 +152,7 @@ class SpeechToSpeechInstance(SpeechToTextInstance):
150
152
  self.durations.append(duration_ms)
151
153
 
152
154
  curr_delay = self.len_sample_to_ms(self.step)
153
- curr_elapsed = curr_delay + (current_time - self.start_time) * 1000
155
+ curr_elapsed = (current_time - self.start_time) * 1000
154
156
 
155
157
  self.delays.append(curr_delay)
156
158
  self.elapsed.append(curr_elapsed)
@@ -86,6 +86,30 @@ class LatencyScorer:
86
86
  def subtract(self, arr1, arr2):
87
87
  return [x - y for x, y in zip(arr1, arr2)]
88
88
 
89
+ def get_incremental_compute_times(self, ins, count):
90
+ if not self.computation_aware:
91
+ return [0.0] * count
92
+
93
+ elapsed = list(getattr(ins, "elapsed", []) or [])
94
+ delays = list(getattr(ins, "delays", []) or [])
95
+ if not elapsed or not delays:
96
+ return [0.0] * count
97
+
98
+ limit = min(len(elapsed), len(delays), count)
99
+ if limit <= 0:
100
+ return [0.0] * count
101
+
102
+ compute_elapsed = [max(0.0, e - d) for e, d in zip(elapsed[:limit], delays[:limit])]
103
+ incremental = []
104
+ prev = 0.0
105
+ for value in compute_elapsed:
106
+ incremental.append(max(0.0, value - prev))
107
+ prev = value
108
+
109
+ if limit < count:
110
+ incremental.extend([0.0] * (count - limit))
111
+ return incremental
112
+
89
113
  def get_delays(self, ins):
90
114
  override = getattr(ins, "_alignment_delays", None)
91
115
  if override is not None:
@@ -226,12 +250,7 @@ class ATDScorer(LatencyScorer):
226
250
  compute_times.append(0.0)
227
251
  else:
228
252
  base_emit_delays = list(getattr(ins, "delays", []) or [])
229
- text_compute_times = []
230
- if self.computation_aware and getattr(ins, "elapsed", None):
231
- compute_elapsed = self.subtract(ins.elapsed, ins.delays)
232
- text_compute_times = self.subtract(compute_elapsed, [0] + compute_elapsed[:-1])
233
- else:
234
- text_compute_times = [0.0] * len(delays)
253
+ text_compute_times = self.get_incremental_compute_times(ins, len(delays))
235
254
 
236
255
  for idx, delay in enumerate(delays):
237
256
  emit_delay = base_emit_delays[idx] if idx < len(base_emit_delays) else delay
@@ -239,19 +258,18 @@ class ATDScorer(LatencyScorer):
239
258
  target_delays.append(float(delay))
240
259
  target_chunk_ids.append(source_chunk_id)
241
260
  target_token_lens.append(0.0)
242
- compute_times.append(float(text_compute_times[idx]) if idx < len(text_compute_times) else 0.0)
261
+ extra_compute = 0.0 if self.computation_aware else float(text_compute_times[idx])
262
+ compute_times.append(extra_compute)
243
263
  else:
244
- speech_compute_times = []
245
- if self.computation_aware and getattr(ins, "elapsed", None):
246
- compute_elapsed = self.subtract(ins.elapsed, ins.delays)
247
- speech_compute_times = self.subtract(compute_elapsed, [0] + compute_elapsed[:-1])
248
- else:
249
- speech_compute_times = [0.0] * len(delays)
264
+ speech_compute_times = self.get_incremental_compute_times(ins, len(delays))
250
265
 
251
266
  for idx, duration_ms in enumerate(getattr(ins, "durations", []) or []):
252
267
  source_chunk_id = self.source_chunk_id_from_delay(source_boundaries, ins.delays[idx])
253
268
  token_lens = self.split_duration_into_tokens(duration_ms, token_len_ms=300.0)
254
- per_token_compute = float(speech_compute_times[idx]) / len(token_lens) if token_lens else 0.0
269
+ if self.computation_aware:
270
+ per_token_compute = 0.0
271
+ else:
272
+ per_token_compute = float(speech_compute_times[idx]) / len(token_lens) if token_lens else 0.0
255
273
  for token_len in token_lens:
256
274
  target_delays.append(float(delays[idx]))
257
275
  target_chunk_ids.append(source_chunk_id)
@@ -328,31 +346,25 @@ class CustomATD(ATDScorer):
328
346
  compute_times.append(0.0)
329
347
  else:
330
348
  base_emit_delays = list(getattr(ins, "delays", []) or [])
331
- text_compute_times = []
332
- if self.computation_aware and getattr(ins, "elapsed", None):
333
- compute_elapsed = self.subtract(ins.elapsed, ins.delays)
334
- text_compute_times = self.subtract(compute_elapsed, [0] + compute_elapsed[:-1])
335
- else:
336
- text_compute_times = [0.0] * len(delays)
349
+ text_compute_times = self.get_incremental_compute_times(ins, len(delays))
337
350
 
338
351
  for idx, delay in enumerate(delays):
339
352
  emit_delay = base_emit_delays[idx] if idx < len(base_emit_delays) else delay
340
353
  source_chunk_id = self.source_chunk_id_from_delay(source_boundaries, emit_delay)
341
354
  target_delays.append(float(delay))
342
355
  target_chunk_ids.append(source_chunk_id)
343
- compute_times.append(float(text_compute_times[idx]) if idx < len(text_compute_times) else 0.0)
356
+ extra_compute = 0.0 if self.computation_aware else float(text_compute_times[idx])
357
+ compute_times.append(extra_compute)
344
358
  else:
345
- speech_compute_times = []
346
- if self.computation_aware and getattr(ins, "elapsed", None):
347
- compute_elapsed = self.subtract(ins.elapsed, ins.delays)
348
- speech_compute_times = self.subtract(compute_elapsed, [0] + compute_elapsed[:-1])
349
- else:
350
- speech_compute_times = [0.0] * len(delays)
359
+ speech_compute_times = self.get_incremental_compute_times(ins, len(delays))
351
360
 
352
361
  for idx, duration_ms in enumerate(getattr(ins, "durations", []) or []):
353
362
  source_chunk_id = self.source_chunk_id_from_delay(source_boundaries, ins.delays[idx])
354
363
  token_lens = self.split_duration_into_tokens(duration_ms, token_len_ms=300.0)
355
- per_token_compute = float(speech_compute_times[idx]) / len(token_lens) if token_lens else 0.0
364
+ if self.computation_aware:
365
+ per_token_compute = 0.0
366
+ else:
367
+ per_token_compute = float(speech_compute_times[idx]) / len(token_lens) if token_lens else 0.0
356
368
  for _ in token_lens:
357
369
  target_delays.append(float(delays[idx]))
358
370
  target_chunk_ids.append(source_chunk_id)
File without changes
File without changes