数字人工程解读(二):CyberVerse 实验:FlashHead Lite + wav2vec2 的端到端耗时拆解
./checkpoints/SoulX-FlashHead-1_3B Lite + ./checkpoints/wav2vec2-base-960h 组合下,当前日志显示 FlashHead 单个视频 chunk 生成速度并不是持续播放阶段的主要瓶颈;更明显的问题出现在 Go 侧 DirectPeer 的 VP8 编码与发布队列积压。首帧端到端仍有约 3.713 秒,但这需要更多跨阶段 trace 才能精确拆分。#CyberVerse-local-experiment-2026-06-12这次实验记录的是 CyberVerse 实时数字人链路在一个轻量 Avatar 后端组合下的表现。Avatar 模型使用 ./checkpoints/SoulX-FlashHead-1_3B Lite,音频特征模型使用 ./checkpoints/wav2vec2-base-960h。链路仍然是 CyberVerse 的标准实时路径:Python Inference Server 负责 Avatar 推理,Go API Server 负责编排、VP8 编码和 WebRTC 发布。#CyberVerse-local-experiment-2026-06-12
| 项目 | 配置 / 观测值 | 说明 |
|---|---|---|
| Avatar checkpoint | ./checkpoints/SoulX-FlashHead-1_3B Lite | 用于从音频驱动数字人视频帧。 |
| Audio feature checkpoint | ./checkpoints/wav2vec2-base-960h | 用于提取语音特征,供 Avatar 后端使用。 |
| 视频 chunk | 24 frames,512x512,20 fps | 一个 chunk 约等于 1.2 秒视频。 |
| 音频消耗 | 19200 samples | 若按 16 kHz 采样率计算,也是约 1.2 秒音频。 |
| 推流编码 | VP8,1800 kbps | Go 侧 DirectPeer 日志中观测到的编码配置。 |
原始日志片段
以下是本次实验里最关键的几类日志。它们分别对应 Python 侧 Avatar 生成、Go 编排侧首个视频 chunk、Go DirectPeer 侧编码与发布。#CyberVerse-local-experiment-2026-06-12
INFO:inference.plugins.avatar.flash_head_plugin:FlashHead video chunk generated: chunk_index=3338 num_frames=24 512x512 fps=20 consumed_samples=19200 is_final=False elapsed=0.514
2026/06/12 14:12:03 TTFF std pipeline session=dc3b9d12-96e9-4144-a327-c47cd1508352 first_video_chunk=3.713s
2026/06/12 14:12:03 voice_trace event=direct_vp8_encode_started sid=dc3b9d12-96e9-4144-a327-c47cd1508352 turn=2 reply=standard seg=1 queue_ms=532 bitrate_kbps=1800
2026/06/12 14:12:04 voice_trace event=direct_vp8_encode_done sid=dc3b9d12-96e9-4144-a327-c47cd1508352 turn=2 reply=standard seg=1 encode_ms=677
2026/06/12 14:12:04 voice_trace event=direct_publish_started sid=dc3b9d12-96e9-4144-a327-c47cd1508352 turn=2 reply=standard seg=1 queue_ms=1210
2026/06/12 14:12:04 voice_trace event=direct_first_video_sample_written sid=dc3b9d12-96e9-4144-a327-c47cd1508352 turn=2 reply=standard seg=1 publish_ms=24
1. Python Avatar 生成:单 chunk 约 0.514 秒
FlashHead 日志显示,一个 24 帧、512x512、20 fps 的视频 chunk 生成耗时为 0.514 秒。因为 24 帧在 20 fps 下对应约 1.2 秒视频,所以这个样本里的 Avatar 生成速度快于实时播放速度。#CyberVerse-local-experiment-2026-06-12
2. Go 编排到首个视频 chunk:约 3.713 秒
Go 侧日志给出了标准链路的首个视频 chunk 时间:first_video_chunk=3.713s。这不是单纯的 Avatar 推理耗时,而是端到端链路里的一个关键 TTFF 指标,前面可能包含用户语音结束、ASR、LLM、TTS、Avatar 启动、gRPC 流传输等多个阶段。#CyberVerse-local-experiment-2026-06-12
不要把 3.713 秒直接归因给 FlashHead
当前日志只说明 Go 标准链路拿到首个视频 chunk 用了 3.713 秒。要进一步判断是 ASR、LLM、TTS、Avatar warmup 还是 gRPC 传输导致,需要同时打开 flashhead_generation_started、go_avatar_first_video_received、TTS 首包、LLM 首 token 等 trace。
3. VP8 编码:前几段约 0.5–0.7 秒
VP8 是一种面向实时视频传输的视频压缩编码格式。它的作用不是“生成数字人画面”,而是把 FlashHead 产出的原始 RGB 视频帧压缩成浏览器 WebRTC 可以接收和播放的视频 payload。换句话说,FlashHead 负责“画面内容”,VP8 编码负责“把画面压小并打包成可传输的视频流”。#CyberVerse-source-code-2026-06-12
在 CyberVerse 里,这一步主要由 Go 侧负责:server/internal/direct/peer.go 的 DirectPeer.runEncoder 从 encodeCh 取出 RawAVSegment,打印 direct_vp8_encode_started,然后调用 mediapeer.EncodeRGBChunkToVP8SamplesWithBitrate。真正执行编码的函数在 server/internal/mediapeer/segment.go:它启动 ffmpeg 子进程,用 libvpx 把 rgb24 原始帧编码成 VP8,并输出 IVF 容器;随后再用 Pion 的 ivfreader 解析成 media.Sample,交给 DirectPeer 后续发布。#CyberVerse-source-code-2026-06-12
DirectPeer.runEncoder
-> mediapeer.EncodeRGBChunkToVP8SamplesWithBitrate
-> ffmpeg -c:v libvpx -deadline realtime -cpu-used 8 -f ivf
-> ivfreader.ParseNextFrame
-> []media.Sample
-> publishCh
-> WebRTC video track
DirectPeer 日志显示,前几段 VP8 编码耗时分别大约为 677ms、485ms、632ms、543ms、592ms。这个阶段已经明显接近甚至超过一个短 segment 可以承受的实时预算。#CyberVerse-local-experiment-2026-06-12
| Segment | 编码等待 queue_ms | VP8 编码 encode_ms | 发布等待 queue_ms | 写入 publish_ms |
|---|---|---|---|---|
| seg 1 | 532ms | 677ms | 1210ms | 24ms |
| seg 2 | 643ms | 485ms | 1867ms | 26ms |
| seg 3 | 839ms | 632ms | 2803ms | 25ms |
| seg 4 | 924ms | 543ms | 3375ms | 27ms |
| seg 5 | 1479ms | 592ms | 3931ms | 25ms |
4. WebRTC 发布写入:本身不慢,但队列已经堆起来
direct_first_video_sample_written 里的 publish_ms 多数只有 17–29ms,说明真正写入 WebRTC 的动作并不慢。问题在于 direct_publish_started 前面的 queue_ms 从 1.2 秒一路增长到接近 4.8 秒,说明发布侧开始处理时,前面已经积压了大量待发布的视频段。#CyberVerse-local-experiment-2026-06-12
publish_ms 小,不代表发布链路没有问题;这里的问题不是“写得慢”,而是“轮到它写之前已经等太久”。持续播放阶段:DirectPeer VP8 编码与队列积压是主瓶颈
从日志趋势看,上游持续产生 segment,而 Go 侧 DirectPeer 的编码与发布消费速度跟不上,导致队列逐步累积。尤其是发布队列从 1210ms 增长到 4850ms 左右后才趋于稳定,这说明系统进入了一个带着数秒 backlog 播放的状态。#CyberVerse-local-experiment-2026-06-12
flowchart TD A["Avatar 视频 chunk 到达"] --> B["direct_segment_enqueued"] B --> C["等待 VP8 编码队列"] C --> D["direct_vp8_encode_started"] D --> E["VP8 编码\n约 0.5–0.7 秒"] E --> F["direct_vp8_encode_done"] F --> G["等待发布队列\n最高约 4.8 秒"] G --> H["direct_publish_started"] H --> I["写入 WebRTC\n约 17–29ms"]
首帧阶段:3.713 秒需要更细 trace 才能归因
首个视频 chunk 的 3.713s 是用户实际体验里非常重要的延迟,但当前日志不足以把它精确分摊到 ASR、LLM、TTS、Avatar 生成或 gRPC 传输。它只能说明标准链路首帧还偏慢,不能单独证明 FlashHead 是首帧瓶颈。#CyberVerse-local-experiment-2026-06-12
| 候选瓶颈 | 当前证据 | 判断 | 下一步需要的日志 |
|---|---|---|---|
| FlashHead 单 chunk 生成 | elapsed=0.514,生成 24 帧 / 1.2 秒视频 | 不像持续阶段主瓶颈 | 连续 chunk 的 elapsed 分布、首个 chunk 生成开始时间 |
| 首帧端到端链路 | first_video_chunk=3.713s | 确认偏慢,但无法单点归因 | ASR final、LLM first token、TTS first audio、Avatar first request、Avatar first response |
| VP8 编码 | 多段 encode_ms 为 0.5–0.7 秒 | 明确是重要耗时点 | 编码器初始化、preset、线程数、是否每段重建 encoder |
| WebRTC 写入 | publish_ms 多为 17–29ms | 写入动作本身不慢 | RTP pacing、轨道阻塞、是否按 segment 串行等待 |
| 发布队列 | queue_ms 增至约 4.8 秒 | 明确存在积压 | 队列长度、消费协程数、背压策略、丢帧策略 |
优化优先级
排查顺序
- 第一优先级:DirectPeer VP8 编码。检查是否每个 segment 都重新初始化 encoder,确认编码参数、线程数、preset 与分辨率是否适合实时链路。
- 第二优先级:发布队列与背压策略。当前队列积压到约 4.8 秒,如果目标是实时对话,应该考虑丢弃过期帧、合并 segment 或限制上游生产速度。
- 第三优先级:首帧链路 trace。补齐 ASR、LLM、TTS、Avatar 的首包时间,确认 3.713 秒主要耗在哪个阶段。
- 第四优先级:Avatar 生成稳定性。继续收集 FlashHead 连续 chunk 的
elapsed,确认是否有偶发慢 chunk 或首轮 warmup。
这次结果可以概括为三句话。第一,SoulX-FlashHead-1_3B Lite 在样本日志中生成 1.2 秒视频只花了 0.514 秒,持续生成能力看起来有余量。第二,端到端首个视频 chunk 仍然需要 3.713 秒,这会影响用户第一次看到数字人回应的体感。第三,持续播放阶段最明显的工程瓶颈在 Go DirectPeer 侧:VP8 编码耗时偏高,发布队列积压到约 4.8 秒,而真正写 WebRTC 的 publish_ms 很低。#CyberVerse-local-experiment-2026-06-12
参考来源
- CyberVerse 本地实验日志(2026-06-12):使用
./checkpoints/SoulX-FlashHead-1_3B Lite与./checkpoints/wav2vec2-base-960h的 Avatar 生成、Go 编排、DirectPeer VP8 编码与 WebRTC 发布日志。 - CyberVerse 源码(2026-06-12):
server/internal/direct/peer.go的DirectPeer.runEncoder,以及server/internal/mediapeer/segment.go的EncodeRGBChunkToVP8SamplesWithBitrate。