[Bugfix] Build the forced-aligner prompt without a chat template (word timestamps one bin late) (#7240)
Signed-off-by: Tianyao Wu <rayroy31@gmail.com>
This commit is contained in:
1 parent
5e4baea64d
commit
b3dd45874a
3 files changed
+28
-13
No files matched your search
@@ -10,7 +10,21 @@ def test_build_prompt_has_boundary_timestamp_markers():
|
||||
|
||||
assert prompt.count("<timestamp>") == 4
|
||||
assert "hello<timestamp><timestamp>world" in prompt
|
||||
assert prompt.endswith("<|im_start|>assistant\n")
|
||||
# Official aligner format: audio placeholder + words, no chat template
|
||||
# (a leading "<|im_start|>user\n" shifts the predicted markers one bin late).
|
||||
assert prompt == f"{processor.AUDIO_PLACEHOLDER}hello<timestamp><timestamp>world<timestamp><timestamp>"
|
||||
assert "<|im_start|>" not in prompt
|
||||
assert processor.build_prompt([]) == f"{processor.AUDIO_PLACEHOLDER}<timestamp><timestamp>"
|
||||
|
||||
|
||||
def test_build_prompt_matches_official_encode_timestamp():
|
||||
pytest.importorskip("qwen_asr")
|
||||
from qwen_asr.inference.qwen3_forced_aligner import Qwen3ForceAlignProcessor
|
||||
|
||||
text = "It's 3 o'clock, 你好 world."
|
||||
word_list, official_prompt = Qwen3ForceAlignProcessor().encode_timestamp(text, "english")
|
||||
|
||||
assert processor.build_prompt(list(word_list)) == official_prompt
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
# --forced-aligner sets just the model path.
|
||||
#
|
||||
# The stage runs the model with runner="pooling" + pooling_task="token_classify".
|
||||
# Prompt tokens, chat template and word segmentation are Qwen-specific and live
|
||||
# in qwen3_force_align_processor (not configurable here).
|
||||
# Prompt tokens and word segmentation are Qwen-specific and live in
|
||||
# qwen3_force_align_processor (not configurable here); the prompt is the same
|
||||
# string qwen_asr's encode_timestamp builds, with no chat template.
|
||||
|
||||
forced_aligner:
|
||||
model: Qwen/Qwen3-ForcedAligner-0.6B
|
||||
|
||||
@@ -65,18 +65,18 @@ _LANG_ALIASES = {
|
||||
|
||||
|
||||
def build_prompt(words: list[str]) -> str:
|
||||
"""Wrap segmented words in the Qwen3 aligner prompt template.
|
||||
"""Build the Qwen3 aligner prompt exactly as qwen_asr's
|
||||
``Qwen3ForceAlignProcessor.encode_timestamp`` does: the audio placeholder
|
||||
followed by the words, each with two trailing ``<timestamp>`` markers
|
||||
(start + end) that the model classifies into audio time bins.
|
||||
|
||||
Each word gets two trailing ``<timestamp>`` markers (start + end); the
|
||||
model classifies each marker into an audio time bin.
|
||||
No chat template. The official aligner feeds this string straight to the
|
||||
tokenizer; wrapping it in ``<|im_start|>user ... <|im_start|>assistant``
|
||||
puts three tokens in front of the audio and shifts almost every predicted
|
||||
marker one 80 ms bin later than the official output.
|
||||
"""
|
||||
if not words:
|
||||
# Pad with one timestamp so the decoder always has something to
|
||||
# read; an empty result still surfaces as "[]" upstream.
|
||||
body = TIMESTAMP_TOKEN
|
||||
else:
|
||||
body = f"{TIMESTAMP_TOKEN}{TIMESTAMP_TOKEN}".join(words) + f"{TIMESTAMP_TOKEN}{TIMESTAMP_TOKEN}"
|
||||
return f"<|im_start|>user\n{AUDIO_PLACEHOLDER}{body}<|im_end|>\n<|im_start|>assistant\n"
|
||||
body = f"{TIMESTAMP_TOKEN}{TIMESTAMP_TOKEN}".join(words) + f"{TIMESTAMP_TOKEN}{TIMESTAMP_TOKEN}"
|
||||
return f"{AUDIO_PLACEHOLDER}{body}"
|
||||
|
||||
|
||||
# --- word segmentation (port of Qwen3ForceAlignProcessor) ----------------
|
||||
|
||||
Reference in new issue
Block a user