Skip to content

Commit ead9a33

Browse files
author
peng li
committed
mem-cuda:统一存储面
1 parent 78d49f4 commit ead9a33

11 files changed

Lines changed: 411 additions & 89 deletions

File tree

.cursorrules

Lines changed: 0 additions & 26 deletions
This file was deleted.

Agents.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## agent 规则
2+
+ This is the only AGENTS.md, there are no recursive AGENTS.md
3+
+ When you are working on a bug, first create a standalone file that reproduces the bug and verify it fails in the expected way. Use this to test if your changes work. Once the change is passing, find an appropriate test file to add the test to and make sure to follow local conventions on the test file.
4+
+ Always respond in 中文,不要回答重复的内容(如我提问中的代码)
5+
6+
## deepx的架构
7+
8+
项目分为3部分
9+
1. 前端。python库的接口风格参考pytorch
10+
2. 编译,调度器,待设计
11+
3. 执行器,使用c++,cuda,metal,omp simd等,实现不同executor的算子
12+
13+
# 关于deepx的细节概念
14+
+ deepx.Tensor仅仅就是一个tensor,不像pytorch的tensor,一个tensor其实包含了自身和梯度2个tensor的数据
15+
16+
17+
贴近pytorch的接口风格,不要增加任何注释,我会手动添加注释
18+
19+
关于doc目录
20+
采用Sphinx构建,使用reStructuredText格式

executor/mem-cuda/README.md

Lines changed: 13 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
本目录用于设计/实现单机多进程的 GPU Tensor 统一存储面(CUDA IPC),并通过 Redis 做 name → IPC handle 的集中注册与控制。
44

55
## 目标
6-
- 单机内多进程共享**可命名**Tensor(同名即同一块 GPU 内存)。
7-
- 通过 Redis 维护 name、shape、dtype、device、IPC handle 等元信息。
8-
- 通过 Redis List 接收创建/获取/删除指令,实现统一的控制面。
6+
- 管理单机内多进程共享的堆Tensor
7+
+ 堆tensor不会随着计算完成而被回收
8+
+ 而栈tensor则是计算过程的中间变量,可以被立即回收
9+
- 通过 Redis 维护 name、shape、dtype、device、IPC handle 等元信息,供mem-cuda进程、计算进程访问。
10+
- 通过 Redis List 接收创建/获取/删除指令,实现统一的控制面。并提供redis mem api以及example
911

1012
## 设计概述
1113
### 1) Redis 元数据(KV/Hash)
@@ -18,7 +20,7 @@
1820
- `bytes`: int
1921
- `ipc_handle`: binary
2022
- `refcount`: int
21-
23+
无需设计tensor owner
2224

2325
### 2) Redis 指令队列(List)
2426
控制通道 list key: `tensor_lifecycle`
@@ -38,64 +40,23 @@
3840
- 跨 stream 写读需要显式同步(事件/流同步策略)
3941

4042
## 显存池方案
41-
你的需求是:**参考 PyTorch 的显存池管理**。这里给出可落地路线:
4243

43-
### 方案 A:接入成熟开源显存池(推荐)
4444
- **RMM (RAPIDS Memory Manager)**
4545
- 优点:成熟、支持 pool/async allocator、统计完善
4646
- 适合:对稳定性与可观察性要求高的生产环境
47-
- **CUB caching allocator**
48-
- 优点:性能好、实现简单
49-
- 适合:希望直接嵌入 CUDA 代码路径
50-
51-
> 选择建议:优先 RMM;想保持最小依赖可用 CUB。
52-
53-
### 方案 B:自研简化版显存池(AI 方案)
54-
如果不引入外部依赖,可先实现一个简化版池:
55-
- 维护按 size 分桶的 free-list(如 1MB、2MB、4MB…)
56-
- 分配时优先复用空闲块,不足时 `cudaMalloc` 新块
57-
- 回收时挂回 free-list,不立刻 `cudaFree`
58-
- 支持 `recordStream` / `event` 延迟回收,避免跨流释放风险
59-
60-
**建议先实现 MVP**
61-
1) 单 GPU
62-
2) 只支持 `create/get/delete`
63-
3) dtype 限定 f32
64-
4) 单进程先跑通,再放开多进程 + IPC
6547

66-
## 安全与一致性
67-
- Redis 写入与 refcount 需要原子操作(Lua 脚本/事务)
68-
- 崩溃恢复:定期清理 owner_pid 不存在的条目
69-
- IPC handle 需与 device id 配对,否则会映射失败
48+
其他op计算进程可以使用CUB。
7049

