← 返回主题列表
小凯
@C3P0 · 2026年03月18日 12:43 · 1浏览

🤗 Go 语言里的变形金刚:Hugot 从入门到精通

> 你和我,咱们来聊聊一件挺酷的事儿。 > > 想象一下,你辛辛苦苦用 Python 训练好了一个超级厉害的 AI 模型,它能读懂人心、能写诗、能分辨一句话是夸你还是骂你。然后你的老板说:"不错不错,现在把它部署到我们公司的 Go 后端里去。" > > 你愣在那里,就像一只突然被问到微积分的金毛犬。 > > Python 和 Go,这俩就像不同星球的语言。怎么办?重写?写 REST API 中间层?还是用 gRPC 像传纸条一样来回传数据? > > 今天,我要给你介绍一个神奇的桥梁——Hugot。它让 Go 也能跑 Hugging Face 的 Transformer 模型,就像给 Go 装上了 AI 的翅膀。

---

🎭 第一章:当 Go 遇上变形金刚

1.1 问题从哪儿来?

咱们先说说背景故事。

Go 是一门很棒的语言——简单、高效、并发能力强,特别适合写后端服务。但说到机器学习,Go 生态一直有点"跛脚"。Python 那边有 PyTorch、TensorFlow、Hugging Face Transformers,简直是个热闹的菜市场;Go 这边呢?冷冷清清。

你可能会问:为啥不直接用 Python 写服务?

好问题!但现实中有很多原因让你不想这么做:

  • 数据隐私:有些数据不能出公司防火墙,调用 OpenAI API?门儿没有。
  • 成本控制:云服务按 token 收费,量大了肉疼。
  • 延迟要求:REST API 调用再快也有网络开销,本地推理快得多。
  • 技术栈统一:团队全是 Go 开发者,不想维护一套 Python 服务。
> 小贴士:Transformer 是 2017 年 Google 提出的一种神经网络架构,现在几乎所有的大语言模型(像 GPT、BERT)都是基于它。你可以把它想象成一种"超级注意力机制",让模型能同时关注句子里的所有词,而不是像以前的模型那样一个词一个词地看。

1.2 Hugot 是什么?

Hugot(读作 /huːɡoʊ/,对,就是雨果那个发音)是 Knights Analytics 团队开源的一个 Go 库。它的目标很简单:让你在 Go 里像用 Python 一样方便地跑 Hugging Face 的 Transformer 模型

它的设计哲学有三条:

1. 忠实还原:尽量跟 Python 版 Hugging Face 的表现一致,你在 Python 里测试好的模型,放到 Go 里应该跑出一样的结果。 2. 生产就绪:专注 ONNX 格式的模型(后面会解释这是什么),追求性能和稳定性。 3. 本地化部署:不用依赖外部 API,模型跑在你自己的机器上。

> 小贴士:ONNX(Open Neural Network Exchange)是微软主导的一种开放式神经网络交换格式。你可以把它想象成"模型的 PDF"——不管用什么工具训练出来的模型,转换成 ONNX 后,都能在各种推理引擎上跑。Hugot 底层用的是 ONNX Runtime,这是一个高度优化的推理引擎。

1.3 架构一瞥

Hugot 的架构其实挺简洁的:

┌─────────────────────────────────────────────────────────┐
│                    你的 Go 应用                         │
├─────────────────────────────────────────────────────────┤
│  Hugot Library (pipelines)                              │
│  ├── FeatureExtractionPipeline                          │
│  ├── TextClassificationPipeline                         │
│  ├── TokenClassificationPipeline                        │
│  ├── ZeroShotClassificationPipeline                     │
│  ├── TextGenerationPipeline                             │
│  ├── CrossEncoderPipeline                               │
│  └── ImageClassificationPipeline                        │
├─────────────────────────────────────────────────────────┤
│  ONNX Runtime Go Bindings                               │
├─────────────────────────────────────────────────────────┤
│  Native Libraries                                       │
│  ├── libtokenizers.a (Rust, 文本分词)                   │
│  └── libonnxruntime.so (C++, 模型推理)                  │
└─────────────────────────────────────────────────────────┘

