跳到主要内容

52 篇博文 含有标签「Engineering」

工程实践

查看所有标签

向量索引内幕:HNSW 与 IVF-PQ 的召回、延迟、内存三方账本

· 阅读需 11 分钟

向量检索上线后最常见的两类问题,一类是「延迟一高就加机器」,另一类是「召回不够就把 efSearch 往上调两倍」——两种都治不了根,因为向量索引不存在「更好的参数」,只存在召回、延迟、内存三角里你愿意让出哪条边。基础链路见 RAG 全链路解析,召回与重排的工程取舍见 RAG 进阶实战,这篇下沉一层,讲索引本身:HNSW 的图怎么长、IVF-PQ 的码本怎么省内存、过滤检索为什么会塌方、删除为什么永远不还内存。

分词器才是真正的接口:BPE 从零实现,以及它如何决定你的账单、缓存命中率与算术能力

· 阅读需 16 分钟

后端工程师看 LLM 应用,最容易忽略的一层是分词器。它夹在你的文本和模型之间:你发出去的每一个请求,先被转换成一串整数编号;你按量付费的每一个 token,就是这串编号里的一位。这一层没有超时、没有重试、没有连接池,看起来不像"系统",但它是唯一决定 token 数量的环节——而 token 数量同时决定了账单、KV Cache 显存、有效上下文窗口和 decode 阶段的吞吐。

更要命的是,分词器不是中立的。它的行为由训练语料的语言配比决定:一个主要用英文语料训练出来的词表,会把绝大多数额度花在英文词素上,中文、代码和数字只能退化成更短、数量更多的符号。本文不讲"分词器很重要"这种废话,直接从零写一个 BPE,然后用它跑四组实验,把每一项都变成可以写在容量规划表里的数字。所有代码只用 Python 标准库,你可以直接复制运行。

RAG 进阶实战:切块策略、混合检索与 Re-ranking 的量化收益与工程取舍

· 阅读需 9 分钟

RAG 的链路谁都能搭:文档切块、embedding 进库、相似度召回、拼 prompt。但同一套链路,有人线上答对率八成,有人五成——差距不在「会不会搭」,而在「每一步的效果账算没算清」。基础链路见 RAG 全链路解析,这篇讲进阶:切块、混合检索、Re-ranking 各值多少分、花多少钱,Graph RAG 什么时候才值得上。

GUI Agent 工程化:让大模型操作电脑的感知、行动与安全拆解 — Computer Use 从原理到落地

· 阅读需 9 分钟

文本 Agent 的标配是「工具调用」:模型说一句 search_order("sku"),代码去执行,接口是结构化的。但 2024 年底开始,OpenAI Operator、Anthropic Computer Use、各类桌面/浏览器 Agent 把目标换成了像人一样直接操作电脑——没有 API、没有 DOM、只有像素和窗口。这条技术线工程上比想象中难得多,本文按「感知 → 行动 → 验证 → 成本 → 评测 → 安全」拆开讲,附可运行的骨架代码与实测踩坑。

流式 TTS 合成工程:延迟预算、模型选型与评测回环

· 阅读需 9 分钟

语音 Agent 的体验差距,经常不是死在 ASR 听错,而是死在最后一跳:合成的声音不够快、不够自然。上一篇 语音 Agent 与流式 ASR 讲了 VAD、ASR 和打断,本篇专攻合成侧:模型选型、流式机制、中文特有的工程坑、以及一套不依赖人耳的评测方法。

先把预算摆出来:语音场景 800ms 的端到端延迟预算里,TTS 通常只能分到 300-400ms,其中首包延迟(TTFB)是硬指标——用户听到第一个字之前的所有环节,VAD、ASR、LLM、TTS,都在为这 800ms 打工。

一、三代 TTS 架构:选型先看你要什么​

第一代:自回归系(VALL-E / XTTS)。神经编解码器(EnCodec 这类)把音频打成离散 token,再用 transformer 逐 token 生成。VALL-E 在 2023 年证明了 3 秒参考音频就能克隆音色,这是声音克隆的起点。代价:逐 token 生成天然慢,长句容易重复、吞字,而且不支持流式。今天的定位基本只剩离线高质量生成。

第二代:非自回归系(VITS / FastSpeech 2)。一步出整句 mel 谱,快、稳、RTF 极低,但克隆能力弱、韵律平。适合 IVR 播报这类对音色无要求的场景,或者作为流式拼接链路的底层单元。