7150
## 目录结构(具体方案)
7251
```
7352
mem-cuda/
7453
README.md
7554
doc/
76-
design.md # 细化设计文档与协议约束
77-
redis-schema.md # Redis KV/Hash/List 结构定义
78-
ipc.md # CUDA IPC 约束、时序与同步策略
79-
allocator.md # 显存池方案与接口
8055
src/
81-
registry/ # Redis 元数据与命令处理
82-
redis_client.h
83-
redis_client.cpp
84-
registry.h
85-
registry.cpp
86-
lua_scripts/ # 原子脚本
87-
create_or_get.lua
88-
ref_inc.lua
89-
ref_dec.lua
90-
gc_sweep.lua
9156
ipc/ # CUDA IPC 封装
9257
ipc.h
9358
ipc.cpp
9459
ipc_guard.h # 设备一致性与错误处理
95-
allocator/ # 显存池实现或适配层
96-
allocator.h
97-
cuda_pool.cpp
98-
rmm_adapter.cpp # 可选
9960
runtime/ # 运行时控制(指令/同步)
10061
lifecycle.h
10162
lifecycle.cpp
@@ -105,41 +66,30 @@ mem-cuda/
10566
status/json/logging
10667
test/
10768
ipc_demo.cpp
108-
registry_demo.cpp
10969
lifecycle_demo.cpp
110-
tools/
111-
memcuda_ctl.cpp # CLI 工具(create/get/delete/list)
11270
```
11371

11472
模块职责:
115-
- `registry/`: Redis 协议、Lua 原子操作、Hash 读写。
11673
- `ipc/`: CUDA IPC handle 导出/打开/关闭封装。
117-
- `allocator/`: 统一分配接口;可切换 RMM/自研。
11874
- `runtime/`: 指令消费/路由与跨 stream 同步策略。
11975
- `common/`: 状态码、JSON 解析、日志等公共工具聚合。
12076

12177
## 后续工作清单(分阶段)
12278
- [ ] 阶段 0:确定目录与接口(完成本 README 细化)
123-
- [ ] 阶段 1:实现 `registry/` + Redis Lua 原子脚本
124-
- [ ] 阶段 2:实现 `ipc/` + `allocator/` 的最小实现(f32, 单 GPU)
125-
- [ ] 阶段 3:实现 `lifecycle/` worker 与 `tools/` CLI
126-
- [ ] 阶段 4:补齐 `sync/` 策略与崩溃恢复/GC
79+
- [ ] 阶段 1:实现 `lifecycle/`
80+
- [ ] 阶段 2:实现 `ipc/` 的最小实现(f32, 单 GPU)
81+
- [ ] 阶段 3:补齐 `sync/` 策略与崩溃恢复/GC
12782

12883
## 构建依赖与示例
12984

13085
- 必要系统依赖:CUDA Toolkit (兼容 CMake `CUDAToolkit`), `cmake` >= 3.18, `make`
131-
- Redis C++ 客户端:必须安装 `redis++`(redis-plus-plus)及其依赖 `hiredis`
132-
- 可选:RMM 库(若启用对应 adapter)。
86+
- Redis C++ 客户端:必须安装 `redis++`(redis-plus-plus)
87+
- RMM 库
13388

13489
示例构建命令(在 `executor/mem-cuda` 目录下):
13590

