Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 系统与高性能 / Systems & Performance

计算系统 / Computing Systems

1. 计算系统:计算机如何执行与加速程序

2. 从 C++ 源码到 CPU 执行

3. CPU 流水线、乱序执行与分支预测

4. Cache、一致性、伪共享与 NUMA

5. GPU、SM、Warp 与显存

6. 计算执行模型:程序怎样映射到机器

7. SIMD 与编译器向量化

8. C++ 多线程与 OpenMP

9. CUDA 平台与编程模型

10. CUDA Kernel、内存与性能

11. CPU-GPU 异构流水线

12. MPI 与分布式并行

13. 并行算法模式

14. 性能模型与工具

15. 递进学习项目:从单线程到集群

历史完整正文 / Original Deep Dives

1. 历史完整正文:统一前文章逐篇保留

原体系结构与硬件 / Original Architecture

1. 硬件编程与高性能计算:一张可走通的学习地图 / A Practical Learning Map for Hardware Programming and HPC

2. 计算机体系结构:CPU、内存与 GPU / Computer Architecture: CPUs, Memory, and GPUs

3. 计算机架构基础——为什么 GPU 比 CPU 更快 / Computer Architecture Fundamentals: Why GPUs Outperform CPUs

4. 并行计算理论——30 天训练能优化到多快? / Parallel Computing Theory and the Limits of Training Acceleration

5. GPU 架构深入——上万个核心如何分工协作 / GPU Architecture and Massive Parallel Execution

6. CUDA 编程模型——把矩阵乘法映射到 GPU / The CUDA Programming Model for Mapping Matrix Multiplication to GPUs

7. CUDA 内存管理——百亿参数如何装进显存 / CUDA Memory Management for Large Models

8. CUDA 性能优化——从 30 天缩短到 10 天 / CUDA Performance Optimization

9. CPU 并行编程——OpenMP 与 SIMD 向量化 / CPU Parallel Programming with OpenMP and SIMD

10. HPC 集群与 MPI——多节点分布式训练 / HPC Clusters and MPI for Distributed Training

11. 异构计算——CPU 与 GPU 如何协同工作 / Heterogeneous Computing with CPUs and GPUs

12. 深度学习训练优化实战——从 30 天到 3 天 / Deep Learning Training Optimization from Thirty Days to Three

13. 性能分析工具——找到真正的瓶颈 / Performance Analysis Tools for Finding Real Bottlenecks

14. NPU 全景——昇腾/寒武纪/TPU/苹果生态 / The NPU Landscape: Ascend, Cambricon, TPU, and Apple

15. 未来趋势——2030 年的计算机会是什么形态 / Future Computing Trends Toward 2030

16. 硬件与高性能计算:从“程序为什么慢”开始 / Hardware and HPC Starting from Why Programs Are Slow

原并行计算 / Original Parallel Computing

1. 并行计算:从 SIMD 到 MPI / Parallel Computing from SIMD to MPI

2. 并行计算全景:从晶体管、CPU、GPU 到计算集群 / Parallel Computing from Transistors, CPUs, and GPUs to Clusters

3. 并行计算基础:任务分解、加速比与可扩展性 / Parallel Computing Fundamentals: Decomposition, Speedup, and Scalability

4. 处理器体系结构:从指令流水线到多核芯片 / Processor Architecture from Instruction Pipelines to Multicore Chips

5. CPU 并行:多线程、SIMD、Cache 一致性与 NUMA / CPU Parallelism with Threads, SIMD, Cache Coherence, and NUMA

6. 内存层次:Cache、带宽、局部性与一致性 / Memory Hierarchies, Bandwidth, Locality, and Coherence

7. GPU 体系结构:SIMT、Warp、SM 与吞吐优先设计 / GPU Architecture with SIMT, Warps, and Streaming Multiprocessors

8. CUDA 编程模型:Thread、Block、Grid 与内存协作 / CUDA Threads, Blocks, Grids, and Cooperative Memory Access

9. 并行算法模式:Map、Reduce、Scan、Stencil 与任务图 / Parallel Patterns: Map, Reduce, Scan, Stencil, and Task Graphs

10. 异构计算:CPU、GPU、NPU 如何协同工作 / Heterogeneous Computing with CPUs, GPUs, and NPUs

