惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
T
Threat Research - Cisco Blogs
N
News and Events Feed by Topic
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 叶小钗
Security Latest
Security Latest
Spread Privacy
Spread Privacy
aimingoo的专栏
aimingoo的专栏
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
U
Unit 42
Cyberwarzone
Cyberwarzone
小众软件
小众软件
Scott Helme
Scott Helme
Engineering at Meta
Engineering at Meta
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
爱范儿
爱范儿
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
Latest news
Latest news
GbyAI
GbyAI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
PCI Perspectives
PCI Perspectives
Jina AI
Jina AI
AI
AI
NISL@THU
NISL@THU
I
Intezer
G
GRAHAM CLULEY
B
Blog
S
Secure Thoughts
IT之家
IT之家
宝玉的分享
宝玉的分享
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
Recorded Future
Recorded Future
Hacker News - Newest:
Hacker News - Newest: "LLM"

博客园 - marsggbo

Eurosys26 | FineMoE如何用「细粒度」打破MoE推理的显存-延迟死局 LoRA fine-tune吞吐量提升1.96倍!LoRAFusion如何把内存带宽浪费和pipeline bubble一起干掉 Fast26 | LLM 推理启动慢?华为用一个「可编程 Page Cache」把模型加载砍了 79% KV Cache 的两层存储到底卡在哪?FAST'26 这篇论文给出了答案 NeurIPS24 | 把Dense LLM变身MoE还提速 ICML25 | EPIC:KV Cache 复用的「编译-链接」范式(附可运行代码复现) KV Cache 复用的第三条路:FAST 2026 CacheSlide 是怎么解决 Agent 推理的位置漂移问题的 MoE 推理的内存墙,被一块多芯粒芯片打穿了? KVCOMM:让多 Agent 系统的 KV Cache 真正“通起来”,TTFT 直接砍掉 7.8 倍 NSDI26 | DroidSpeak让不同 LLM 之间共享 KV Cache TokenDance 解决多 Agent LLM 推理的 KV Cache 冗余问题 当 AI 开始学会"记住":LLM Agent 记忆系统的统一视角 【转载】ACM MM 投稿论文模板修改成投稿模式 尝试从源头理解 SVD 原理和计算 LLM 场景下的强化学习技术扫盲 解决 Overleaf 中插入 PDF 图片失败的问题:排查与修复 Tmux ctrl+B快捷键失效处理办法 对抗训练综述学习笔记 【转知乎回答】一文看懂 LLaMA 中的旋转式位置编码(Rotary Position Embedding) 二进制中为什么负数是正数取反再加一 leetcode 常见题型代码总结 Prompt-Tuning、P-Tuning和Prefix-Tuning区别和代码实现【转】 Deepspeed ZeRO系列算法原理+通信开销详解 NSCC集群使用笔记 Huggingface Transformers实现张量并行的小坑 set/get_output_embeddings TACC 集群使用笔记 图解 vLLM 的推理调度策略 大模型推理框架 vLLM 源码解析(二):Block 模块分配和管理 OpenAI 的视频生成大模型Sora的核心技术详解(一):Diffusion模型原理和代码详解 大模型推理框架 vLLM 源码解析(一)
Pytorch 如何使用 storage 实现参数 offload?
marsggbo · 2024-04-21 · via 博客园 - marsggbo

在深入探讨 PyTorch 中的 Storage 类以及其在参数 offload 场景中的应用之前,让我们首先了解一下 PyTorch 和它的基础组件。PyTorch 是一个广泛使用的开源机器学习库,它不仅提供了强大的计算图功能和自动梯度计算,还允许开发者直接操作底层数据结构,这其中就包括 Storage

1. 什么是 torch.Storage?

在 PyTorch 中,Storage 是一种容纳数据的一维数组,它可以看作是一个底层的内存块,其中存储着特定类型的数据。与 Tensor 的关系非常紧密,实际上,每个 Tensor 都有一个与之关联的 Storage 对象。Tensor 提供了一个高维视图来操作存储在 Storage 中的数据。

Storage 的一个关键特性是它的数据排列是连续的,这使得数据可以迅速地在设备之间传输,例如从 CPU 到 GPU,省去了频繁索引的操作。此外,Storage 可以存在于不同的设备上,如 CPU 或 CUDA(GPU)。