第三代:Flow Matching / DiT 系(F5-TTS / CosyVoice2)。把语音生成建模成流匹配问题,推理步数可压缩——F5-TTS 默认 10 步,论文报告单卡 A100 上 RTF 约 0.15(RTF = 合成耗时 / 音频时长,低于 1 才够实时,流式场景要更低)。CosyVoice2 走 LLM + Flow 双栈,官方口径是把流式合成延迟压到 150ms 量级、推理速度较上一代提升约 50%。零样本克隆 + 可控步数 = 自然度与速度兼得,是 2026 年生产的事实标准。

维度自回归非自回归Flow/DiT
自然度高中高
声音克隆强(3s 提示)弱强
推理速度慢极快快
原生流式不支持不支持支持(如 CosyVoice2)
代表模型VALL-E、XTTSVITS、FastSpeech2F5-TTS、CosyVoice2

选型结论:做语音 Agent 直接选 Flow/DiT 系;做稳定的播报机器人可以用 VITS 系省 GPU;自回归留给离线精修。别在自回归模型上硬做流式——那是在给架构缺陷打补丁。

二、流式合成的两条路线​

路线 A:模型原生流式。CosyVoice2 的 speech token 是流式产出的,stream=True 就能边生成边吐音频块,这是延迟最低的路线:

from cosyvoice.cli.cosyvoice import CosyVoice2
import torchaudio

cosyvoice = CosyVoice2("pretrained_models/CosyVoice2-0.5B",
load_jit=False, load_trt=False, fp16=False)

prompt_wav, sr = torchaudio.load("ref.wav") # 3-10 秒参考音频
prompt_speech_16k = torchaudio.transforms.Resample(sr, 16000)(prompt_wav)

# stream=True:每个 chunk 出来立刻可以推给客户端
for chunk in cosyvoice.inference_zero_shot(
"今天上海多云转晴,气温二十四到三十一度。",
"希望你以后能够做的比我还好呦。", # 参考音频的原文,用于对齐
prompt_speech_16k,
stream=True,
):
torchaudio.save("chunk.wav", chunk["tts_speech"], cosyvoice.sample_rate)
# 这里把 chunk 编码成 opus 推 WebSocket

路线 B:chunk 级流式封装(模型无关)。任何非流式模型都能切成块:按句/按标点切,块间透传上下文。两个坑必须处理:句边界韵律断裂、块间静音过长。做法是 overlap——把上一句结尾的 200ms 作为下一句的 prompt 前缀,让模型"接着读"而不是"重新读"。服务骨架:

import asyncio
from fastapi import FastAPI, WebSocket

app = FastAPI()

def tts_one(sentence: str) -> bytes: # 任意非流式模型,返回 24k PCM
...

async def synthesize(text: str):
for sent in split_sentences(text):
yield tts_one(sent) # 逐句合成即推,不等整段

@app.websocket("/tts")
async def tts_ws(ws: WebSocket):
await ws.accept()
text = await ws.receive_text()
async for chunk in synthesize(text):
await ws.send_bytes(chunk) # 帧头带上 seq 与句子边界标记
await ws.close()

端到端延迟预算的典型量级:VAD 50-100ms + 流式 ASR 150-250ms + LLM 首 token 100-300ms + TTS 首包 150-300ms。TTS 段能抠的点:模型常驻 warmup(首次推理含权重加载和图编译,可能多出几百 ms)、首 chunk 用低步数、文本归一化放到与 LLM 并行的流水线里做。

三、中文 TTS 的特有坑:归一化与多音字​

英文数字转读法靠规则就能覆盖九成,中文不行——"二十四"和"两千四百"的读法规则、单位量词的搭配、"一行代码"和"银行行长"的多音,规则表写起来是无底洞。文本归一化 + G2P 是中文 TTS 的前端核心,不是模型的事。

最小归一化实现(整数和百分比,够演示思路):

import re

CN_NUM = "零一二三四五六七八九"

