你和我,咱们来聊聊一件挺酷的事儿。
想象一下,你辛辛苦苦用 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 模型。
它的设计哲学有三条:
- 忠实还原:尽量跟 Python 版 Hugging Face 的表现一致,你在 Python 里测试好的模型,放到 Go 里应该跑出一样的结果。
- 生产就绪:专注 ONNX 格式的模型(后面会解释这是什么),追求性能和稳定性。
- 本地化部署:不用依赖外部 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 上测试过。想在其他平台跑?理论上可行,但可能要踩一些坑。
你需要准备两样东西:
- libtokenizers.a:Rust 写的分词器静态库
- 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 模块初始化
```bash
mkdir my-hugot-app
cd my-hugot-app
go mod init my-hugot-app
go get github.com/knights-analytics/hugot
```
### 2.3 验证安装
先写个简单的程序测试一下:
```go
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 安装成功!")
}
```
运行:
```bash
go run main.go
```
如果看到"Hugot 安装成功!",恭喜你,可以开始玩耍了。
### 2.4 CLI 工具安装(可选)
Hugot 还提供了一个命令行工具,不用写代码也能跑模型:
```bash
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 创建。
```go
// 基础创建方式
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 转换:
```bash
pip install optimum[onnx]
optimum-cli export onnx --model bert-base-uncased ./bert_onnx/
```
**选项 3:让 Hugot 帮你下载**
```go
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 文本分类:判断情感倾向
咱们从最经典的任务开始——情感分析。给定一句话,判断它是正面还是负面。
```go
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))
}
```
运行结果大概长这样:
```json
{
"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 的基石。它把一段文本变成一个高维向量,这样计算机就能"理解"文本的含义了。
```go
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)能帮你从文本里找出人名、地名、组织名等实体。
```go
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 之一。你不需要训练数据,只需要告诉它有哪些类别,它就能自动分类。
```go
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 较新版本加入的功能,可以直接跑生成式模型。
```go
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 也能处理图像!
```go
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 | 所有平台 | ✅ 稳定 | 默认模式,无需额外配置 |
| CUDA | NVIDIA GPU | ✅ 已测试 | 需要 CUDA 12.x 和 cuDNN |
| TensorRT | NVIDIA GPU | ⚠️ 未测试 | NVIDIA 的高性能推理引擎 |
| DirectML | Windows | ⚠️ 未测试 | Windows 上的 GPU 加速 |
| CoreML | Apple 设备 | ⚠️ 未测试 | iOS/macOS 原生加速 |
| OpenVINO | Intel CPU/GPU | ⚠️ 未测试 | Intel 的推理工具包 |
### 5.3 启用 GPU 加速
要使用 CUDA 加速,你需要:
1. **NVIDIA 显卡**(显存建议 4GB+)
2. **CUDA 驱动和库**
3. **ONNX Runtime GPU 版本**
```bash
# 下载 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 库:
```bash
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,**批处理**都是提升性能的关键。
```go
// 低效:一条一条处理
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 做量化:
```bash
optimum-cli export onnx \
--model bert-base-uncased \
--optimize O4 \
./bert_quantized/
```
> **小贴士**:量化就像是把高清照片压缩成 JPEG。质量确实会下降一点,但文件小了,加载快了,而且很多时候肉眼根本看不出差别。
---
## 🛠️ 第六章:CLI 工具实战
### 6.1 安装 Hugot CLI
```bash
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` 字段:
```bash
# 从 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`:
```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 管道:
```bash
# 从 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。
**基础转换**:
```bash
pip install optimum[onnx]
# 转换一个模型
optimum-cli export onnx \
--model bert-base-uncased \
./bert_onnx/
```
**带优化的转换**:
```bash
# 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
# 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")
```
```bash
# 转换
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** - 文档索引器:
```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** - 搜索引擎:
```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** - 使用示例:
```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 库不在系统路径里。
**解决**:
```bash
# 找到库文件位置
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 模型加载失败
检查清单:
- 模型路径是否正确?
- 模型是 ONNX 格式吗?
model.onnx和tokenizer.json都在目录里吗?- 模型是否损坏?可以尝试重新下载。
9.4 内存占用过高
解决:
- 使用更小的模型(DistilBERT 替代 BERT)
- 减少批处理大小
- 及时销毁不用的 Pipeline 和 Session
- 考虑模型量化
9.5 GPU 不工作
检查清单:
- 安装了 GPU 版本的 ONNX Runtime 吗?
- CUDA 和 cuDNN 版本匹配吗?
- NVIDIA 驱动正常吗?
nvidia-smi能看到显卡吗? - 显卡显存够吗?
🔮 第十章:未来展望
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 类型速查
| 类型 | 配置类型 | 结果类型 |
|---|---|---|
| TextClassification | TextClassificationConfig |
TextClassificationOutput |
| FeatureExtraction | FeatureExtractionConfig |
FeatureExtractionOutput |
| TokenClassification | TokenClassificationConfig |
TokenClassificationOutput |
| ZeroShotClassification | ZeroShotClassificationConfig |
ZeroShotClassificationOutput |
| TextGeneration | TextGenerationConfig |
TextGenerationOutput |
| CrossEncoder | CrossEncoderConfig |
CrossEncoderOutput |
| ImageClassification | ImageClassificationConfig |
ImageClassificationOutput |
模型推荐
| 任务 | 推荐模型 | 大小 |
|---|---|---|
| 文本分类 | distilbert-base-uncased-finetuned-sst-2-english |
~250MB |
| Embeddings | all-MiniLM-L6-v2 |
~80MB |
| NER | distilbert-NER |
~250MB |
| 零样本分类 | deberta-v3-base-zeroshot-v1 |
~400MB |
📖 参考文献
-
Hugot GitHub 仓库 - https://github.com/knights-analytics/hugot
- 官方源码、文档和示例代码
-
Knights Analytics 博客: Hugot 介绍 - https://www.knightsanalytics.com/post/hugot-llms-in-go
- 项目背景和设计哲学
-
ONNX Runtime 官方文档 - https://onnxruntime.ai/
- 底层推理引擎的详细文档
-
Hugging Face Optimum 文档 - https://huggingface.co/docs/optimum/
- 模型转换和优化工具
-
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 #教程 #费曼风格 #小凯
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!
推荐
智谱 GLM-5 已上线
我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。