NVIDIA Grace Hopper NVLink-C2C 统一内存与 Linux 内核页迁移在 AI 推理工程中的深度实践
引言
当我们谈论 AI 推理基础设施时,GPU 与 CPU 之间的数据搬运始终是性能瓶颈的核心。传统的 PCIe 分离内存模型要求开发者显式地管理 Host↔Device 拷贝,不仅增加了代码复杂度,更在高并发推理场景下造成带宽浪费和延迟抖动。NVIDIA Grace Hopper 超级芯片通过 NVLink-C2C 总线将 Grace CPU 与 Hopper GPU 封装在同一基板上,实现了硬件级统一内存(Unified Memory),从根本上改变了这一格局。
本文将从硬件架构、Linux 内核页迁移机制、统一内存编程模型、AI 推理场景的实战优化四个维度,完整解析这一技术栈的工程落地路径。
---
一、Grace Hopper 硬件架构剖析
1.1 NVLink-C2C 系统互连
Grace Hopper 超级芯片(如 GH200)的核心创新在于抛弃了传统的 PCIe 5.0 作为 CPU-GPU 互连通道,转而采用 NVLink-C22 一致性总线:
| 指标 | PCIe 5.0 x16 | NVLink-C2C (Grace Hopper) | NVLink 4.0 (H100 集群) |
|---|---|---|---|
| 双向带宽 | ~128 GB/s | ~900 GB/s | ~900 GB/s |
| 延迟 | ~1μs | ~250ns | ~300ns |
| 一致性 | 无(需显式拷贝) | 硬件缓存一致性 | GPU间一致,CPU侧无 |
| 内存模型 | 分离地址空间 | 统一地址空间 | 分离地址空间 |
关键差异在于 NVLink-C2C 提供了硬件缓存一致性(Hardware Cache Coherence),这意味着 CPU 的 L3 Cache 和 GPU 的 L2 Cache 在硬件层面保持一致性,无需软件层面的缓存刷新操作。
1.2 内存子系统拓扑
在 Grace Hopper 架构中:
- Grace CPU:搭载 LPDDR5X 内存(4800MHz),容量可达 480GB,提供 ~500 GB/s 的内存带宽
- Hopper GPU:搭载 HBM3/HBM3e,容量可达 96GB(H200)或 141GB(GH200),带宽 > 5 TB/s
- 统一地址空间:CPU 和 GPU 共享同一虚拟地址,页面可以在两种物理内存之间透明迁移
- 带宽提升 7 倍:NVLink-C2C 使页面迁移的实际带宽不再受限于 PCIe
- 一致性协议:硬件层面保证 CPU/GPU 缓存一致性,迁移时无需无效化远程缓存行
- 大页面支持:硬件原生支持 2MB/1GB 大页在统一内存中的迁移
- 页错误触发:GPU MMU 检测到该页面本地不可用,触发 Page Fault
- 故障分类:UVM 驱动判断故障类型(Minor Fault / Major Fault / Migration)
- 迁移决策:
- 若该页面被 CPU 频繁访问 → 保留在 CPU 内存
- 若该页面主要被 GPU 访问 → 迁移到 HBM
- 若访问模式不确定 → 启用访问计数器(Access Counter)进行动态跟踪
- 实际迁移:
- 分配目标 HBM 页面
- 通过 NVLink-C2C DMA 引擎执行页面拷贝
- 更新 GPU 页表映射到新物理地址
- 释放原 LPDDR5X 页面(若不再被 CPU 引用)
- 一致性维护:硬件自动处理 CPU 缓存中该页面旧副本的无效化
- 当 GPU 页面发生 fault,UVM 不立即迁移,而是记录一次访问
- 若同一页面在短时间窗口内(默认约 10ms)连续发生 N 次 fault,认为该页面应迁移到 GPU
- 这一"懒惰迁移"策略避免了乒乓效应(thrashing),即页面在 CPU/GPU 之间反复迁移导致的性能恶化
- 利用统一内存的透明页迁移,实现"按需加载"而非"全量预加载"
- 第一层权重预取到 HBM 后开始推理,后续层在推理过程中自动迁出/迁入
- 适用于模型总大小超过 HBM 容量的场景
- 使用 cudaMemAdvise 明确偏好位置:一旦确定某页面的主要访问者,立即设置 PreferredLocation
- 批量操作聚合:避免单个页面被 CPU 和 GPU 交替访问,改为批量读写后再切换
- 大页配置:启用 2MB 或 1GB 大页,减少迁移的页面数量(从 4KB 页面管理开销降低 512x)
- 优先使用 cudaMemAdvise 明确数据流向,避免运行时猜测带来的乒乓效应
- 结合 cudaMemPrefetchAsync 实现流水线化的数据预取,将页面迁移开销隐藏在计算时间内
- 利用 CUDA Graph 固化推理模式,使页面状态在首次捕获后保持稳定
- 监控 UVM 统计信息和 NVLink-C2C 健康状态,及时发现性能退化
- NVIDIA GH200 Grace Hopper Superchip Architecture Whitepaper
- CUDA Programming Guide: Unified Memory
- Linux Kernel Documentation: Memory Management (mm/migrate.c)
- NVIDIA UVM Driver Internals (Kernel Module)
- CUDA C++ Programming Best Practices Guide
对于 AI 推理工作负载而言,这种架构的意义在于:模型的权重驻留在 HBM 中,而 CPU 侧的前处理、后处理、请求调度可以直接访问同一页面,零拷贝开销。
1.3 与 Unified Memory 的历史对比
NVIDIA 在 Pascal 架构(2016 年)首次引入 Unified Memory,但当时基于 PCIe,页面迁移由 UVM(Unified Virtual Memory)驱动按需触发页错误,延迟极高。Grace Hopper 的优势在于:
---
二、Linux 内核统一内存迁移机制
2.1 内核相关子系统
Grace Hopper 统一内存的页迁移依赖于 Linux 内核的多个子系统协同工作:
┌──────────────────────────────────────────────────────┐
│ 用户空间 CUDA Runtime │
│ cudaMallocManaged() / cudaMemAdvise() │
└──────────────────────┬───────────────────────────────┘
│ uvm驱动 / kernel module
┌──────────────────────▼──────────────────────────────┐
│ NVIDIA UVM (Unified Virtual Memory) │
│ - 页面迁移决策(Migrator) │
│ - 访问计数器(Access Counter) │
│ - 内存建议执行(MemAdvise) │
│ - 预取引擎(Prefetcher) │
└──────────────────────┬───────────────────────────────┘
│ 调用
┌──────────────────────▼──────────────────────────────┐
│ Linux Memory Management │
│ - migrate_pages() / try_to_migrate_pages() │
│ - page table migration (NUMA balancing) │
│ - Huge page split/migrate │
│ - LRU scan and page reclaim │
└──────────────────────────────────────────────────────┘
2.2 页迁移的完整路径
当一个 GPU 线程访问一个当前位于 CPU 物理内存(LPDDR5X)中的页面时:
2.3 关键内核接口
在 Linux 内核源码中,与统一内存迁移相关的核心接口包括:
// 页面迁移入口(mm/migrate.c)
int migrate_pages(struct list_head *l, new_page_t get_new_page,
free_page_t free_page, unsigned long private,
enum migrate_mode mode, int reason);
// NUMA 自动均衡中的页面迁移(mm/mempolicy.c)
static int migrate_to_node(struct mm_struct *mm, int source, int dest,
int flags);
// 大页分裂与迁移(mm/huge_memory.c)
int split_huge_page_to_list(struct page *page, struct list_head *list);
// 页面隔离(mm/page_isolate.c)
int start_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn,
unsigned long migratetype,
bool skip_hwpoison, bool isolated);
2.4 访问计数器(Access Counter)机制
这是 Grace Hopper 相比前代 Unified Memory最重要的改进。UVM 驱动维护一个访问计数器,跟踪每个页面的 CPU/GPU 访问频率:
页面状态机:
GPU访问 CPU访问
┌─────────────┐ ──────────> ┌─────────────┐ ──────────> ┌─────────────┐
│ CPU Resident│ │ Migrating │ │ GPU Resident│
│ (CPU内存) │ <────────── │ (迁移中) │ <────────── │ (HBM内存) │
└─────────────┘ GPU迁移完成 └─────────────┘ CPU迁移完成 └─────────────┘
↑ │
│<──────────────── 双向迁移 ────────────────────────────┘
访问计数器的工作流程:
---
三、CUDA 统一内存编程模型实战
3.1 基础用法:cudaMallocManaged
#include <cuda_runtime.h>
#include <cstdio>
// 统一内存分配示例
void* allocate_unified(size_t bytes) {
void* ptr;
cudaError_t err = cudaMallocManaged(&ptr, bytes);
if (err != cudaSuccess) {
fprintf(stderr, "cudaMallocManaged failed: %s\n", cudaGetErrorString(err));
return nullptr;
}
return ptr;
}
// 在统一内存中初始化矩阵(可在 CPU 或 GPU 端访问)
void init_matrix(float* matrix, int rows, int cols) {
for (int i = 0; i < rows * cols; i++) {
matrix[i] = static_cast<float>(rand()) / RAND_MAX;
}
}
3.2 内存建议:cudaMemAdvise
这是性能优化的关键 API,允许开发者向运行时提示页面的访问模式:
#include <cuda_runtime.h>
// 场景:模型权重初始化在 CPU,推理在 GPU
class GPUMemoryPool {
private:
void* weight_ptr_;
void* input_ptr_;
void* output_ptr_;
size_t weight_size_;
size_t input_size_;
size_t output_size_;
public:
GPUMemoryPool(size_t weights, size_t input, size_t output)
: weight_size_(weights), input_size_(input), output_size_(output) {
// 模型权重:分配给 GPU 并提示 CPU 不需要访问
cudaMallocManaged(&weight_ptr_, weight_size_);
// SetPreferredLocation: 建议页面优先驻留在 GPU
cudaMemAdvise(weight_ptr_, weight_size_,
cudaMemAdviseSetPreferredLocation,.cudaDeviceId);
// SetAccessedBy: CPU 不会频繁访问这些权重
cudaMemAdvise(weight_ptr_, weight_size_,
cudaMemAdviseSetAccessedBy, cudaDeviceId);
// 输入缓冲区:可能被 CPU 填充,GPU 消费
cudaMallocManaged(&input_ptr_, input_size_);
cudaMemAdvise(input_ptr_, input_size_,
cudaMemAdviseSetPreferredLocation, cudaDeviceId);
// 输出缓冲区:GPU 写入,CPU 读取
cudaMallocManaged(&output_ptr_, output_size_);
cudaMemAdvise(output_ptr_, output_size_,
cudaMemAdviseSetPreferredLocation, cudaDeviceId);
cudaMemAdvise(output_ptr_, output_size_,
cudaMemAdviseSetAccessedBy, cudaCpuDeviceId);
}
void prefetch_weights(cudaStream_t stream) {
// 预取权重到 GPU 显存,避免首次访问页错误
cudaMemPrefetchAsync(weight_ptr_, weight_size_,
cudaDeviceId, stream);
}
void prefetch_output(cudaStream_t stream) {
// 推理完成后预取输出到 CPU 可访问位置
cudaMemPrefetchAsync(output_ptr_, output_size_,
cudaCpuDeviceId, stream);
}
};
3.3 预取策略:cudaMemPrefetchAsync
对于具有明确数据流模式的推理管道,手动预取可以消除运行时页错误带来的延迟抖动:
// 推理管道中的预取模式
void inference_pipeline(GPUMemoryPool& pool,
float* host_input,
float* host_output,
cudaStream_t stream) {
// Step 1: 预取当前 batch 的输入数据到 GPU
cudaMemcpyAsync(pool.input_ptr(), host_input, pool.input_size(),
cudaMemcpyHostToDevice, stream);
// Step 2: 确保权重已在 GPU(预取或已在 HBM)
pool.prefetch_weights(stream);
// Step 3: 执行 GPU 推理内核
run_inference_kernel<<<grid, block, 0, stream>>>(
pool.weight_ptr(), pool.input_ptr(), pool.output_ptr());
// Step 4: 将输出预取回 CPU 可访问位置(异步)
pool.prefetch_output(stream);
// Step 5: 流同步后 CPU 可直接读取 host_output
cudaStreamSynchronize(stream);
memcpy(host_output, pool.output_ptr(), pool.output_size());
}
3.4 CUDA Graph 与统一内存
CUDA Graph 在 Grace Hopper 统一内存模型下大放异彩:
// 利用 CUDA Graph 捕获重复的推理模式
class CUDAGraphExecutor {
private:
cudaGraph_t graph_;
cudaGraphExec_t instance_;
bool graph_created_ = false;
public:
void capture_inference(void* weights, void* input, void* output,
int batch_size, cudaStream_t stream) {
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
// 在捕获期间执行一次完整推理
run_inference_kernel<<<grid, block, 0, stream>>>(
static_cast<float*>(weights),
static_cast<float*>(input),
static_cast<float*>(output));
cudaStreamEndCapture(stream, &graph_);
cudaGraphInstantiate(&instance_, graph_, nullptr, nullptr, 0);
graph_created_ = true;
}
void launch(cudaStream_t stream) {
if (graph_created_) {
cudaGraphLaunch(instance_, stream);
}
}
};
在 Grace Hopper 上,CUDA Graph 与统一内存配合的独特优势:页面在首次捕获时已预取到 HBM,后续重放完全消除页错误开销,推理延迟可降低 40-60%(取决于模型大小和 batch size)。
---
四、AI 推理场景深度优化
4.1 大模型推理的内存分层策略
在实际的大模型(如 Llama 3 70B)推理部署中,Grace Hopper 的统一内存架构允许设计更灵活的内存管理策略:
┌────────────────────────────────────────────────────────────────┐
│ Llama 3 70B 推理内存层次设计 │
├──────────────────┬──────────────────┬──────────────────────────┤
│ 层级 │ 物理位置 │ 内容 │
├──────────────────┼──────────────────┼──────────────────────────┤
│ L1 (热层) │ GPU HBM 3e │ KV Cache (活跃序列) │
│ L2 (温层) │ CPU LPDDR5X │ 权重分片(当前未使用层) │
│ L3 (冷层) │ NVMe SSD │ 完整模型权重(冷备份) │
└──────────────────┴──────────────────┴──────────────────────────┘
权重按需加载(Weight On-Demand) 模式:
4.2 KV Cache 与统一内存的零拷贝投机解码
投机解码(Speculative Decoding)中,草稿模型生成候选 token,大模型验证时读取 KV Cache。在 Grace Hopper 上:
// 投机解码中的 KV Cache 管理
struct UnifiedKVCache {
void* k_cache; // 键缓存(统一内存)
void* v_cache; // 值缓存(统一内存)
size_t layer_size_; // 单层 KV 大小
int num_layers_;
int num_heads_;
int head_dim_;
void initialize(int layers, int heads, int dim, int max_seq) {
num_layers_ = layers;
num_heads_ = heads;
head_dim_ = dim;
layer_size_ = 2 *heads * dim * max_seq * sizeof(float16);
// 分配整个统一的 KV Cache 空间
size_t total = layer_size_ * layers;
cudaMallocManaged(&k_cache, total / 2);
v_cache = static_cast<uint8_t*>(k_cache) + total / 2;
// 提示运行时:前 N 层将立即使用,预取到 GPU
cudaMemAdvise(k_cache, layer_size_ * 16,
cudaMemAdviseSetPreferredLocation, 0);
cudaMemPrefetchAsync(k_cache, layer_size_ * 16, 0, 0);
}
// 验证阶段:大模型读取草稿模型写入的 KV Cache
void* get_kv_for_layer(int layer_idx) {
return static_cast<uint8_t*>(k_cache) + layer_size_ * layer_idx;
}
};
关键洞察:在统一内存架构中,草稿模型(可能运行在 CPU 或另一个 GPU 上)写入的 KV Cache 验证读取时,页面已在 HBM 中(或被硬件一致性协议保证可见),无需显式内存拷贝。
4.3 请求调度中的 NUMA 感知
在 Grace Hopper 系统上运行推理服务时,CPU 核心与 GPU 的物理临近性影响延迟:
#include <numa.h>
#include <sched.h>
// NUMA 感知的推理工作线程绑定
class NumaAwareScheduler {
private:
int numa_node_; // GPU 所在的 NUMA 节点
int preferred_cpu_; // 离 GPU 最近的 CPU 核心
public:
void pin_thread_to_gpu_cpu(int gpu_id) {
// 获取 GPU 所在的 NUMA 节点
char path[256];
snprintf(path, sizeof(path),
"/sys/bus/pci/devices/0000:%02x:00.0/numa_node", gpu_id);
FILE* f = fopen(path, "r");
if (f) {
fscanf(f, "%d", &numa_node_);
fclose(f);
}
// 绑定 CPU 线程到该 NUMA 节点的核心
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
struct bitmask* numa_nodes = numa_allocate_nodemask();
numa_bitmask_setbit(numa_nodes, numa_node_);
// 获取该 NUMA 节点的 CPU 列表
struct bitmask* cpus = numa_node_to_cpus(numa_nodes);
for (int i = 0; i < numa_num_configured_cpus(); i++) {
if ( numa_bitmask_isbitset(cpus, i)) {
CPU_SET(i, &cpuset);
preferred_cpu_ = i;
break; // 取第一个可用核心
}
}
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
numa_free_nodemask(numa_nodes);
numa_free_cpumask(cpus);
}
};
4.4 性能基准测试模型
基于 Grace Hopper GH200 的典型推理场景,统一内存相比传统 PCIe 方案的预期收益:
| 工作负载 | PCIe 5.0 | NVLink-C2C | 提升 | 原因 |
|---|---|---|---|---|
| 权重加载延时 | ~200ms (140GB/700GBs) | ~50ms | 4x | 页迁移带宽优势 |
| First Token Latency | +15~30% | 基线 | - | 消除首次访问页错误 |
| 小 batch 推理 | 优势不明显 | +5-15% | 低 | 计算瓶颈为主 |
| 大 batch 高吞吐 | +20-40% | 基线 | 显著 | 减少 CPU-GPU 数据搬运 |
| 投机解码验证 | +30-50% | 基线 | 显著 | KV Cache 零拷贝共享 |
| 长序列生成(>8K) | +25-40% | 基线 | 显著 | 连续页迁移摊销 |
---
五、工程实践注意事项
5.1 乒乓效应避免
统一内存最大的陷阱是页面在 CPU/GPU 之间反复迁移。预防措施:
# 在 Grace Hopper 上配置大页
echo 2048 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# CUDA 启动参数启用大页 CUDA 统一内存
export CUDA_HMM_USE_HUGEPAGES=1
export CUDA_HMM_THRESHOLD_MB=64
5.2 错误处理与降级
NVLink-C2C 链路可能因各种原因(热插拔、硬件故障)降级:
#include <cuda_runtime.h>
#include <cuda.h>
bool check_nvlink_c2c_health() {
CUdevice device;
cuDeviceGet(&device, 0);
int link_count = 0;
cuDeviceGetAttribute(&link_count,
CU_DEVICE_ATTRIBUTE_NUM_NVIPC2C_LINKS,
device);
if (link_count == 0) {
fprintf(stderr, "WARNING: NVLink-C2C 不可用,"
"降级为 PCIe 统一内存模式\n");
return false;
}
// 检查链路带宽完整性
for (int i = 0; i < link_count; i++) {
CUipc2cLink link;
cuDeviceGetIpc2cLinkAttribute(i,
CU_IPC2C_LINK_ATTR_BW, &link.bandwidth);
if (link.bandwidth < EXPECTED_BW_THRESHOLD) {
fprintf(stderr, "WARNING: NVLink-C2C 链路 %d "
"带宽降级: %lu GB/s\n", i, link.bandwidth);
}
}
return true;
}
5.3 监控与可观测性
# nvidia-smi 查看统一内存页面迁移统计
nvidia-smi --query-gpu=pci.link.gen.max,pci.link.width.max,
nvlink.c2c.bw --format=csv
# 查看 UVM 迁移统计
cat /proc/driver/nvidia/uvm/stats
# perf 追踪页迁移事件
perf record -e migrate:mm_migrate_pages -p $(pgrep vllm_server)
---
六、架构对比与场景选型
6.1 Grace Hopper vs H100 PCIe 系统:何时选择 GH200
┌─────────────────────────────────────────────────────────────────┐
│ 架构选型决策矩阵 │
├─────────────────┬─────────────────────┬─────────────────────────┤
│ 维度 │ GH200 (统一内存) │ H100 PCIe (传统分离) │
├─────────────────┼─────────────────────┼─────────────────────────┤
│ 模型内存需求 │ 适合超大模型 │ 需显著 model parallel │
│ 部署复杂度 │ 低(自动迁移) │ 高(显式管理 H2D/D2H) │
│ 推理延迟一致性 │ 更稳定(少数峰值) │ 页错误抖动明显 │
│ 多租户资源共享 │ 灵活分配 │ 需预分配,利用率低 │
│ 单位算力 TCO │ 高单机成本/低分布式 │ 低单机成本/高分布式开销 │
│ 投机解码适用性 │ 极佳 │ 一般(需显式 KV 拷贝) │
└─────────────────┴─────────────────────┴─────────────────────────┘
6.2 与 AMD MI300X 统一内存的对比
AMD MI300X APU 也提供 CPU-GPU 统一内存(通过 Infinity Fabric),但实现细节不同:
| 特性 | GH200 (Grace+Hopper) | MI300X (APU) |
|---|---|---|
| 一致性协议 | 基于目录的硬件一致性 | 基于令牌的软件辅助一致性 |
| CPU-GPU 带宽 | ~900 GB/s | ~512 GB/s (Infinity Fabric) |
| CPU 类型 | ARM Neoverse V2 | x86 Zen 4 |
| HBM 容量 | 141 GB (HBM3e) | 192 GB (HBM3) |
| CUDA 兼容性 | 原生 CUDA + 全栈优化 | ROCm/HIP,需移植 |
---
七、实际部署代码示例
以下是一个完整的、可编译的统一内存推理引擎骨架:
#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include <cstdio>
#include <cstring>
#include <vector>
// 统一内存 AI 推理引擎
class UnifiedInferenceEngine {
private:
void* model_weights_; // 模型权重(统一内存)
void* kv_cache_; // KV Cache(统一内存)
void* io_buffers_; // 输入输出缓冲区(统一内存)
size_t weight_size_;
size_t kv_size_;
size_t io_size_;
int device_id_;
cudaStream_t stream_;
// 页面迁移性能追踪
struct {
size_t migrations = 0;
size_t prefetch_hits = 0;
size_t page_faults = 0;
} perf_;
public:
struct Config {
size_t weights_bytes;
size_t kv_bytes;
size_t io_bytes;
int gpu_id = 0;
};
bool initialize(const Config& cfg) {
device_id_ = cfg.gpu_id;
cudaSetDevice(device_id_);
cudaStreamCreate(&stream_);
weight_size_ = cfg.weights_bytes;
kv_size_ = cfg.kv_bytes;
io_size_ = cfg.io_bytes;
// 分配统一内存
if (cudaMallocManaged(&model_weights_, weight_size_) != cudaSuccess ||
cudaMallocManaged(&kv_cache_, kv_size_) != cudaSuccess ||
cudaMallocManaged(&io_buffers_, io_size_) != cudaSuccess) {
fprintf(stderr, "统一内存分配失败\n");
return false;
}
// 设置内存访问建议
// 权重:首选 GPU,CPU 初始化后不再访问
cudaMemAdvise(model_weights_, weight_size_,
cudaMemAdviseSetPreferredLocation, device_id_);
cudaMemAdvise(model_weights_, weight_size_,
cudaMemAdviseSetAccessedBy, device_id_);
// KV Cache:主要在 GPU 端
cudaMemAdvise(kv_cache_, kv_size_,
cudaMemAdviseSetPreferredLocation, device_id_);
// IO 缓冲区:两端都需要访问
cudaMemAdvise(io_buffers_, io_size_,
cudaMemAdviceSetReadMostly, device_id_);
// 预取权重到 GPU
cudaMemPrefetchAsync(model_weights_, weight_size_,
device_id_, stream_);
cudaStreamSynchronize(stream_);
return true;
}
// 加载模型权重(从 CPU 端操作,然后触发迁移到 GPU)
void load_weights(const float* src, size_t count) {
// 检查权重大小超过分配的空间
size_t bytes = count * sizeof(float);
if (bytes > weight_size_) {
fprintf(stderr, "权重大小超出分配!\n");
return;
}
memcpy(model_weights_, src, bytes);
cudaMemPrefetchAsync(model_weights_, bytes, device_id_, stream_);
}
// 执行推理
bool infer(const void* input, void* output, size_t bytes) {
// 输入数据已在统一内存中,注册预取
cudaMemPrefetchAsync(const_cast<void*>(input), bytes,
device_id_, stream_);
// 准备推理内核参数
void* args[] = {
&model_weights_,
&input,
&output,
&kv_cache_
};
// 启动 CUDA 内核
// cudaGraphLaunch / cudaLaunchKernel
// 此处简化表示
run_inference_kernel<<<dim3(256), dim3(256), 0, stream_>>>(
static_cast<float*>(model_weights_),
static_cast<const float*>(input),
static_cast<float*>(output),
static_cast<float*>(kv_cache_)
);
// 将输出预回 CPU 可访问
cudaMemPrefetchAsync(output, bytes, cudaCpuDeviceId, stream_);
cudaStreamSynchronize(stream_);
return true;
}
void* model_weights() { return model_weights_; }
void* kv_cache() { return kv_cache_; }
void* io_buffers() { return io_buffers_; }
~UnifiedInferenceEngine() {
if (model_weights_) cudaFree(model_weights_);
if (kv_cache_) cudaFree(kv_cache_);
if (io_buffers_) cudaFree(io_buffers_);
if (stream_) cudaStreamDestroy(stream_);
}
};
---
八、总结与展望
Grace Hopper 的统一内存架构代表了 AI 推理基础设施的一个重要方向:通过硬件层面的内存一致性,让软件层面的数据管理变得透明简单。对于工程师来说:
展望未来,随着 NVIDIA 的 Blackwell B200/B300 继续强化统一内存能力,以及 Intel Gaudi 3、AMD MI300 系列的竞争跟进,"CPU-GPU 统一内存"将成为 AI 推理服务器的行业标准配置。理解这一底层机制,是构建下一代高性能推理服务的基础功。
---

发表评论 取消回复