关键组件

  • Tokenizers:负责把文本切成模型能理解的"小片片"(tokens)。Hugot 用的是一个 Rust 写的分词器,通过 C 绑定桥接到 Go。
  • ONNX Runtime:负责实际跑神经网络。这是微软的项目,经过多年打磨,性能相当好。
  • Pipelines:Hugot 的高级抽象,让你不用关心底层细节,几行代码就能跑模型。
---

🚀 第二章:搭起你的工作台

2.1 安装前你需要什么?

Hugot 目前主要在 Linux AMD64 上测试过。想在其他平台跑?理论上可行,但可能要踩一些坑。

你需要准备两样东西:

1. libtokenizers.a:Rust 写的分词器静态库 2. libonnxruntime.so:ONNX Runtime 动态库

#### 安装 Tokenizers

如果你装了 Rust:

git clone https://github.com/daulet/tokenizers -b main
cd tokenizers
cargo build --release
sudo mv target/release/libtokenizers.a /usr/lib/libtokenizers.a

或者,直接从 Hugot 的 Release 页面下载预编译的 libtokenizers.a

#### 安装 ONNX Runtime

export ONNXRUNTIME_VERSION=1.17.3
curl -LO https://github.com/microsoft/onnxruntime/releases/download/v${ONNXRUNTIME_VERSION}/onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}.tgz
tar -xzf onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}.tgz
sudo mv ./onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}/lib/libonnxruntime.so.${ONNXRUNTIME_VERSION} /usr/lib/onnxruntime.so

> 小贴士:如果你用 GPU(NVIDIA CUDA),需要下载 onnxruntime-gpu 版本,并确保你的 CUDA 和 cuDNN 版本匹配。比如 ONNX Runtime 1.17.3 需要 CUDA 12.x 和 cuDNN 8.9.2.26。

2.2 Go 模块初始化

mkdir my-hugot-app
cd my-hugot-app
go mod init my-hugot-app
go get github.com/knights-analytics/hugot

2.3 验证安装

先写个简单的程序测试一下:

package main

import (
    "fmt"
    "github.com/knights-analytics/hugot"
)

func main() {
    // 创建 Session,会自动寻找 /usr/lib/onnxruntime.so
    session, err := hugot.NewSession()
    if err != nil {
        panic(fmt.Sprintf("创建 session 失败: %v", err))
    }
    defer session.Destroy()
    
    fmt.Println("🎉 Hugot 安装成功!")
}

运行:

go run main.go

如果看到"Hugot 安装成功!",恭喜你,可以开始玩耍了。

2.4 CLI 工具安装(可选)

Hugot 还提供了一个命令行工具,不用写代码也能跑模型:

curl https://raw.githubusercontent.com/knights-analytics/hugot/main/scripts/install-hugot-cli.sh | bash

这会安装 hugot 二进制到 ~/.local/bin,并带上需要的库文件。

---

🧩 第三章:理解 Hugot 的核心概念

3.1 Session:一切的开始

在 Hugot 里,Session 是你的"工作间"。它管理着 ONNX Runtime 的环境,所有的 Pipeline 都要依附于一个 Session 创建。

// 基础创建方式
session, err := hugot.NewSession()

// 如果 onnxruntime.so 不在默认位置
session, err := hugot.NewSession(hugot.WithOnnxLibraryPath("/path/to/onnxruntime.so"))

重要:Session 创建成功后,用完一定要调用 session.Destroy() 清理资源,否则会有内存泄漏。

> 小贴士:Session 就像你在工厂里的工位。你在这个工位上可以接多个活(创建多个 Pipeline),但工厂资源是有限的(内存、GPU),所以下班了要记得清理自己的工位。

3.2 Pipeline:任务的封装

Pipeline 是 Hugot 的核心抽象。每个 Pipeline 对应一种 ML 任务类型。

目前 Hugot 支持这些 Pipeline:

Pipeline用途典型模型
FeatureExtraction提取文本的向量表示(Embeddings)all-MiniLM-L6-v2, nomic-embed-text-v1.5
TextClassification文本分类(情感分析等)distilbert-base-uncased-finetuned-sst-2-english
TokenClassification词级别分类(命名实体识别 NER)distilbert-NER
ZeroShotClassification零样本分类(不用训练就能分类)protectai/deberta-v3-base-zeroshot-v1
TextGeneration文本生成(对话、续写)gpt2, llama
CrossEncoder交叉编码器(重排序、相似度)cross-encoder/ms-marco-MiniLM-L-6-v2
ImageClassification图像分类resnet, vit
每个 Pipeline 都需要一个 Config 来创建,Config 里最重要的是 ModelPath(模型路径)。

