This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
建立您的博客“-”部分的#3:“理解嵌入”+&“矢量数据库
Wednesday, 12 November 2025
一. 导言导言 导言 导言 导言
欢迎使用 {3!} we' 已让 GPU 堆放工作@ (} @部分#2@),和我们理解 建筑#(部分#1现在该是跳进魔法的时间 让语义搜索成为可能了 嵌入 和 矢量数据库.
这是我自己编辑的A )Q MS K3Q 同一声音 同样的实用性 ,Q 一样
这里的东西变得有趣了 .We're将理解如何用数字来表达文本, 从而捕捉到“,”的含义, 而不仅仅是关键字@. ItMS K44在查找包含“"docker"与寻找关于集装箱化概念的语义文章之间有区别。 @.
什么是内嵌? ?
嵌入是作为矢量的 text {(} 基本上是一个数字列表 .
直觉
以其含义为根据, 在“ 2” D 空格中绘制单词
graph TD
subgraph "2D Embedding Space (simplified)"
A[cat: 0.8, 0.2]
B[kitten: 0.7, 0.3]
C[dog: 0.6, 0.1]
D[puppy: 0.5, 0.2]
E[car: -0.5, 0.8]
F[vehicle: -0.6, 0.7]
G[database: 0.1, -0.7]
H[SQL: 0.2, -0.8]
end
class A,B cats
class C,D dogs
class E,F vehicles
class G,H tech
classDef cats stroke:#333
classDef dogs stroke:#333
classDef vehicles stroke:#333
classDef tech stroke:#333
类似的概念组群集合起来:
- 宠物: (Red/GreenMS K2}彼此相邻
- 车辆=(Blue)分组
- Tech 术语 @(_ yellow})}组群
- 无关联的概念相去甚远
实际嵌入 使用“384”到 “1536”维度 @(}不只是“MS K3”捕捉更多小菜@.
如何运作
嵌入模型是一个神经网络,受过训练可以将文字映射到矢量上,如:
- 类似的意思@→关闭矢量
- 不同的含义 @ →% 远端矢量
graph LR
A[Text: 'Docker container'] --> B[Embedding Model]
B --> C[Vector: 384 floats]
D[Text: 'containerization'] --> B
B --> E[Vector: 384 floats]
C -.Similar.-> E
F[Text: 'chocolate cake'] --> B
B --> G[Vector: 384 floats]
C -.Very Different.-> G
class B model
class C,E similar
class G different
classDef model stroke:#333,stroke-width:4px
classDef similar stroke:#333
classDef different stroke:#333
测量相似性
我们用 余弦相似性 用于测量两个矢量的距离@: @%
public static float CosineSimilarity(float[] vectorA, float[] vectorB)
{
if (vectorA.Length != vectorB.Length)
throw new ArgumentException("Vectors must have same length");
// Dot product: sum of element-wise multiplication
float dotProduct = 0;
for (int i = 0; i < vectorA.Length; i++)
{
dotProduct += vectorA[i] * vectorB[i];
}
// Magnitude of each vector: sqrt(sum of squares)
float magnitudeA = 0;
float magnitudeB = 0;
for (int i = 0; i < vectorA.Length; i++)
{
magnitudeA += vectorA[i] * vectorA[i];
magnitudeB += vectorB[i] * vectorB[i];
}
magnitudeA = MathF.Sqrt(magnitudeA);
magnitudeB = MathF.Sqrt(magnitudeB);
// Cosine similarity: dot product / (magnitude_a * magnitude_b)
return dotProduct / (magnitudeA * magnitudeB);
}
相近的余波范围从“-1”到“MSC1”不等。:
1.0= 相同含义0.0=不相干-1.0@=}反义意思是#(_rare在实践上 @MS K2
示例实例:
var dockerEmbed = new float[] { 0.5f, 0.3f, -0.2f, 0.8f }; // "Docker container"
var containerEmbed = new float[] { 0.45f, 0.35f, -0.18f, 0.75f }; // "containerization"
var cakeEmbed = new float[] { -0.7f, 0.1f, 0.9f, -0.3f }; // "chocolate cake"
Console.WriteLine(CosineSimilarity(dockerEmbed, containerEmbed)); // ~0.95 (very similar!)
Console.WriteLine(CosineSimilarity(dockerEmbed, cakeEmbed)); // ~0.15 (unrelated)
为何嵌入为适合我们使用的案例
当我开始写:时,
@"_在此文章中将展示如何使用实体框架,
系统应该能够找到关于 @:@%
- 实体框架
- 数据库使用模式
- ORM 配置
- 相关核心主题
不只是关键字匹配@! @% 它应该明白,“"EF核心移民”是相关联的,即使它不认为' t说‘"’`实体框架><".′
嵌入式对传统搜索
graph TB
subgraph "Traditional Keyword Search"
A1[Query: 'Docker setup'] --> B1[Find: 'Docker' OR 'setup']
B1 --> C1[❌ Misses: 'containerization guide']
B1 --> D1[❌ Misses: 'running containers']
B1 --> E1[✅ Finds: 'Docker setup tutorial']
end
subgraph "Embedding-Based Semantic Search"
A2[Query: 'Docker setup'] --> B2[Generate embedding]
B2 --> C2[Find similar embeddings]
C2 --> D2[✅ Finds: 'containerization guide']
C2 --> E2[✅ Finds: 'running containers']
C2 --> F2[✅ Finds: 'Docker setup tutorial']
end
class B2,C2 semantic
classDef semantic stroke:#333,stroke-width:2px
选择嵌入模型
有许多嵌入模型.}我们需要一个
- 本地运行 -=YTET -伊甸园字幕组=- 翻译:
- 使用ONNX工作运行时间 (所以我们可以使用GPU=)。
- 良好质量 “( 准确的语义理解”“)”
- 右右侧大小 384-768 维度是良好的平衡
人民选择
| Q 型号为 @ | Q 尺寸@ | Q 大小 @MS K3Q 质量 @ | Q 速度 @MSC5Q |
|---|---|---|---|
| {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2款 @ | 384 | @ @MS K3 MPK4 好~ | 非常快~⚡⚡⚡# |
| {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2款 | MS K1 I | NSK3 MASSK4 更好的是 | 快速⚡⚡ |
| {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}哦! | MS K1 I | NSK3 MASSK4 更好的 | 非常快速的⚡⚡⚡ MPK7 |
| -=YTET -伊甸园字幕组=- 翻译: | MS K1 NSK2 I436 MASSK4 最佳 | 快速⚡⚡ MPK7 | |
| OpenAI text@- @ comedding_ -}% 1 @ ada @ MS K2 超棒的 | 慢速 |
我的建议建议: -=YTET -伊甸园字幕组=- 翻译:
- 状态@-_of @-_the-}开放源码模型
- 768 维度 ** 好平衡*
- 与ONNX合作很好,运行时间
- 本地自由运行
转换为 ONNX 格式
多数模型都以PyTorrch格式为主,
安裝 Optimm (Python 要轉換的庫@ ):
pip install optimum[exporters]
转换 BGE 模式:
optimum-cli export onnx --model BAAI/bge-base-en-v1.5 --task feature-extraction bge-base-en-onnx/
此创建@ : @%
bge-base-en-onnx/
model.onnx # The neural network
tokenizer.json # Text → tokens converter
tokenizer_config.json
special_tokens_map.json
config.json
如果您不想转换, 请下载@ ' @}: 许多模型都预设为“-” , 并被转换为“ "onnx” 和“ I.” 。
使用 C# 中的嵌入符
使用ONNX Runtime.建立实用嵌入生成器
项目设置
mkdir EmbeddingTest
cd EmbeddingTest
dotnet new console
dotnet add package Microsoft.ML.OnnxRuntime.Gpu --version 1.16.3
dotnet add package Microsoft.ML.Tokenizers --version 0.1.0-preview.23511.1
为什么这些软件包?
OnnxRuntime.Gpu- 运行 GPU 的模型Microsoft.ML.Tokenizers- 将文字转换为代号 ID {( 模式输入格式@)
名化第一
在嵌入@ , @ 之前, 我们必须将 & MS K1 转换文本到数字@ ):}
using Microsoft.ML.Tokenizers;
using System;
using System.Linq;
public class SimpleTokenizer
{
private readonly Tokenizer _tokenizer;
public SimpleTokenizer(string tokenizerPath)
{
// Load the tokenizer.json file
_tokenizer = Tokenizer.CreateTokenizer(tokenizerPath);
}
public (long[] InputIds, long[] AttentionMask) Tokenize(string text, int maxLength = 512)
{
// Tokenize the text
var encoding = _tokenizer.Encode(text);
// Get token IDs
var ids = encoding.Ids.Select(i => (long)i).ToArray();
// Pad or truncate to maxLength
var inputIds = new long[maxLength];
var attentionMask = new long[maxLength];
int length = Math.Min(ids.Length, maxLength);
// Copy actual tokens
Array.Copy(ids, inputIds, length);
// Set attention mask (1 = real token, 0 = padding)
for (int i = 0; i < length; i++)
{
attentionMask[i] = 1;
}
return (inputIds, attentionMask);
}
}
这里发生什么了?
编码编码 ? - MS K1 你好 世界 " NSK3
[101, 8667, 2088, 102]- 每个数字都是来自 model @'_%s 词汇表的标记代号
- 特殊标记@: @ @ @ 101} @ I=# [CLS=], 102 NSK2 [
贴面 @- 模型期待固定@-长输入
- 如果文本是短@ : @% 带零的页面
- 如果文本是长@ : @ tuncate
注意遮罩 - 告诉模型哪个符号是真实的 vs padding
1= 处理此标记0@= @ 忽略@ ( @ it @ MS K2_ 跳板 @ MPK3 @
graph LR
A["Text: 'Docker setup'"] --> B[Tokenizer]
B --> C[Token IDs:<br/>101, 12849, 12229, 102]
C --> D[Pad to 512]
D --> E[Input IDs:<br/>101, 12849, 12229, 102, 0, 0,...]
D --> F[Attention Mask:<br/>1, 1, 1, 1, 0, 0,...]
E --> G[Feed to Model]
F --> G
class B,G process
classDef process stroke:#333,stroke-width:2px
嵌入式发电机类
现在完整嵌入的管道:
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using System;
using System.Collections.Generic;
using System.Linq;
public class EmbeddingGenerator : IDisposable
{
private readonly InferenceSession _session;
private readonly SimpleTokenizer _tokenizer;
private readonly int _embeddingDimension;
public EmbeddingGenerator(string modelPath, string tokenizerPath, bool useGpu = true)
{
// Setup session options
var options = new SessionOptions();
if (useGpu)
{
options.AppendExecutionProvider_CUDA(0);
}
// Load model
_session = new InferenceSession(modelPath, options);
// Load tokenizer
_tokenizer = new SimpleTokenizer(tokenizerPath);
// Get embedding dimension from model output shape
var outputMetadata = _session.OutputMetadata["last_hidden_state"];
_embeddingDimension = outputMetadata.Dimensions[2]; // Usually 768 for base models
}
public float[] GenerateEmbedding(string text)
{
// Step 1: Tokenize
var (inputIds, attentionMask) = _tokenizer.Tokenize(text);
// Step 2: Create input tensors
var inputIdsTensor = new DenseTensor<long>(inputIds, new[] { 1, inputIds.Length });
var attentionMaskTensor = new DenseTensor<long>(attentionMask, new[] { 1, attentionMask.Length });
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input_ids", inputIdsTensor),
NamedOnnxValue.CreateFromTensor("attention_mask", attentionMaskTensor)
};
// Step 3: Run inference
using var results = _session.Run(inputs);
// Step 4: Extract embeddings from output
var outputTensor = results.First().AsTensor<float>();
// Output shape is [batch_size, sequence_length, embedding_dim]
// We want [batch_size, embedding_dim] by mean pooling
return MeanPooling(outputTensor, attentionMask);
}
private float[] MeanPooling(Tensor<float> outputTensor, long[] attentionMask)
{
int seqLength = outputTensor.Dimensions[1];
int embeddingDim = outputTensor.Dimensions[2];
var embedding = new float[embeddingDim];
int tokenCount = 0;
// Average across all non-padded tokens
for (int seq = 0; seq < seqLength; seq++)
{
if (attentionMask[seq] == 0) continue; // Skip padding
tokenCount++;
for (int dim = 0; dim < embeddingDim; dim++)
{
embedding[dim] += outputTensor[0, seq, dim];
}
}
// Divide by count to get mean
for (int dim = 0; dim < embeddingDim; dim++)
{
embedding[dim] /= tokenCount;
}
// Normalize to unit length (common practice)
return Normalize(embedding);
}
private float[] Normalize(float[] vector)
{
float magnitude = 0;
foreach (var val in vector)
{
magnitude += val * val;
}
magnitude = MathF.Sqrt(magnitude);
var normalized = new float[vector.Length];
for (int i = 0; i < vector.Length; i++)
{
normalized[i] = vector[i] / magnitude;
}
return normalized;
}
public void Dispose()
{
_session?.Dispose();
}
}
代码分类明细:
- 模型装货 - 在 GPU 支持下创建 ONNX 会话
- 声当化 - 将文本转换为代号 ID
- 建立电温器 - 用于模型输入的形状数据
- 推断 - 运行神经网络
- 平均共用 - 平均象征性嵌入 * @→ 单句嵌入
- 正常化 @-_ 使矢量长度@1}(* 简化相似性计算 )
平均合用资金
graph TB
A[Model Output:<br/>Token Embeddings] --> B["Token 0 (CLS):<br/>(0.1, 0.5, -0.3, ...)"]
A --> C["Token 1 (Docker):<br/>(0.4, 0.2, -0.1, ...)"]
A --> D["Token 2 (setup):<br/>(0.3, 0.6, -0.2, ...)"]
A --> E["Token 3 (SEP):<br/>(0.2, 0.3, -0.4, ...)"]
B --> F[Average]
C --> F
D --> F
E --> F
F --> G["Sentence Embedding:<br/>(0.25, 0.4, -0.25, ...)"]
class A input
class F process
class G output
classDef input stroke:#333
classDef process stroke:#333,stroke-width:2px
classDef output stroke:#333,stroke-width:2px
我们平均是因为:
- 每件信物都有自己的嵌入
- 整个句子需要一个嵌入
-
- 动 动
使用率示例
using System;
class Program
{
static void Main(string[] args)
{
using var embedder = new EmbeddingGenerator(
modelPath: "bge-base-en-onnx/model.onnx",
tokenizerPath: "bge-base-en-onnx/tokenizer.json",
useGpu: true
);
// Generate embeddings
var embedding1 = embedder.GenerateEmbedding("Docker containerization tutorial");
var embedding2 = embedder.GenerateEmbedding("Setting up containers with Docker");
var embedding3 = embedder.GenerateEmbedding("Baking a chocolate cake");
Console.WriteLine($"Embedding dimension: {embedding1.Length}");
Console.WriteLine($"First 5 values: {string.Join(", ", embedding1.Take(5).Select(f => f.ToString("F4")))}");
// Calculate similarities
float sim12 = CosineSimilarity(embedding1, embedding2);
float sim13 = CosineSimilarity(embedding1, embedding3);
Console.WriteLine($"\nSimilarity (Docker vs Containers): {sim12:F4}"); // ~0.85
Console.WriteLine($"Similarity (Docker vs Cake): {sim13:F4}"); // ~0.10
}
static float CosineSimilarity(float[] a, float[] b)
{
// Since vectors are normalized, dot product = cosine similarity
float dot = 0;
for (int i = 0; i < a.Length; i++)
{
dot += a[i] * b[i];
}
return dot;
}
}
产出产出产出 产出 输出产出产出:
Embedding dimension: 768
First 5 values: 0.0123, -0.0456, 0.0789, -0.0234, 0.0567
Similarity (Docker vs Containers): 0.8542
Similarity (Docker vs Cake): 0.1023
美丽的!QSmellical相似的内容有高度相似的@,}不相干的内容低 .}
矢量数据库
现在,我们已经嵌入了 . 我们需要储存上百万的它们 并高效搜索.
问题
@=}10,000 嵌入□.
原始搜索:
float bestSimilarity = -1;
int bestIndex = -1;
for (int i = 0; i < 10000; i++)
{
float sim = CosineSimilarity(queryEmbedding, storedEmbeddings[i]);
if (sim > bestSimilarity)
{
bestSimilarity = sim;
bestIndex = i;
}
}
问题这是O (\n MS K2} @ @ -}我们检查每一个嵌入的@.}慢速! *
用于“10,000”嵌入的“MS K1” “768”维度,每个“:”
- 百万个浮点行动
- 即使在快速的CPU上
我们需要快一点的东西 .
矢量数据库解决方案
矢量数据库使用智能数据结构@(}类似 HNSW @-}世界级可控性小星图)}以在 OZ(log nMS K4 time中找到最近的邻居
graph TB
A[Query Embedding] --> B[Vector Database]
B --> C{HNSW Index}
C --> D[Layer 2:<br/>Coarse Search]
D --> E[Layer 1:<br/>Refined Search]
E --> F[Layer 0:<br/>Exact Search]
F --> G[Top K Results]
H[10,000 vectors] -.Indexed.-> C
class B db
class C index
class G results
classDef db stroke:#333,stroke-width:4px
classDef index stroke:#333,stroke-width:2px
classDef results stroke:#333,stroke-width:2px
速度比较:
- @ 10QK 矢量的NAive 搜索@: @ @ I50-100 @ ms
- 矢量 DB @(#HNSW):} @ 1-5}%10QK 向量的矢量
- 10-50#x 更快 #!}
并且它将百万个矢量的向量 缩放成“~10-20”和“MSSK2”
选择矢量数据库
我们的C#计划 我们需要:
- C#客户端库
- 容易部署 @( @ docker@)}
- 良好业绩
- 自由 & M/ @ 开放源代码
| Q 性能= | 驾照@ | |||
|---|---|---|---|---|
| 解冻 @ | ✅ 棒极了 @MS K2 #Docker# | 非常快 * | 阿帕奇□2.0~ | * |
| pgvictor 变量 | ✅MS K2Via NpgsqlMSQ} | 邮递分机 | ||
| 断断 "M | ✅" "好" | "多克" *超快" | ||
| 米尔伏人 | MS K1有限公司 | 多克/K8 | ||
| 色谱 | MS K1 Python MASK2 先是 | Docker MSSK4 快速 | 阿帕奇2.0 NSK7 |
我的选择@: @Qdrant
- 优秀的 C# 客户端 @ ( *
Qdrant.Client) - 简单嵌入部署
- 专门为矢量搜索而建造的 @(}没有在)上锁定
- 特大文件
- 积极发展
替代品@: pgvictor
- 我们已使用 PostgreSQL 加入博客@! @
- 能把所有东西都保存在一个数据库里
- 性能稍差但较简单的建筑
本序列集 @ , @ I'# 将使用 解冻 @._BARBAR_因为它的目的#'}%-}建立起来而且更容易理解这些概念 但是 I'}会显示 pgvector 作为替代的@.}
设置 Qdrant
嵌入器部署
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
港口港口:
6333-=YTET -伊甸园字幕组=- 翻译:6334-+GRPCAPI+(+Faster#,+W$'}使用这个)+%
储存: 永久数据到 ./qdrant_storage 主机
C#客户端设置
dotnet add package Qdrant.Client --version 1.7.0
创建收藏
一个收藏就像一个表格 @ -它持有特定维度的矢量@ MS K1
using Qdrant.Client;
using Qdrant.Client.Grpc;
public class QdrantSetup
{
private readonly QdrantClient _client;
public QdrantSetup(string host = "localhost", int port = 6334)
{
_client = new QdrantClient(host, port);
}
public async Task CreateCollectionAsync(string collectionName, ulong vectorSize)
{
// Check if collection exists
var collections = await _client.ListCollectionsAsync();
if (collections.Any(c => c.Name == collectionName))
{
Console.WriteLine($"Collection '{collectionName}' already exists");
return;
}
// Create collection
await _client.CreateCollectionAsync(
collectionName: collectionName,
vectorsConfig: new VectorParams
{
Size = vectorSize, // 768 for bge-base
Distance = Distance.Cosine // Cosine similarity
}
);
Console.WriteLine($"Created collection '{collectionName}' with {vectorSize} dimensions");
}
}
关键参数:
Size-}必须匹配您的嵌入模型 {(768} 为 bge-}base_)}Distance如何测量相似度?Distance.Cosine@- @Consocial 相似性 @(}最常见的)Distance.Euclid欧几里德距离Distance.Dot-Dot产品
插入矢量
using Qdrant.Client.Grpc;
using System.Collections.Generic;
public class QdrantInserter
{
private readonly QdrantClient _client;
public QdrantInserter(QdrantClient client)
{
_client = client;
}
public async Task InsertBlogChunkAsync(
string collectionName,
ulong id,
float[] embedding,
string blogPostSlug,
string chunkText,
int chunkIndex)
{
var point = new PointStruct
{
Id = id,
Vectors = embedding,
Payload =
{
["blog_post_slug"] = blogPostSlug,
["chunk_text"] = chunkText,
["chunk_index"] = chunkIndex,
["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}
};
await _client.UpsertAsync(collectionName, new[] { point });
}
public async Task InsertBatchAsync(
string collectionName,
List<(ulong id, float[] embedding, Dictionary<string, object> payload)> points)
{
var qdrantPoints = points.Select(p => new PointStruct
{
Id = p.id,
Vectors = p.embedding,
Payload = { p.payload }
}).ToList();
// Batch insert for efficiency
await _client.UpsertAsync(collectionName, qdrantPoints);
Console.WriteLine($"Inserted {points.Count} points");
}
}
有效载荷解释:
- 类似附于每个矢量的元数据
- 能够存储任何文件@: @ post 标题 @ ,} text_, @ date{,类别
- 可搜索和可过滤@ ! @%
批次插入:
- 速度比一个快得多
- Qdrant 高效地处理批量的 @ 100-1000 @% 。
搜索矢量
public class QdrantSearcher
{
private readonly QdrantClient _client;
public QdrantSearcher(QdrantClient client)
{
_client = client;
}
public async Task<List<SearchResult>> SearchAsync(
string collectionName,
float[] queryEmbedding,
int topK = 10)
{
var searchResult = await _client.SearchAsync(
collectionName: collectionName,
vector: queryEmbedding,
limit: (ulong)topK,
scoreThreshold: 0.7f // Only return if similarity > 0.7
);
return searchResult.Select(r => new SearchResult
{
Id = r.Id.Num,
Score = r.Score,
BlogPostSlug = r.Payload["blog_post_slug"].StringValue,
ChunkText = r.Payload["chunk_text"].StringValue,
ChunkIndex = (int)r.Payload["chunk_index"].IntegerValue
}).ToList();
}
public async Task<List<SearchResult>> SearchWithFilterAsync(
string collectionName,
float[] queryEmbedding,
string blogPostSlug, // Only search within this post
int topK = 5)
{
var filter = new Filter
{
Must =
{
new Condition
{
Field = new FieldCondition
{
Key = "blog_post_slug",
Match = new Match { Keyword = blogPostSlug }
}
}
}
};
var searchResult = await _client.SearchAsync(
collectionName: collectionName,
vector: queryEmbedding,
filter: filter,
limit: (ulong)topK
);
return searchResult.Select(r => new SearchResult
{
Id = r.Id.Num,
Score = r.Score,
BlogPostSlug = r.Payload["blog_post_slug"].StringValue,
ChunkText = r.Payload["chunk_text"].StringValue,
ChunkIndex = (int)r.Payload["chunk_index"].IntegerValue
}).ToList();
}
}
public class SearchResult
{
public ulong Id { get; set; }
public float Score { get; set; }
public string BlogPostSlug { get; set; }
public string ChunkText { get; set; }
public int ChunkIndex { get; set; }
}
搜索功能:
limit- 返回最相似的 KscoreThreshold只有在相似性超过阈值时才返回filter- 通过元数据“(e.gMS K3只搜索特定位置”{)
完整示例@:搜索管道
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Setup
var embedder = new EmbeddingGenerator(
"bge-base-en-onnx/model.onnx",
"bge-base-en-onnx/tokenizer.json",
useGpu: true
);
var client = new QdrantClient("localhost", 6334);
var searcher = new QdrantSearcher(client);
// User query
string query = "How do I set up Docker with ASP.NET Core?";
// Generate query embedding
Console.WriteLine($"Searching for: {query}");
var queryEmbedding = embedder.GenerateEmbedding(query);
// Search
var results = await searcher.SearchAsync(
collectionName: "blog_embeddings",
queryEmbedding: queryEmbedding,
topK: 5
);
// Display results
Console.WriteLine($"\nFound {results.Count} results:\n");
foreach (var result in results)
{
Console.WriteLine($"Score: {result.Score:F4}");
Console.WriteLine($"Post: {result.BlogPostSlug}");
Console.WriteLine($"Chunk: {result.ChunkText.Substring(0, Math.Min(100, result.ChunkText.Length))}...");
Console.WriteLine();
}
}
}
产出产出产出 产出 输出产出产出:
Searching for: How do I set up Docker with ASP.NET Core?
Found 5 results:
Score: 0.8923
Post: dockercomposedevdeps
Chunk: In this post, I'll show you how to set up a development environment using Docker Compose. This is p...
Score: 0.8654
Post: dockercompose
Chunk: Docker Compose is a tool for defining and running multi-container Docker applications. With Compose...
Score: 0.8102
Post: addingentityframeworkforblogpostspt1
Chunk: You can set it up either as a windows service or using Docker as I presented in a previous post on...
Score: 0.7891
Post: imagesharpwithdocker
Chunk: When running ASP.NET Core applications in Docker containers, you may encounter issues with ImageSha...
Score: 0.7654
Post: selfhostingseq
Chunk: I use Docker Compose to run all my services. Here's the relevant part of my docker-compose.yml file...
发现有关Docker和ASP.NET Core{,}的相关文章,
业绩优化
批量嵌入生成
Don''t 生成嵌入@ -_ by @ MS K2\ one}. 批評它們@!
public class BatchEmbeddingGenerator
{
private readonly EmbeddingGenerator _embedder;
public BatchEmbeddingGenerator(EmbeddingGenerator embedder)
{
_embedder = embedder;
}
public List<float[]> GenerateBatch(List<string> texts, int batchSize = 32)
{
var embeddings = new List<float[]>();
for (int i = 0; i < texts.Count; i += batchSize)
{
var batch = texts.Skip(i).Take(batchSize).ToList();
foreach (var text in batch)
{
embeddings.Add(_embedder.GenerateEmbedding(text));
}
Console.WriteLine($"Processed {Math.Min(i + batchSize, texts.Count)} / {texts.Count}");
}
return embeddings;
}
}
为何要批发?
- GPU 使用@ : @ 使GPU保持忙碌
- 内存效率:再利用缓冲
- 用户反馈
缓存的嵌套
Donó't 为未变内容重新生成嵌入@ !
using System.Security.Cryptography;
using System.Text;
public class EmbeddingCache
{
private readonly Dictionary<string, float[]> _cache = new();
public float[] GetOrGenerate(string text, Func<string, float[]> generator)
{
string hash = ComputeHash(text);
if (_cache.TryGetValue(hash, out var cached))
{
return cached;
}
var embedding = generator(text);
_cache[hash] = embedding;
return embedding;
}
private string ComputeHash(string text)
{
using var sha256 = SHA256.Create();
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
return Convert.ToBase64String(bytes);
}
}
pgvictor 替代品
如果您想将一切保留在 PostgreSQL: 中
安裝 pgvictor 扩展
CREATE EXTENSION vector;
创建表格
CREATE TABLE blog_embeddings (
id SERIAL PRIMARY KEY,
blog_post_slug VARCHAR(255),
chunk_text TEXT,
chunk_index INT,
embedding VECTOR(768) -- 768 dimensions
);
-- Create HNSW index for fast search
CREATE INDEX ON blog_embeddings USING hnsw (embedding vector_cosine_ops);
从 C# 插入
using Npgsql;
using Pgvector;
public async Task InsertEmbeddingAsync(
string slug,
string chunkText,
int chunkIndex,
float[] embedding)
{
await using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand(
"INSERT INTO blog_embeddings (blog_post_slug, chunk_text, chunk_index, embedding) VALUES ($1, $2, $3, $4)",
conn
)
{
Parameters =
{
new() { Value = slug },
new() { Value = chunkText },
new() { Value = chunkIndex },
new() { Value = new Vector(embedding) }
}
};
await cmd.ExecuteNonQueryAsync();
}
使用 pgvictor 搜索
public async Task<List<SearchResult>> SearchAsync(float[] queryEmbedding, int topK = 10)
{
await using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand(
@"SELECT blog_post_slug, chunk_text, chunk_index,
1 - (embedding <=> $1) as similarity
FROM blog_embeddings
ORDER BY embedding <=> $1
LIMIT $2",
conn
)
{
Parameters =
{
new() { Value = new Vector(queryEmbedding) },
new() { Value = topK }
}
};
var results = new List<SearchResult>();
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
results.Add(new SearchResult
{
BlogPostSlug = reader.GetString(0),
ChunkText = reader.GetString(1),
ChunkIndex = reader.GetInt32(2),
Score = reader.GetFloat(3)
});
}
return results;
}
<=> 操作员@: @Consine 距离@(#Llower更相似 #)}
1 - distance@: 转换为相似得分 *#(higher是更好的=@)
摘要摘要
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}我们
- ✅ 嵌入的是什么以及为什么它们能够进行语义搜索
- 如何选择并转换嵌入模型到ONNX?
- ✅ 生成嵌入 C# 与ONNX 运行时间
- ✅ 矢量数据库概念 @(HNSW,相似性搜索@)
- 使用 Qdrant 存储和搜索矢量
- 在 PostgreSQL 中的 ✅ 替代@: pgvistor
- “✅ 性能优化”“( bagging , caching )”
我们现在有核心技术 找到语言相似的博客内容*!
什么是' 下一步 @?
内 建立接收管道{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2款
- 正在读取和解析标记文件 @ ( @ we already do this for the blog {!)} 我们已经这样做了。
- 智能散块策略
- 生成所有内容的嵌入
- 散装插入 解冻
- 处理更新和删除
- 监测进展情况
We'll处理整个博客, 并拥有可搜索的知识库@!#
系列导航
- 部分: 1:_ 导言 _&建筑
- 部分@2:GPU 设置 @&CUDA 在 CQ#
- “3: 理解嵌入器”部分 校对:Soup
- 建立接收管道
- 窗口客户端
- 6:地方LLM整合部分
- 内容生成 *&}快速工程
- 生产部署
资源资源资源 资源和资源资源资源
回头见 部分#4!