行业资讯
📅 2026/9/7 20:31:42
从零搭建一个 AI Infra 实验室⑤:从 Infra 角度看懂一个 LLM 到底是什么
前面四篇我们已经一路走过了GPU ↓ CUDA ↓ PyTorch ↓ Tensor上一篇我们第一次真正让 PyTorch 的 Tensor 进入 GPU。从System RAM经过PCIe进入GPU VRAM然后执行矩阵计算。到这里整个Python ↓ PyTorch ↓ CUDA ↓ GPU已经真正跑通了。但问题也随之而来。我们现在运行的依然只是c a b这种人为制造出来的矩阵计算。真正的 AI 模型显然不会只有两个 Tensor。一个大语言模型中还有Embedding Attention MLP Normalization Transformer Block以及数量巨大的Parameter Weight于是下一步的问题自然变成了一个所谓的 LLM到底是什么为什么我们经常看到0.5B 1.5B 7B 14B 70B这些数字所谓7B Model真的就是有 70 亿个数字吗这些 Parameter 和 Weight 到底是什么为什么一个模型下载下来可能就是几 GB、几十 GB为什么model.to(cuda)执行以后GPU 显存会突然增加一大块还有我们每天使用大模型时经常碰到的Prompt Token Tokenizer Context Transformer又分别是什么这一篇我们暂时不研究Prefill Decode KV Cache TTFT TPOT这些属于“模型已经加载之后一次请求到底怎么运行”的问题。这一篇只回答一个更基础的问题从 AI Infra 的角度一个 LLM 本身究竟是什么一、一个 LLM并不是一个巨大的“程序”第一次接触大模型时我很容易把Qwen Llama DeepSeek理解成某种特别大的软件。好像我们平时安装Chrome Nginx MySQL一样。下载一个模型然后运行它。但从 Infra 角度来看一个已经训练好的 LLM 更接近模型架构 模型参数 Tokenizer 配置文件如果去看一个 Hugging Face 模型目录经常会看到类似config.json model.safetensors tokenizer.json tokenizer_config.json generation_config.json ...这些文件扮演的角色并不一样。例如config.json主要描述模型结构。里面可能包含Hidden Size Transformer Layers Attention Heads Vocabulary Size Context Length ...而tokenizer.json描述的是文字 ↓ Token应该怎样转换。真正最占空间的通常是model.safetensors或者被拆成多个model-00001-of-000xx.safetensors这样的文件。因为这里面保存的是训练结束以后得到的大量Model Weights也就是模型权重。所以可以先建立一个非常粗略但对 Infra 很有用的理解LLM │ ├── Architecture │ ├── Parameters / Weights │ ├── Tokenizer │ └── Config而真正吃Disk RAM VRAM的主要就是其中那一大堆Parameters / Weights二、0.5B、7B、70BB 到底是什么意思我们经常看到模型名字里写0.5B 1.5B 7B 14B 32B 70B这里的B就是Billion十亿。所以0.5B ≈ 5 亿参数 7B ≈ 70 亿参数 70B ≈ 700 亿参数这里所谓的Parameter就是模型训练过程中学习出来并在训练结束以后保存下来的参数。假设一个非常简单的神经网络层torch.nn.Linear(1024, 4096)它内部就会包含一组巨大的Weight Tensor以及可能存在的Bias Tensor而真正的大语言模型中会有很多层Embedding ↓ Transformer Block ↓ Transformer Block ↓ Transformer Block ↓ ... ↓ Output Layer每一层里面又包含大量Weight最终把整个模型中的参数全部加起来就可能变成5 亿 70 亿 700 亿甚至更多。所以7B Model最直接的含义并不是这个模型文件有 7GB。而是这个模型大约拥有 70 亿个 Parameter。三、Parameter 和 Weight 有什么区别这两个词在 AI 文章里经常混着出现。严格来说Parameter范围更大。模型里面需要训练和保存的Weight Bias ...都可能属于 Parameter。而Weight通常更具体地指某一层中的权重矩阵例如一个 Linear Layer 里面的Weight Tensor不过到了大模型领域我们经常会听到下载模型权重 加载模型权重 权重占多少显存 Model Weights这里的Weights通常已经被比较宽泛地用来表示模型训练以后保存下来的那些参数数据。所以从 Infra 角度目前不用过度纠结两者的边界。先理解成Model │ ├── Parameter │ ├── Weight Tensor │ ├── Bias Tensor │ └── ... │ ├── Parameter │ └── ...就够了。更重要的是这些 Parameter 最终仍然是我们上一篇已经认识过的 Tensor。只不过现在不是1 个 Tensor而是成千上万个 Tensor 几亿甚至几十亿个元素共同组成了一个模型。四、前几篇的 Tensor终于和 Model 接起来了上一篇我们已经知道一个 Tensor 最值得关注的几个属性包括shape dtype device现在到了 Model这些知识仍然有效。模型参数本身也是 Tensor。所以一个模型里的某个 Weight 可能是shape [4096, 4096] dtype float16 device cpu当模型进入 GPU 后则变成device cuda:0于是前几篇一路留下来的几个概念终于接起来Tensor ↓ Parameter ↓ Model ↓ GPU VRAM这也是我觉得学习 AI Infra 时很重要的一点。如果直接从AutoModelForCausalLM.from_pretrained(...)开始很容易觉得Model是一个巨大的黑盒。但拆开以后会发现所谓模型本质上还是数量巨大的 Tensor 被按照某种网络结构组织起来。五、为什么参数数量会直接影响模型大小现在可以做一个非常简单的估算。假设模型有0.5B Parameters也就是大约5 × 10^8个参数。如果每个参数使用FP16上一篇已经知道FP16 2 Bytes那么只算模型参数0.5 × 10^9 × 2 Bytes ≈ 1GB如果换成7B FP16就是7 × 10^9 × 2 Bytes ≈ 14GB现在第二篇里我们曾经提前做过的7B × 2 Bytes ≈ 14GB终于不再只是一个公式。因为现在知道7B代表70 亿个 Parameter而2 Bytes来自Parameter Tensor 的 dtype于是关系真正变成Parameter Count × Parameter dtype ↓ Weight Size ↓ RAM / VRAM这是一条非常重要的 AI Infra 关系。六、模型文件大小为什么和显存占用有关现在再去看model.safetensors就容易理解了。里面主要保存的是训练完成后的 Parameter Tensor所以如果一个模型大约0.5B Parameters并且权重以FP16 / BF16保存那么模型权重文件大约在1GB这个量级并不奇怪。但这里要特别注意模型文件大小和模型运行时 GPU 显存占用并不一定完全相等。因为模型真正运行以后显存里以后还会出现其他数据。这一篇我们暂时只知道VRAM │ ├── Model Weights │ └── Runtime Data其中Model Weights比较像模型加载以后长期驻留 GPU 的静态资源。至于 Runtime Data 到底有哪些、为什么会随着 Prompt 和请求数量变化我们留到下一篇再展开。这一篇只需要先记住模型权重通常是 LLM 显存中最大、最基础的一块长期资源。七、Transformer 又是什么讲 LLM 绕不开Transformer但这一篇我们不准备推导Q K V Softmax Attention Formula这些数学。对于 AI Infra 来说现在只需要知道它处在哪一层。今天绝大多数主流 LLM核心都建立在 Transformer 架构之上。一个 Decoder-only LLM 可以非常粗略地理解成Input ↓ Embedding ↓ ┌──────────────────────┐ │ Transformer Block │ │ │ │ Attention │ │ MLP │ │ Normalization │ └──────────────────────┘ ↓ ┌──────────────────────┐ │ Transformer Block │ └──────────────────────┘ ↓ ... ↓ ┌──────────────────────┐ │ Transformer Block │ └──────────────────────┘ ↓ Output关键是Transformer Block并不是程序里一段没有数据的逻辑。里面存在大量Weight Tensor于是Transformer Layer 越多 Hidden Size 越大 各种矩阵越大通常也就意味着Parameter 更多最终Model 更大所以以后看到模型配置里的num_hidden_layers hidden_size num_attention_heads就知道这些并不只是 AI 算法工程师才需要看的东西。它们最终都会影响参数数量 模型文件大小 显存 计算量也就是 Infra。八、但 Transformer 并不能直接读懂一句中文现在模型已经有了Transformer 几亿个 Parameter假设用户输入请解释一下什么是 AI Infra。能不能直接把这句话送进 GPU当然不行。因为前面几篇已经知道GPU 真正处理的是Tensor里面是Number不是中文 英文 标点符号所以文字进入模型以前还需要经过另外一个非常重要的组件Tokenizer九、Tokenizer把人类文字转换成模型能够处理的 TokenTokenizer 可以先简单理解成Text ↓ Tokenizer ↓ Token ↓ Token ID例如AI Infra 到底是什么经过 Tokenizer 以后可能变成若干Token然后每个 Token 再对应一个整数[xxxx, xxxx, xxxx, xxxx, ...]真正送给 PyTorch Model 的最后其实是一组类似input_ids的 Tensor。所以现在数据链路变成人类文字 AI Infra 到底是什么 ↓ Tokenizer ↓ Token IDs ↓ PyTorch Tensor ↓ Model这时突然会发现Tokenizer 其实是连接“人类语言世界”和“Tensor 世界”的那座桥。十、Token 并不等于一个字也不等于一个单词这是大模型里面特别容易误解的一件事。我们经常看到100 Tokens 1000 Tokens 32K Context 128K Context于是很容易把Token理解成一个汉字或者一个英文单词其实都不准确。不同 Tokenizer 会按照自己的词表和规则拆分文字。所以字符数 ≠ 单词数 ≠ Token 数例如AI Infrastructure可能被拆成多个 Token。中文一句话也可能按照汉字、词语或者其他子词方式拆分。对于 Infra 来说真正值得关注的是最终产生了多少 Token因为以后很多东西都会围绕 Token 来讨论。这一篇暂时先记住LLM 处理文字时最基本的输入单位不是“字数”而是 Token。十一、Prompt 和 Context 又是什么这两个词也经常一起出现。Prompt可以先理解成我们送给模型的输入。例如请用三句话解释什么是 Kubernetes。就是一个简单 Prompt。但真正的聊天系统中送给模型的内容可能不只有用户这一句话。还可能包括System Prompt 聊天历史 用户当前问题 其他额外信息这些内容最后会被组合起来。Context模型这一次能够看到的完整 Token 序列可以粗略理解成Context例如System Prompt History Current Prompt经过 Tokenizer↓ Token Token Token ...这些共同构成模型当前的 Context。于是Prompt更偏向给模型的输入内容而Context更偏向模型这一次真正能够参考的整个 Token 上下文以后我们还会不断看到Context Window例如32K 128K这表示模型能够处理的上下文 Token 数存在一定范围。至于Context 越长 为什么显存增加 为什么速度下降这些就已经属于下一篇的推理运行问题了。这一篇先不展开。十二、现在把一个 LLM 从 Infra 角度完整画出来到这里可以画出这一篇最重要的一张图LLM │ ┌─────────┴─────────┐ │ │ ▼ ▼ Model Structure Tokenizer │ │ │ │ ▼ ▼ Transformer Text → Token │ │ │ ▼ │ Token IDs │ │ │ │ ├───────────────────┘ │ ▼ Model Parameters │ ▼ Weight Tensors │ ▼ PyTorch │ ▼ GPU VRAM如果再加入模型文件Model Repository │ ├── config.json │ ↓ │ Model Structure │ ├── tokenizer.json │ ↓ │ Tokenizer │ └── model.safetensors ↓ Parameters ↓ Weight Tensor ↓ System RAM ↓ PCIe ↓ GPU VRAM这就是这一篇真正想建立的LLM Infra 第一张完整地图。十三、实验环境实验机器继续沿用前几篇Ubuntu 24.04 Server Intel Core i5-10400F 16GB RAM NVIDIA GeForce RTX 3060 12GB前面已经完成NVIDIA Driver CUDA PyTorch GPU Tensor验证。所以这一篇不再重新检查lspci nvidia-smi nvcc torch.cuda.is_available()那些内容。直接继续增加Transformers Model实验使用一个比较小的模型Qwen/Qwen2.5-0.5B-Instruct原因很简单。这一篇不是比较模型能力。而是观察Tokenizer Parameter Model Loading RAM VRAM所以小模型反而更加适合实验下载快 加载快 显存压力小 现象又足够完整RTX 3060 12GB 运行这种规模的模型也比较轻松。十四、实验一安装 Transformers继续进入上一篇的 Python Virtual Environmentcd ~/ai-infra-lab source .venv/bin/activate安装pip install -U transformers accelerate safetensors检查python -c import transformers; print(transformers.__version__)现在我们的软件栈继续向上增加了一层Application ↓ Transformers ↓ PyTorch ↓ CUDA ↓ NVIDIA Driver ↓ RTX 3060这里顺便注意两个名字Transformer和Transformers不是一回事。前者Transformer是模型架构。后者Transformers是 Hugging Face 提供的 Python Library。十五、实验二先看看 Tokenizer 到底干了什么先不加载模型。创建nano tokenizer_test.py写入from transformers import AutoTokenizer MODEL_ID Qwen/Qwen2.5-0.5B-Instruct tokenizer AutoTokenizer.from_pretrained( MODEL_ID ) text 从 Infra 角度看大语言模型到底是什么 inputs tokenizer( text, return_tensorspt ) print(Text:) print(text) print() print(Input IDs:) print(inputs[input_ids]) print() print( Token Count:, inputs[input_ids].shape[-1] ) print() print(Tokens:) print( tokenizer.convert_ids_to_tokens( inputs[input_ids][0] ) )运行python tokenizer_test.py重点不是记住那些 Token ID。而是观察自然语言 ↓ Tokenizer ↓ Token ↓ Integer ID ↓ PyTorch Tensor可以继续修改Hello World AI Infrastructure 人工智能基础设施 Kubernetes 为什么需要 Scheduler然后观察字符串长度和Token Count并不是简单的一一对应。这一小段实验以后还会反复使用。因为到了真正的 LLM Infra多少 Token会比多少字重要得多。十六、实验三真正把一个 Model 加载出来接下来进入这一篇最重要的实验。创建nano load_model.py写入import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, ) MODEL_ID Qwen/Qwen2.5-0.5B-Instruct def mib(value): return value / 1024**2 def show_gpu_memory(title): print() print(f {title} ) print( Allocated:, f{mib(torch.cuda.memory_allocated()):.1f} MiB ) print( Reserved :, f{mib(torch.cuda.memory_reserved()):.1f} MiB ) print( GPU:, torch.cuda.get_device_name(0) ) show_gpu_memory(Before Model Loading) print() print(Loading Tokenizer...) tokenizer AutoTokenizer.from_pretrained( MODEL_ID ) print() print(Loading Model to CPU RAM...) model AutoModelForCausalLM.from_pretrained( MODEL_ID, dtypetorch.float16, low_cpu_mem_usageTrue, ) model.eval() parameter_count sum( p.numel() for p in model.parameters() ) parameter_bytes sum( p.numel() * p.element_size() for p in model.parameters() ) print() print( Parameter Count:, f{parameter_count:,} ) print( Parameter Memory:, f{parameter_bytes / 1024**3:.3f} GiB ) print( Model dtype:, next(model.parameters()).dtype ) print( Model device:, next(model.parameters()).device ) show_gpu_memory(Model on CPU) print() print(Moving Model to GPU...) model.to(cuda) torch.cuda.synchronize() print( Model device:, next(model.parameters()).device ) show_gpu_memory(Model on GPU) input( \nPress Enter to exit... )运行python load_model.py最后程序会暂停。这样就有时间在另一个 Terminal 中观察nvidia-smi甚至可以一直watch -n 0.5 nvidia-smi十七、这次应该观察哪些数据这一篇不要只看一句Model loaded successfully真正值得记录的是下面几组数据。首先Parameter Count看看实际模型到底有多少参数。然后看Model dtype这次我们明确选择float16再看Parameter Memory理论上它应该和Parameter Count × 2 Bytes处在相近量级。于是参数数量 ↓ dtype ↓ 理论 Weight Size第一次可以和真实模型对应起来。十八、最重要的观察Model 在 CPU 和 GPU 是两个状态代码刚执行AutoModelForCausalLM.from_pretrained(...)的时候Model其实已经存在了。但next(model.parameters()).device应该仍然显示cpu这意味着Model File ↓ PyTorch Model ↓ System RAM模型虽然加载了但还没有进入 GPU。接下来执行model.to(cuda)以后device变成cuda:0与此同时nvidia-smi里的显存也会明显增加。于是模型真正完成Model File ↓ System RAM ↓ PyTorch Parameter Tensor ↓ PCIe ↓ GPU VRAM这就是上一篇tensor.to(cuda)的进一步放大。上一篇搬过去的是一个普通 Tensor。这一篇搬过去的是组成整个 LLM 的几亿个 Parameter。十九、这里终于可以解释“模型占显存”是什么意思我们经常说这个模型需要多少显存以前听起来好像是某种特殊的大模型机制。现在已经可以拆开理解。当model.to(cuda)以后模型中的大量Parameter Tensor全部需要拥有自己的GPU Memory所以首先可以近似认为Model Weight VRAM ≈ Parameter Count × Parameter dtype例如0.5B × FP16大约就是1GB 量级而7B × FP16就是14GB 量级到这里0.5B 7B FP16 VRAM几个原本看起来完全不同的词终于被一条线连接起来了。二十、建议记录一张自己的实验表我建议把自己的真实结果填进去。这是我的结果项目实测结果ModelQwen2.5-0.5B-InstructParameter Count494,032,768dtypeFP16理论 Parameter Memory0.920 GiBModel 在 CPU 时 GPU Allocated1 MiBModel 进入 GPU 后 Allocated950.2 MiBnvidia-smi显存1105 MiB另外还可以补一张free -h在模型加载前模型加载到 CPU 后的对比。这样就能真正看到Model File ↓ System RAM ↓ GPU VRAM整个资源变化过程。这里同样不建议提前写死某个显存数字。因为PyTorch Version Transformers Version CUDA Context Caching Allocator都会带来一定差异。真正应该观察的是资源变化的方向和数量级。注意本篇实验代码也可通过命令获取git clone https://github.com/mosesyyoung/ai-infra-lab.git二十一、几个这一篇特别值得注意的坑1. 第一次加载模型很慢不一定是模型加载慢第一次运行from_pretrained(...)很可能还在下载config tokenizer model weights因此Download Time和Model Loading Time不要混在一起。后面如果要测试模型加载速度应该先让模型完整缓存到本地。2. Tokenizer 加载了不代表 Model 也加载了AutoTokenizer.from_pretrained(...)主要是在加载Tokenizer并没有把几亿个模型参数放进 GPU。所以运行 Tokenizer 时GPU VRAM一般不会因为模型权重而大幅增加。3.from_pretrained()和model.to(cuda)不是一回事这是这一篇非常值得记住的一点。from_pretrained(...)首先完成Model File ↓ PyTorch Model而model.to(cuda)才真正完成Parameter ↓ GPU VRAM以后遇到CPU Offload Device Map Multi-GPU本质上仍然是在研究这些 Parameter 到底应该放在哪里。4. 模型文件 1GB不代表nvidia-smi必须刚好增加 1GB因为 GPU 进程除了Model Weight还可能有CUDA Context PyTorch Runtime Caching Allocator Temporary Buffer所以模型文件大小和nvidia-smi Used Memory不应该要求完全相等。上一篇我们已经知道memory_allocated memory_reserved nvidia-smi本身就是不同视角。这一篇重点看Model 进入 GPU 前和Model 进入 GPU 后的变化即可。5. 0.5B 不等于 0.5GB这是一个很容易产生的误会。0.5B表示大约 5 亿参数不是模型文件大小。模型实际大小还取决于dtype 量化方式 保存格式例如同样的 Parameter CountFP32 FP16 INT8 INT4最终需要的存储空间可能完全不同。量化我们后面再专门研究。6. Context Length 不是模型文件大小例如看到32K Context它描述的是模型能够处理多长的 Token 上下文不是模型拥有 32K 参数也不是模型文件有 32KB它属于模型运行时输入能力的一部分。至于为什么 Context 越长会影响显存和速度下一篇再真正解释。7. Wi-Fi 下载大模型时 SSH 频繁断开检查 Power Save如果实验机通过 Wi-Fi 连接网络在下载较大的模型文件时偶尔可能遇到 SSH 会话突然断开的情况。这次我的现象就是平时 SSH 基本正常但 Hugging Face 持续下载近 1GB 模型文件时连接频繁中断。最后发现无线网卡开启了 Power Save。以实验机无线网卡连接 wlp2s0 为例先检查iw dev wlp2s0 get power_save如果看到Power save: on可以先临时关闭验证sudo iw dev wlp2s0 set power_save off再次检查iw dev wlp2s0 get power_save如果变成Power save: off并且 SSH 和模型下载恢复稳定就可以考虑永久关闭。我的 Ubuntu Server 使用下面的 systemd Servicesudo nano /etc/systemd/system/wifi-powersave-off.service写入[Unit] DescriptionDisable WiFi power saving on wlp2s0 Afternetwork.target [Service] Typeoneshot ExecStart/usr/sbin/iw dev wlp2s0 set power_save off RemainAfterExityes [Install] WantedBymulti-user.target然后启用sudo systemctl daemon-reload sudo systemctl enable --now wifi-powersave-off.service重启后再确认iw dev wlp2s0 get power_save仍然显示Power save: off即可。当然这个问题只针对使用 Wi-Fi 的实验机。如果使用稳定的有线网络可以直接跳过这一项。这里真正值得记住的是下载大模型这种持续网络负载有时会把平时不明显的网络稳定性问题暴露出来不要一看到 SSH 断开就先怀疑 PyTorch、GPU 或模型本身。二十二、现在重新看一个 LLM它已经没那么神秘了在这一篇以前一个大模型可能只是Prompt ↓ LLM ↓ Answer中间整个LLM都是黑盒。现在可以把它展开LLM │ ┌─────────────┼─────────────┐ │ │ │ ▼ ▼ ▼ Config Tokenizer Weights │ │ │ ▼ ▼ ▼ Transformer Token Parameter Architecture │ │ │ ▼ ▼ │ Token IDs Weight Tensor │ │ │ └─────────────┴──────┬──────┘ │ ▼ PyTorch │ ▼ GPU VRAM从 Infra 角度来看一个已经训练好的 LLM 最重要的几样东西其实就是Architecture Parameters Tokenizer Context而我们今天真正亲手验证的是Model File ↓ Parameters ↓ PyTorch Tensor ↓ System RAM ↓ GPU VRAM这一条链。二十三、总结这一篇我们终于从PyTorch真正进入Model这一层。最值得记住的几个概念是Parameter Weight Tokenizer Token Transformer Prompt Context第一所谓 0.5B、7B、70B首先描述的是模型 Parameter 的数量。第二模型 Parameter 最终仍然是 Tensor。所以前面学过的shape dtype device到了 Model 依然成立。第三Parameter Count × dtype可以粗略估算模型 Weight 需要多少空间。例如7B × FP16 ≈ 14GB终于有了完整含义。第四Tokenizer 是人类文字进入模型之前必须经过的一层。真正进入模型的是Token IDs而不是字符串本身。第五Model Loading 和 Model 进入 GPU 不是同一个动作。可以简单分成Model File ↓ System RAM以及System RAM ↓ PCIe ↓ GPU VRAM两个阶段。第六模型权重是 LLM 显存里最基础、最稳定的一块长期资源。至于真正执行请求以后显存里还会出现什么我们还没有展开。这恰好就是下一篇的问题。二十四、下一篇为什么自然会出现 Prefill 和 Decode到现在我们已经完成GPU ↓ CUDA ↓ PyTorch ↓ LLM模型终于真正躺在GPU VRAM里了。但我们还没有认真研究当一个 Prompt 真正送进这个模型以后到底发生了什么例如请解释一下什么是 AI Infra。经过Tokenizer以后变成几十个 Token。接下来呢为什么模型不是一次把整个答案算出来为什么 ChatGPT、Qwen 这些模型回答问题时看起来都是一个 Token ↓ 一个 Token ↓ 再一个 Token不断往外生成Prompt 越长为什么第一个字往往等得越久一次请求为什么还会继续增加显存模型明明已经全部放在 GPU 里了新的显存又是谁占掉的于是下一步我们终于要把Inference真正打开。