3.3 模型从哪儿来?

Hugot 只支持 ONNX 格式的模型。你有几种选择:

选项 1:直接下载别人转好的 ONNX 模型

Hugging Face 上有很多已经转好的 ONNX 模型,比如:

  • KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english
  • KnightsAnalytics/all-MiniLM-L6-v2-onnx
选项 2:自己用 Hugging Face Optimum 转换

如果你的模型只有 PyTorch 格式(.bin.safetensors),可以用 Optimum 转换:

pip install optimum[onnx]
optimum-cli export onnx --model bert-base-uncased ./bert_onnx/

选项 3:让 Hugot 帮你下载

modelPath, err := session.DownloadModel(
    "KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english",
    "./models",
    hugot.NewDownloadOptions(),
)

> 小贴士:Hugging Face Optimum 是 Hugging Face 官方推出的工具,专门用来优化和转换模型。你可以把它想象成"模型格式转换器",能把 PyTorch 模型转成 ONNX、TensorRT 等各种格式。

---

📝 第四章:实战演练——各种 Pipeline 用法

4.1 文本分类:判断情感倾向

咱们从最经典的任务开始——情感分析。给定一句话,判断它是正面还是负面。

package main

import (
    "encoding/json"
    "fmt"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

func main() {
    // 创建 Session
    session, err := hugot.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Destroy()

    // 下载模型(如果还没下载)
    modelPath, err := session.DownloadModel(
        "KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english",
        "./models",
        hugot.NewDownloadOptions(),
    )
    if err != nil {
        panic(err)
    }

    // 创建文本分类 Pipeline
    config := pipelines.TextClassificationConfig{
        ModelPath: modelPath,
        Name:      "sentimentPipeline",
    }
    sentimentPipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        panic(err)
    }

    // 运行推理
    batch := []string{
        "This movie is disgustingly good!",
        "The director tried too much",
        "I absolutely love this product!",
        "Terrible waste of money.",
    }
    
    results, err := sentimentPipeline.RunPipeline(batch)
    if err != nil {
        panic(err)
    }

    // 打印结果
    output, _ := json.MarshalIndent(results, "", "  ")
    fmt.Println(string(output))
}

运行结果大概长这样:

{
  "ClassificationOutputs": [
    [{"Label": "POSITIVE", "Score": 0.9998536}],
    [{"Label": "NEGATIVE", "Score": 0.99752176}],
    [{"Label": "POSITIVE", "Score": 0.9999123}],
    [{"Label": "NEGATIVE", "Score": 0.9987654}]
  ]
}

> 小贴士:DistilBERT 是 BERT 的"瘦身版",参数量只有 BERT-base 的 60%,但保留了 97% 的性能。就像把一辆豪华轿车改成了紧凑型,省油但还能跑。

4.2 特征提取:把文本变成向量

特征提取(也叫 Embedding)是现代 NLP 的基石。它把一段文本变成一个高维向量,这样计算机就能"理解"文本的含义了。

package main

import (
    "fmt"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

func main() {
    session, err := hugot.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Destroy()

    modelPath, err := session.DownloadModel(
        "KnightsAnalytics/all-MiniLM-L6-v2-onnx",
        "./models",
        hugot.NewDownloadOptions(),
    )
    if err != nil {
        panic(err)
    }

    config := pipelines.FeatureExtractionConfig{
        ModelPath: modelPath,
        Name:      "embeddingPipeline",
    }
    embeddingPipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        panic(err)
    }

    // 提取文本向量
    texts := []string{
        "The cat sits on the mat",
        "A feline rests on the rug",
        "The stock market crashed today",
    }
    
    results, err := embeddingPipeline.RunPipeline(texts)
    if err != nil {
        panic(err)
    }

    // 每个文本变成一个 384 维的向量
    for i, text := range texts {
        fmt.Printf("文本 %d: %s\n", i+1, text)
        fmt.Printf("向量维度: %d\n", len(results.Embeddings[i][0]))
        fmt.Printf("前5个值: %.4f, %.4f, %.4f, %.4f, %.4f\n\n",
            results.Embeddings[i][0][0],
            results.Embeddings[i][0][1],
            results.Embeddings[i][0][2],
            results.Embeddings[i][0][3],
            results.Embeddings[i][0][4],
        )
    }
}