11. 分布式并行:MPI、集合通信、RDMA 与多机多卡 / Distributed Parallelism with MPI, Collective Communication, and RDMA

12. 性能工程:测量、Roofline、瓶颈定位与优化闭环 / Performance Engineering with Measurement, Roofline, and Bottleneck Analysis

13. 并行计算实战:AI、CAE、图像与科学计算 / Parallel Computing for AI, CAE, Imaging, and Scientific Computing

14. 并行计算实践路线:从单核优化到多机多卡 / A Parallel Computing Project Path from Single-Core to Multi-Node GPUs

本页目录

异构计算——CPU 与 GPU 如何协同工作 / Heterogeneous Computing with CPUs and GPUs ​

📅 创建时间:2026-06-02 🏷️ 标签:#异构计算 #CPU #GPU #流水线 #异步执行 #PinnedMemory 📚 前置知识:[[04-cuda-programming-model]](CUDA 编程) [[08-mpi-cluster-hpc]](集群通信) 📚 相关知识:[[10-dl-training-optimization]](训练优化实战)


先抓住直觉 ​

CPU 与 GPU 不是竞争关系,而是一条流水线的不同工位。最差的情况是 CPU 准备数据时 GPU 闲着,GPU 计算时传输通道又闲着;协同优化就是让准备、搬运和计算尽量重叠。

  • 必须理解:异步执行、流水线、Pinned Memory 为何能帮助传输。
  • 用到再查:Stream API、Unified Memory 提示函数和双缓冲模板。
  • 读完能回答:为什么 GPU 利用率低不一定是 GPU Kernel 的问题?

场景:CPU 和 GPU 谁才是瓶颈? ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  训练一个 epoch,耗时分析:                               │
│                                                             │
│  GPU Forward/Backward:  10 秒(90%)                    │
│  CPU 数据加载:         1 秒(9%)                        │
│  CPU→GPU 数据传输:    0.1 秒(1%)                      │
│                                                             │
│  看起来 GPU 占据了大部分时间。                           │
│  但为什么 GPU 利用率只有 60%?                          │
│                                                             │
│  原因:CPU 数据加载太慢,GPU 经常在等待数据!           │
│                                                             │
│  本章学习如何让 CPU 和 GPU 协同工作,最大化效率。     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

第1节:异构系统的基本架构 ​

CPU-GPU 数据流 ​

┌─────────────────────────────────────────────────────────────┐
│                    CPU-GPU 异构系统                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                        CPU                                  │
│  ┌─────────────────────────────────────────────────────┐  │
│  │                                                      │  │
│  │   数据加载    数据预处理    调度控制    日志记录    │  │
│  │   (SSD)      (OpenMP)     (CUDA API)              │  │
│  │                                                      │  │
│  └────────────────────────────┬────────────────────────┘  │
│                               │ PCIe                        │
│                               ↓                             │
│  ┌────────────────────────────┐                           │
│  │       GPU 显存 (HBM)       │                           │
│  │  ┌──────────────────────┐  │                           │
│  │  │  Forward  |  Backward │  │  ← GPU 计算            │
│  │  │  Kernel   |  Kernel   │  │                           │
│  │  └──────────────────────┘  │                           │
│  └────────────────────────────┘                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

关键问题:
1. 数据从 SSD → CPU → GPU,链路长
2. 如果 CPU 太慢,GPU 等待
3. 需要 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

异步执行原理 ​

cpp
// ❌ 同步执行:CPU 等 GPU,浪费时间
cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);
kernel<<<...>>>(d_data);  // GPU 执行时 CPU 空闲
cudaMemcpy(h_result, d_result, size, cudaMemcpyDeviceToHost);

// ================================================================
// ✅ 异步执行:CPU 和 GPU 并行工作
// ================================================================
cudaStream_t stream1, stream2;
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);

// CPU 做数据加载(stream1)
cudaMemcpyAsync(d_data, h_data, size, cudaMemcpyHostToDevice, stream1);

// GPU 执行 kernel(stream1)
kernel<<<blocks, threads, 0, stream1>>>(d_data);

// CPU 同时做其他事情(不阻塞!)
preprocess_next_batch_cpu();  // CPU 预处理下一批
log_to_disk();                 // CPU 写日志

// 最后才等待 GPU 完成
cudaStreamSynchronize(stream1);
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节:Pinned Memory——加速 CPU-GPU 传输 ​

