创建仓库
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期四, 十一月 20日 2025, 8:40:05 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 8:48:20 晚上
|
||||
---
|
||||
|
||||
# 2.2.1 锁页内存管理与分配策略 (Page-Locked&Pinned Memory Management)
|
||||
|
||||
### 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
根据前序审计与设计文档,我们面临以下**硬性约束**:
|
||||
|
||||
1. **OS 内存机制**: Kylin V10 (Linux) 使用虚拟内存分页。普通的 `malloc/new` 分配的是**可分页内存 (Pageable Memory)**。
|
||||
2. **DMA 物理限制**: GPU 的 DMA 引擎(Copy Engine)需要访问**物理地址**。如果使用可分页内存,驱动必须先隐式锁定页面(CPU 开销),再分块传输,导致带宽严重下降。
|
||||
3. **吞吐量目标**: 雷达接收模块要求 **\> 10,000 packets/sec**。频繁的系统调用(`malloc` / `free` / `cudaMallocHost`)是不可接受的。
|
||||
4. **硬件平台**: 智铠 MR-V100 的 SDK (CoreX) 兼容 CUDA 10.2 API。
|
||||
|
||||
-----
|
||||
|
||||
### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
#### 议题 1:锁页内存申请 API (Allocation API)
|
||||
|
||||
| 选项 | A. `cudaMallocHost` (推荐) | B. `malloc` + `cudaHostRegister` |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 直接由 GPU 驱动在内核态分配**物理连续**(尽可能)且**已锁定**的内存。 | 用户先申请普通内存,再通知驱动去锁定这些页面。 |
|
||||
| **DMA 性能** | **最高**。驱动对物理地址布局有完全控制权,TLB 命中率高。 | **中等/高**。取决于 OS 分配的物理页碎片化程度。 |
|
||||
| **UVA 适配性** | **完美**。配合 `cudaHostAllocMapped` 标志,可直接映射到 GPU 地址空间(为 2.2.4 铺路)。 | **较差**。虽然也支持 Mapped,但对齐要求严格,容易出错。 |
|
||||
| **CPU 开销** | 分配时开销极大(重系统调用),必须配合**内存池**使用。 | 注册/注销开销大,同样需要配合内存池。 |
|
||||
|
||||
#### 议题 2:内存池架构 (Pool Architecture)
|
||||
|
||||
| 选项 | A. 预分配固定块池 (Fixed-Block Pool) (推荐) | B. 动态堆内存池 (Dynamic Heap) |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 启动时申请一大块内存(如 512MB),切分为 N 个固定大小(如 64KB)的块。 | 像 OS 堆一样支持任意大小的 `alloc/free`。 |
|
||||
| **适配场景** | **雷达原始数据**。脉冲/包大小通常是固定的或有明确上限。 | 通用计算,大小不一的对象。 |
|
||||
| **性能** | **O(1) 极速分配**。无内存碎片。 | O(log n) 分配。存在外部碎片风险。 |
|
||||
| **设计一致性** | 符合 `01_数据接收模块设计.md` 中定义的 `packet_block_size_kb`。 | 增加不必要的复杂度。 |
|
||||
|
||||
-----
|
||||
|
||||
### 三、 基线确立与论证
|
||||
|
||||
基于上述分析,针对 **2.2.1 锁页内存管理与分配策略**,确立以下工程基线:
|
||||
|
||||
#### 1\. 分配 API 基线:`cudaMallocHost` + `cudaHostAllocMapped`
|
||||
|
||||
- **决策**:摒弃标准的 `new/malloc`,**强制**使用 GPU 运行时 API `cudaMallocHost` (在 CoreX SDK 中对应同名 API) 分配所有用于 H2D 传输的缓冲区。
|
||||
- **标志位 (Critical)**:必须使用 **`cudaHostAllocMapped`** 标志。
|
||||
- *论证*:这不仅锁定了页面,还将其映射到了 GPU 的统一地址空间(UVA)。这是实现后续 **2.2.4 零拷贝技术** 的先决条件。如果没有此标志,GPU 无法通过 PCIe 直接访问这块 CPU 内存。
|
||||
- **对齐约束**:`cudaMallocHost` 自动保证了页面对齐(通常 4KB 或 64KB),满足 DMA 要求。
|
||||
|
||||
#### 2\. 内存池策略:静态预分配 + 固定块管理
|
||||
|
||||
- **决策**:在系统初始化阶段(`initialize()`)一次性分配所有所需的锁页内存,**严禁**在 `start()` 后的运行时路径中调用 `cudaMallocHost`。
|
||||
- **实现细节**:
|
||||
- **池大小**:依据设计文档,预分配 **256MB**。
|
||||
- **块大小**:**64KB**。这足以容纳 9000 字节的 JUMBO Frame,且对齐友好。
|
||||
- **管理结构**:使用简单的 `std::stack<void*>` 或无锁队列 `SPSC` 管理空闲块指针,实现 O(1) 复杂度的获取与归还。
|
||||
|
||||
#### 3\. 伪代码规范 (C++14 Standard)
|
||||
|
||||
```cpp
|
||||
// 基线实现范式
|
||||
void* d_ptr; // Device pointer (mapped)
|
||||
void* h_ptr; // Host pointer
|
||||
|
||||
// 1. 必须使用 Mapped 标志以启用 UVA (为 2.2.4 准备)
|
||||
// 2. 必须使用 Portable 标志以支持多 GPU 上下文 (尽管目前是单卡,但在异构设计中是好习惯)
|
||||
unsigned int flags = cudaHostAllocMapped | cudaHostAllocPortable;
|
||||
|
||||
// 3. 分配 - 仅在初始化阶段执行
|
||||
cudaError_t err = cudaHostAlloc(&h_ptr, POOL_SIZE_BYTES, flags);
|
||||
if (err != cudaSuccess) {
|
||||
// 触发 Fatal Error 事件
|
||||
}
|
||||
|
||||
// 4. 获取对应的 Device 指针 (用于后续 2.2.4 Zero-Copy)
|
||||
cudaHostGetDevicePointer(&d_ptr, h_ptr, 0);
|
||||
|
||||
// … 将 h_ptr 切分为 64KB 的块放入 free_list …
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
我们已确立了**如何分配**这块特殊的“高速内存”。接下来,我们需要讨论**如何调度**这块内存的传输,即 **2.2.2 异步流水线与计算通信重叠**,这将决定 GPU 的 Copy Engine 是否能被充分利用,从而掩盖 PCIe 带宽瓶颈。
|
||||
|
||||
**提问**:您是否同意使用带有 `Mapped` 标志的 `cudaMallocHost` 作为分配基线?确认后我们将进入 2.2.2 讨论三级流水线设计。
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
tags:
|
||||
date created: 星期四, 十一月 20日 2025, 9:50:03 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 9:50:24 晚上
|
||||
---
|
||||
|
||||
# 2.2.2 异步流水线与计算通信重叠 (Asynchronous Pipelining & Compute-Copy Overlap)
|
||||
|
||||
**覆盖范围**:定义如何利用智铠 GPU 的独立 Copy Engine (DMA 引擎) 与 Compute Engine (计算引擎) 的并行能力,通过 **CUDA Streams** 实现“传输 - 计算 - 传输”的三级流水线并行,从而掩盖 PCIe 总线的物理延迟。
|
||||
|
||||
#### 一、 约束输入与对齐
|
||||
|
||||
1. **硬件能力**:Iluvatar MR-V100 通常具备独立的 Copy Engine(用于 H2D/D2H)和 Compute Engine。这意味着 **数据拷贝** 和 **Kernel 执行** 在硬件上是物理隔离的,可以同时进行。
|
||||
2. **API 约束**:必须使用 **Async** 系列 API (如 `cudaMemcpyAsync`) 配合 **Non-Default Stream** 才能触发重叠。
|
||||
3. **业务逻辑**:雷达信号处理通常是流式的:`接收(H2D) -> 处理(Kernel) -> 输出(D2H)`。
|
||||
|
||||
#### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们主要在**流的设计模式**上进行权衡:
|
||||
|
||||
| 选项 | A. 单流串行 (Serial Stream) | B. 多流乒乓/多缓冲 (Multi-Stream Ping-Pong) **(推荐)** | C. 细粒度多流 (Hyper-Q) |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 1 个流。H2D -\> Kernel -\> D2H 顺序执行。 | 2-3 个流。Stream A 做计算时,Stream B 做 H2D 拷贝。 | N 个流(N \>\> 3)。将任务切分为极小片。 |
|
||||
| **PCIe 利用率** | **低**。总线在 Kernel 计算期间闲置。 | **高**。总线和计算单元始终处于忙碌状态。 | **极高**,但调度开销大。 |
|
||||
| **延迟掩盖** | 无掩盖。总耗时 = T(copy) + T(compute)。 | **完全掩盖**。理想情况下总耗时 = max(T(copy), T(compute))。 | 同上,但可能引入调度抖动。 |
|
||||
| **实现复杂度** | 低。 | 中。需要管理多个 Buffer 的状态 (Ping-Pong)。 | 高。 |
|
||||
| **适用性** | 调试模式。 | **雷达实时处理标准范式。** | 超大规模并发任务。 |
|
||||
|
||||
#### 三、 基线确立与实施规范
|
||||
|
||||
为了最大化吞吐量,我们确立 **B. 多流乒乓 (Multi-Stream Ping-Pong)** 为设计基线。
|
||||
|
||||
##### 1\. 流水线架构基线:三级流水线 + 双流 (Double Buffering)
|
||||
|
||||
- **核心逻辑**:创建 **2 个 CUDA Stream** (Stream 0, Stream 1) 和 **2 组页锁定内存 Buffer** (Buffer A, Buffer B)。
|
||||
- **调度策略**:
|
||||
- **时刻 T0**:Stream 0 开始传输 Buffer A (H2D)。
|
||||
- **时刻 T1**:
|
||||
- Stream 0 开始处理 Buffer A (Kernel)。
|
||||
- **同时**,Stream 1 开始传输 Buffer B (H2D) —— **此处发生了 Copy 与 Compute 的重叠**。
|
||||
- **时刻 T2**:
|
||||
- Stream 0 开始回传 Buffer A 结果 (D2H)。
|
||||
- Stream 1 开始处理 Buffer B (Kernel)。
|
||||
|
||||
##### 2\. 关键 API 实施规范
|
||||
|
||||
- **流创建**:
|
||||
|
||||
```cpp
|
||||
cudaStream_t streams[2];
|
||||
for(int i=0; i<2; i++) cudaStreamCreateWithFlags(&streams[i], cudaStreamNonBlocking);
|
||||
```
|
||||
|
||||
- *注意*:必须使用 `cudaStreamNonBlocking`,防止与默认流(Default Stream)发生隐式同步,导致流水线断流。
|
||||
- **异步传输**:
|
||||
|
||||
```cpp
|
||||
// 必须使用 Async 版本,且指定 stream
|
||||
cudaMemcpyAsync(d_ptr, h_ptr, size, cudaMemcpyHostToDevice, streams[i]);
|
||||
```
|
||||
|
||||
- **同步策略**:
|
||||
- **严禁**使用 `cudaDeviceSynchronize()`(全卡同步)。
|
||||
- **推荐**使用 `cudaStreamSynchronize(streams[i])` 或 `cudaEventRecord/Synchronize` 来精细控制单个 Buffer 的生命周期,确保 Host 端在复用 Buffer 前,GPU 已经操作完毕。
|
||||
|
||||
##### 3\. 缓冲区管理状态机
|
||||
|
||||
为了配合 `01_数据接收模块` 的 `MemoryPool`,我们需要一个简单的状态机来管理 Buffer 在 Host 和 Device 之间的流转:
|
||||
|
||||
- `HOST_OWNED` (I/O 线程填充数据)
|
||||
- `DEVICE_OWNED_H2D` (正在上传)
|
||||
- `DEVICE_OWNED_COMPUTE` (正在计算)
|
||||
- `DEVICE_OWNED_D2H` (正在回传)
|
||||
- `RELEASED` (回传完毕,归还 Pool)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
%% 状态定义
|
||||
state "HOST_OWNED<br/>(主机所有)" as HOST
|
||||
state "DEVICE_OWNED_H2D<br/>(传输中: H->D)" as H2D
|
||||
state "DEVICE_OWNED_COMPUTE<br/>(计算中: Kernel)" as COMPUTE
|
||||
state "DEVICE_OWNED_D2H<br/>(传输中: D->H)" as D2H
|
||||
state "RELEASED<br/>(待归还)" as RELEASED
|
||||
|
||||
%% 流程流转
|
||||
[*] --> HOST : 从 MemoryPool 申请
|
||||
|
||||
HOST --> H2D : I/O线程填充数据\n并调用 cudaMemcpyAsync
|
||||
note right of HOST
|
||||
此时数据位于页锁定内存
|
||||
CPU 写入完成
|
||||
end note
|
||||
|
||||
H2D --> COMPUTE : 记录 H2D_Event\nStreamWaitEvent
|
||||
note right of H2D
|
||||
DMA 引擎正在搬运
|
||||
CPU 不阻塞
|
||||
end note
|
||||
|
||||
COMPUTE --> D2H : Kernel 执行完毕\n自动触发 D2H
|
||||
note right of COMPUTE
|
||||
GPU 核心正在计算
|
||||
数据驻留显存
|
||||
end note
|
||||
|
||||
D2H --> RELEASED : D2H 完成回调\n或 Event 同步
|
||||
note right of D2H
|
||||
结果已写回 Host
|
||||
end note
|
||||
|
||||
RELEASED --> HOST : DataPacket 析构\n自动归还 Pool
|
||||
|
||||
RELEASED --> [*]
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
我们已经定义了 **“怎么传”**(Pinned Memory)和 **“怎么调度”**(Async Streams)。
|
||||
接下来,我们需要解决 **“传给谁** 的问题,即 **2.2.3 NUMA 感知的内存亲和性控制**。考虑到飞腾 S5000C 的双路架构,如果数据传错了 CPU 节点,上述所有优化都会因为 QPI 总线瓶颈而大打折扣。
|
||||
|
||||
**提问**:您是否同意将 **“双流乒乓 (Double Stream Ping-Pong)”** 作为异步流水线的基线?确认后我们进入 2.2.3 NUMA 亲和性的讨论。
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.2.3 NUMA 感知的内存亲和性控制 (NUMA-Aware Memory Affinity Control)
|
||||
date created: 星期四, 十一月 20日 2025, 10:14:01 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 10:14:41 晚上
|
||||
---
|
||||
|
||||
# 2.2.3 NUMA 感知的内存亲和性控制 (NUMA-Aware Memory Affinity Control)
|
||||
|
||||
### 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于第一章的审计报告,我们面临以下**硬性物理约束**:
|
||||
|
||||
1. **CPU 拓扑**:
|
||||
- **Node 0**: CPU 0-15
|
||||
- **Node 1**: CPU 16-31
|
||||
2. **GPU 位置**:Iluvatar MR-V100 物理挂载在 **Node 1** 上。
|
||||
3. **OS 策略**:`numa_balancing` 已被禁用。这意味着我们不能指望操作系统自动把内存迁移到正确的节点,**必须**手动管理。
|
||||
4. **性能陷阱**:如果 Host 内存分配在 Node 0,而 DMA 引擎在 GPU (Node 1) 上,DMA 读取将必须穿过片间互联总线 (Inter-Chip Interconnect),这通常只有本地内存带宽的一半甚至更低。
|
||||
|
||||
-----
|
||||
|
||||
### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
#### 议题:如何强制内存与计算位于 Node 1?
|
||||
|
||||
| 选项 | A. 仅依赖 `numactl` (进程级绑定) | B. 代码级硬亲和性 (线程级绑定) | C. `mbind` / `set_mempolicy` (API 级内存绑定) |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 在启动命令前加 `numactl --cpunodebind=1 --membind=1`。 | 在 C++ 代码中调用 `pthread_setaffinity_np` 将关键线程钉死在 Core 16-31。 | 在调用 `malloc` / `cudaMallocHost` 前设置内存分配策略。 |
|
||||
| **可靠性** | **高**。这是最稳健的保底方案,确保进程内所有内存页都在 Node 1。 | **极高**。可以精细控制哪个线程跑在哪个核(如 I/O 线程绑 Core 16, Worker 绑 Core 17-20)。 | **中**。`cudaMallocHost` 的行为可能受驱动实现影响,不如 `numactl` 强制有效。 |
|
||||
| **灵活性** | 低。整个进程被限制在半个 CPU 上。 | 高。允许非关键线程(如日志、监控)漂移到 Node 0。 | 高。允许精细控制每块内存的位置。 |
|
||||
| **实施成本** | 零代码修改。运维配置即可。 | 需要修改 `ExecutionEngine` 代码。 | 需要修改内存池代码。 |
|
||||
|
||||
-----
|
||||
|
||||
### 三、 基线确立与实施规范
|
||||
|
||||
为了达成 **P0 级的性能稳定性**,我们采取 **“运维强制 + 代码辅助”** 的双重保险策略。
|
||||
|
||||
#### 1\. 运维基线:全进程约束 (Process-Level)
|
||||
|
||||
- **决策**:所有雷达信号处理进程 **必须** 通过 `numactl` 启动。
|
||||
- **命令规范**:
|
||||
|
||||
```bash
|
||||
# 强制 CPU 和 内存 都在 Node 1
|
||||
numactl --cpunodebind=1 --membind=1 ./main_app
|
||||
```
|
||||
|
||||
- **论证**:这是最底层的安全网。即使代码写错了,OS 也不会把内存分配到 Node 0 去,只会报 OOM (Out of Memory),这比“默默变慢”更容易排查。
|
||||
|
||||
#### 2\. 代码基线:线程亲和性 (Thread-Level)
|
||||
|
||||
- **决策**:在 `ExecutionEngine` 中启动 I/O 线程和 Worker 线程时,**显式设置 CPU 亲和性**。
|
||||
- **资源规划 (示例)**:
|
||||
- **Core 16 (Node 1)**: `DataReceiver` 的 **I/O 线程** (独占,处理中断聚合后的高速包)。
|
||||
- **Core 17-24 (Node 1)**: `SignalProcessor` 的 **计算/Worker 线程** (负责 CUDA API 调用和数据封包)。
|
||||
- **Core 0-15 (Node 0)**: *非关键路径*(日志落盘、监控数据聚合、显控交互)。需要注意,虽然 `numactl` 限制了 `--cpunodebind=1`,但我们可以通过 `numactl --preferred=1` 或者在代码中用 `sched_setaffinity` 突破限制,将非实时任务扔回 Node 0(如果确实需要利用那 16 个核)。**但在 V1.0 阶段,建议简单化,全部限制在 Node 1。**
|
||||
- **C++ 实现规范**:
|
||||
|
||||
```cpp
|
||||
void set_thread_affinity(int core_id) {
|
||||
cpu_set_t cpuset;
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(core_id, &cpuset);
|
||||
// 必须检查返回值,确保绑定成功
|
||||
if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) {
|
||||
// 记录致命错误,因为实时性无法保证
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3\. 内存分配时机:First-Touch 原则
|
||||
|
||||
- **决策**:鉴于我们使用了 `cudaMallocHost`,CUDA 驱动通常会在**调用分配函数的那个线程**所在的 NUMA 节点上分配物理内存(或者遵循进程的 `membind` 策略)。
|
||||
- **规范**:**必须在 `initialize()` 阶段,且在已经绑定了 CPU 亲和性的线程中** 执行 `cudaMallocHost`。
|
||||
- *错误做法*:在主线程(可能还没绑定核)分配内存池,然后传递给工作线程。
|
||||
- *正确做法*:主线程先将自己绑定到 Node 1,或者通过 `numactl` 启动,然后再初始化 `MemoryPool`。
|
||||
|
||||
-----
|
||||
|
||||
### 总结与下一步行动
|
||||
|
||||
我们已经确立了:
|
||||
|
||||
1. **怎么分**:`cudaMallocHost` + Pinned + Mapped (2.2.1)
|
||||
2. **怎么传**:双流乒乓 + 重叠 (2.2.2)
|
||||
3. **在哪传**:**NUMA Node 1** (通过 `numactl` + 线程绑定) (2.2.3)
|
||||
|
||||
现在,物理层和传输层的地基已经打牢。下一步,我们需要讨论 **2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy)**。这将决定我们在某些特定场景下(如传输波控码或小批量参数),是否可以**完全省去** `cudaMemcpy`,直接让 GPU " 伸手 " 到 Host 内存里拿数据。
|
||||
|
||||
**提问**:您是否确认 **“numactl 强制绑定 Node 1 + 关键线程显式钉核”** 的基线?确认后我们进入 2.2.4。
|
||||
116
系统基座文件/2/2.2/2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy).md
Normal file
116
系统基座文件/2/2.2/2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy).md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy)
|
||||
date created: 星期四, 十一月 20日 2025, 10:24:28 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 10:25:20 晚上
|
||||
---
|
||||
|
||||
# 2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
根据审计结果与硬件特性,我们拥有以下有利条件:
|
||||
|
||||
1. **驱动支持 (UVA Ready)**:审计显示 `iluvatar.ko` 模块参数 `itr_enable_vmm_va:Y`,说明智铠驱动已开启虚拟内存管理,支持 UVA。这意味着 Host 指针可以直接被 GPU Kernel 解引用,无需显式指针转换(`cudaHostGetDevicePointer` 仍建议调用以确保兼容性,但逻辑上地址空间是统一的)。
|
||||
2. **物理通道**:PCIe 4.0 x8 (或 x16 修复后)。带宽虽高,但\*\* 延迟(Latency)\*\* 仍远高于访问板载显存(VRAM)。
|
||||
3. **计算特性**:雷达信号处理(FFT、滤波)是**访存密集型**任务,同一个数据点会被多次读取(例如 FFT 的蝶形运算)。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们将数据分为两类场景进行权衡:**“小数据/控制流”** 与 **“大数据/原始回波”**。
|
||||
|
||||
### 场景 A:小数据传输(如波控码、雷达参数、状态字)
|
||||
|
||||
- **特征**:数据量小(\< 4KB),更新频率低,GPU 仅读取一次或极少次。
|
||||
|
||||
| 选项 | 1. 显式拷贝 (`cudaMemcpyAsync`) | 2. 零拷贝直接访问 (Zero-Copy) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `Host -> PCIe -> VRAM -> Kernel` | `Kernel -> PCIe -> Host RAM` |
|
||||
| **启动开销** | **高**。API 调用开销 + DMA 启动开销(约 10-20us)。 | **零**。无 API 调用,Kernel 直接读取指针。 |
|
||||
| **总线效率** | 低。对于几十字节的数据,DMA 建立连接的成本远超传输本身。 | 中。虽然单次 PCIe 访问延迟高,但省去了 DMA 启动时间,总体更快。 |
|
||||
| **适用性** | 不推荐。“杀鸡用牛刀”。 | **最佳实践**。适合传递动态参数结构体。 |
|
||||
|
||||
### 场景 B:大数据传输(原始回波 I/Q 数据)
|
||||
|
||||
- **特征**:数据量大(MB 级),吞吐要求高,Kernel 需**反复多次**读取同一块数据。
|
||||
|
||||
| 选项 | 1. 显式拷贝 (`cudaMemcpyAsync`) **(推荐)** | 2. 零拷贝直接访问 (Zero-Copy) |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `Host -> DMA(Burst) -> VRAM -> Kernel` | `Kernel -> PCIe(TLP) -> Host RAM` |
|
||||
| **访存带宽** | **极高 (VRAM)**。HBM/GDDR 带宽(900GB/s+)。 | **极低 (PCIe)**。受限于 PCIe x8/x16(16-32GB/s)。 |
|
||||
| **TLB 风险** | 无。数据在 VRAM 中物理连续。 | **高 (TLB Miss)**。GPU 需频繁通过 IOMMU 查询 Host 页表,导致流水线停顿。 |
|
||||
| **计算影响** | 计算核心全速运行,无 IO 等待。 | **计算核心饥饿**。Kernel 算几步就要等几百个时钟周期的 PCIe 数据。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
基于上述分析,我们确立 **“小数据零拷贝,大数据显式拷贝”** 的混合策略基线。
|
||||
|
||||
### 1\. 小数据基线:UVA 零拷贝 (Zero-Copy)
|
||||
|
||||
针对雷达的**控制参数**(如 `DataContext` 中的元数据、当前波束指向信息),我们利用 UVA 特性实现零拷贝。
|
||||
|
||||
- **分配规范**:
|
||||
- 继续使用 `cudaMallocHost`。
|
||||
- **必须**添加 `cudaHostAllocMapped` | `cudaHostAllocWriteCombined` 标志。
|
||||
- *注意*:`WriteCombined` (WC) 会禁止 CPU 缓存。这对 CPU 读取极慢,但对 CPU 顺序写入 +GPU 读取性能极佳。因为这些参数通常是 CPU 写一次、GPU 读一次,WC 是绝佳选择。
|
||||
- **访问规范**:
|
||||
- CPU 端:直接写入结构体成员。
|
||||
- GPU 端:将 Host 指针直接传给 Kernel,Kernel 像访问普通显存一样解引用。
|
||||
|
||||
### 2\. 大数据基线:显式异步 DMA (Explicit DMA)
|
||||
|
||||
针对**原始回波数据**(即 `DataReceiver` 传递过来的 Payload),**严禁**使用零拷贝。
|
||||
|
||||
- **决策**:维持 2.2.2 确立的 `cudaMemcpyAsync` 三级流水线。
|
||||
- **论证**:
|
||||
- **带宽瓶颈**:雷达信号处理算法(如 FFT)的算术强度(Compute-to-Memory Ratio)通常较低,主要受限于显存带宽。如果让 Kernel 直接跨 PCIe 去读 Host 内存,带宽将从 \~900GB/s 骤降至 \~16GB/s,导致 GPU 算力闲置率高达 98%,这绝对是不可接受的。
|
||||
- **TLB 抖动**:大数据量的随机访问(或大跨度访问,如转置)会打爆 IOMMU 的 TLB 缓存,引发严重的性能抖动。
|
||||
|
||||
### 3\. 基线实施代码范式
|
||||
|
||||
```cpp
|
||||
// 1. 小数据 (参数/配置): 使用 Zero-Copy + WriteCombined
|
||||
struct RadarParams {
|
||||
float beam_azimuth;
|
||||
float beam_elevation;
|
||||
int waveform_id;
|
||||
};
|
||||
|
||||
RadarParams* h_params;
|
||||
// 分配时启用 Mapped (UVA) 和 WriteCombined (CPU写优化)
|
||||
cudaHostAlloc(&h_params, sizeof(RadarParams),
|
||||
cudaHostAllocMapped | cudaHostAllocWriteCombined);
|
||||
|
||||
// CPU 写入 (极快,直接进 Write Combine Buffer)
|
||||
h_params->beam_azimuth = 45.0f;
|
||||
|
||||
// GPU Kernel 启动 (直接传指针,无需 Memcpy)
|
||||
// d_params 可以通过 cudaHostGetDevicePointer 获取,或者在 UVA 下直接用 h_params
|
||||
process_kernel<<<…>>>(…, h_params);
|
||||
|
||||
|
||||
// 2. 大数据 (回波): 使用 Explicit Async Copy
|
||||
// 维持之前确立的 MemoryPool (Pinned) + Stream Async Copy
|
||||
cudaMemcpyAsync(d_buffer, h_pinned_buffer, size, cudaMemcpyHostToDevice, stream);
|
||||
process_data_kernel<<<…>>>(…, d_buffer); // Kernel 访问的是高速 VRAM
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.2.4 统一虚拟寻址与零拷贝技术** 的基线:
|
||||
|
||||
1. **控制流**:启用 **Zero-Copy (UVA + WC)**,消除小数据的传输开销。
|
||||
2. **数据流**:坚守 **Explicit DMA (VRAM)**,保障计算吞吐量。
|
||||
|
||||
现在,我们解决了“怎么传”、“传给谁”、“谁不用传”。只剩最后一个细节:**“传多大一块?”**
|
||||
|
||||
这是 **2.2.5 传输粒度与 TLP 效率优化**。PCIe 总线传输 1 个字节和传输 128 字节的物理开销(TLP Header)是一样的。如果我们的 `DataPacket` 切分太碎,PCIe 有效带宽就会打折。我们需要结合您的 `01_数据接收模块设计.md` 中的 `packet_block_size_kb` (64KB) 来最后确认这一粒度。
|
||||
|
||||
**提问**:您是否确认 **“控制流零拷贝,数据流显式拷贝”** 的混合基线?确认后我们进入 2.2.5。
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
- 2.2.5 传输粒度与 TLP 效率优化 (Transfer Granularity & TLP Efficiency)
|
||||
date created: 星期四, 十一月 20日 2025, 11:17:17 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:17:43 晚上
|
||||
---
|
||||
|
||||
# 2.2.5 传输粒度与 TLP 效率优化 (Transfer Granularity & TLP Efficiency)
|
||||
|
||||
在确定了物理通道(Node 1)和传输机制(DMA Async)后,这一节解决的是**“一次搬运多少数据最划算”**的问题。这看似是细节,但在 PCIe 物理瓶颈下(x8 降级 + 小 MPS),错误的粒度会导致总线有效带宽暴跌。
|
||||
|
||||
### 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于之前的审计结果和设计文档,我们面临两个严峻的物理与逻辑约束:
|
||||
|
||||
1. **PCIe 物理瓶颈 (P0)**:
|
||||
- **链路状态**:PCIe 4.0 x8 (Downgraded)。
|
||||
- **MPS (Max Payload Size)**:审计发现部分设备仅为 **128 Bytes** 或 **256 Bytes**。
|
||||
- *解读*:这是 PCIe 协议层的最大包长。这意味着无论您上层 DMA 发多大的数据块,到底层都会被切碎成 128 字节的小片。
|
||||
- *代价*:PCIe TLP (Transaction Layer Packet) 头部开销约 12-16 字节。如果 MPS 只有 128 字节,**固定协议开销占比高达 ~10%**。这是物理层“税”,我们无法改变,只能通过上层策略来稀释**驱动层的启动开销**。
|
||||
|
||||
2. **逻辑数据块定义**:
|
||||
- **内存池块大小**:`01_数据接收模块设计.md` 中定义 `packet_block_size_kb` 默认为 **64KB**。
|
||||
- **信号处理单位**:雷达处理通常基于 **CPI (Coherent Processing Interval)** 或 **脉冲 (Pulse)**,其数据量通常在 MB 级别。
|
||||
|
||||
---
|
||||
|
||||
### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们需要在**实时性(低延迟)**和**总线吞吐率**EHOLDER}总线吞吐率**之间寻找平衡点。
|
||||
|
||||
#### 议题:DMA 传输粒度 (Transfer Batch Size)
|
||||
|
||||
| 选项 | A. 单包/单脉冲传输 (Fine-Grained) | B. 块/批次传输 (Coarse-Grained) **和** |
|
||||
| :--- | :--- | :--- |
|
||||
| **(推荐)** | 9KB (1 个 JUMBO Frame) 或 32KB (1 个脉冲) | **粒度示例** (多个脉冲或完整 CPI) |
|
||||
| **64KB - 2MB** | **驱动开销**。每次 DMA 启动都需要 CPU 陷入内核态写寄存器(约 5-10us)。如果每秒 10,000 包,CPU 光启动 DMA 就占满核心。 | **极高**。启动开销被大量数据摊薄。 |
|
||||
| **低** | **PCIe 效率**。频繁的小传输会导致 PCIe 链路在“空闲”和“忙碌”间切换,难以形成突发传输 (Burst),无法填满 MPS 限制下的带宽。 | **低**。长传输能让 PCIe 控制器充分利用总线,连续发送 TLP,达到物理带宽极限。 |
|
||||
| **高** | 理论延迟最低,但容易受 CPU 抖动影响。 | 引入了 **延迟表现** (等待凑够一批数据),但抖动更小,流水线更稳。 |
|
||||
|
||||
---
|
||||
|
||||
### 三、 基线确立与实施规范
|
||||
|
||||
为了在 PCIe x8 和小 MPS 的双重限制下“榨干”带宽,我们必须采取 **“组包延迟”** 的策略。
|
||||
|
||||
#### 1. 传输粒度基线:**“大块聚合”**
|
||||
|
||||
- **≥ 64KB (对齐内存池块)**:确立 **决策** 为最小 DMA 传输单元(Minimum DMA Unit)。
|
||||
- **64KB**:
|
||||
- 您的 `MemoryPool` 设计为 **论证** 一块,这恰好是一个平衡点。
|
||||
- 在 PCIe 4.0 x8 上,传输 64KB 耗时约 4-5us。这足以掩盖 DMA 引擎的启动开销(Launch Overhead),使总线利用率进入“高效区”。
|
||||
- **64KB**针对每个 9KB 的 UDP 包单独发起 `cudaMemcpyAsync`。这会引发 CPU 中断风暴并导致 GPU 指令队列溢出。
|
||||
|
||||
#### 2. 动态批处理策略 (Adaptive Batching)
|
||||
|
||||
考虑到雷达工作模式(搜索/跟踪)的脉冲重复频率(PRF)不同,建议在 `ExecutionEngine` 中实施动态策略:
|
||||
|
||||
- **严禁**:
|
||||
- **策略逻辑**:当 `DataReceiver` 填满一个 64KB 的 `MemoryBlock` 时,立即标记为就绪。
|
||||
- **空间触发**:如果数据流较慢(如低重频模式),设定一个 **时间触发**。如果 200us 内没填满 64KB,强制推送当前已有数据。
|
||||
- **超时阈值 (e.g., 200us)**:防止在低数据率下,为了凑满 64KB 而导致首个数据包滞留过久,破坏 **目的** 的延迟 KPI。
|
||||
|
||||
#### 3. 显存对齐与 TLP 优化
|
||||
|
||||
- **P99 < 5ms**:DMA 的目标地址(GPU 显存)首地址必须 **决策**。
|
||||
- **256 字节对齐**:
|
||||
- 虽然审计显示 MPS 可能是 128B,但为了适配可能的 256B MPS 设备及 GPU 内存控制器的合并访问需求(通常要求 128B/256B 对齐),**论证**是通用且安全的基线。
|
||||
- **256B 对齐**:`cudaMalloc` 分配的内存天然是 256B 对齐的。关键在于如果我们在 Host 端把多个小包拼到一个大 Buffer 里,**实现**最好也是 128B/256B 的倍数。
|
||||
|
||||
#### 4. TLP 效率的终极计算 (Reality Check)
|
||||
|
||||
- **每个子块的偏移量**:MPS = 128 Bytes。
|
||||
- **现状**:每个 TLP 包 = 12-16B Header + 128B Data。
|
||||
- **理论极限**:$128 / (128 + 16) \approx 88.8\%$。
|
||||
- **最高有效率**:无论软件层如何优化,PCIe 层的物理开销决定了您**结论**。在评估带宽 KPI (`> 70% of theoretical max`) 时,必须扣除这 ~11% 的硬件损耗。**永远无法达到 100% 的理论带宽**。
|
||||
|
||||
---
|
||||
|
||||
### 总结与下一步行动
|
||||
|
||||
我们确立了:
|
||||
1. **目标设定为理论值的 75%-80% 是合理的极限**:**粒度** (与内存池对齐),严禁单包传输。
|
||||
2. **最小 64KB**:**策略** 双触发。
|
||||
3. **空间满 (64KB) 或 时间到 (200us)**:强制 **对齐**。
|
||||
|
||||
至此,H2D (Host-to-Device) 的传输策略已完全定型。数据进入显存后,如何存放才能让 GPU 算得快?这是 **256 字节对齐** 的内容,涉及 SoA vs AoS 以及 Padding 策略,这直接影响 Kernel 的访存效率。
|
||||
|
||||
**2.2.6 显存布局与对齐约束**:您是否确认 **提问** 的基线?确认后我们进入 2.2.6。
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期四, 十一月 20日 2025, 11:20:35 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:21:14 晚上
|
||||
---
|
||||
|
||||
# 2.2.6 显存布局与对齐约束 (VRAM Layout & Alignment Constraints)
|
||||
|
||||
- **覆盖范围**:定义雷达数据立方体(Radar Data Cube)在显存中的物理排列格式。重点解决 **SoA (结构数组)** vs **AoS (数组结构)** 的选择、**Padding (填充)** 策略,以及适配 `cuFFT` / `cuBLAS` 库要求的复数存储格式。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 `02_信号处理模块设计.md` 和 GPU 硬件特性,我们要对齐以下约束:
|
||||
|
||||
1. **硬件合并访问 (Coalesced Access)**:GPU 读取显存的最佳模式是“一刀切”。同一个 Warp(32 个线程)必须访问一段**连续且对齐**的内存(通常是 128 字节)。如果数据是跳跃的(Strided),有效带宽会下降 80% 以上。
|
||||
2. **雷达数据立方体特性**:数据具有三个维度:`[通道 (Channel)]`、`[脉冲 (Pulse)]`、`[距离门 (Range)]`。
|
||||
3. **算法库约束**:
|
||||
- **CoreX Math Libs (cuFFT)**:智铠重构版 `cuFFT` 通常要求输入数据为 **Interleaved Complex** (`float2` 或 `cuComplex`,即 `real, imag` 相邻) 或 **Split Complex** (`real[]`, `imag[]` 分离)。标准 CUDA 库倾向于 **Interleaved**。
|
||||
4. **并行维度**:
|
||||
- **脉冲压缩**:在 **距离门** 维度并行。这意味着“距离”维必须是内存中最连续的维度(Stride = 1)。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:复数数据格式 (Complex Number Format)
|
||||
|
||||
| 选项 | A. 交织存储 (Interleaved / AoS) **(推荐)** | B. 分离存储 (Split / SoA) |
|
||||
| :--- | :--- | :--- |
|
||||
| **格式** | `R I R I R I …` (`struct {float r, i}`) | `R R R …` / `I I I …` |
|
||||
| **cuFFT 兼容性** | **原生支持**。`cufftExecC2C` 默认接受此格式。 | 需要使用 `cufftExecZ2Z` 并配置 stride,或者手动转换,稍显麻烦。 |
|
||||
| **访存效率** | **高**。读取一个复数只需一次 64-bit 加载指令(`LD.E`)。 | **中**。读取一个复数需要两次 32-bit 加载指令,且地址相隔很远,增加指令发射压力。 |
|
||||
| **结论** | **基线标准**。 | 不推荐,除非特定算法有强需求。 |
|
||||
|
||||
### 议题 2:数据立方体排列 (Data Cube Layout)
|
||||
|
||||
假设我们处理一个 `C` 通道、`P` 脉冲、`R` 距离门的数据块。
|
||||
|
||||
| 选项 | A. `[Channel][Pulse][Range]` (推荐) | B. `[Range][Pulse][Channel]` |
|
||||
| :--- | :--- | :--- |
|
||||
| **最内层维度** | **Range (距离)**。内存中连续存放 `R0, R1, R2…`。 | **Channel (通道)**。内存中连续存放 `C0, C1, C2…`。 |
|
||||
| **脉冲压缩友好度** | **完美**。FFT 是针对 Range 做的,数据连续,读取效率 100%。 | **灾难**。FFT 需要读 Range 维,这里 Range 维跨度极大,导致严重的 TLB Miss 和非合并访问。 |
|
||||
| **波束合成友好度** | **差**。DBF 需要跨通道计算。但在脉压之后做一次**转置**即可解决。 | **好**。 |
|
||||
| **结论** | **基线标准**。符合“先脉压,后多普勒/DBF”的处理流。 | 仅适用于纯 DBF 前置的特殊雷达。 |
|
||||
|
||||
### 议题 3:行对齐与 Pitch (Padding Strategy)
|
||||
|
||||
显存是按“行”管理的。如果一行的字节数不是 256 字节的倍数,换行访问时就会错位,破坏对齐。
|
||||
|
||||
| 选项 | A. 紧凑排列 (Packed) | B. 对齐填充 (Pitched / Padded) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 数据紧挨着放。`Row1_End` 紧接 `Row2_Start`。 | 在每行末尾填充垃圾数据,使得 `Row_Stride` 是 256B 的倍数。 |
|
||||
| **空间利用** | 100%。 | 略有浪费(\< 1%)。 |
|
||||
| **访问性能** | **不稳定**。如果 `R` 不是 64 的倍数,第二行的起始地址就未对齐,导致 Warp 访问分裂,性能下降。 | **极致稳定**。确保每一行的起始地址都是对齐的,所有 Kernel 都能全速运行。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了让 GPU 的吞吐量 KPI 达标,我们确立以下显存基线:
|
||||
|
||||
### 1\. 数据结构基线:Interleaved Complex (`float2`)
|
||||
|
||||
- **决策**:所有 I/Q 信号在显存中统一存储为 **`float2`** 类型(对应 `std::complex<float>`)。
|
||||
- **规范**:
|
||||
|
||||
```cpp
|
||||
// 严禁使用自定义结构体,直接用 CUDA 内置类型以确保编译器优化
|
||||
using Complex = float2;
|
||||
// 实部: .x, 虚部: .y
|
||||
```
|
||||
|
||||
### 2\. 物理布局基线:`[Channel][Pulse][Range]` + Padding
|
||||
|
||||
- **决策**:采用 **Channel-Major, Pulse-Major, Range-Contiguous** 的布局,并对 **Range** 维度实施 **Pitch 对齐**。
|
||||
- **二维视图**:将三维立方体视为一个巨大的二维矩阵:
|
||||
- **宽度 (Width)**:`Range * sizeof(Complex)`
|
||||
- **高度 (Height)**:`Channel * Pulse`
|
||||
- **分配规范**:**必须**使用 `cudaMallocPitch` 分配显存,而不是 `cudaMalloc`。
|
||||
- *论证*:`cudaMallocPitch` 会自动计算最佳的 `pitch`(字节步长),确保每一行(即每一个脉冲)的起始地址都满足硬件(Iluvatar MR-V100)的 256 字节对齐要求。
|
||||
|
||||
### 3\. 传输与访问规范
|
||||
|
||||
- **H2D 传输**:使用 `cudaMemcpy2DAsync`。
|
||||
- 虽然数据源(Host Pinned Memory)可能是紧凑的,但目标(Device Memory)是有 Padding 的。`cudaMemcpy2D` 能自动处理这种“紧凑到对齐”的搬运。
|
||||
- **Kernel 索引**:
|
||||
|
||||
```cpp
|
||||
// Kernel 代码范式
|
||||
// pitch 是字节单位,需要转为 float2 单位
|
||||
int stride = pitch / sizeof(float2);
|
||||
int p = blockIdx.y; // 脉冲索引 (或 C*P 的扁平索引)
|
||||
int r = threadIdx.x + blockIdx.x * blockDim.x; // 距离门索引
|
||||
|
||||
if (r < Range) {
|
||||
// 使用 stride 而不是 Range 来换行,跳过 Padding 区域
|
||||
Complex data = input_data[p * stride + r];
|
||||
// … 计算 …
|
||||
}
|
||||
```
|
||||
|
||||
## 总结:2.2 章节最终基线图谱
|
||||
|
||||
至此,我们完成了 **2.2 异构 DMA 与内存传输机制** 的全链路设计。从 Host 内存分配到 PCIe 传输,再到显存落地,每一环都扣上了“高性能”的锁扣。
|
||||
|
||||
| 环节 | 基线决策 | 关键配置/API |
|
||||
| :--- | :--- | :--- |
|
||||
| **2.2.1 内存分配** | **锁页内存 + UVA 映射** | `cudaMallocHost` + `cudaHostAllocMapped` |
|
||||
| **2.2.2 传输调度** | **双流乒乓 (Double Buffering)** | 2x `cudaStream_t`, `cudaMemcpyAsync` |
|
||||
| **2.2.3 物理位置** | **NUMA Node 1 本地化** | `numactl --cpunodebind=1 --membind=1` |
|
||||
| **2.2.4 零拷贝** | **控制流 Zero-Copy,数据流 Explicit** | `WC` 标志 (控制流) vs `Async Copy` (数据流) |
|
||||
| **2.2.5 传输粒度** | **≥ 64KB 块,256B 对齐** | 空间/时间双触发策略 |
|
||||
| **2.2.6 显存布局** | **`[C][P][R]` 布局 + Pitch 对齐** | `cudaMallocPitch`, `float2` (Interleaved) |
|
||||
|
||||
-----
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
2.2 章节已圆满完成。我们可以进入 **2.3 内部控制平面通信接口 (Internal Control Plane Interface - IPC)**。
|
||||
|
||||
虽然数据流(Data Plane)的问题解决了,但 Host 端的**任务调度器**如何优雅地指挥**信号处理模块**?如何不通过锁竞争就能实现高频的控制指令下发?这将是 2.3 讨论的重点。
|
||||
|
||||
**提问**:您是否确认 **2.2.6 的 Pitch 对齐与布局基线**?确认后我们结束 2.2 章节,进入 2.3。
|
||||
Reference in New Issue
Block a user