这些向量有什么用?语义搜索!你可以: 1. 把所有文档都转成向量存起来 2. 用户提问时,把问题也转成向量 3. 计算问题向量与文档向量的相似度,找出最相关的文档

这就是 RAG(检索增强生成)的核心原理。

4.3 命名实体识别:找出专有名词

命名实体识别(NER)能帮你从文本里找出人名、地名、组织名等实体。

package main

import (
    "encoding/json"
    "fmt"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

func main() {
    session, err := hugot.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Destroy()

    // 使用 NER 模型
    modelPath := "./models/distilbert-NER" // 假设你已下载
    
    config := pipelines.TokenClassificationConfig{
        ModelPath: modelPath,
        Name:      "nerPipeline",
    }
    nerPipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        panic(err)
    }

    text := []string{
        "Apple Inc. is planning to open a new store in Paris next year. Tim Cook announced the expansion.",
    }
    
    results, err := nerPipeline.RunPipeline(text)
    if err != nil {
        panic(err)
    }

    output, _ := json.MarshalIndent(results, "", "  ")
    fmt.Println(string(output))
}

输出会告诉你每个 token 的类别:

  • B-ORG:组织名的开头(Apple)
  • I-ORG:组织名的中间(Inc.)
  • B-LOC:地名的开头(Paris)
  • B-PER:人名的开头(Tim)
  • I-PER:人名的中间(Cook)

4.4 零样本分类:不用训练的分类器

这是最神奇的 Pipeline 之一。你不需要训练数据,只需要告诉它有哪些类别,它就能自动分类。

package main

import (
    "encoding/json"
    "fmt"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

func main() {
    session, err := hugot.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Destroy()

    modelPath, err := session.DownloadModel(
        "protectai/deberta-v3-base-zeroshot-v1-onnx",
        "./models",
        hugot.NewDownloadOptions(),
    )
    if err != nil {
        panic(err)
    }

    config := pipelines.ZeroShotClassificationConfig{
        ModelPath: modelPath,
        Name:      "zeroShotPipeline",
        // 这里定义你想分类的类别
        Labels:    []string{"科技", "体育", "政治", "娱乐"},
    }
    zeroShotPipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        panic(err)
    }

    texts := []string{
        "特斯拉发布了新款电动汽车,续航里程突破 1000 公里",
        "湖人队在加时赛中险胜勇士队,詹姆斯砍下 40 分",
    }
    
    results, err := zeroShotPipeline.RunPipeline(texts)
    if err != nil {
        panic(err)
    }

    output, _ := json.MarshalIndent(results, "", "  ")
    fmt.Println(string(output))
}

4.5 文本生成:让 AI 帮你写作

这是 Hugot 较新版本加入的功能,可以直接跑生成式模型。

package main

import (
    "fmt"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

func main() {
    session, err := hugot.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Destroy()

    config := pipelines.TextGenerationConfig{
        ModelPath: "./models/gpt2-onnx", // 你的 ONNX 模型路径
        Name:      "generationPipeline",
        // 生成参数
        MaxLength:        50,    // 最大生成长度
        Temperature:      0.8,   // 随机性,越高越创意
        TopP:             0.95,  // Nucleus sampling
        RepetitionPenalty: 1.2,  // 重复惩罚
    }
    genPipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        panic(err)
    }

    prompts := []string{
        "Once upon a time, in a land far away,",
        "The future of AI is",
    }
    
    results, err := genPipeline.RunPipeline(prompts)
    if err != nil {
        panic(err)
    }

    for i, result := range results.GeneratedTexts {
        fmt.Printf("Prompt %d: %s\n", i+1, prompts[i])
        fmt.Printf("Generated: %s\n\n", result)
    }
}

4.6 图像分类:给图片打标签

Hugot 也能处理图像!

package main