使用 storage 实现 offload 参数场景大致有如下:

  • 模型训练时的内存优化
    在深度学习模型训练过程中,特别是当使用的模型非常大,以至于单个 GPU 显存不足时,可以使用 offload 技术将部分数据暂时存储到 CPU 内存中,从而释放 GPU 显存用于计算。

  • 数据预处理
    在进行大规模数据处理时,可以将不活跃的数据段 offload 到 CPU,以保持 GPU 资源用于执行高优先级的任务。

  • 长期数据存储
    对于不需要频繁访问的大量数据,可以将其 offload 到 CPU 或其他存储系统,以减少昂贵的 GPU 存储资源的占用。

2. 理解 Storage

2.1 简单例子

import torch

x = torch.arange(3, dtype=torch.float32).cuda()
print(x.storage())

输出结果如下,可以看到打印出来的结果符合预期,有三个浮点数,storage 的类型是 torch.storage.TypedStorage

 0.0
 1.0
 2.0
[torch.storage.TypedStorage(dtype=torch.float32, device=cuda:0) of size 3]

更一般地,我们还能打印看看无类型的 storage 是什么样的

x_storage = x.storage()._untyped_storage
print(x_storage)

输出结果如下,可以看到总共有 12 个整数,这是因为前面我们使用的数据类型是 float32,也就是说每个数由 4 个字节(bytes)表示。因为 变量 x 总共有 3 个数,所有它的 storage 总共有 12 个字节。

 0
 0
 0
 0
 0
 0
 128
 63
 0
 0
 0
 64
[torch.storage.UntypedStorage(device=cuda:0) of size 12]

这些值实际上是浮点数012在内存中的字节级表示。需要注意的是,上面输出结果并不是随机值,而是这些浮点数在 IEEE 754 标准下的二进制表达。我们可以逐个解释这些值如何来的。

2.2 浮点数的 IEEE 754 表示

对于类型 float32(即单精度浮点数),每个数字占用 4 个字节(32位),具体编码方式为:

  • 1 位符号位(最高位)
  • 8 位指数位
  • 23 位尾数位

在解释这些值之前,我们先了解一下计算机中的 小端序(Little Endian) 存储方式:在这种存储方式中,低位字节存放在内存的低地址端,高位字节存放在高地址端。