Pageable vs Pinned Memory ​

┌─────────────────────────────────────────────────────────────┐
│                    Pinned Memory(页锁定内存)                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Pageable Memory(普通内存):                              │
│  - 操作系统可以把这块内存换页到磁盘(swap)                │
│  - CPU 访问可能触发缺页中断                                │
│  - GPU 传输前需要先拷贝到临时缓冲区                        │
│  → 传输慢                                                  │
│                                                             │
│  Pinned Memory(页锁定内存):                             │
│  - 永远保持在物理内存,不会被换页                         │
│  - GPU 可以直接传输,不需要临时拷贝                        │
│  → 传输快 2-3 倍!但会占用系统内存                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Pinned Memory 代码 ​

cpp
// 普通方式(pageable memory)
float* h_data = (float*)malloc(size);  // pageable
cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);

// ================================================================
// Pinned Memory 方式
// ================================================================
float* h_data_pinned;
cudaMallocHost(&h_data_pinned, size);  // 分配页锁定内存

// 使用方式一样,但传输更快
cudaMemcpy(d_data, h_data_pinned, size, cudaMemcpyHostToDevice);

// 释放
cudaFreeHost(h_data_pinned);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

异步传输 + Pinned Memory ​

cpp
// 最佳实践:Pinned Memory + Async + Double Buffering
cudaStream_t stream;
cudaStreamCreate(&stream);

// 两套缓冲区,轮换使用
float *d_buf[2], *h_buf[2];
for (int i = 0; i < 2; i++) {
    cudaMalloc(&d_buf[i], size);
    cudaMallocHost(&h_buf[i], size);  // pinned memory
}

int buf_idx = 0;
bool first_iteration = true;

// 主循环
while (has_more_data()) {
    // CPU 加载下一批数据到当前缓冲区
    load_data(h_buf[buf_idx], size);

    // CPU→GPU 异步拷贝(数据加载完成后)
    cudaMemcpyAsync(d_buf[buf_idx], h_buf[buf_idx],
                    size, cudaMemcpyHostToDevice, stream);

    // 如果不是第一次,等待上一个 kernel 完成
    if (!first_iteration) {
        int prev_idx = 1 - buf_idx;
        cudaStreamSynchronize(stream);  // 等待前一批 GPU 计算完成
        // 处理前一批的结果
        process_results(d_buf[prev_idx]);
    }

    // 启动当前批次的 kernel(与下一次 CPU 加载并行!)
    kernel<<<blocks, threads, 0, stream>>>(d_buf[buf_idx], size);

    buf_idx = 1 - buf_idx;  // 切换缓冲区
    first_iteration = false;
}
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
33
34
35
36
37

第3节:CUDA Streams 实现流水线 ​

数据加载流水线 ​

┌─────────────────────────────────────────────────────────────┐
│                    3-Stage 流水线                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Time →                                                    │
│                                                             │
│  Stage 1: [Load GPU0][Load GPU1][Load GPU2][Load GPU3]...│
│  Stage 2:     [Preprocess][Preprocess][Preprocess]...   │
│  Stage 3:         [GPU Fwd][GPU Fwd][GPU Fwd]...        │
│                                                             │
│  理想情况:3 个阶段并行,满载运转                          │
│                                                             │
│  如果数据加载太慢(Stage 2 瓶颈):                        │
│  Stage 1: [Load][Load][Load][Load][Load][Load]...       │
│  Stage 2:      [Pre ][Pre ][Pre ][Pre ][Pre ][Pre ]    │
│  Stage 3:           [Fwd ][Fwd ][Fwd ][Fwd ]            │
│             ─────────→ GPU 大量空闲等待                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

多 Stream 实现流水线 ​

cpp
// 3-Stream 流水线实现
cudaStream_t stream_load, stream_preprocess, stream_compute;
cudaStreamCreate(&stream_load);
cudaStreamCreate(&stream_preprocess);
cudaStreamCreate(&stream_compute);

// 使用事件控制流水线顺序
cudaEvent_t preprocess_done, compute_done;