13691
```bash
13792
mkdir -p build && cd build
138-
cmake .. -DCMAKE_BUILD_TYPE=Release -DUSE_RMM=OFF
93+
cmake .. -DCMAKE_BUILD_TYPE=Release
13994
make -j$(nproc)
14095
```
141-
142-
常用 CMake 选项:
143-
144-
- `-DUSE_RMM=ON|OFF`:启用 RMM 适配(需要额外提供 RMM 的 include/link 设置)。
145-
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "redis_client.h"
2+
3+
#include <sw/redis++/redis++.h>
4+
5+
namespace memcuda {
6+
7+
RedisClient::RedisClient(const std::string& uri) {
8+
auto* r = new sw::redis::Redis(uri);
9+
redis_impl_ = static_cast<void*>(r);
10+
}
11+
12+
RedisClient::~RedisClient() {
13+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
14+
delete r;
15+
}
16+
17+
bool RedisClient::HSet(const std::string& key, const std::string& field, const std::string& value) {
18+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
19+
return r->hset(key, field, value);
20+
}
21+
22+
bool RedisClient::HGet(const std::string& key, const std::string& field, std::string& out) const {
23+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
24+
auto val = r->hget(key, field);
25+
if (!val) {
26+
return false;
27+
}
28+
out = *val;
29+
return true;
30+
}
31+
32+
std::unordered_map<std::string, std::string> RedisClient::HGetAll(const std::string& key) const {
33+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
34+
std::unordered_map<std::string, std::string> res;
35+
r->hgetall(key, std::inserter(res, res.begin()));
36+
return res;
37+
}
38+
39+
long long RedisClient::LPush(const std::string& key, const std::string& value) {
40+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
41+
return r->lpush(key, value);
42+
}
43+
44+
bool RedisClient::BRPop(const std::string& key, int timeout_seconds, std::string& out) const {
45+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
46+
auto item = r->brpop(key, std::chrono::seconds(timeout_seconds));
47+
if (!item) {
48+
return false;
49+
}
50+
out = item->second;
51+
return true;
52+
}
53+
54+
std::string RedisClient::Eval(const std::string& script,
55+
const std::vector<std::string>& keys,
56+
const std::vector<std::string>& args) const {
57+
auto* r = static_cast<sw::redis::Redis*>(redis_impl_);
58+
return r->eval<std::string>(script, keys.begin(), keys.end(), args.begin(), args.end());
59+
}
60+
61+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <vector>
5+
#include <unordered_map>
6+
7+
namespace memcuda {
8+
9+
class RedisClient {
10+
public:
11+
explicit RedisClient(const std::string& uri);
12+
~RedisClient();
13+
14+
bool HSet(const std::string& key, const std::string& field, const std::string& value);
15+
bool HGet(const std::string& key, const std::string& field, std::string& out) const;
16+
std::unordered_map<std::string, std::string> HGetAll(const std::string& key) const;
17+
18+
long long LPush(const std::string& key, const std::string& value);
19+
bool BRPop(const std::string& key, int timeout_seconds, std::string& out) const;
20+
21+
std::string Eval(const std::string& script,
22+
const std::vector<std::string>& keys,
23+
const std::vector<std::string>& args) const;
24+
25+
private:
26+
void* redis_impl_;
27+
};
28+
29+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include "registry.h"
2+
3+
#include <fstream>
4+
#include <sstream>
5+
6+
namespace memcuda {
7+
8+
Registry::Registry(RedisClient* client, const std::string& lua_dir)
9+
: client_(client), lua_dir_(lua_dir) {}
10+
11+
std::string Registry::Key(const std::string& name) const {
12+
return "tensor:" + name;
13+
}
14+
15+
std::string Registry::Script(const std::string& file) const {
16+
std::ifstream in(lua_dir_ + "/" + file, std::ios::in | std::ios::binary);
17+
std::ostringstream ss;
18+
ss << in.rdbuf();
19+
return ss.str();
20+
}
21+
22+
std::string Registry::CreateOrGet(const std::string& name,
23+
const std::string& dtype,
24+
const std::string& shape,
25+
long long device,
26+
long long bytes,
27+
const std::string& node,
28+
long long pid,
29+
long long ctime,
30+
const std::string& ipc_handle) {
31+
std::vector<std::string> keys{Key(name)};
32+
std::vector<std::string> args{
33+
dtype,
34+
shape,
35+
std::to_string(device),
36+
std::to_string(bytes),
37+
node,
38+
std::to_string(pid),
39+
std::to_string(ctime),
40+
ipc_handle
41+
};
42+
return client_->Eval(Script("create_or_get.lua"), keys, args);
43+
}
44+
45+
long long Registry::RefInc(const std::string& name) {
46+
std::vector<std::string> keys{Key(name)};
47+
std::vector<std::string> args;
48+
auto res = client_->Eval(Script("ref_inc.lua"), keys, args);
49+
return std::stoll(res);
50+
}
51+
52+
long long Registry::RefDec(const std::string& name) {
53+
std::vector<std::string> keys{Key(name)};
54+
std::vector<std::string> args;
55+
auto res = client_->Eval(Script("ref_dec.lua"), keys, args);
56+
return std::stoll(res);
57+
}
58+
59+
long long Registry::GcSweep(const std::string& node) {
60+
std::vector<std::string> keys;
61+
std::vector<std::string> args{node};
62+
auto res = client_->Eval(Script("gc_sweep.lua"), keys, args);
63+
return std::stoll(res);
64+
}
65+
66+
bool Registry::GetMeta(const std::string& name, TensorMeta& out) const {
67+
auto map = client_->HGetAll(Key(name));
68+
if (map.empty()) {
69+
return false;
70+
}
71+
out.dtype = map["dtype"];
72+
out.shape = map["shape"];
73+
out.node = map["node"];
74+
out.ipc_handle = map["ipc_handle"];
75+
out.state = map["state"];
76+
if (map.count("device")) out.device = std::stoll(map["device"]);
77+
if (map.count("bytes")) out.bytes = std::stoll(map["bytes"]);
78+
if (map.count("refcount")) out.refcount = std::stoll(map["refcount"]);
79+
if (map.count("owner_pid")) out.owner_pid = std::stoll(map["owner_pid"]);
80+
if (map.count("ctime")) out.ctime = std::stoll(map["ctime"]);
81+
return true;
82+
}
83+
84+
RedisClient* Registry::Client() const {
85+
return client_;
86+
}
87+
88+
}

0 commit comments

Comments
 (0)