模型训练和计算过程中的一些优化
优化 I/O 加载 主要是为了减少 CPU-GPU 之间的数据传输瓶颈,提升推理效率。DeepSeek 可能采用了以下几种 极致优化方案:
🔹 1. CPU-GPU 直接访问(Pinned Memory & Zero Copy)
✅ 问题:普通数据传输
• CPU 内存(系统 RAM)和 GPU 显存(VRAM)是分开的,数据传输通常通过 PCIe 总线。
• 如果 CPU 需要把数据传输给 GPU,通常要经过:
• CPU RAM → PCIe → GPU VRAM
• GPU 计算后,数据再传回 CPU
瓶颈:
• PCIe 传输速度有限(PCIe 4.0 x16 带宽 ~32 GB/s,而 HBM 显存可达 1000 GB/s)。
• CPU-GPU 频繁交换数据会导致 I/O 阻塞,影响吞吐量。
🔥 解决方案:Pinned Memory + Zero Copy
✅ Pinned Memory(固定内存)
• 默认情况下,CPU 分配的内存是 可分页的,在传输时需要额外拷贝到 可访问的 DMA(直接内存访问)区域,这很慢。
• 但如果使用 pinned memory(锁页内存),数据就可以 直接传输 给 GPU,减少拷贝时间。
✅ Zero Copy
• 让 GPU 直接访问 CPU 的 Pinned Memory,无需复制到 VRAM!
• 适用于小批量数据传输,减少 PCIe 传输开销。
C++ CUDA 代码示例
#include <cuda_runtime.h> #include <iostream> // 使用 Pinned Memory 加速 CPU-GPU 传输 void testPinnedMemory() { const int SIZE = 1024 * 1024; float* h_data; float* d_data; // 1️⃣ 分配 Pinned Memory cudaMallocHost(&h_data, SIZE * sizeof(float)); // Pinned Memory cudaMalloc(&d_data, SIZE * sizeof(float)); // GPU 显存 // 2️⃣ 进行数据传输 cudaMemcpy(d_data, h_data, SIZE * sizeof(float), cudaMemcpyHostToDevice); // 加速传输 // 3️⃣ 释放内存 cudaFreeHost(h_data); cudaFree(d_data); } int main() { testPinnedMemory(); std::cout << "Pinned Memory 传输完成!" << std::endl; return 0; }
优势:
• Pinned Memory 让数据 直通 PCIe 传输,比普通 CPU RAM 快 30%+!
• Zero Copy 适合小数据访问,避免 PCIe 传输瓶颈。
🔹 2. 直接加载分片权重(Sharded Weights)
✅ 问题:大模型权重加载
• 大模型权重(如 80B 参数)可能超过 400GB+!
• 但单个 GPU(如 A100 80GB)显存不够放下整个模型。
• 普通方法:
• 先从磁盘(SSD)加载到 CPU 内存
• 再从 CPU 传输到 GPU 显存
• 这个 I/O 过程很慢,启动推理需要几十秒!
瓶颈:
• CPU 需要 一次性加载整个模型,导致内存占用高、I/O 堵塞。
解决方案:权重分片 + 按需加载
✅ Sharded Weights(分片权重)
• 把大模型参数拆分成 多个小块,分布到 多个 GPU。
• 每个 GPU 只加载它负责的部分,而不是整个模型!
✅ Lazy Loading(按需加载)
• 只加载 当前推理需要的权重,而不是一次性加载全部参数。
• 提前缓存 KV Cache,避免重复加载权重。
C++ 代码示例(模拟分片加载)
#include <iostream> #include <vector> // 模拟从磁盘加载权重 std::vector<float> loadShard(int shard_id, int shard_size) { std::vector<float> weights(shard_size, 0.1f * shard_id); // 模拟加载 std::cout << "加载权重分片:" << shard_id << std::endl; return weights; } int main() { const int TOTAL_SHARDS = 4; const int SHARD_SIZE = 1024; std::vector<std::vector<float>> model_shards; // 仅按需加载部分权重 for (int i = 0; i < TOTAL_SHARDS; ++i) { model_shards.push_back(loadShard(i, SHARD_SIZE)); } std::cout << "所有分片加载完成!" << std::endl; return 0; }
🚀 优势:
• 减少内存占用:每个 GPU 只加载部分权重,不浪费显存。
• 按需加载:减少 I/O 瓶颈,加快推理启动速度。
🔹 3. 预加载 & Streaming 加载
✅ 问题:磁盘 I/O 慢
• 大模型推理时,磁盘 I/O 可能成为瓶颈(即使是 SSD)。
• 例如:每次推理都需要 重新加载 权重或 KV Cache,会严重影响吞吐量。
🔥 解决方案:Streaming 加载
✅ 预加载权重(Prefetching)
• 在 后台 预加载即将用到的数据,减少 GPU 等待时间。
• 适用于 Transformer Decoder 结构,提前加载下一个 token 需要的权重。
✅ Streaming 数据加载
• 边加载边计算,不会等数据全加载完再计算!
• 适合 长序列推理(如 32K+ tokens),可以分批次加载。
示例:异步加载
#include <iostream> #include <thread> #include <vector> void loadDataAsync(std::vector<float>& data, int size) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 模拟 I/O data.resize(size, 1.0f); std::cout << "数据加载完成!" << std::endl; } int main() { std::vector<float> model_data; // 启动异步加载 std::thread data_loader(loadDataAsync, std::ref(model_data), 1024); std::cout << "同时进行其他计算..." << std::endl; // 等待数据加载完成 data_loader.join(); std::cout << "推理可以继续进行!" << std::endl; return 0; }
🚀 优势:
• 异步加载,不阻塞计算,提升吞吐量。
• 提前加载,减少 GPU 等待数据的时间。
🔹 总结
DeepSeek 通过以下优化 极致加速 I/O 传输:
✅ Pinned Memory + Zero Copy(加速 CPU-GPU 传输,减少 PCIe 瓶颈)
✅ Sharded Weights + Lazy Loading(减少模型加载时间)
✅ Streaming 预加载(长序列推理时不阻塞计算)
这些优化让 大模型推理 时,比传统方法 更快、更省内存