def num_to_cn(n: int) -> str:
if n < 10:
return CN_NUM[n]
if n < 100:
tens = "" if n < 20 else CN_NUM[n // 10] + "十"
return tens + (CN_NUM[n % 10] if n % 10 else "")
return str(n) # 三位以上兜底给 G2P

def normalize(text: str) -> str:
text = re.sub(r"(\d+)%", lambda m: f"百分之{num_to_cn(int(m.group(1)))}", text)
text = re.sub(r"(?<!\d)(\d{1,2})(?!\d)", lambda m: num_to_cn(int(m.group(1))), text)
return text

多音字消歧,2026 年的生产做法是 LLM 前端:把句子丢给一个小模型,输出拼音 + 停顿位置 + 情感标记,一次把 G2P 和韵律控制全做掉:

输入: 重庆的银行行长今天下午到达重庆北站。
输出: {"pinyin": "chong2qing4 ... hang2zhang3 ...",
"pauses": [4, 12], "emotion": "neutral"}

踩坑记录:LLM 前端本身有 30-80ms 延迟,必须与 TTS 合成并行流水线化,否则它自己就成了延迟大头;生产上建议规则优先、LLM 兜底——常见词查表秒出,查不到的再走模型,把 LLM 调用量压到一成以下。

四、声音克隆:zero-shot 的工程真相​

原理一句话:参考音频抽说话人表征(嵌入或 speech token),文本条件生成时把音色"带"过去。VALL-E 证明 3 秒可行,CosyVoice2 把跨语种克隆做进了同一条流水线——中文参考音色直接说英文。工程上真正决定成败的不是模型,是参考音频:

  • 质量上限:16k 采样、无噪声、3-10 秒、内容与目标文本无关
  • 必须预处理:VAD 裁剪 + 响度归一,别拿整段播客去克隆
  • 合规:克隆真实人物音色上生产,先过授权关

五、评测:MOS 会骗人,回环才客观​

人工 MOS 贵、慢、不稳定。UTMOS 这类预测器论文报告系统级 Spearman 相关约 0.94、句子级只有约 0.5——适合给系统排序,不适合逐句验收。生产三件套:

1. ASR 回环(可懂度):合成 → ASR 转写 → 算 CER。这是回归测试的必备件,改前端、换模型、调步数,跑一遍回环就知道有没有退化:

from faster_whisper import WhisperModel
import numpy as np

model = WhisperModel("small", device="cpu", compute_type="int8")

def cer(ref: str, hyp: str) -> float:
dp = np.zeros((len(ref) + 1, len(hyp) + 1), dtype=int)
for i in range(1, len(ref) + 1):
for j in range(1, len(hyp) + 1):
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1,
dp[i - 1][j - 1] + (ref[i - 1] != hyp[j - 1]))
return dp[len(ref)][len(hyp)] / max(1, len(ref))

ref = "今天上海多云转晴"
segments, _ = model.transcribe("tts_out.wav", language="zh")
hyp = "".join(s.text for s in segments)
print(f"CER = {cer(ref, hyp):.2%}")

注意回环用 whisper small 做基线就够了,别用大模型——它的纠错能力会把 TTS 的口齿不清"修好",测不出问题。

2. SPK-SIM(音色相似度):说话人嵌入(WeSpeaker / ResNet34 系)的余弦相似度,验收克隆效果。经验阈值:同人同设备一般 0.8 上下,克隆验收从 0.6 起,低于 0.5 基本是克隆失败了。

3. RTF 与首包延迟(性能):压测统计合成耗时 / 音频时长,目标 RTF 低于 0.3(给编码、网络留余量);流式场景额外记 TTFB 和 chunk 间隔抖动——chunk 间隔抖动比平均延迟更伤听感,用户听的是"卡不卡",不是"平均多少毫秒"。

六、实战踩坑清单​

  • chunk 边界爆破音:块间加 10-20ms 交叉淡入淡出,或把上句末尾作下句 prompt 前缀;两招都试过,后者对自然度提升更明显
  • 首包慢的隐藏原因:模型 warmup(首次推理)、G2P 未预热、归一化在合成线程里串行做——把这三样都提前,首包能砍掉一截
  • 并发上限:TTS 是计算密集任务,多路并发按 RTF × 显存反推,别裸开线程池;FP16 比 FP32 省一半显存,先开 FP16
  • 长文本切句:超过 30 秒的合成任务必须前端切句、后端逐句流式返回,等整段合成就别谈延迟了
  • 格式统一:内部 24k/16bit PCM,出口按端侧转 opus/PCM;采样率转换只在出口做一次,别在合成路径里反复转
  • 预合成缓存:欢迎语、系统播报这类高频固定文本,预合成 + 语义 hash 缓存,命中直接推音频,连模型都不进

小结​