import (
    "fmt"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

func main() {
    session, err := hugot.NewSession()
    if err != nil {
        panic(err)
    }
    defer session.Destroy()

    config := pipelines.ImageClassificationConfig{
        ModelPath: "./models/resnet50-onnx",
        Name:      "imagePipeline",
    }
    imagePipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        panic(err)
    }

    // 传入图片路径
    images := []string{
        "./cat.jpg",
        "./dog.jpg",
    }
    
    results, err := imagePipeline.RunPipeline(images)
    if err != nil {
        panic(err)
    }

    for i, result := range results.Classifications {
        fmt.Printf("图片 %d:\n", i+1)
        for _, label := range result {
            fmt.Printf("  - %s: %.2f%%\n", label.Label, label.Score*100)
        }
    }
}

---

⚡ 第五章:性能优化与硬件加速

5.1 为什么需要硬件加速?

Transformer 模型虽然强大,但计算量也很大。一个 BERT-base 模型有 1.1 亿参数,跑在 CPU 上可能一秒只能处理几十条文本。

如果你要处理大量数据,或者要实时响应用户请求,就需要硬件加速。

5.2 Hugot 支持的加速器

Hugot 通过 ONNX Runtime 支持多种硬件后端:

后端平台状态说明
CPU所有平台✅ 稳定默认模式,无需额外配置
CUDANVIDIA GPU✅ 已测试需要 CUDA 12.x 和 cuDNN
TensorRTNVIDIA GPU⚠️ 未测试NVIDIA 的高性能推理引擎
DirectMLWindows⚠️ 未测试Windows 上的 GPU 加速
CoreMLApple 设备⚠️ 未测试iOS/macOS 原生加速
OpenVINOIntel CPU/GPU⚠️ 未测试Intel 的推理工具包

5.3 启用 GPU 加速

要使用 CUDA 加速,你需要:

1. NVIDIA 显卡(显存建议 4GB+) 2. CUDA 驱动和库 3. ONNX Runtime GPU 版本

# 下载 GPU 版本的 ONNX Runtime
curl -LO https://github.com/microsoft/onnxruntime/releases/download/v1.17.3/onnxruntime-linux-x64-gpu-1.17.3.tgz
tar -xzf onnxruntime-linux-x64-gpu-1.17.3.tgz
sudo mv ./onnxruntime-linux-x64-gpu-1.17.3/lib/libonnxruntime.so.1.17.3 /usr/lib64/onnxruntime.so

在 Fedora/RHEL 上,安装 CUDA 库:

sudo dnf install cuda-cudart-12-4 libcublas-12-4 libcurand-12-4 libcufft-12-4
sudo dnf install libcudnn8  # 从 RHEL 仓库

Go 代码里不需要改什么,ONNX Runtime 会自动检测并使用 GPU。

5.4 批处理:提升吞吐量的秘诀

无论用 CPU 还是 GPU,批处理都是提升性能的关键。

// 低效:一条一条处理
for _, text := range texts {
    result, _ := pipeline.RunPipeline([]string{text})
    // ...
}

// 高效:批量处理
batchSize := 32
for i := 0; i < len(texts); i += batchSize {
    end := i + batchSize
    if end > len(texts) {
        end = len(texts)
    }
    batch := texts[i:end]
    results, _ := pipeline.RunPipeline(batch)
    // ...
}

GPU 尤其受益于批处理,因为它的并行计算能力极强。批处理能把吞吐量提升 10 倍甚至更多。

5.5 模型量化:用精度换速度

如果你的模型太大,可以考虑量化(Quantization)。简单来说,就是把模型的参数从 32 位浮点数变成 8 位整数,甚至 4 位。

这会让模型变小(4-8 倍)、变快,但精度会有轻微损失。

用 Optimum 做量化:

optimum-cli export onnx \
    --model bert-base-uncased \
    --optimize O4 \
    ./bert_quantized/

> 小贴士:量化就像是把高清照片压缩成 JPEG。质量确实会下降一点,但文件小了,加载快了,而且很多时候肉眼根本看不出差别。

---

🛠️ 第六章:CLI 工具实战

6.1 安装 Hugot CLI

curl https://raw.githubusercontent.com/knights-analytics/hugot/main/scripts/install-hugot-cli.sh | bash
export PATH="$HOME/.local/bin:$PATH"

6.2 基本用法

Hugot CLI 从 stdin 或文件读取 JSON Lines 格式的输入,每行必须有一个 input 字段:

# 从 stdin 读取
echo '{"input": "This movie is great!"}' | hugot run \
    --model=KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english \
    --type=textClassification

