从 Bit 到 AGI第一章:比特(Bit)

内存是什么

RAM、虚拟地址与页表——16 GB 权重如何映射进进程地址空间。

作者 David更新于 第 11 篇(共 186 篇)

「内存不够 OOM」「模型要 24 GB 显存」——这里的内存指 RAM(随机存取存储器):断电即失、纳秒级读写、CPU/GPU 直接寻址的工作区。文件在磁盘上持久;推理时权重和激活值必须在内存(或显存)里,速度才跟得上。

直觉:书桌 vs 仓库

磁盘像仓库(大、慢、便宜);内存像书桌(小、快、贵)。做数学题(计算)只能把当前要用的纸摊在书桌上;摊不下就得换页(swap)或分批(模型分片)——慢几个数量级。

定义

术语说明典型量级(2024 桌面/服务器)
DRAM主内存,按地址随机访问16–512 GB
VRAMGPU 专用 GDDR/HBM8–80 GB(消费级到 H100)
虚拟地址进程看到的连续 0…264−12^{64}-1 空间48 bit 常用
物理页通常 4 KB 一块,MMU 映射—
带宽字节/秒DDR5 ~50–100 GB/s;H100 HBM ~3 TB/s

虚拟内存:每个进程独立地址空间;OS 页表 把虚拟页映射到物理帧或磁盘 swap。malloc 返回虚拟地址,首次 touch 才分配物理页。

例子一:加载 7B FP16 模型

权重 14 GB + 运行时 KV Cache(视 batch 与序列长)+ 激活 + CUDA context:

  • 仅权重 14 GB → 16 GB 显卡不够(系统还要占 1–2 GB)
  • 24 GB 卡(3090/4090)可跑 7B FP16,余量给 KV
  • CPU RAM 推理 7B INT4 ~4 GB 权重可行,但 ~5–20 token/s vs GPU ~50–100+

例子二:虚拟地址连续 ≠ 物理连续

import numpy as np
# NumPy 数组在虚拟地址空间通常连续(便于 SIMD)
a = np.zeros((1024, 1024), dtype=np.float32)  # 4 MB
print(a.flags['C_CONTIGUOUS'])  # True
# 底层仍由 OS 按页映射;物理页可能分散

矩阵连续存储(后文)是为了 cache line 和 DMA 友好,不是物理磁盘式连续。

量化:内存预算公式(推理粗算)

权重:参数量 × 字节/参数(FP16=2,INT4≈0.5)

KV Cache(Transformer decoder,粗算): KV≈2×L×nkv×dhead×seq_len×batch×bytes\text{KV} \approx 2 \times L \times n_{kv} \times d_{\text{head}} \times \text{seq\_len} \times \text{batch} \times \text{bytes}

LL 层数,factor 2 是 K 和 V,nkv×dheadn_{kv} \times d_{\text{head}} 是每层每 token 的 KV 宽度。

这里有个几乎人人踩的坑:别拿 dmodeld_{\text{model}} 当 KV 宽度。

现代模型普遍用 GQA(多个 attention 头共享一组 KV 头,第 8 章细讲)。Llama 3 8B 有 32 个 attention 头,但只有 8 个 KV 头:KV 宽度是 8×128=10248 \times 128 = 1024,而 dmodel=4096d_{\text{model}} = 4096——差 4 倍。

更别把两者相乘(dmodel×nkvd_{\text{model}} \times n_{kv})——那是把 head 维算了两遍,差 32 倍。

Llama 3 8B:L=32L=32,nkv=8n_{kv}=8,dhead=128d_{\text{head}}=128,seq=4096,batch=1,FP16: 2×32×8×128×4096×1×2≈0.5 GB2 \times 32 \times 8 \times 128 \times 4096 \times 1 \times 2 \approx 0.5 \text{ GB}

即 128 KB/token——这个数值得记住,长上下文的账全从它来。