int batch = 0;
while (true) {
    // Stage 1: CPU 加载数据到 pinned memory
    load_data_cpu(h_pinned[batch % 2]);

    // 等待 preprocess 完成,才能开始下一次加载
    if (batch > 0) {
        cudaEventSynchronize(preprocess_done);
    }

    // 异步拷贝到 GPU
    cudaMemcpyAsync(d_pinned[batch % 2], h_pinned[batch % 2],
                     size, cudaMemcpyHostToDevice, stream_load);

    // Stage 2: GPU 预处理(resize, normalize 等)
    preprocess<<<blocks, threads, 0, stream_preprocess>>>(
        d_pinned[batch % 2], d_preprocessed[batch % 2]);
    cudaEventRecord(preprocess_done, stream_preprocess);

    // 等待 preprocess 完成,才能开始计算
    cudaEventSynchronize(preprocess_done);

    // Stage 3: GPU 训练
    train<<<blocks, threads, 0, stream_compute>>>(
        d_preprocessed[batch % 2], model_params);
    cudaEventRecord(compute_done, stream_compute);

    batch++;
}
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
33
34
35
36
37
38

第4节:统一内存的异构使用 ​

┌─────────────────────────────────────────────────────────────┐
│                    Unified Memory 在异构场景的应用              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  场景:CPU 和 GPU 交替访问同一块数据                       │
│                                                             │
│  例如:                                                    │
│  - CPU 加载数据 → GPU 处理 → CPU 读取结果 → CPU 写日志   │
│  - 循环很多次                                             │
│                                                             │
│  传统方式:每次都需要 cudaMemcpy                         │
│  Unified Memory:自动迁移,无需手动管理                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cpp
// Unified Memory 方式
float* data;
cudaMallocManaged(&data, size);

// CPU 处理
preprocess_cpu(data, size);

// GPU 处理(OS 自动把数据迁移到 GPU)
cudaMemcpy(data, size, cudaMemcpyHostToDevice);  // 显式提示迁移
train<<<blocks, threads>>>(data, size);
cudaDeviceSynchronize();

// CPU 读取结果(OS 自动把数据迁移回 CPU)
read_results_cpu(data, size);

// 性能提示:预取到 GPU
cudaMemPrefetchAsync(data, size, 0);  // deviceId=0 = GPU
train<<<blocks, threads>>>(data, size);
cudaDeviceSynchronize();

// 预取回 CPU
cudaMemPrefetchAsync(data, size, cudaCpuDeviceId);
read_results_cpu(data, size);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

第5节:CPU 和 GPU 的任务分配策略 ​

┌─────────────────────────────────────────────────────────────┐
│                    CPU vs GPU 任务分配                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  适合 CPU:                                                │
│  - IO 密集型:磁盘读写、网络通信                          │
│  - 分支密集型:if-else 多的逻辑                          │
│  - 串行任务:数据预处理、格式转换                         │
│  - 任务量小的任务:启动 GPU 开销不划算                   │
│                                                             │
│  适合 GPU:                                                │
│  - 计算密集型:矩阵乘法、卷积                             │
│  - 数据并行:大量相同操作                                  │
│  - 算术强度高:计算量 >> 内存访问量                     │
│                                                             │
│  实际训练中的分配:                                       │
│  ┌─────────────────────────────────────────────────────┐  │
│  │ CPU: 数据加载 → Tokenize → Resize → Normalize    │  │
│  │                          ↓ (Pinned Memory + Async)  │  │
│  │ GPU: Embedding → Transformer → Loss → Backward   │  │
│  │                    ↑ (CUDA Stream 并行)            │  │
│  │ CPU: 日志记录 → Checkpoint → Metrics 计算        │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  核心原则: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

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ cudaStreamCreate/cudaEventCreate 的具体参数
✅ Pinned Memory 的具体使用限制
✅ Unified Memory prefetch 的具体 API

必须理解:
🔴 CPU-GPU 数据传输是瓶颈,需要异步化
🔴 Pinned Memory 比普通内存传输快 2-3 倍
🔴 Double Buffering:两套缓冲区轮换,CPU 和 GPU 并行工作
🔴 CUDA Stream 实现流水线:Load → Preprocess → Compute
🔴 任务分配:CPU 做 IO/预处理,GPU 做计算密集型任务
1
2
3
4
5
6
7
8
9
10
11

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇10. HPC 集群与 MPI——多节点分布式训练 / HPC Clusters and MPI for Distributed Training
下一篇12. 深度学习训练优化实战——从 30 天到 3 天 / Deep Learning Training Optimization from Thirty Days to Three

持续记录,持续成长

Copyright © Tidenflow