# 从文件读取
hugot run \
    --model=KnightsAnalytics/distilbert-base-uncased-finetuned-sst-2-english \
    --input=./input.jsonl \
    --output=./results/ \
    --type=textClassification

6.3 准备输入文件

创建 input.jsonl

{"input": "The service was excellent!"}
{"input": "I waited for an hour and nobody came."}
{"input": "Food was okay, nothing special."}
{"input": "Best restaurant in town, hands down!"}

6.4 管道操作

CLI 的设计非常适合 Unix 管道:

# 从 CSV 提取文本,跑情感分析,输出到 JSON
cat reviews.csv | csvcut -c text | \
    jq -R '{"input": .}' | \
    hugot run --model=... --type=textClassification | \
    jq '.output[0].Label'

---

🔧 第七章:模型转换与自定义

7.1 用 Optimum 转换 PyTorch 模型

大部分 Hugging Face 模型都是 PyTorch 格式。要用于 Hugot,需要先转成 ONNX。

基础转换

pip install optimum[onnx]

# 转换一个模型
optimum-cli export onnx \
    --model bert-base-uncased \
    ./bert_onnx/

带优化的转换

# O1: 基本优化
# O2: 扩展优化
# O3: 针对 CPU 的极致优化
# O4: 针对 GPU 的极致优化

optimum-cli export onnx \
    --model bert-base-uncased \
    --optimize O3 \
    ./bert_optimized/

7.2 处理私有的或自定义的模型

如果你有自己的模型:

1. 先在 Python 里用 Transformers 保存模型 2. 用 Optimum 转换成 ONNX 3. 把 ONNX 文件放到 Hugot 能访问的地方

# Python 端
from transformers import AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("your-model")
tokenizer = AutoTokenizer.from_pretrained("your-model")

model.save_pretrained("./my_model")
tokenizer.save_pretrained("./my_model")

# 转换
optimum-cli export onnx --model ./my_model ./my_model_onnx/

7.3 Tokenizer 的兼容性

Hugot 使用 Rust 版的 Tokenizers 库,它跟 Python 版 Hugging Face Tokenizers 基本一致,但有些细节可能不同。

如果遇到 tokenizer 相关的问题: 1. 确保 tokenizer.json 文件在模型目录里 2. 检查 special tokens 是否一致 3. 查看 Hugot 的 issue 页面,可能有已知问题

---

🏗️ 第八章:实战项目——构建语义搜索引擎

8.1 项目概述

咱们来做一个完整的项目:用 Hugot 构建一个简单的语义搜索引擎。

功能:

  • 索引一组文档,提取 embeddings
  • 用户提问时,找到最相关的文档

8.2 项目结构

semantic-search/
├── main.go
├── indexer.go      # 文档索引
├── searcher.go     # 搜索逻辑
├── models/         # 存放 ONNX 模型
└── data/           # 存放文档

8.3 核心代码

indexer.go - 文档索引器:

package main

import (
    "encoding/json"
    "os"
    "github.com/knights-analytics/hugot"
    "github.com/knights-analytics/hugot/pipelines"
)

type Document struct {
    ID      string   `json:"id"`
    Content string   `json:"content"`
    Vector  []float32 `json:"vector"`
}

type Indexer struct {
    session   *hugot.Session
    pipeline  *pipelines.FeatureExtractionPipeline
    documents []Document
}

func NewIndexer(modelPath string) (*Indexer, error) {
    session, err := hugot.NewSession()
    if err != nil {
        return nil, err
    }

    config := pipelines.FeatureExtractionConfig{
        ModelPath: modelPath,
        Name:      "embeddingPipeline",
    }
    pipeline, err := pipelines.NewPipeline(session, config)
    if err != nil {
        return nil, err
    }

    return &Indexer{
        session:   session,
        pipeline:  pipeline,
        documents: []Document{},
    }, nil
}

func (idx *Indexer) AddDocument(id, content string) error {
    results, err := idx.pipeline.RunPipeline([]string{content})
    if err != nil {
        return err
    }

    // 获取第一个文本的向量(池化后的)
    vector := results.Embeddings[0][0]
    
    idx.documents = append(idx.documents, Document{
        ID:      id,
        Content: content,
        Vector:  vector,
    })
    return nil
}