总 VRAM ≈ 16(权重)+ 0.5(KV)+ overhead ≈ 17 GB 量级。

自己拖一下

这笔账是个乘法,而乘法的手感只能拖出来。把 context 从 4K 拖到 32K,看 KV 那一段怎么从一小截长到把卡顶爆:

拖 context,看 KV 怎么反超权重
模型
权重精度
显卡
4K
1
KV/token = 2 × 32 层 × 8 KV头 × 128 × 2B = 128 KB(32 个 attention 头共享 8 组 KV → GQA 省了 4×)
权重 14.96 GB + KV 512.0 MB + 余量 1.55 GB = 17.00 GB/ 24 GB紧张

还有余量,但别再加并发了。

■ 权重■ KV■ 余量71% 占用

KV 宽度是「KV 头数 × 每头维度」,不是 d_model。切到「Llama 2 7B」再切回「Llama 3 8B」:两者 d_model 都是 4096、层数都是 32,但 7B 是 MHA(32 个 KV 头)、8B 是 GQA(只有 8 个)——每 token 的 KV 差 4 倍(512 KB vs 128 KB)。这就是 GQA 买到的东西:它换的不是算力,是并发。
「余量」一段是估算(CUDA context + 激活 + 碎片,取 1.5 GB 与总量 10% 的较大者),真实值随框架和 batch 波动。

Swap 与 OOM

物理 RAM 耗尽 → OS swap 到 SSD → 延迟从 100 ns 跳到 100 μs–ms。GPU OOM 通常直接 CUDA out of memory,无透明 swap(Unified Memory 可 oversubscribe 但极慢)。

NUMA 与多路服务器

双路 Xeon/EPYC 有 NUMA(Non-Uniform Memory Access):CPU0 本地内存快,访问 CPU1 本地内存慢 ~1.5–2×。大模型 CPU 推理绑核 + numactl --membind 可省 10–20% 延迟。GPU 服务器 CPU 侧 DataLoader 同样受益。

Unified Memory(CUDA):CPU/GPU 共享虚拟地址,oversubscribe 显存时 page migrate 到 host——能跑但极慢,只适合 prototyping。

显存与内存的统一视角

资源典型容量带宽
DDR5 系统 RAM128 GB~80 GB/s
RTX 4090 GDDR6X24 GB~1 TB/s
H100 HBM380 GB~3.35 TB/s

7B INT4 ~4 GB 权重可放消费级 GPU;70B INT4 ~35 GB 需 A100 80GB 或 CPU offload(权重放 RAM,逐层拷 GPU 算——token/s 个位数)。内存/显存预算表是部署第一页 checklist。

Swap 与 zram:内存不够时的比特去向

Linux swap 把冷页写到 SSD;zram 在 RAM 里压缩页(算法如 lzo/zstd)——适合内存略不够、SSD 慢的场景。训练大模型 禁止依赖 swap;一步 backward 随机访问全权重,swap 导致 分钟级 stall。

Gradient checkpointing:不存全部中间激活,forward 时重算——用 额外 FLOPs 换 RAM/VRAM bit 容量,典型省 30–50% 激活内存,训练慢 ~20–33%。

场景实战:算一张 A100 80GB 能跑多长 context

Llama 3 8B FP16 权重 16 GB,余 ~64 GB 给 KV 与 overhead。KV 公式 2L nkvdhead⋅seq⋅batch⋅22 L\, n_{kv} d_{head} \cdot \text{seq} \cdot batch \cdot 2 bytes,L=32,nkv=8,dhead=128L=32, n_{kv}=8, d_{head}=128(GQA,KV 宽度是 nkv×dhead=1024n_{kv} \times d_{head}=1024,不是 dmodel=4096d_{model}=4096):seq=32768 batch=1 → 每 token 128 KB × 32768 ≈ KV 4 GB——context 与权重争同一 VRAM bit 池。INT4 权重 4 GB 则 context 可翻倍——量化首先省的是 weight bit,间接释放 KV 空间。

