架构概览
理解 HTS 的内部设计
目录
系统概览
HTS 采用分层架构,将策略与机制分离:
┌─────────────────────────────────────────────────────────────────┐
│ 用户应用层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ TaskBuilder │ │ TaskGroup │ │ TaskBarrier │ │
│ │ (流式 API) │ │ (批量操作) │ │ (同步) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 调度层 │
│ ┌─────────────────┐ ┌───────────────────────────────────────┐ │
│ │ 任务图 │ │ 调度器 │ │
│ │ (DAG 存储) │→ │ ┌───────────┐ ┌───────────────────┐ │ │
│ │ │ │ │ 策略 │ │ 依赖管理器 │ │ │
│ └─────────────────┘ │ └───────────┘ └───────────────────┘ │ │
│ └───────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 执行层 │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ CPU 线程 │ │ GPU 流 │ │ 资源 │ │
│ │ 池 │ │ 管理器 │ │ 限制器 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 内存管理层 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 伙伴系统内存池 │ │
│ │ (高效的 GPU 内存分配) │ │
│ └───────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 可观测性层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 性能分析器 │ │ 日志器 │ │ 事件系统 │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
组件架构
1. 任务图与 DAG 管理
cpp
class TaskGraph {
// 存储任务及其依赖
std::unordered_map<TaskId, TaskPtr> tasks_;
std::unordered_map<TaskId, std::vector<TaskId>> dependencies_;
std::unordered_map<TaskId, std::vector<TaskId>> dependents_;
public:
TaskPtr add_task(DeviceType device);
void add_dependency(TaskId from, TaskId to);
std::vector<TaskId> topological_sort() const;
bool has_cycle() const;
};1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
主要特性:
- 执行前进行环检测
- 拓扑排序确定执行顺序
- 自动依赖追踪
- 线程安全的任务添加
2. 调度器
协调执行的中央控制器:
cpp
class Scheduler {
TaskGraph graph_;
std::unique_ptr<SchedulingPolicy> policy_;
ExecutionEngine executor_;
Profiler profiler_;
public:
void execute();
void set_policy(std::unique_ptr<SchedulingPolicy> policy);
void set_profiling(bool enabled);
};1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
职责:
- 验证任务图(环检测)
- 选择调度策略
- 协调执行引擎
- 管理性能分析和日志
3. 执行引擎
┌─────────────────────────────────────────────┐
│ 执行引擎 │
│ ┌─────────────────────┐ ┌───────────────┐ │
│ │ CPU 线程池 │ │ 流管理器 │ │
│ │ ┌───┐ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ │ │
│ │ │ T │ │ T │ │ T │ │ │ │S0 │ │S1 │ │ │
│ │ └───┘ └───┘ └───┘ │ │ └───┘ └───┘ │ │
│ └─────────────────────┘ └───────────────┘ │
└─────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
CPU 线程池:
- 工作窃取队列实现负载均衡
- 可配置线程数(默认:硬件并发数)
- 线程局部分配缓存
GPU 流管理器:
- CUDA 流池实现并发执行
- 自动流分配
- 跨流依赖的同步原语
4. 调度策略
| 策略 | 算法 | 适用场景 |
|---|---|---|
DefaultPolicy | 基于负载选择 | 通用场景 |
GpuFirstPolicy | 优先 GPU | GPU 密集型工作负载 |
CpuFirstPolicy | 优先 CPU | CPU 密集型工作负载 |
RoundRobinPolicy | 轮询 | 均衡利用 |
ShortestJobFirstPolicy | 优先队列 | 延迟敏感型 |
策略接口:
cpp
class SchedulingPolicy {
public:
virtual DeviceType select_device(
const Task& task,
const SystemStatus& status
) = 0;
};1
2
3
4
5
6
7
2
3
4
5
6
7
5. 内存池
用于高效 GPU 内存分配的伙伴系统分配器:
┌─────────────────────────────────────────────────────────┐
│ 伙伴系统分配器 │
├─────────────────────────────────────────────────────────┤
│ 阶数 0 │ 64B │ ██ │ ██ │ ██ │ ██ │ ██ │ ██ │ ██ │ │
├─────────┼───────┼────┼────┼────┼────┼────┼────┼────┤ │
│ 阶数 1 │ 128B │ ████████ │ ████████ │ │
├─────────┼───────┼────────────────┼────────────────┤ │
│ 阶数 2 │ 256B │ █████████████████ │ │
├─────────┼───────┼──────────────────────────────────┤ │
│ 阶数 3 │ 512B │ █████████████████████████████████ │ │
└─────────────────────────────────────────────────────────┘
█ = 已分配1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
优势:
- 消除 cudaMalloc/cudaFree 开销
- 减少内存碎片
- O(log n) 分配/释放
- 自动合并空闲块
6. 依赖管理器
追踪任务就绪状态和调度:
cpp
class DependencyManager {
std::unordered_map<TaskId, size_t> remaining_deps_;
std::queue<TaskId> ready_queue_;
std::mutex mutex_;
public:
void initialize(const TaskGraph& graph);
std::optional<TaskId> next_ready_task();
void mark_completed(TaskId id);
};1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
数据流
任务执行流水线
┌─────────┐ ┌─────────────┐ ┌─────────────────┐ ┌──────────┐
│ 用户 │ │ 调度器 │ │ 执行引擎 │ │ 设备 │
│ 代码 │───→│ │───→│ │───→│ │
└─────────┘ └─────────────┘ └─────────────────┘ └──────────┘
│ │ │ │
│ ┌───────┴────────┐ ┌────┴────┐ │
│ │ 1. 验证 │ │ 3. 选择 │ │
│ │ 图结构 │ │ 策略 │ │
│ └───────┬────────┘ └────┬────┘ │
│ │ │ │
│ ┌───────▼────────┐ ┌────▼────┐ │
│ │ 2. 拓扑排序 │ │ 4. 执行 │ │
│ └───────┬────────┘ └────┬────┘ │
│ │ │ │
│ └────────────────────┘ │
│ │ │
└──────────────────────────┼──────────────────────────┘
▼
[完成 / 错误 / 重试]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 验证:检查依赖图是否有环
- 拓扑排序:确定执行顺序
- 策略选择:为每个任务选择 CPU 或 GPU
- 执行:分发到适当的执行队列
- 完成:触发依赖任务或处理错误
执行模型
状态机
┌─────────┐
│ 已创建 │
└────┬────┘
│ 添加依赖
▼
┌──────────────────────┐
│ 就绪 │
│ (依赖满足) │
└────┬─────────────────┘
│ 调度
▼
┌───────────────────────────────┐
│ 运行中 │
│ ┌───────────┐ ┌──────────┐ │
│ │ CPU │ │ GPU │ │
│ └───────────┘ └──────────┘ │
└────┬──────────────────────────┘
│
┌────┴────┬──────────┐
│ │ │
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌─────────┐
│ 成功 │ │ 失败 │ │ 已取消 │
└───┬───┘ └───┬───┘ └─────────┘
│ │
│ ┌────┘
│ │ 重试
│ ▼
│ ┌─────────────┐
└→│ 重试中 │
└─────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
线程模型
CPU 执行
主线程 工作线程
│ │
│ submit(task) │
│──────────────┐ │
│ │ │
│ ▼ ▼
│ ┌─────────────────────────┐
│ │ 任务队列 │
│ │ ┌─────┐ ┌─────┐ ┌─────┐│
│ │ │ T1 │ │ T2 │ │ T3 ││
│ │ └─────┘ └─────┘ └─────┘│
│ └─────────────────────────┘
│ │
│ ┌────────┼────────┐
│ │ │ │
│ ▼ ▼ ▼
│ ┌───┐ ┌───┐ ┌───┐
│ │ W1│ │ W2│ │ W3│ ← 工作循环
│ └───┘ └───┘ └───┘
│ │ │ │
│ └────────┼────────┘
│ │
▼ ▼
wait_for_completion() execute(task)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GPU 执行
CPU 线程 GPU 流
│ │
│ launch_kernel(stream) │
│───────────────────────────→│
│ │
│ cudaStreamSynchronize │
│───────────────────────────→│
│ │ 内核执行
│ │
│◄───────────────────────────│ 回调
│ │ (通知完成)1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
设计决策
1. 为什么选择 DAG?
- 表达力:可以表示任何并行模式
- 优化:静态分析实现更好的调度
- 确定性:相同的图总是产生相同的执行
2. 为什么选择伙伴系统内存管理?
| 分配器 | 优点 | 缺点 |
|---|---|---|
| cudaMalloc | 简单 | 高开销、碎片化 |
| 池(固定) | 快速 | 空间浪费、大小限制 |
| 伙伴系统 | 快速、灵活 | 内部碎片(有界) |
3. 为什么采用基于策略的调度?
- 不同工作负载的灵活性
- 易于扩展自定义策略
- 不同策略的 A/B 测试
4. 线程安全模型
| 组件 | 线程安全 | 说明 |
|---|---|---|
| TaskGraph | 读:安全 | 写:仅在 execute() 之前 |
| Scheduler | 安全 | 所有公共方法 |
| ExecutionEngine | 内部 | 内部加锁 |
| MemoryPool | 安全 | 细粒度锁 |
性能特性
| 操作 | 复杂度 | 说明 |
|---|---|---|
| 添加任务 | O(1) | 均摊 |
| 添加依赖 | O(1) | 均摊 |
| 环检测 | O(V + E) | 在 execute() 时执行一次 |
| 拓扑排序 | O(V + E) | 在 execute() 时执行一次 |
| 内存分配 | O(log n) | n = 池大小 |
| 任务调度 | O(1) | 每个任务 |