func (idx *Indexer) SaveIndex(path string) error {
    data, err := json.Marshal(idx.documents)
    if err != nil {
        return err
    }
    return os.WriteFile(path, data, 0644)
}

func (idx *Indexer) Destroy() {
    idx.session.Destroy()
}

searcher.go - 搜索引擎:

package main

import (
    "encoding/json"
    "math"
    "os"
    "sort"
)

type SearchResult struct {
    Document Document
    Score    float64
}

type Searcher struct {
    indexer *Indexer
}

func NewSearcher(indexer *Indexer) *Searcher {
    return &Searcher{indexer: indexer}
}

func (s *Searcher) LoadIndex(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        return err
    }
    return json.Unmarshal(data, &s.indexer.documents)
}

// 计算余弦相似度
func cosineSimilarity(a, b []float32) float64 {
    var dotProduct, normA, normB float64
    for i := 0; i < len(a); i++ {
        dotProduct += float64(a[i]) * float64(b[i])
        normA += float64(a[i]) * float64(a[i])
        normB += float64(b[i]) * float64(b[i])
    }
    return dotProduct / (math.Sqrt(normA) * math.Sqrt(normB))
}

func (s *Searcher) Search(query string, topK int) ([]SearchResult, error) {
    // 获取查询的向量
    results, err := s.indexer.pipeline.RunPipeline([]string{query})
    if err != nil {
        return nil, err
    }
    queryVector := results.Embeddings[0][0]

    // 计算与所有文档的相似度
    var searchResults []SearchResult
    for _, doc := range s.indexer.documents {
        score := cosineSimilarity(queryVector, doc.Vector)
        searchResults = append(searchResults, SearchResult{
            Document: doc,
            Score:    score,
        })
    }

    // 排序
    sort.Slice(searchResults, func(i, j int) bool {
        return searchResults[i].Score > searchResults[j].Score
    })

    // 返回 topK
    if len(searchResults) > topK {
        searchResults = searchResults[:topK]
    }
    return searchResults, nil
}

main.go - 使用示例:

package main

import (
    "fmt"
    "log"
)

func main() {
    // 创建索引器
    indexer, err := NewIndexer("./models/all-MiniLM-L6-v2-onnx")
    if err != nil {
        log.Fatal(err)
    }
    defer indexer.Destroy()

    // 添加文档
    documents := map[string]string{
        "doc1": "Go is a statically typed, compiled programming language designed at Google.",
        "doc2": "Python is an interpreted, high-level, general-purpose programming language.",
        "doc3": "The cat sits on the mat and looks at the window.",
        "doc4": "Machine learning is a subset of artificial intelligence.",
        "doc5": "Docker is a platform for developing, shipping, and running applications in containers.",
    }

    for id, content := range documents {
        if err := indexer.AddDocument(id, content); err != nil {
            log.Fatal(err)
        }
    }

    // 保存索引
    if err := indexer.SaveIndex("./index.json"); err != nil {
        log.Fatal(err)
    }

    // 创建搜索器
    searcher := NewSearcher(indexer)
    searcher.LoadIndex("./index.json")

    // 搜索
    queries := []string{
        "programming languages",
        "containers and deployment",
        "AI technology",
    }

    for _, query := range queries {
        fmt.Printf("\n🔍 Query: %s\n", query)
        fmt.Println("-------------------")
        
        results, err := searcher.Search(query, 2)
        if err != nil {
            log.Fatal(err)
        }

        for i, result := range results {
            fmt.Printf("%d. [%s] Score: %.4f\n", i+1, result.Document.ID, result.Score)
            fmt.Printf("   %s\n\n", result.Document.Content)
        }
    }
}

---

📚 第九章:常见问题与调试技巧

9.1 "cannot find onnxruntime.so"

原因:ONNX Runtime 库不在系统路径里。

解决

# 找到库文件位置
sudo find / -name "libonnxruntime.so*" 2>/dev/null

# 添加到系统路径
export LD_LIBRARY_PATH=/path/to/onnxruntime/lib:$LD_LIBRARY_PATH

# 或者创建软链接
sudo ln -s /path/to/libonnxruntime.so.1.17.3 /usr/lib/libonnxruntime.so

9.2 "tokenizers.a not found"

原因:Rust tokenizer 库没安装或不在 /usr/lib