CPU RAM 跑 7B INT4:4 GB 权重 + OS + Python 需 8 GB+ 机器;16 GB 笔记本可行但 token/s 个位数。

常见误区

  1. 「内存和硬盘都是存储,差不多」——带宽差 100–1000×,延迟差 10⁵× 量级。
  2. 「free 显示还有很多就能开新进程」——Linux 会用空闲 RAM 做 page cache;available 才靠谱。
  3. 「显存大就只放更大的模型」——长上下文 KV 线性涨,batch 也线性涨。

动手试一试

看本进程 RSS 与数组占用:

import numpy as np
try:
    import psutil, os
    proc = psutil.Process(os.getpid())
    print("RSS before MB:", proc.memory_info().rss / 1e6)
    big = np.zeros((2000, 2000), dtype=np.float64)  # 32 MB
    print("RSS after MB:", proc.memory_info().rss / 1e6)
    print("array MB:", big.nbytes / 1e6)
except ImportError:
    x = np.zeros((2000, 2000))
    print(x.nbytes / 1e6, "MB")

延伸思考

Unified Memory 与 CPU offload(llama.cpp --n-gpu-layers)都在 VRAM bit 不够时 spill 到 RAM bit,代价是 PCIe 或 NVLink 搬运 latency。ZeRO-3 训练把 optimizer state bit 分片到多机 RAM,forward 时 all-gather 参数 bit——内存层次从单机扩展到集群。规划 405B 训练时,内存/显存/网络 三条 bit 管道同时饱和;只加 GPU 不加 NVLink/IB 带宽 会 comm-bound。

补充:Swap 与 training

Never train with swap active——若 vmstat 见 si/so 非零,训练 step 时间会从 秒变分钟。Activation checkpointing 与 ZeRO 是正路;swap 是 把 RAM bit 暂存到磁盘 bit,延迟 10510^5× 级,不适合 iterative gradient 访问模式。

温故:内存规划 checklist

部署前填表:权重 GB + KV GB(max seq × batch) + 激活峰值 GB + 2 GB OS/driver ≤ VRAM/RAM。不够则:量化、减 context、GQA/MQA、offload、TP 分片——全是 在 bit 容量约束下换时间或精度 的旋钮。

小结

内存是 CPU/GPU 的高速工作区:虚拟地址、物理页、带宽与容量共同约束 AI 能跑多大模型、多长上下文。权重与 KV 的字节账算不清,部署必踩 OOM。HBM 带宽 常比 容量 更早成为 decode 瓶颈——规划 serving 时两个数字都要看。

动手作业:打开 nvidia-smi 或活动监视器,对照本章公式估算当前进程的 权重与 KV 占用,误差在一个数量级内即合格。

Page fault 首次 touch 分配物理页;大 mmap 模型启动时有 延迟尖峰。

KV cache 量化(FP8/INT8)是 2024+ 长 context Serving 热点方向之一。

Gradient checkpointing 用算力换激活 bit,训练大模型常用。

Activation 峰值常出现在 中间层 width 最大处(如 FFN 4× hidden);算显存时别只算权重与 KV,要留 20–30% 给激活与 workspace。

CPU offload 每层 forward 多一次 PCIe round-trip,只适合权重大于算力的超大模型。

zombie CUDA context 占 VRAM 不释放;nvidia-smi 查无进程但 memory used 高时常见。

第一章衔接

本章用 bit 语言 描述 AI 底座;第二章 数学 用 向量与矩阵 重述同一物理过程。两章对照读:bytes 是存储,FLOPs 是计算,带宽是瓶颈——三个量构成工程三角。

下一篇:Cache 是什么——为何矩阵布局影响「有效内存速度」。Unified Memory oversubscribe 适合 demo,不适合 SLA 生产;用户 P99 latency 敏感时,权重与 KV 必须物理在 VRAM。

本页目录