2026 年做 TTS 的路线图很清晰:模型选 Flow Matching/DiT 系(F5-TTS、CosyVoice2),流式优先用模型原生能力、次选 chunk 封装,中文必须自建归一化 + G2P 前端,验收靠 ASR 回环 + SPK-SIM + RTF 三件套。语音 Agent 的体验优化顺序建议先攻 TTS——用户对"答得快不快、声音像不像人"的感知,比对 LLM 智商差异敏感得多。合成侧补齐之后,接上 语音 Agent 全链路 和 多模态 VLM 工程,多模态 Agent 的地基就齐了。

MCP Server 生产级实战:从 stdio 骨架到 Streamable HTTP 与鉴权部署

· 阅读需 11 分钟

2026 年 6 月,MCP 完成「无状态核心」改造,HTTP+SSE 双端点退役;定稿版把远程 server 的 OAuth 2.1 变成硬性要求。协议层的来龙去脉,MCP 协议深度解析 和 无状态核心规范解读 已经讲透。但「读懂了协议」和「写得出一个能上生产的 server」之间还隔着一条沟:stdio 帧格式的坑、工具描述怎么写模型才肯调用、鉴权怎么做、客户端怎么接。本文用一段完整可跑的代码把这条沟填上。

Prompt 工程的下半场:System Prompt 分层、缓存感知设计与评测回归

· 阅读需 9 分钟

2023 年的 prompt 技巧("扮演 XX 角色"、few-shot 堆例子、Let's think step by step)在 2026 年基本失效了:推理模型自带思维链,few-shot 反而拖后腿;上下文窗口动辄 128K 起步,token 成本大头从输出转移到了输入;更重要的是,prompt 一旦进入生产,它就是代码——要版本化、要评测、要灰度、要监控缓存命中率。

本文不聊"怎么写好一句 prompt",那部分已经被 上下文工程 和 结构化输出 覆盖了。本文聊的是 prompt 作为生产系统的另一半:分层体系怎么搭、推理模型怎么伺候、缓存怎么薅、改动怎么验证。

分布式训练工程课:3D 并行、ZeRO/FSDP 与通信开销的账怎么算

· 阅读需 8 分钟

先给结论:大模型训练并行只有三件事——显存放不下、算力不够用、通信不能成为瓶颈。数据并行(DP)摊算力、张量并行(TP)摊单层显存、流水线并行(PP)摊层数,ZeRO/FSDP 是数据并行的显存优化形态,实际生产(GPT-3、Llama、Qwen 系列)都是把它们叠成 3D/4D 并行来用。选型的本质是一道通信开销的算术题:每步训练要搬多少字节,你的互联带宽扛不扛得住。

上一篇讲了参数高效微调 LoRA/QLoRA,那是"少训点参数"的路线;这篇是"多卡一起训"的路线,两者互补。集群怎么调度 GPU 见K8s GPU 调度。

LLM 推理引擎选型:vLLM、SGLang、TensorRT-LLM、TGI 到底怎么选?

· 阅读需 8 分钟

推理引擎选型是 LLM 服务化里最容易被低估的架构决策。同样的模型、同样的 GPU,换一个引擎吞吐可能差 2-3 倍;但反过来,厂商 benchmark 里好看的引擎,落到你的真实负载上可能反而更慢。这篇文章把 vLLM、SGLang、TensorRT-LLM、TGI 四个主流引擎放在一起对比,从调度机制、前缀缓存、结构化输出、量化、MoE 支持五个维度拆,最后给选型决策表和生产踩坑记录。

Token 即金钱:LLM 用量计量与计费系统的工程实践

· 阅读需 11 分钟

聊 LLM 应用的工程,大家津津乐道的是推理优化、RAG、Agent 编排,但真正决定一个 ToB/ToC 产品能不能赚钱的,往往是没人愿意写的计量与计费。模型调用不像 MySQL 查询——没有免费的 EXPLAIN,每一次 API 调用都是真金白银的流出;而一旦你开始对用户收费、对开发者分成,计费就从「省钱工具」升级成「信任地基」:计量错了,不是亏钱就是信任崩塌。

这篇文章从后端工程师视角,把「一次模型调用 → 一条 usage 事件 → 一张账单 → 一笔分账」的完整链路拆开:usage 从哪来、按什么价、怎么记、怎么算、怎么对账。基于真实平台(以 DeepSeek 2026-08 官方计价为例)的实操经验,有 schema、有代码、有坑。