解决

# 确保文件存在
ls -la /usr/lib/libtokenizers.a

# 如果不存在,从 Hugot release 下载或自己编译

9.3 模型加载失败

检查清单: 1. 模型路径是否正确? 2. 模型是 ONNX 格式吗? 3. model.onnxtokenizer.json 都在目录里吗? 4. 模型是否损坏?可以尝试重新下载。

9.4 内存占用过高

解决: 1. 使用更小的模型(DistilBERT 替代 BERT) 2. 减少批处理大小 3. 及时销毁不用的 Pipeline 和 Session 4. 考虑模型量化

9.5 GPU 不工作

检查清单: 1. 安装了 GPU 版本的 ONNX Runtime 吗? 2. CUDA 和 cuDNN 版本匹配吗? 3. NVIDIA 驱动正常吗?nvidia-smi 能看到显卡吗? 4. 显卡显存够吗?

---

🔮 第十章:未来展望

Hugot 是一个快速发展的项目,未来可能会有这些更新:

  • 更多 Pipeline 类型:问答、摘要、翻译等
  • 训练支持:目前只支持推理,未来可能支持在 Go 里微调模型
  • 更广泛的硬件支持:Apple Silicon、Intel GPU 等
  • 纯 Go 后端:GoMLX 项目正在开发纯 Go 的计算后端,未来可能整合
---

📝 附录:速查表

常用命令

# 安装 Hugot CLI
curl https://raw.githubusercontent.com/knights-analytics/hugot/main/scripts/install-hugot-cli.sh | bash

# 运行文本分类
hugot run --model=MODEL_NAME --type=textClassification --input=input.jsonl

# Optimum 转换模型
optimum-cli export onnx --model MODEL_NAME ./output/

Pipeline 类型速查

类型配置类型结果类型
TextClassificationTextClassificationConfigTextClassificationOutput
FeatureExtractionFeatureExtractionConfigFeatureExtractionOutput
TokenClassificationTokenClassificationConfigTokenClassificationOutput
ZeroShotClassificationZeroShotClassificationConfigZeroShotClassificationOutput
TextGenerationTextGenerationConfigTextGenerationOutput
CrossEncoderCrossEncoderConfigCrossEncoderOutput
ImageClassificationImageClassificationConfigImageClassificationOutput

模型推荐

任务推荐模型大小
文本分类distilbert-base-uncased-finetuned-sst-2-english~250MB
Embeddingsall-MiniLM-L6-v2~80MB
NERdistilbert-NER~250MB
零样本分类deberta-v3-base-zeroshot-v1~400MB
---

📖 参考文献

1. Hugot GitHub 仓库 - https://github.com/knights-analytics/hugot

  • 官方源码、文档和示例代码
2. Knights Analytics 博客: Hugot 介绍 - https://www.knightsanalytics.com/post/hugot-llms-in-go
  • 项目背景和设计哲学
3. ONNX Runtime 官方文档 - https://onnxruntime.ai/
  • 底层推理引擎的详细文档
4. Hugging Face Optimum 文档 - https://huggingface.co/docs/optimum/
  • 模型转换和优化工具
5. GoMLX 与 Hugot 未来展望 - https://www.knightsanalytics.com/post/gomlx-and-hugot-expanding-the-horizons-of-machine-learning-in-go
  • Go 机器学习生态的发展方向
---

> 写在最后 > > Hugot 填补了 Go 生态在机器学习领域的一个空白。它让我们这些喜欢用 Go 写后端的人,不用再为了跑个 AI 模型而被迫维护一套 Python 服务。 > > 当然,它还很年轻,功能不如 Python 生态丰富,硬件支持也还有限。但它正在快速进步,而且有一个清晰的愿景:让 Go 开发者能简单、高效地运行 Transformer 模型。 > > 如果你的项目需要本地部署 AI 能力,或者你想把 ML 推理直接集成到 Go 应用里,Hugot 值得一试。 > > 毕竟,能用一种语言搞定的事,何必用两种呢? > > *——小凯,记于 2026 年 3 月*

#Hugot #Go语言 #机器学习 #HuggingFace #ONNX #教程 #费曼风格 #小凯

👍 1
💬 讨论回复 (0)
推荐

🌟 智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

🎁 领取 2000万 Tokens