tables-overview
Table 实现全览
概述
Table 是 NV Embedding Cache SDK 中所有存储后端的统一抽象基类,定义了以下纯虚接口:
class Table {
virtual ~Table() = default;
virtual void clear(context_ptr_t& ctx) = 0;
virtual void erase(context_ptr_t& ctx, int64_t n, const void* keys) = 0;
virtual void find(context_ptr_t& ctx, int64_t n, const void* keys,
max_bitmask_repr_t* hit_mask, int64_t value_stride,
void* values, int64_t* value_sizes) const = 0;
virtual void insert(context_ptr_t& ctx, int64_t n, const void* keys,
int64_t value_stride, int64_t value_size, const void* values) = 0;
virtual void update(...) = 0;
virtual void update_accumulate(...) = 0;
virtual context_ptr_t create_execution_context(...) = 0;
virtual void reset_lookup_counter(...) = 0;
virtual void get_lookup_counter(...) = 0;
virtual bool lookup_counter_hits() = 0;
virtual int32_t get_device_id() const = 0;
virtual int64_t get_max_row_size() const = 0;
virtual int64_t get_invalid_key() const = 0;
};
项目实现了 8 种表,按存储位置和底层技术可分为:
| 类别 | 表实现 | 存储位置 | 数据持久化 | 依赖 |
|---|---|---|---|---|
| GPU 缓存表 | GpuTable |
GPU 显存 | ❌ | cuEmbed + EmbedCacheSA |
| CPU 线性表 | LinearHostTable |
CPU 内存(用户分配) | ❌ | 无(memcpy 直接读写) |
| CPU 哈希表(内置) | STLContainerTable |
CPU 内存 | ❌ | std::unordered_map |
| CPU 哈希表(Abseil) | AbseilFlatMapTable |
CPU 内存 | ❌ | Abseil Swiss Table |
| CPU 哈希表(PHMAP) | PHMapFlatMapTable |
CPU 内存 | ❌ | Parallel Hashmap |
| CPU GPU 哈希表 | NvhmMapTable |
CPU 内存(nvhashmap) | ❌ | NVIDIA nvhashmap |
| 远程缓存 | RedisClusterTable |
远程 Redis 集群 | ✅ 持久化 | hiredis + redis++ |
| 磁盘存储 | RocksDBTable |
本地磁盘 | ✅ 持久化 | Facebook RocksDB |
一、Table 基类接口详解
核心操作契约
find(ctx, n, keys, hit_mask, value_stride, values, value_sizes)
- 输入:
hit_mask中 bit=0 的键需要被查找,bit=1 的键应跳过 - 输出:对于 bit=0 且在表中找到的键,将其 bit 设为 1,并将值写入
values的对应位置 - 紧凑写入:如果
values是 nullptr,则只做计数/键存在性检查
insert(ctx, n, keys, value_stride, value_size, values)
- 向表中插入键值对。如果键已存在,行为取决于实现(替换或忽略)
- 最佳努力操作:如果触发了超额淘汰,新插入的键可能立即被淘汰
update(ctx, n, keys, value_stride, value_size, values)
- 只覆盖表中已存在的键,不存在的键被忽略
GpuTable如果配置了uvm_table且未禁用 UVM 更新,会同时写入 UVM 后备表
update_accumulate(ctx, n, keys, update_stride, update_size, updates, update_dtype)
- 梯度累积模式:
table[key] += updates[i] update_dtype可以不同于表的存储类型(如 fp32 梯度累积到 fp16 表)
命中率计数
| 方法 | 作用 |
|---|---|
reset_lookup_counter(ctx) |
重置计数器 |
get_lookup_counter(ctx, counter) |
读取计数器值(GPU 表需先 cudaStreamSynchronize) |
lookup_counter_hits() |
false = 计数的是 miss,true = 计数的是 hit |
二、GpuTable — GPU 集合关联缓存表
头文件:include/gpu_table.hpp | 实现:src/gpu_table.cu
GpuTable 是 NVE 的核心 GPU 端表实现,内部封装了 EmbedCacheSA<KeyType, KeyType> ——一个集合关联(Set-Associative)软件管理缓存。
架构层次
GpuTable
└── EmbedCacheSA<KeyType, KeyType> (GPU 集合关联缓存)
├── EmbedCacheSA 基类: 标签匹配 + 数据区 + 替换策略
├── CacheSADeviceModify: 修改操作在 GPU 上执行
└── CacheSAHostModify: 修改操作在 CPU 上执行(调试用途)
查找的四种 kernel 模式
当 uvm_table 非空时,run_find_uvm() 根据键数量选择执行策略:
| Kernel 模式 | 阈值条件 | 行为 |
|---|---|---|
LookupUVM |
默认:num_keys < 1M | 一次 cache->lookup 调用,cache miss 时直接从 UVM table 读取 |
SortGather |
默认:num_keys ≥ 1M | 先排序键(去重),大块连续读取 UVM,改善 page fault 局部性 |
PipelineGather |
用户明确指定 | 流水线 gather kernel,分 tile 处理 |
DynamicKernel |
— | 暂未实现 |
Insert 的直方图优化
GpuTable 的 insert 操作使用 DefaultGPUHistogram 在 GPU 上对键做频率统计和去重:
输入: [1, 2, 1, 3, 2, 1, 1] (7 个键)
↓
DefaultGPUHistogram(GPU kernel)
↓
输出: 键 [1, 2, 3] 按频率排序 (4, 2, 1)
↓
cache->insert() → 高频键优先插入 cache
频率越高的键在 insert 中获得越高优先级,在集合关联缓存中更不容易被淘汰。
三、LinearHostTable — CPU 线性表
头文件:include/linear_host_table.hpp | 实现:src/linear_host_table.cpp
设计理念
LinearHostTable 是最简单的 CPU 端表实现:嵌入向量存储在用户提供的连续内存缓冲区中,通过 key * row_size 直接计算地址后 memcpy 读写。无哈希、无索引、无淘汰——这与 GPUEmbeddingLayer 在 GPU 端的角色完全对称。
实现
// 查找: 最简单的 memcpy
void find(...) {
thread_pool->execute_n(0, num_tasks, [=](int64_t idx) {
for each key 跳过 hitmask 已标记的:
memcpy(output + k * output_stride,
src + keys[k] * row_size_in_bytes,
row_size_in_bytes);
});
memset(hit_mask, 0xff, ...);
}
适用场景
- 与
GpuTable配合,UVM 后备表的 CPU 端视图 - 纯 CPU 推理场景,嵌入表可完全装入系统内存
- 作为
HierarchicalEmbeddingLayer的中间层
四、STLContainerTable — 内置 CPU 哈希表
头文件:include/stl_map_backed_table.hpp | 实现:src/stl_map_backed_table.cpp
架构
STLContainerTable 是所有基于 STL 风格容器的 CPU 哈希表实现的公共基类。它使用 std::unordered_map<KeyType, char*> 作为底层映射,核心价值在于它实现了一套完整的内存管理、并发控制、超额淘汰基础设施。
STLContainerTable (模板基类)
├── 持有 std::vector<Partition>
│ └── Partition:
│ ├── std::shared_mutex (读写锁)
│ ├── std::unordered_map<KeyType, char*> (键 → 槽指针)
│ └── SlotBuffer (手动管理的内存池)
│
├── 查找: 分片并发 + hitmask 过滤 + 软件预取
├── 插入: 分片加锁 + 槽分配 + 超额淘汰
├── 更新: 分片加锁 + 直接覆盖
├── 累积: 分片加锁 + 原子累加 + inline meta
└── 淘汰: 三种策略 → EvictRandom / EvictLRU / EvictLFU
分片与并发
所有操作按 partitioner(key) & (num_partitions - 1) 分配到对应分片:
- 查找:每个分片加共享锁(
shared_lock),多线程并行 - 修改:每个分片加独占锁(
unique_lock),保证写一致性
内存槽管理
STLContainerTable 不把值直接存在 std::unordered_map 的 value 中,而是手动管理一个连续的 slot buffer:
Slot Buffer 布局:
┌────────────────────────────────────────────────┐
│ Row 0 │ value_bytes │ meta_bytes │ padding│
│ Row 1 │ value_bytes │ meta_bytes │ padding│
│ ...... │ .............│ .............│ .......│
│ Row N │ value_bytes │ meta_bytes │ padding│
└────────────────────────────────────────────────┘
- value_bytes:嵌入向量数据
- meta_bytes:inline metadata(LRU 时间戳或 LFU 频率计数)
std::unordered_map只存key → char*(指向 slot 起始位置)
这种设计将哈希表索引和数据存储分离,元数据跟随数据一起移动,淘汰时只需排序 meta 即可决定谁被淘汰。
超额淘汰(Overflow)
当插入导致分片容量超过阈值时触发淘汰:
overflow_margin = 512(额外条目数)
resolution_margin = 0.2(淘汰比例 20%)
触发条件: partition.size() > initial_capacity + overflow_margin
淘汰过程:
1. 收集所有 key + meta
2. 按 meta 排序:
- EvictRandom: 随机打乱
- EvictLRU: 按时间戳排序(最久未使用优先)
- EvictLFU: 按频率排序(最少使用优先)
3. 淘汰 resolution_margin 比例的条目
4. 释放对应的 slot 内存
模板分发
produce() 方法按 mask_size → key_size → overflow_handler → partitioner 四维 switch 分发,生成约 192 种编译时类型特化。
内置别名
系统中预注册了 6 个别名指向同一个基于 std::unordered_map 的 STLMapTableFactory:
{"stl_map", "map", "stl_unordered_map", "unordered_map", "stl_umap", "umap"}
五、AbseilFlatMapTable — Abseil Swiss Table 插件
头文件:plugins/abseil/include/abseil_flat_map_table.hpp | 注册名:abseil_flat_map
继承 STLContainerTable,将底层容器替换为 absl::flat_hash_map<KeyType, char*>。absl::flat_hash_map 使用 Google 的 Swiss Table 算法(开放寻址 + 元数据字节 + SIMD 查找),比 std::unordered_map 有更好的内存局部性和查找性能。
| 维度 | STLContainerTable | AbseilFlatMapTable |
|---|---|---|
| 底层容器 | std::unordered_map(链式哈希) |
absl::flat_hash_map(Swiss Table) |
| 额外配置 | 无 | initial_capacity 用于 reserve() |
| 链接依赖 | 无 | absl_raw_logging_internal + absl_hash |
六、PHMapFlatMapTable — Parallel Hashmap 插件
头文件:plugins/phmap/include/phmap_flat_map_table.hpp | 注册名:phmap_flat_map
实现方式与 Abseil 插件完全相同——继承 STLContainerTable,底层替换为 phmap::flat_hash_map<KeyType, char*>。PHMAP 使用与 Abseil 类似的开放寻址算法,但实现更轻量,无外部链接依赖(header-only)。
七、NvhmMapTable — NVIDIA nvhashmap 插件
头文件:plugins/nvhm/include/nvhm_map_table.hpp | 注册名:nvhm_map
与 STLContainerTable 的区别
NvhmMapTable 不继承 STLContainerTable,而是直接继承 HostTable<NvhmMapTableConfig>,直接使用 NVIDIA nvhashmap 的 map 接口:
template <typename MaskType, typename MapType, typename PartitionerType>
class NvhmMapTable final : public HostTable<NvhmMapTableConfig> {
struct Partition {
mutable std::shared_mutex read_write;
map_type map; // nvhm::map<KeyType, ValueType, ...>
};
std::vector<Partition> parts_;
};
配置
| 字段 | 默认 | 说明 |
|---|---|---|
kernel_size |
nvhm 默认 | 内核大小 1 |
key_fetch_queue_length |
8 | 软件预取管道深度 {0,1,2,4,8} |
prefetch_values |
true | 是否发出值预取指令 |
minimize_psl |
false | 缩短 probe search length |
auto_shrink |
false | 大量淘汰后自动缩容 |
查找中的软件预取
nvhashmap 的 ring_prefetch_queue 实现了软件流水线预取:
for (int i = 0; i < KeyFetchQueueLength; i++) {
__builtin_prefetch(next_key_address);
}
在 find_() 模板方法中,通过 KeyFetchQueueLength 和 PrefetchValues 两个编译时参数控制预取行为,switch 生成 5 × 2 = 10 种变体。
模板分发
NvhmMapTable 的模板分发维度最多:
mask_size × key_size × overflow_handler × kernel_size × minimize_psl × auto_shrink × partitioner
→ 约 6144 种编译时类型组合。
八、RedisClusterTable — Redis 远程表
头文件:plugins/redis/include/redis_cluster_table.hpp | 注册名:redis_cluster
架构
RedisClusterTable 是 NVE 中唯一基于网络通信的表实现。它通过 hiredis(C 客户端)和 redis++(C++ 客户端)连接到外部 Redis Cluster,将嵌入向量以序列化形式存储在 Redis 中。
配置
| 字段 | 默认 | 说明 |
|---|---|---|
address |
localhost:6379 |
Redis 集群任意节点地址 |
user_name |
default |
用户名 |
password |
空 | 密码 |
connections_per_node |
5 | 每节点最大并行连接数 |
connection_lifetime |
180s | 连接生命周期 |
max_batch_size |
16384 | 批量操作最大大小 |
num_partitions |
1 | 并行分片数(2 的幂) |
查找实现
auto reply = redis_cluster_->pipeline(keys.begin(), keys.end(),
[](const auto& key) { return sw::redis::command("GET", key); });
对于每个分片,使用 redis++ 的 pipeline 功能批量发送 GET 命令,减少网络往返次数。
链接依赖
hiredis + hiredis_ssl + redis++_static + ssl + crypto
九、RocksDBTable — 磁盘持久化表
头文件:plugins/rocksdb/include/rocksdb_table.hpp | 注册名:rocksdb
架构
RocksDBTable 通过 Facebook RocksDB 提供本地磁盘持久化存储。嵌入向量以 key → value 形式存储在 RocksDB 的 Column Family 中。
配置
| 字段 | 默认 | 说明 |
|---|---|---|
column_family |
default |
RocksDB Column Family |
max_batch_size |
16384 | 批量操作最大大小 |
verify_checksums |
true | 是否校验 checksum |
path |
/tmp/rocksdb |
数据库文件路径 |
read_only |
false | 只读模式(允许多进程并发读取) |
num_threads |
16 | 后台线程数 |
查找实现
db_->MultiGet(read_opts_, col_families_, keys_slices, &values, &statuses);
使用 RocksDB 的 MultiGet 实现批量读取,单次调用返回多个键的状态。
十、所有 Table 的对比总表
| 维度 | GpuTable | LinearHostTable | STLContainer | AbseilFlatMap | PHMapFlatMap | NvhmMapTable | RedisCluster | RocksDB |
|---|---|---|---|---|---|---|---|---|
| 存储位置 | GPU 显存 | CPU 线性内存 | CPU 堆内存 | CPU 堆内存 | CPU 堆内存 | CPU 堆内存 | 远程集群 | 本地磁盘 |
| 数据持久化 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| 查找算法 | 集合关联 tag | key * stride |
unordered_map O(1) |
Swiss Table O(1) | PHMAP O(1) | nvhashmap O(1) | Redis GET | LSM-Tree |
| 并发粒控 | CUDA stream | 线程池并行 | 分片读写锁 | 分片读写锁 | 分片读写锁 | 分片读写锁 | pipeline + 连接池 | RocksDB 内部 |
| 淘汰策略 | 集合关联 LRU | 无(固定数组) | EvictRandom/LRU/LFU | 同左 | 同左 | 同左 | 无(Redis 自管) | 无(磁盘存储) |
| 是否插件 | 核心库内置 | 核心库内置 | 核心库内置 | ✅ abseil 插件 | ✅ phmap 插件 | ✅ nvhm 插件 | ✅ redis 插件 | ✅ rocksdb 插件 |
| 启动依赖 | CUDA + nvhashmap | 无 | 无 | absl | PHMAP header-only | nvhashmap | hiredis + redis++ | librocksdb |
| JSON 实现名 | — | — | stl_map/umap | abseil_flat_map | phmap_flat_map | nvhm_map | redis_cluster | rocksdb |
| 典型扮演角色 | 层级首表 | 层级中/末表 | 层级中层 | 层级中层 | 层级中层 | 层级中层 | 层级末表 | 层级末表 |
十一、在嵌入层中的角色总结
HierarchicalEmbeddingLayer:
tables[0] ─── GpuTable (GPU cache, 最快层)
tables[1] ─── NvhmMapTable / AbseilFlatMapTable / STLContainerTable (CPU hash, 中间层)
tables[2] ─── RedisClusterTable / RocksDBTable (远程/持久化, 末层)
LinearUVMEmbeddingLayer:
GpuTable.cache_ = EmbedCacheSA (GPU cache)
GpuTable.config_.uvm_table = UVM 线性内存 (硬件透明回退)
GPUEmbeddingLayer:
无 Table (直接 cuEmbed Forward kernel, 线性 GPU 内存)