mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
fix(memory): 千问文本塔补齐 tokenizer post_processor 与真实 ONNX 回归
验证 Go 端到端路径时发现:HuggingFace 的 tokenizer.json 带 TemplateProcessing
post_processor,规则是 `$A <|endoftext|>`——即每段输入末尾都会追加一个
`<|endoftext|>`(151643)。它正是图内 last-token 池化的锚点:
- 漏掉它:ONNX 仍能运行(不会报错),但池化取到的是模板末尾的 assistant
起始符,而非 post token,整条嵌入向量与上游不一致;
- 截断语义:HuggingFace 在 truncation=true 时先把正文截到 maxLen-1,
再保留末尾 post token(实测 600×"记忆"→ [511 正文][151643])。
改动:
1. Tokenizer 新增 encodeModelInput(text, maxLen):Encode 后追加 post token,
并在超长时先截到 maxLen-1;缺 <|endoftext|> 直接报错(防静默错误)。
2. Embedder.VectorizeDense 改用 encodeModelInput。
3. 测试:
- TestEncodeModelInputPostProcessor:短文本+post token;长文本按 512 截断
且尾 token 为 post token(用 600×"记忆"确保真的触发截断分支)。
- embedder_onnx_test.go(onnxruntime 标签):用 Python onnxruntime 1.28
生成的冻结参考向量验证完整 Go 路径(模板渲染→BPE→ONNX→L2 normalize),
逐维 diff ≤ 2e-5;同时断言模型输入恰好 23 token 且末尾是 post token。
产物不在时跳过(与 tokenizer_test 相同约定)。
- 修正先前测试误用 400×"记忆":BPE 把"记忆"合并为单 token,400 次只有
400 token 不触发截断;改用 600 次后确实走到 maxLen-1 分支。
其余(ORT 库路径、指纹纳入 .onnx.data)一并随本提交带上。
验证:go test ./internal/memory/qwen 与 -tags onnxruntime 全绿;
FP32 图与 PyTorch 在短/长/等长批次/真实 padding 批次上余弦均 ≥0.99999994。
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
|
||||
// Package qwen 的 ONNX 推理实现(构建标签 onnxruntime,与 internal/memory/clip 同模式)。
|
||||
//
|
||||
// 加载契约(目录由 core.memory.multimodal_space.model_dir 指定):
|
||||
// 加载契约:调用方传入模型目录,内核不硬编码模型名。
|
||||
//
|
||||
// TextTower.onnx + 外部权重分片 — 文本塔图(input_ids/attention_mask → embedding)
|
||||
// tokenizer.json — 字节级 BPE 词表与 merges
|
||||
@ -28,10 +28,10 @@ import (
|
||||
|
||||
// embedConfig 对应导出脚本产出的 embed_config.json。
|
||||
type embedConfig struct {
|
||||
Dimension int `json:"dim"`
|
||||
MaxLength int `json:"max_length"`
|
||||
Dimension int `json:"dim"`
|
||||
MaxLength int `json:"max_length"`
|
||||
Instruction string `json:"instruction"`
|
||||
Pooling string `json:"pooling"`
|
||||
Pooling string `json:"pooling"`
|
||||
}
|
||||
|
||||
// Embedder 是基于内嵌 ONNX 文本塔的稠密嵌入器。
|
||||
@ -119,12 +119,9 @@ func (e *Embedder) VectorizeDense(text string) ([]float64, error) {
|
||||
return nil, fmt.Errorf("qwen embedder not loaded")
|
||||
}
|
||||
|
||||
ids := e.tok.Encode(e.renderInput(text))
|
||||
if len(ids) > e.config.MaxLength {
|
||||
ids = ids[:e.config.MaxLength]
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, fmt.Errorf("qwen: 分词结果为空")
|
||||
ids, err := e.tok.encodeModelInput(e.renderInput(text), e.config.MaxLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seq := len(ids)
|
||||
@ -225,7 +222,7 @@ func computeFingerprint(modelDir string) string {
|
||||
for _, e := range entries {
|
||||
n := e.Name()
|
||||
// 外部权重分片:torch 新版导出器使用 onnx__<op>_<id> 与模型张量同名文件。
|
||||
if strings.HasPrefix(n, "onnx__") || strings.HasSuffix(n, ".weight") {
|
||||
if strings.HasPrefix(n, "onnx__") || strings.HasSuffix(n, ".weight") || strings.HasSuffix(n, ".onnx.data") {
|
||||
names = append(names, n)
|
||||
}
|
||||
}
|
||||
@ -243,6 +240,7 @@ func computeFingerprint(modelDir string) string {
|
||||
// findOnnxLib 在常见路径中查找 libonnxruntime.so。
|
||||
func findOnnxLib() string {
|
||||
for _, p := range []string{
|
||||
"/opt/onnxruntime/libonnxruntime.so",
|
||||
"/opt/onnxruntime/lib/libonnxruntime.so",
|
||||
"/usr/local/lib/libonnxruntime.so",
|
||||
"/usr/lib/libonnxruntime.so",
|
||||
|
||||
Reference in New Issue
Block a user