Tensor[0., 1., 2.] 为例,我们来看看这些值在内存中是如何表示的:

  1. 数字 0 的浮点表示

    • 符号位:0
    • 指数位:全0(偏移量为127,因此全0表示指数-127)
    • 尾数位:全0
    • 二进制表示00000000 00000000 00000000 00000000
    • 十六进制表示00 00 00 00
    • 小端序下的字节表示00 00 00 00
    • 上面结果转化成十进制表示0 0 0 0
  2. 数字 1 的浮点表示

    • 符号位:0
    • 指数位:127(偏移后为0,01111111
    • 尾数位:全0(因为1.0的尾数部分无需额外存储)
    • 二进制表示001111111 00000000000000000000000
    • 十六进制表示3F 80 00 00
    • 小端序下的字节表示00 00 80 3F
    • 上面结果转化成十进制表示0 0 128 63 (80 十六进制转十进制是 1283F 转十进制是 63)
  3. 数字 2 的浮点表示

    • 符号位:0
    • 指数位:128(偏移后为1,10000000
    • 尾数位:全0(因为2.0的尾数部分也无需额外存储)
    • 二进制表示010000000 00000000000000000000000
    • 十六进制表示40 00 00 00
    • 小端序下的字节表示00 00 00 40

3. 使用 Storage 实现参数 offload 到 cpu

前面例子中的变量x在 cuda上,为了实现 offload,我们需要在 cpu 上创建一个 storage,如下:

offload_storage = torch.UntypedStorage(x.nbytes).pin_memory(x.device)
print(offload_storage.device)
print(offload_storage)

输出结果如下,可以看到offload_storage是在 cpu 上,目前其上面的值都是一些随机值。

cpu
 208
 238
 22
 7
 0
 0
 0
 0
 208
 66
 20
 6
[torch.storage.UntypedStorage(device=cpu) of size 12]

接下来我们需要把 x offload 到 cpu 上,只需要对 storage 做 copy 操作即可,代码如下:

offload_storage.copy_(x_storage)
print(offload_storage.device)
print(offload_storage)

输出结果如下:

cpu
 0
 0
 0
 0
 0
 0
 128
 63
 0
 0
 0
 64
[torch.storage.UntypedStorage(device=cpu) of size 12]

可以看到x的值被成功拷贝到 cpu 上,但是这离实现 offload 还有一步之遥,我们接下来继续看一个简单的 offload 例子。

4. gpu 参数 和 cpu 参数互换

我们接着将探讨如何利用 Storage 实现 GPU 和 CPU 之间的数据互换,这对于处理大型数据集或进行复杂的数据处理任务时尤其有用。

假设我们有以下设置:

  • 一个 CUDA Tensor 用于当前计算。
  • 多个 CPU Storage 用于存储额外的数据集,这些数据集可能在不同时间被需求到 GPU。

4.1 初始化环境

首先,我们定义一个在 CUDA 上的 Tensor 和多个在 CPU 上的 Storage,准备用于数据交换:

import torch

# 定义 CUDA Tensors (用于当前计算)
current_data = torch.tensor([0.0, 1.0], device='cuda')

# 定义 CPU Storages (用于存储额外数据)
extra_data1 = torch.FloatTensor([2.0, 3.0]).storage().pin_memory()
extra_data2 = torch.FloatTensor([4.0, 5.0]).storage().pin_memory()
extra_data3 = torch.FloatTensor([6.0, 7.0]).storage().pin_memory()

print("Initial CUDA Tensor (Current Data):")
print(current_data)

print("\nInitial CPU Storages (Extra Data):")
print("Extra Data 1:", list(extra_data1))
print("Extra Data 2:", list(extra_data2))
print("Extra Data 3:", list(extra_data3))

输出结果为:

Initial CUDA Tensor (Current Data):
tensor([0., 1.], device='cuda:0')

Initial CPU Storages (Extra Data):
Extra Data 1: [2.0, 3.0]
Extra Data 2: [4.0, 5.0]
Extra Data 3: [6.0, 7.0]

4.2 使用缓冲区进行数据交换

接下来,我们将根据需要将 CPU 上的数据加载到 CUDA Tensor 中,同时将当前 CUDA Tensor 的数据存储回某个 CPU Storage,这可以申请一个 buffer 来作为中间变量,反正数据丢失。

# 缓冲区定义
cpu_buffer = torch.FloatTensor(current_data.size()).storage().pin_memory()  # CPU buffer storage

# 场景1:将 current_data 保存到 extra_data1,从 extra_data1 加载新数据到 current_data
cpu_buffer.copy_(current_data.storage())  # Save current GPU data to CPU buffer
current_data.storage().copy_(extra_data1)  # Move from CUDA buffer to current_data
extra_data1.copy_(cpu_buffer)  # Move from CPU buffer to extra_data1 Storage

print("\nAfter Data Exchange Scenario 1:")
print(f"Updated Current Data on {current_data.device}:", current_data)
print(f"Updated Extra Data 1 on {extra_data1.device}:", list(extra_data1))

print("Extra Data 2:", list(extra_data2))
print("Extra Data 3:", list(extra_data3))

输出结果

After Data Exchange Scenario 1:
Updated Current Data on cuda:0: tensor([0., 1.], device='cuda:0')
Updated Extra Data 1 on cpu: [2.0, 3.0]
Extra Data 2: [4.0, 5.0]
Extra Data 3: [6.0, 7.0]

此示例清晰地展示了如何利用 PyTorch 的 Storage 类来有效管理内存资源,并通过使用 CPU 和 CUDA 缓冲区动态切换数据来优化应用性能。这种方法尤其适用于需要频繁在不同计算设备之间迁移数据的场景,从而保证计算效率和响应速度。

尽管可以通过 PyTorch 的 to('cpu') 或 to('cuda') 方法简单地在设备间迁移数据,使用 Storage 提供了更细粒度的控制。这在处理需要大量连续物理存储空间的复杂模型时显得尤为重要。

例如在混合专家模型(MoE)中,系统需要根据不同的请求动态调用不同的专家(模型)。每个专家可能包含的是由多层感知机 (MLP) 或更复杂结构组成的模型,其中每层的参数在内存中通常是不连续的。这种不连续性可能导致在将参数 offload 到 CPU 或重新加载到 GPU 时,因频繁的内存访问和索引操作而增加通信开销。