Skip to content

正在识别的文字,显示的文字比较全,但是确定后,会发现丢文字;这个一般怎么解决 #3867

Open

Description

@andyuan1000

Notice: In order to resolve issues efficiently, please follow the template and include reproducible details.
(注意:为了更加高效率解决您遇到的问题,请按照模板提问,补充可复现细节。)

🐛 Bug

Image

To Reproduce

Steps to reproduce the behavior. Always include the exact command you ran.

  1. Install with: ...
  2. Run: ...
  3. See error: ...

Code sample

Expected behavior

Error logs


Environment

  • OS:
  • Python version:
  • FunASR version:
  • ModelScope version:
  • PyTorch / torchaudio version:
  • Install method (pip, source, Docker):
  • Device (cuda, cpu, mps):
  • GPU model:
  • CUDA/cuDNN version:
  • Docker image tag, if used:

Audio details

If the audio cannot be shared, please describe:

  • Duration:
  • Sample rate:
  • Format:
  • Language/dialect:
  • Speaker count:
  • Background noise/music:

Activity

  1. added
    bugSomething isn't working
    needs triageNeeds maintainer triage and routing
    on Nov 10, 2026
  2. andyuan1000 commented on Nov 10, 2026

    @andyuan1000
    Author

    请大神给点思路...

  3. changed the title [-]识别中的文字,比较全,一旦确定后,会发现丢文字;这个一般怎么姐姐姐[/-] [+]识别中的文字,比较全,一旦确定后,会发现丢文字;这个一般怎么解决[/+] on Nov 10, 2026
  4. LauraGPT commented on Nov 10, 2026

    @LauraGPT
    Collaborator

    目前只有截图,无法区分这是流式中间结果被正常修订,还是最终结果在 VAD/标点/ITN 阶段丢失。请补充以下可复现信息:

    1. 精确模型 ID/版本、FunASR 版本或 main commit、安装方式;
    2. 最小调用代码和完整参数(尤其是 streaming/offline、VAD、punc、ITN、chunk 配置);
    3. 同一段音频的逐次中间输出与最终输出原文;
    4. 若可以公开,请提供最短复现音频;否则至少提供时长、采样率、语言及问题发生的时间段。

    收到这些信息后才能判断应在流式解码、VAD 合并还是后处理路径复现。请勿包含私有路径、令牌或敏感音频。

  5. changed the title [-]识别中的文字,比较全,一旦确定后,会发现丢文字;这个一般怎么解决[/-] [+]正在识别的文字,显示的文字比较全,但是确定后,会发现丢文字;这个一般怎么解决[/+] on Nov 11, 2026
  6. andyuan1000 commented on Nov 11, 2026

    @andyuan1000
    Author

    @LauraGPT 老师,帮忙排查下

  7. andyuan1000 commented on Nov 11, 2026

    @andyuan1000
    Author

    【复现补充】流式中间结果较全、确定后丢文字 —— 以 meeting测试重复.wav 为例

    对应问题:正在识别的文字显示比较全,但确定后丢文字,开头尤其明显

    以下所有数据均在 GPU 生产容器的同款模型/配置上实测得到,非推测。



    1. 环境信息

    项 值
    FunASR 1.4.15(pip 安装,conda 环境)
    Python 3.11.14
    torch / torchaudio 2.7.1+cu128
    OS / GPU Ubuntu 22.04.5 LTS / NVIDIA L20 48GB
    部署 容器镜像内部 tag v40-1(无 git 仓库,无法提供 commit)
    ASR 模型 iic/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404(SeACo-Paraformer-Large)
    VAD 模型 iic/speech_fsmn_vad_zh-cn-16k-common-pytorch
    标点模型 iic/punc_ct-transformer_cn-en-common-vocab471067-large
    说话人模型 iic/speech_campplus_sv_zh-cn_16k-common
    大模型逐句修正 已关闭(llm.isOpen: false),排除后处理改写文本的可能

    关键配置(config_online.yml)

    asr:
      engine: paraformer        # 非 fun-asr-nano,非流式模型
      fp16: false
      punc: true                # 仅补标点,不做 ITN
    
    vad:
      enable: true
      max_single_segment_time: 15000
      max_end_silence_time: 800
      max_start_silence_time: 3000
      speech_noise_thres: 0.6
      speech_to_sil_time_thres: 150
      chunk_size_ms: 60
    
    performance:
      partial_window_sec: 15.0  # ★ 中间结果重解码窗口上限
      decode_interval: 0.48
      decode_batch_wait_ms: 10.0
    
    llm:
      isOpen: false

    调用方式:WebSocket 流式推 16kHz / mono / 16-bit PCM,每 60ms 一帧喂 DynamicStreamingVAD.feed(..., is_final=False);
    VAD 确认段后走 _decode_segment() 整段重解码 → _reconcile_completed_segment_text() → _append_completed_segment()。


    2. 复现音频

    项 值
    文件 meeting测试重复.wav
    格式 WAV / PCM s16le / 16kHz / mono / 16-bit
    时长 125.94 s(2,015,062 samples)
    语言 中文(普通话,影视剧对白,非真实会议)
    电平 rms=869, peak=32000(-0.2 dBFS)
    问题时间段 开头 0–1 s,以及 7.09–22.10 s 这个 15 秒长段

    开头的能量分布(这是"开头丢字"的诱因)

    时间 200ms 窗口 RMS 说明
    0.0s 1449 开头短促语音
    0.2s 810 开头短促语音
    0.4s 1046 开头短促语音
    0.6s – 6.8s 40 ~ 140 长达约 6 s 的静音
    7.0s 起 600 ~ 1400 连续正文

    即:一句很短的话(约 0.6 s)后面紧跟 6 秒静音,然后才开始正文。VAD 在 t≈2.70s 才确认第一段 [0-980ms]。


    3. VAD 分段结果(完整音频,生产配置,60ms 逐帧喂入 + finalize())

    [0-980ms]  [7090-22100ms]  [22100-37110ms]  [37110-39760ms]
    [41270-45430ms]  [46660-53680ms]  [54680-69690ms]  [69690-84700ms]
    [84700-95080ms]  [95830-110840ms]  [110840-125850ms]
    
    • 共 11 段,与生产日志中该任务的 确定Locked 记录逐段吻合;
    • 以 100ms 为粒度比对能量,「有语音但未被任何段覆盖」的区间数 = 0;
    • 因此可以排除「VAD 把开头语音切掉了」「VAD 合并时丢段」这两种假设。

    4. 逐次中间输出 与 最终输出原文(核心复现数据)

    以下均在容器里用同一份 Paraformer 权重、generate() 直接解码得到。
    PARTIAL = 按生产 get_partial_decode_audio() 的规则取窗口(从 vad.current_speech_start 到当前时刻,上限 15s);
    FINAL = VAD 确认段后 _decode_segment() 实际解码的段区间。

    4.1 第一段 [0-980ms](开头那句短话)

    时刻 解码窗口(s) 输出原文
    PARTIAL t=0.5 [0, 0.5] 你几呀
    PARTIAL t=1.0 [0, 1.0] 星期几啊
    PARTIAL t=1.5 [0, 1.5] 星期几啊
    PARTIAL t=2.0 [0, 2.0] 星期几啊
    PARTIAL t=2.5 [0, 2.5] 你几啊
    PARTIAL t=2.6 [0, 2.6] 你几啊
    FINAL / LOCKED [0, 0.98] 星期几啊

    现象:同一句开头,在后续长静音里被反复重解码,结果在「星期几啊」和「你几啊 / 你几呀」之间来回跳,首字「星」会丢。这就是"开头丢文字"的直接体现——开头短语音后面跟长静音时,中间结果不稳定。

    4.2 第二段 [7090-22100ms](15 秒长段,问题最明显)

    时刻 解码窗口(s) 输出原文
    PARTIAL t=8.0 [7.09, 8.0] 这个事儿你还
    PARTIAL t=10.0 [7.09, 10.0] 这个事你还可以找那个谭秘书
    PARTIAL t=12.0 [7.09, 12.0] 这个事儿你还可以找那个谭秘书和张明他们两个人现在他们俩一
    PARTIAL t=15.0 [7.09, 15.0] 这个事儿你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副区长
    PARTIAL t=18.0 [7.09, 18.0] 这个事你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副区长一个是在政府办当呃接待处的副
    PARTIAL t=20.0 [7.09, 20.0] 这个事你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副局长一个是在政府办当呃接待处的副处长这两个人还都在好吧
    PARTIAL t=21.0 [7.09, 21.0] …这两个人还都在好吧我
    PARTIAL t=22.0 [7.09, 22.0] …这两个人还都在好吧我们根据你
    PARTIAL t=22.9 [7.90, 22.9] 你可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副区长一个是在政府办当呃接待处的副组长这两个人还都在好吧我们根据你说的情况
    FINAL / LOCKED [7.09, 22.10] 这个事你还可以找那个谭秘书和张明他们两两个人现他他们俩一个在这个城城区区当副副局一一个在政政府办当接待处的副处长这两个人还都在好吧我们会根据你

    两个关键差异:

    1. 段首丢失:t=22.9s 时因为 partial_window_sec=15.0,窗口起点从 7.09s 右移到 7.90s,开头的「这个事还」(4 字)在中间结果里消失。段越长越容易发生。
    2. 确定后质量退化:FINAL 对整段一次性重解码,相比 t=20.0s 的流畅中间结果,出现了大量单字/双字重复(两两个人、现他他们俩、城城区区、副副局、一一个、政政府办),并把「我们根据你」变成「我们会根据你」。前端把流畅的 partial 替换成这个版本,观感就是"确定后文字变差/丢了"。

    生产日志中该段记录为:
    确定Locked: [7090-22100ms] "这个事你还可以找那个谭秘书和张明他们两个个人。现他们俩一个是在这个城城区域当副局" —— 同样带 两个个人、城城 重复,与上述 FINAL 结果一致,说明线上确实走了整段重解码这条路径。


    5. 机理分析(对应代码)

    环节 代码位置 问题
    partial 窗口上限 get_partial_decode_audio(),partial_window_sec=15.0 min_start = total_samples - partial_window_samples 会把长段段首截掉
    VAD 确认后整段重解码 _decode_segment() 对 15s 长段一次性 generate(max_new_tokens=512),Paraformer 出现重复字
    中间结果补救判据 _reconcile_completed_segment_text() 只在 catastrophically_short / tail_regressed / truncated_prefix 时保留 partial,三者都基于长度,未检测"重复字激增/质量退化",故 FINAL 被直接采纳
    幻觉处理 detect_and_fix_hallucination() 按 n-gram 重复截断,处理不了 两两个人、城城区区 这类单字重复
    文本清理 _clean_asr_text() 只去 tag / 特殊字符 / 空白,不会删正常汉字,排除此项
    标点 ct-punc 只插入 ,。?、,不删字,排除 ITN/标点丢字

    补充:_finalize_speakers → HybridSpeakerTracker._try_split() 会按说话人重切段,但本次复现未启用该路径即已出现差异,故不是主因。


    6. 最小复现步骤

    1. 取附件 repro_03_seg2_15s.wav(7.09–22.10s,16kHz/mono/16-bit,15.01s);
    2. 以 60ms 为一帧流式推入,保持 partial_window_sec=15.0;
    3. 在 t=20s 左右记录 partial 文本(应为流畅长句);
    4. 等待 VAD 确认端点(或触发 is_final),记录 sentences 中该段的最终文本;
    5. 对比:最终文本是否出现重复字、是否比中间结果短/差。

    若只需复现"开头丢字",用 repro_01_head_2s.wav(0–2s):把这段反复重解码(模拟长静音期间的 partial 刷新),观察「星期几啊」退化为「你几啊」。


    7. 附件(均为 16kHz / mono / 16-bit PCM WAV,另附 44.1kHz mp3 便于试听)

    文件 时长 对应原音频区间 用途
    repro_01_head_2s.wav / .mp3 2.00s 0–2s 开头短语音 + 静音,复现开头退化
    repro_02_head_12s.wav / .mp3 12.00s 0–12s 完整开头场景(短句 + 6s 静音 + 正文起)
    repro_03_seg2_15s.wav / .mp3 15.01s 7.09–22.10s 核心复现:VAD 长段,partial 与 final 差异最大

    音频内容为影视剧对白,无真实人名、无内部业务信息、无令牌与私有路径,可公开用于复现。


    8. 想请教的问题

    1. Paraformer 对 15 秒级别长段一次性解码产生单字重复(两两个人、城城区区),是否属于预期行为?是否有推荐的段长上限或解码参数?
    2. 流式场景下,中间结果(滑动窗口多次解码)明显优于整段重解码时,FunASR 是否有官方的"保留中间结果"策略或置信度判据?
    3. partial_window_sec 这类窗口上限导致段首文字丢失,是否有推荐做法(例如窗口起点固定在 VAD 段起点、只在尾部截断)?
  8. LauraGPT commented on Nov 11, 2026

    @LauraGPT
    Collaborator

    补充信息已读,模型 ID、参数、逐次结果和音频链接不需要重发。目前尚未完成这组音频的独立 GPU 复现;下面是对代码和你提供的时间轴的定位,不是“已解决”的结论。

    1. 滚动窗口移除段首,与 final 识别退化应分开追踪。 按你给出的区间,t=22.9s 的 15s 窗口确实从 7.90s 开始,比该段 7.09s 的起点晚了 810ms。这与主线 get_partial_decode_audio() 的取窗规则一致。该次 partial 的输入已不含段首,不能直接把它当作覆盖整段的替代文本。
    2. 你给出的第一段 FINAL 没有丢掉“星期几啊”。 这一例显示的是追加静音后的 partial 不稳定;第二段整段 FINAL 的重复字则是另一项需要复现的问题。不能仅凭更长或更流畅的 partial 判断它一定更准确,也不宜统一删除重复汉字,否则正常叠词会受损。
    3. 裁剪音频的时间轴需要换算。 repro_03_seg2_15s.wav 按说明对应原录音 7.09–22.10s,因此原录音 t=20.0s 对应裁剪文件 12.91s,t=22.0s 对应 14.91s;这份 15.01s 的裁剪本身不包含 t=22.9s 的后续音频。复现脚本应分别标明原录音时间与裁剪内时间,避免比较不同输入却归因于同一解码。

    目前主线已经有保留对齐 partial 的启发式逻辑,但它不是模型置信度保证。你报告的是 SeACo-Paraformer,而所引用的主线 _decode_segment() 调用的是传入的 vllm_engine.generate(inputs=...)。下一步需要补齐的是:把 config_online.yml、该会话包装器和 Paraformer AutoModel.generate() 连起来的最小调用脚本,以及模型 revision/快照版本。尤其请保留输入切片、实际传参和 _reconcile_completed_segment_text() 前后的原始文本;不需要提供生产配置中的私密字段。

    先以同一权重、同一段 PCM 和明确的 12.91s/14.91s/15.01s 输入核对模型原始输出,再区分窗口展示、整段推理和 reconcile 的影响。目前不把调整段长或 max_new_tokens 当作已验证修复,issue 保持打开。

  9. andyuan1000 commented on Nov 12, 2026

    @andyuan1000
    Author

    感谢逐条定位。我们按你要求做了控制变量复现,先更正两处我们之前给错的前提,再补齐你要的证据。这不是"已解决"的结论,issue 继续保持 open;本轮未改任何业务代码/配置。


    1. 两处更正(重要,会影响后续推算)

    1.1 partial 窗口实际是 8.0s,不是 15s

    你按 15s 推出的 [7.90, 22.90]、晚 810ms,结论方向没错,但前提是我们给错的:之前文档把 vad.max_single_segment_time: 15000 当成了 partial 取窗长度,那是 VAD 单段上限。

    realtime_ws.py 里 partial_window_sec=8.0(__init__ 默认值 / --partial-window-sec 默认值 / 会话构造处,三处一致),生产启动命令未传 --partial-window-sec,故实际窗口为 8.0s:

    python top/hdic/online/newnano/realtime_ws.py --device cuda --log-file ws.log \
      --ws-ping-interval 30 --ws-ping-timeout 60 --ws-close-timeout 60
    

    get_partial_decode_audio() 取窗:decode_start = max(current_speech_start, total_samples - partial_window_samples)。所以 t=22.9s、段起点 7.09s 时:

    窗口设定 取窗 段首缺失
    此前文档 15s [7.90, 22.90] 0.81s
    实际运行 8s [14.90, 22.90] 7.81s

    实测裸模型输出:8s 窗口 35 字、15s 窗口 70 字、VAD 整段 [7.09,22.10] 71 字。段首「这个事你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副局长」整段都没进入 8s 窗口的解码输入。窗口缺字属输入切片层,与模型退化分开追踪,这一点完全接受。

    1.2 "第二段整段 FINAL 的重复字"不是确定性的 final 退化

    我们之前把某一次带重复字的输出标为 FINAL/LOCKED 并据此断言整段重解码退化,这是采样偏差,需要撤回。

    补做了字节比对 + 重复调用(同一权重、同一 AutoModel 实例、同一数组、同一调用形式,CPU fp32):

    PCM_EQ clip[0,15.01] vs full[7.09,22.10]: shape (240160,) identical=True maxdiff=0.0
    5 次连续 generate(): ALL_IDENTICAL = False(5 个不同文本,长度均为 71,重复字位置各异)
    

    其中第 2 次几乎完全流畅(仅「这个事儿」「那谭秘秘书」两处),第 3/5 次带明显重复字。另有一处旁证:M-15.01 与 W-SEG 是同一段 240160 samples 的 PCM,两次 direct 输出也不同。

    因此更准确的表述是:重复字是同一段 PCM 上模型输出的波动,在流式里表现为"确定后变差",而不是一次稳定可复现的 final 退化。你提醒的"不能仅凭更长或更流畅的 partial 判断它一定更准确"在这里得到直接支持。非确定性来源我们尚未定位。


    2. 其余几点的确认

    • "第一段 FINAL 没丢『星期几啊』":接受,不再主张第一段丢字,追加静音后的 partial 不稳定另行看待。

    • "不宜统一删除重复汉字":接受,也不提出该改法。包装层 POST_PROCESS_CONFIG 的 max_char_repeat=5 / max_2gram_repeat=3 阈值远高于本例的"2 字重复 2 次",下调会伤及「刚刚 / 看看 / 渐渐」等正常叠词。

    • 双时间轴:已按要求分别标注。repro_03_seg2_15s.wav = 原录音 7.09–22.10s:

      原录音 裁剪文件内 说明
      7.09s 0.00s 第二段 VAD 起点 / 裁剪起点
      20.00s 12.91s 输入 1
      22.00s 14.91s 输入 2
      22.10s 15.01s VAD 段终点 / 裁剪结束,输入 3
      22.90s 15.81s 超出本裁剪,只存在于全量音频

      窗口右移实验只用全量音频,未在裁剪文件上比较,避免把不同输入归因于同一解码。音频链接沿用之前提供的,不重发。


    3. 环境与模型 revision / 快照版本

    项 值
    镜像 / 容器 fun-asr-nano-realtime:v40-1
    Python / funasr 3.11.14 / 1.4.15
    torch / modelscope 2.7.1+cu128 / 1.39.1
    代码传入模型 ID iic/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404
    权重 容器内本地目录(AutoModel(model_path=...),以本地为准)
    VAD / 标点 iic/speech_fsmn_vad_zh-cn-16k-common-pytorch / iic/punc_ct-transformer_cn-en-common-vocab471067-large
    本轮设备 CPU fp32(生产为 --device cuda)

    本地权重目录 .mv:

    Revision:master,CreatedAt:1781822385
    

    .msc 文件级 revision(ModelScope blob hash):

    文件 revision
    model.pt / config.yaml / configuration.json / tokens.json ff922d0e9af830cee4d1ec9b57b193196941efd8
    am.mvn / seg_dict / fig/struct.png b0bf20ac984e61faae6d997e1c9af20c9b1b48d5
    README.md 6059253cbfda9ea43d6c6f198d38854f09adb298
    example/asr_example.wav 17d593c496945f14a7ac387dc47e20a3333a7f9f

    MD5:model.pt = da55f0cb422ac0c1a32d1a7a0a295c92(880,502,012 bytes)、tokens.json = 2d505fc2487d5acbc5e8d71f9c30d9b7。

    config_online.yml 相关项(已脱敏,不含地址/账号/密钥):

    asr:
      version: 2.1.24
      engine: paraformer
      fp16: false
      punc: true
    vad:
      enable: true
      max_single_segment_time: 15000
      max_end_silence_time: 800
      max_start_silence_time: 3000
      speech_noise_thres: 0.6
      speech_to_sil_time_thres: 150
      chunk_size_ms: 60
    llm:
      isOpen: false

    4. 最小调用链路:vllm_engine 不是 vLLM

    config_online.yml: asr.engine = paraformer → load_models() 走 paraformer 分支 → ParaformerEngineWrapper(AutoModel(...), punc_model) → 包进 RealtimeBatchingEngine → 赋给模块级 _vllm_engine(变量名沿用,实际不是 vLLM)→ RealtimeASRSession._decode_segment(seg):

    self.vllm_engine.generate(inputs=[audio_tensor],
                              hotwords=self.asr_kwargs.get("hotwords"),
                              language=self.asr_kwargs.get("language"),
                              max_new_tokens=512)

    ParaformerEngineWrapper.generate() 内部是 self._asr_model.generate(input=audios, disable_pbar=True),其中 input 是 list of tensor;之后 _normalize_output() → _apply_punc() → _post_process_text();回到 _decode_segment() 再走 _clean_asr_text() → _reconcile_completed_segment_text() → (条件满足时)边界 retry → _postprocess_result_text()。

    本轮三种调用形式都测了,实际传参逐条记录:

    形式 调用
    direct AutoModel.generate(input=torch.Tensor[N], disable_pbar=True)
    batch AutoModel.generate(input=[torch.Tensor[N]], disable_pbar=True)(生产包装层形式)
    wrapper ParaformerEngineWrapper.generate(inputs=[Tensor], hotwords=None, language=None, max_new_tokens=512)

    均无 hotword、无 language;paraformer 路径忽略 max_new_tokens。包装层函数用 AST 从源码抽到隔离命名空间执行,未 import 业务模块、未启动服务。

    4.1 可独立运行的最小脚本(L0 裸模型,含双时间轴与重复调用)

    import argparse, wave
    import numpy as np
    import torch
    from funasr import AutoModel
    
    MODEL_ID = "iic/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404"
    CLIP_ORIGIN = 7.09   # repro_03_seg2_15s.wav = 原录音 7.09-22.10s
    
    ap = argparse.ArgumentParser()
    ap.add_argument("--model-path", required=True)   # 本地权重目录,自行指定
    ap.add_argument("--wav", default="repro_03_seg2_15s.wav")
    ap.add_argument("--device", default="cpu")
    ap.add_argument("--repeat", type=int, default=1)
    a = ap.parse_args()
    
    w = wave.open(a.wav, "rb"); rate = w.getframerate()
    pcm = np.frombuffer(w.readframes(w.getnframes()), dtype="<i2").astype(np.float32) / 32768.0
    w.close()
    
    m = AutoModel(model=MODEL_ID, model_path=a.model_path, device=a.device,
                  disable_update=True, disable_pbar=True)
    
    for t in (12.91, 14.91, 15.01):          # 裁剪文件内时间
        x = pcm[:int(round(t * rate))]
        for i in range(a.repeat):
            r = m.generate(input=torch.from_numpy(x).float(), disable_pbar=True)
            text = r[0].get("text", "") if r else ""
            print("clip[0,%.2f] orig[%.2f,%.2f] repeat=%d len=%d" %
                  (t, CLIP_ORIGIN, CLIP_ORIGIN + t, i, len(text)))
            print("  TEXT %r" % text)

    把 input=torch.from_numpy(x).float() 换成 input=[torch.from_numpy(x).float()] 即对应生产包装层形式。完整分层脚本、10 组 case 的原始结果与 reconcile before/after 原文我们可以按需要附上。


    5. 同一权重、同一段 PCM 的模型原始输出(12.91 / 14.91 / 15.01s)

    均为裁剪文件内时间,direct 形式(未过任何后处理):

    12.91s(原 [7.09,20.00],65 字,流畅)

    这个事你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副局长一个是在政府办当呃接待处的副处长这两个人还都在好吧
    

    14.91s(原 [7.09,22.00],70 字,流畅)

    这个事你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副局长一个是在政府办当呃接待处的副处长这两个人还都在好吧我们根据你
    

    15.01s(原 [7.09,22.10],71 字,出现重复字)

    这个事你还可以找那个谭秘书和张明他们两个个人在他们们一个是在这个城城区域当副局长一个是在政政办办当接待处的副处长这两个人还都在好吧我们会根据你
    

    同一 15.01s 输入的三种形式字数都 71,但重复位置不同:batch 为「张明明们们…这城城区区当当副局长」,wrapper 为「两个个人现他他们俩…城城区域当副副长…政政府办」。结合第 1.2 节的重复调用结果,差异更可能来自解码非确定性,而非调用形式。

    结论 A:重复字在裸模型 direct 输出里就已存在,不是包装层 / 标点 / ITN / _post_process_text() 引入;_normalize_output()(去中文间空格)与 max_char_repeat=5 / max_2gram_repeat=3 也都不会削掉"2 字重复 2 次"这种模式。


    6. 窗口展示 / 整段推理 / reconcile 三层分离

    6.1 输入切片(原录音时间轴,裸模型字数 direct/batch/wrapper)

    输入 字数 开头
    [14.90,22.90] 实际 8s 窗口 35/35/35 「一个是在政府办当呃接待处的副组长…」
    [7.90,22.90] 文档 15s 窗口 70/70/70 「你可以找那个谭秘书和张明…」
    [7.09,22.10] VAD 整段 71/71/71 「这个事你还可以找那个谭秘书和张明…」

    6.2 _reconcile_completed_segment_text() 前后原文

    输入 seg=[7090,22100]、decode_succeeded=True、segment_partial_stable_count=3、last_partial_eligible=True:

    before_decoded_text(= 15.01s 整段 batch 输出,71 字,带重复字;四种场景相同):

    这个事你还可以找那个谭秘书和张明明们们两个人现在他们俩一个是在这城城区区当当副局长一个是在政府办当接待处的副处长这两个人还都在好吧我们会根据你
    

    before_partial_text(= 12.91s batch 输出,65 字,流畅;四种场景相同):

    这个事你还可以找那个谭秘书和张明他们两个人现在他们俩一个是在这个城关区域当副局长一个是在政府办当呃接待处的副处长这两个人还都在好吧
    
    场景 partial start/end (ms) adopted_partial after
    首尾都对齐(理想) 7090 / 22100 false 与 before_decoded 逐字相同,71→71
    仅首不对齐(8s 窗口) 14900 / 22100 false 同上
    15s 窗口 7900 / 22100 false 同上
    首尾都不对齐(实际运行) 14900 / 22900 false 同上

    即使构造了首尾完全对齐、观察 3 次、eligible 的理想条件,启发式仍直接返回 final_text。逐条判据:final_hallucinated 未触发(本例是 2 字重复 2 次);truncated_prefix 不满足(partial 65 字 < final 71 字);catastrophically_short 不满足(65 ≥ 213 不成立);tail_regressed 不满足(65 − 71 = −6)。此外 _partial_fallback_candidate 还要求 start_ms == seg[0] 且 |end_ms − seg[1]| <= 100,窗口一旦右移就进一步失效。

    结论 B:该启发式的判据只覆盖"变短 / 尾部回退 / 明显幻听",对"等长或略长、但重复字激增"这类退化没有判据。这具体印证了你说的"保留对齐 partial 的启发式不是模型置信度保证"。reconcile 层既没有制造也没有修复重复字。

    6.3 另一个音频上的旁证(不作 VAD 归因)

    另取一段 1h 会议音频的前 120s 转 16kHz mono:[0,7s] 输出「我」、[0,15s] 输出「嗯」、[0,30s] 才输出完整 28 字句子;[7,15s] 近静音窗口出现 the the the… 幻听(三种形式 19/35/27 字)。即高能量开头段未产生可识别文本,与"开头丢字"同构。本组未跑 VAD 段边界,只作旁证。


    7. 新增一处想请你判断的不一致

    磁盘快照的 configuration.json:

    "model_name_in_hub": {"ms": "iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch", "hf": ""}

    即快照来自普通 paraformer-large 仓库(无 seaco),而代码 LOCAL_PARA_NAME 传的是 SeACo-Paraformer ID;config.yaml 里也是 model: Paraformer / predictor: CifPredictorV2,未见 hotword/bias 相关配置。由于 AutoModel(model=..., model_path=...) 在 model_path 存在时以本地目录为准,实际加载权重可能是普通 Paraformer。我们不做结论,想确认这一组合是否为预期、是否与重复字 / 非确定性相关。本轮所有测试都使用这一份磁盘权重,因此"同一权重"在本次测试内部成立。


    8. 仍未验证(不作为结论)

    1. CPU vs CUDA:生产 --device cuda,本轮 CPU fp32,未在 GPU 上重复第 1 步核对。
    2. 非确定性来源未定位(CPU 多线程浮点归约 / CIF predictor 边界 / 第 7 节的模型 ID 与权重错配,均未排除)。
    3. 生产真实 final 文本未从会话日志捞取;本轮 decoded_text 是同一权重下按 VAD 段边界直接调用 AutoModel.generate() 得到的复现值。
    4. VAD 段边界沿用同容器同配置上一次跑出的结果(全量 11 段,未被覆盖的有声区间 = 0),本轮未重跑 VAD。

    同意不把「调整段长 / max_new_tokens / 统一删除重复汉字」当作已验证修复,issue 保持打开。上述材料不含生产配置中的私密字段,转写原文来自测试用对白音频。

    如果你希望下一步优先做某一项(例如在同一段 PCM 上做 CUDA 复现,或提供完整分层脚本与原始结果 JSON),请指明,我们按该方向补。

    老师 这是AI回复的,我真不知道下一步如何解决,给点思路吧

  10. LauraGPT commented on Nov 12, 2026

    @LauraGPT
    Collaborator

    先别改生产,也别继续调去重阈值。现在建议按这个顺序做两件小事:

    1. 先确认实际加载的模型,不用重新下载。 在现有最小脚本 m = AutoModel(...) 后加 print(type(m.model).__name__)。你提供的本地配置写的是 Paraformer / CifPredictorV2,不能因为传入了 SeACo 的字符串 ID 就把本地权重当作 SeACo;当前加载代码会采用本地目录里的模型配置。先把这条事实固定下来,不要同时换模型。
    2. 只排查“同一输入、连续五次结果不同”。 请打印 m.kwargs["frontend"].dither;若非零,只在离线复现脚本中将这个 frontend 的 dither 改为 0.0,仍用同一份 15.01s PCM、同一实例、相同参数各调用五次,保留全部输出。当前 WavFrontend 会把该值传给 fbank;这是一个值得单独排除的随机扰动来源,还不是已确认的根因或识别质量修复。若本来就是零,不要改其他参数,先补上实际模型类和这个值即可。

    8s 窗口不包含前面 7.81s 的语音,不能靠去重或把这段 partial 直接覆盖整段 final 找回。窗口展示与模型输出波动分开处理。本轮你补的是 CPU 结果,不能据此断言生产 CUDA 同样表现;我也尚未完成你的本地权重在 GPU 上的复现,issue 保持打开。

    代码依据:本地模型配置加载、frontend 的 dither 传递。这些是当前主线的代码定位,不把你镜像里的实现视为已独立核验。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds triageNeeds maintainer triage and routing

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions