前言当我们在本地部署大模型时最常见的问题之一就是显存不足OOM, Out of Memory。很多人遇到这个问题后的第一反应往往是更换更高配置的显卡或者直接换用更小的模型。但实际上在真正做工程部署时更合理的思路通常是先尝试通过量化或降低精度的方式优化显存占用再决定是否需要升级硬件或调整模型规模。通常来说在 Transformers 中缓解显存压力主要有两类常见方式**调整dtype降低模型加载精度**例如将float32降为float16或bfloat16从而减少模型权重占用的显存。**使用bitsandbytes进行更进一步的低比特量化**例如将模型以int8甚至fp4/nf4的形式加载在显存节省方面会更加明显。这两种方式虽然都能达到“减少显存占用”的目的但它们的原理、适用场景以及使用方法并不完全相同。下面我们就先介绍一下量化的概念后再分别来介绍它们的概念与实际用法。基础理论在大语言模型的部署过程中量化Quantization是最常见、也最实用的一类优化手段。它的核心目标并不复杂即用更少的比特来表示模型中的参数和计算过程从而降低显存占用、减少内存带宽压力并尽可能提升推理效率。对于参数规模动辄数十亿、上百亿的大模型来说如果始终使用 FP32 这样的高精度格式模型往往很难在普通消费级显卡上运行。因此量化几乎已经成为大模型落地部署时绕不开的一步。量化的类型从本质上看量化做的事情就是把原本用高精度浮点数表示的权重或激活值映射到更低精度的数据格式中例如 FP16、BF16、INT8甚至进一步压缩到 INT4、1-bit 或 1.58-bit。这样做的直接收益是存储成本显著下降。例如同样一组参数如果从 FP32 变成 INT8那么理论上存储空间可以缩小为原来的四分之一。但与此同时数值表达能力也会下降因此量化本质上是在“压缩效率”与“精度损失”之间做平衡。从数据表示角度来说位数越多能表示的数值范围通常越大精度也越高位数越少表示能力就越有限。于是量化不可避免地会带来量化误差也就是原始值与量化后再还原出来的值之间的偏差。这个误差如果过大就可能影响模型输出质量。因此量化并不是简单地“把数字变小”而是要尽可能在有限的表示空间中保留原始分布的重要信息。就比如像下面两张图一样左侧原图能够使用大量连续且丰富的颜色来表示画面细节因此整体看起来更加自然、平滑而右侧量化后的图像由于可用颜色数量被大幅压缩只能从有限的几种颜色中选择最接近的值来表示原始内容因此虽然整体场景依然保留但局部细节、颜色过渡和纹理表现都会变得更粗糙放大后还能明显看到颗粒感和色块感。对称量化与非对称量化量化时一个核心问题是如何把原来的浮点数范围映射到低比特整数空间中。围绕这一点最常见的两种方法是对称量化和非对称量化。对称量化的思路比较直接它通常以 0 为中心把浮点范围映射到一个对称的整数区间中例如 [−127,127]。这类方法实现简单计算开销也较低因此在很多场景下使用广泛。典型做法是根据张量中的最大绝对值来确定缩放范围也就是常见的 absmax 量化。比如在下面的例子中这里数值的最大值为 10.8最小值为 -7.59。但这里的量化并没有以 [−7.59,10.8] 作为量化区间而是用了一个关于 0 对称的区间[−α, α][−10.8, 10.8]。也就是说虽然最小值只有 -7.59但为了“对称”左边也扩展到了 -10.8。这就是对称量化的第一个关键点先找绝对值最大的那个数再构造 [−α,α][-\alpha, \alpha][−α,α] 的对称区间。然后由于要映射到 INT8 格式所以其值的大小最多就是 。因此一般情况下值应该是从 [−128, 127]这里之所以不是 128 到 -128 是因为 0 也算是一个点。但是由于需要对称量化因此这里也需要变成 [−127, 127]。最后通过等比例缩放的方式获取到对应的近似值比如 10.8 对应的就是 127而 5.47 对应的就是 36。对于对称量化而言其最大的优点就是实现非常简单计算也很高效。但问题就是当数据分布不对称时容易浪费量化区间误差可能更大。而非对称量化则不要求以 0 为中心而是将原始数据的最小值和最大值分别映射到量化区间的边界。为了实现这种偏移映射通常需要额外引入一个零点zero point。这种方法对分布不对称的数据更灵活有时能更充分利用整数表示空间但实现和计算会稍复杂一些。比如说这里就直接采用最大值和最小值来进行映射即量化区间就是 [β,α][−7.59, 10.8]。这也体现出其是按照真实数据分布的最小值和最大值来映射不强行围绕 0 对称。然后对于 INT8 而言其也是把全部范围都进行使用即 [−128,127]这样能尽可能把真实范围压满整个整数空间。但是同样的由于不对称需要的计算公式会比较复杂需要先根据浮点区间和整数区间算出 scale再求出一个 zero-point使得浮点最小值能够对齐到整数最小值从而让整个浮点区间线性贴合到整数区间上。这也是为什么整体效率也会低一些但是效果会更好一些。可以简单理解为对称量化更简洁非对称量化更灵活我们可以通过下图清晰地看到两者的区别权重量化与激活量化在大模型中被量化的对象通常分为两类权重weights和激活activations。权重是模型训练完成后就固定下来的参数因此它们是静态的、可提前分析的。这使得权重量化相对更容易也是部署中最常见的量化对象。很多量化方法主要就是围绕权重量化展开的下面主要讲的就是该部分的量化。比如一个大模型参数量很多FP32每个参数 4 字节FP16每个参数 2 字节INT8每个参数 1 字节INT4每个参数 0.5 字节假如从 FP32 变成 INT4那就节省了 8 倍的存储和显存开销了。而激活则不同。激活值是输入经过模型每一层计算后动态产生的它会随着具体输入内容而不断变化因此更难提前确定其分布范围。这意味着激活量化往往比权重量化更复杂也更容易影响模型效果。一般情况下我们不需要考虑激活部分的量化只有到极致优化显存消耗的端侧模型才需要进行考虑。训练后量化与量化感知训练从应用流程看量化大体可以分为两类训练后量化PTQ, Post-Training Quantization和量化感知训练QAT, Quantization Aware Training。训练后量化是指模型训练完成之后再对其进行量化。这种方式最大的优点是简单高效不需要重新训练模型因此在大模型部署中非常流行。像 GPTQ、GGUF 等常见方案本质上都可以归入这一思路。我们下面所讲的量化都属于这个类型。量化感知训练则更进一步它会在训练或微调阶段就把量化过程考虑进去。常见做法是训练时插入“假量化fake quantization”也就是前向看起来像低精度计算但参数更新仍在较高精度下进行。这样模型会在训练过程中逐步适应量化带来的误差因此通常能比训练后量化取得更好的低比特性能。不过它的代价也更高实现复杂度更大。比如 QLoRA 方法就属于这一范畴。总的来说随着大模型规模不断增大单纯依赖更强的硬件并不是长久之计。相比之下量化提供了一条更具工程价值的路径即在不显著改动模型结构的前提下直接降低部署成本。尤其是在本地部署、边缘设备推理、消费级显卡运行等场景中量化几乎是决定模型能否真正落地的关键技术之一。因此可以把量化理解为它不是改变模型“学到了什么”而是改变模型“如何更高效地存储和计算这些知识”。它解决的核心问题并不是提升模型上限而是让模型在有限硬件条件下仍然具备可用性。代码实操dtype 参数详解在前面讲解模型加载参数时我们提到过dtype的主要作用是指定模型权重在加载时所使用的数据类型。不同的数据类型会直接影响模型的显存占用、计算速度以及数值稳定性。在深度学习里最常见的三种 dtype 是float32FP32float16FP16bfloat16BF16下面我们分别解释它们的区别。torch.float32model AutoModelForCausalLM.from_pretrained( pretrained_model_name_or_pathmodel_path, trust_remote_codeTrue, device_mapauto, dtypetorch.float32,)torch.float32也就是我们常说的 FP32是深度学习里最“标准”的浮点数精度之一遵循IEEE 754 binary32规范。在模型推理语境下它最大的价值不是“跑得快”而是数值表达最稳定、误差最小、行为最可预期——尤其适合做对照实验、排查数值问题、验证实现是否正确。但 FP32 的代价也很直接其非常吃显存/内存与带宽FP32 每个参数 4 字节而 FP16/BF16 是 2 字节。在本地部署大模型时很多时候并不是算力不够而是模型权重、KV Cache、以及中间激活占用把显存顶满了。此时若仍用 FP32 加载权重往往会很快触发 OOM。因此在真实的推理部署中FP32 通常不是默认首选而更多扮演“基准线baseline”的角色然后用它来确认模型在最高精度下的正确性与上限表现再决定是否切换到 FP16/BF16 或量化精度。FP32 浮点数由三部分组成Sign符号位决定正负Exponent指数位决定动态范围能表示多大/多小的数Fraction / Mantissa尾数位决定精度数值刻度有多细对于FP32binary32其 bit 结构是1 8 238 位指数 → 提供非常宽的动态范围范围从-3.4e38到3.4e3823 位尾数 → 提供较高的数值分辨率细腻的刻度torch.float16model AutoModelForCausalLM.from_pretrained( pretrained_model_name_or_pathmodel_path, trust_remote_codeTrue, device_mapauto, **dtypetorch.float16,**)torch.float16也就是我们常说的FP16同样遵循 IEEE 754 的浮点数规范binary16。在模型推理语境下FP16 的核心价值不是“更高级”而是一个非常务实的取舍用可接受的数值近似换来显著的显存节省与更高的推理吞吐潜力。也正因为如此FP16 往往是本地部署大模型时的“默认候选精度”之一。对于FP16binary16其 bit 结构是1 5 105 位指数范围从-65504到65504 → 动态范围显著小于 FP3210 位尾数 → 数值分辨率也低于 FP32由于指数位下降FP16 的两个典型风险会更容易出现overflow溢出 → inf当中间值/激活/logits 峰值过大时更容易发生underflow下溢 → 0当数值非常小比如训练中的小梯度更容易被“舍掉”但和 FP32 相比其最直观的差别在于存储成本。FP16 每个参数2 字节而 FP32 是4 字节。这意味着只看“权重加载”这一项FP16 通常能让显存压力直接减半而在推理中大量的权重读取会受到显存带宽影响数据量更小也往往更利于吞吐具体是否更快还取决于算子实现、batch size 以及是否 memory-bound。因此假如你希望更省显存让模型更容易装进消费级显卡或你愿意接受少量的数值近似通常对推理质量影响不大时可以考虑使用 FP16。需要注意的是假如遇到以下情况比如训练时经常 NaN/不稳定推理场景包含极长上下文或特别极端的输入分布输出一致性要求极高且希望有可对照的稳定基准此时先用 FP32 建 baseline此时可能需要谨慎使用 FP16 进行推理。torch.bfloat16model AutoModelForCausalLM.from_pretrained( pretrained_model_name_or_pathmodel_path, trust_remote_codeTrue, device_mapauto, **dtypetorch.bfloat16,**)torch.bfloat16也就是BF16虽然同样是 16 位浮点数但它和float16的内部结构并不一样。相比float16bfloat16拥有更大的数值范围因此在很多情况下能够兼顾较低的显存占用和更好的数值稳定性。BF16 浮点数的结构是1 8 78 位指数和FP32 一样多→动态范围更接近 FP327 位尾数比 FP16/FP32 都少 →精度更粗所以在推理中BF16 的优势往往体现在动态范围更大尤其在以下场景更有价值长上下文 / 长序列累积数值链路变长时溢出风险更高logits 分布极端softmax 前值可能很大某些模型在 FP16 下偶发输出异常换 BF16 能明显改善一致性也正因如此bfloat16在近年来的大模型训练与推理中越来越常见。很多新硬件和主流框架也都优先支持 BF16。但由于BF16 的尾数位更短意味着它的数值分辨率更低。同样一个区间内BF16 可表示的“刻度点”比 FP16 更少因此在非常细微的数值差异上可能更容易丢失细节这通常在训练时更容易被感知在推理中多数任务影响不大但并非完全无感。总的来说对比 FP16 和 BF16 可以简单理解为float16更强调节省显存和提升速度bfloat16在节省显存的同时通常有更好的稳定性因此如果你的硬件支持 BF16那么它往往会是一个非常值得优先尝试的选择。精度对比分析前面大多数情况下我们对比的都是指数位的差别。那我们这里可以举一个简单的例子来演示一下其中精度的区别比如对于 1/3 而言我们都知道其是一个无穷数 0.33333…当然由于存在浮点近似误差所以显示的值并完全一致value 1/3print(format(value, .60f))这里我们通过 format() 打印出了后六十位的值0.333333333333333314829616256247390992939472198486328125000000那假如此时我们将其转化为 fp32 的格式的话tensor_fp32 torch.tensor(value, dtype torch.float32)print(ffp32 tensor: {format(tensor_fp32.item(), .60f)})同样打印后 60 位可以看出显然精度降低了非常多但是还能保持前面是 8 位是 0.33…fp32 tensor: 0.333333343267440795898437500000000000000000000000000000000000假如进一步转化为 fp16 的话tensor_fp16 torch.tensor(value, dtype torch.float16)print(ffp16 tensor: {format(tensor_fp16.item(), .60f)})此时就只能保证前面 3 位了而且后面大量的内容都变成 0 了fp16 tensor: 0.333251953125000000000000000000000000000000000000000000000000最后再测试一下精度最低的 bf16tensor_bf16 torch.tensor(value, dtype torch.bfloat16)print(fbf16 tensor: {format(tensor_bf16.item(), .60f)})此时的精度差距就更大了剩下九位数字了bf16 tensor: 0.333984375000000000000000000000000000000000000000000000000000显存消耗对比那既然我们前面提到了更换精度能够节省显存那到底能节省多少呢下面我们就用例子来真实的展示一下。这里我让 AI 给我生成了一段监控显存使用峰值的代码import torchdef format_gb(num_bytes: int) - str: 将“字节数”转换为更直观的 GB 字符串显示。 参数: num_bytes: 显存占用单位字节 返回: 形如 1.234 GB 的字符串 returnf{num_bytes / (1024**3):.3f} GBdef get_cuda_devices(): 返回当前进程可用的 GPU id 列表一定是 0..N-1。 同时做一次 CUDA 上下文初始化避免某些 notebook 环境的异常。 ifnot torch.cuda.is_available(): return [] # 触发 CUDA 上下文初始化很轻量 _ torch.cuda.current_device() n torch.cuda.device_count() return list(range(n))def reset_peaks(devices): 对“有效设备 id”重置峰值统计避免 invalid device。 ifnot torch.cuda.is_available(): return n torch.cuda.device_count() for d in devices: if0 int(d) n: torch.cuda.reset_peak_memory_stats(int(d))def sync_all(devices): 同步指定 GPU等待 GPU 上已经提交的 CUDA 运算全部执行完成。 CUDA 默认是异步执行的 - Python 代码可能继续往下走 - GPU 上的计算与显存分配/释放可能还没完成 如果不 synchronize 就立刻读取 max_memory_allocated 可能读到偏小/不稳定的峰值。 参数: devices: List[int]需要同步的 GPU device id 列表 for d in devices: torch.cuda.synchronize(d)def report_peak_allocated(tag: str, devices): 打印指定 GPU 的“峰值实际分配显存”peak_allocated。 peak_allocated 的含义 从最近一次 reset_peak_memory_stats() 之后开始计算 PyTorch 在该 GPU 上“实际分配给张量/中间结果”的显存占用的最大值。 注意 - 这是 PyTorch 统计的“分配给张量”的峰值不包含驱动/其他进程占用的显存。 - 多卡情况下这里也会打印所有 GPU 峰值的求和ALL GPUs SUM。 参数: tag: 本次统计的阶段标签例如“模型加载阶段”“生成阶段” devices: List[int]需要统计的 GPU device id 列表 print(f\n {tag} 峰值显存peak_allocated ) total 0 for d in devices: peak torch.cuda.max_memory_allocated(d) total peak print(fGPU {d}: peak_allocated {format_gb(peak)}) if len(devices) 1: print(fALL GPUs SUM: peak_allocated {format_gb(total)})然后我们可以将其加入到我们的代码中进行使用每一次都需要先清空再进行指定def main(): model_path rD:\微调与部署\qwen devices get_cuda_devices() ifnot devices: print(当前环境未检测到 CUDA GPU无法统计显存峰值。) return # 1) 模型加载阶段峰值含权重搬到 GPU reset_peaks(devices) tokenizer AutoTokenizer.from_pretrained( pretrained_model_name_or_pathmodel_path, use_fastTrue, local_files_onlyTrue ) model AutoModelForCausalLM.from_pretrained( pretrained_model_name_or_pathmodel_path, trust_remote_codeTrue, device_mapauto, dtypetorch.float32, ) sync_all(devices) report_peak_allocated(模型加载阶段, devices) # 2) 生成阶段峰值更贴近推理峰值含激活/KV cache reset_peaks(devices) user_prompt 你是谁 messages [{role: user, content: user_prompt}] text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue, enable_thinkingFalse, ) inputs tokenizer([text], return_tensorspt).to(model.device) with torch.inference_mode(): generated model.generate( **inputs, max_new_tokens60, ) sync_all(devices) report_peak_allocated(生成阶段含 KV Cache, devices) # 输出答案 new_tokens generated[0][inputs[input_ids].shape[1]:] answer tokenizer.decode(new_tokens, skip_special_tokensTrue).strip() print(\n 模型回答 ) print(answer)if __name__ __main__: main()然后当我们的 dtype 设置为 float 32 时可以看到此时的峰值显存为 2.808 GB所以很多时候说 0.6B 的模型只要有 3GB 显存的显卡就能跑 模型加载阶段 峰值显存peak_allocated GPU 0: peak_allocated 2.808 GB 生成阶段含 KV Cache 峰值显存peak_allocated GPU 0: peak_allocated 2.827 GB 模型回答 我是你的智能助手我是你可以随时联系我的朋友。你可以向我提问或者分享一些事情我会尽力帮助你。有什么我可以帮你的吗当我们将值调整为 float16 时其峰值显存就会降到 1.5 GB 左右 模型加载阶段 峰值显存peak_allocated GPU 0: peak_allocated 1.408 GB 生成阶段含 KV Cache 峰值显存peak_allocated GPU 0: peak_allocated 1.421 GB 模型回答 我是你的虚拟助手我由AI模型生成旨在帮助你解决问题、提供支持或分享知识。如果你有任何问题或需要帮助请随时告诉我类似的调整成 bfloat16 的话显存消耗和 float16 是非常接近的但会低一点点 模型加载阶段 峰值显存peak_allocated GPU 0: peak_allocated 1.401 GB 生成阶段含 KV Cache 峰值显存peak_allocated GPU 0: peak_allocated 1.420 GB 模型回答 我是你的虚拟助手我将尽我所能为你提供帮助和支持。有什么可以帮助你的吗所以可以看出在显存消耗方面fp32 确实会比 fp16 或 bf16 要高一倍左右。并且在对一些问题的回复上结果差别并不是很大。小结总的来说FP32、FP16 和 BF16 都属于深度学习中常见的浮点数表示格式它们之间最核心的区别在于数值范围、表示精度以及显存开销。其中FP32精度最高、数值范围也较大计算最稳定但显存占用最多FP16在显存占用上更有优势计算效率也更高但由于指数位较少动态范围较小更容易出现上溢或下溢问题而BF16虽然尾数位更少、精度略低于 FP16但由于其指数位与 FP32 一致因此拥有接近 FP32 的动态范围在大模型训练与推理中通常表现出更好的数值稳定性。因此在实际应用中这三者并不存在绝对的“谁更好”而是要根据具体场景进行选择如果更关注计算稳定性可以优先考虑 FP32如果希望在有限显存下获得更高效率则可以考虑 FP16而如果硬件支持并且希望兼顾低显存占用与训练稳定性那么 BF16 往往是更合适的选择。理解这三种数据格式的差异有助于我们在后续进行模型训练、推理优化与量化部署时做出更加合理的技术选择。bitsandbytes 参数详解除了通过dtype调整浮点精度之外Huggingface 官方还比较推荐使用bitsandbytes来实现更进一步的低比特量化。与float16、bfloat16这种“降低浮点精度”的方式不同bitsandbytes更像是在模型加载阶段直接把权重压缩到更低的表示形式例如8 位整数int8。这样做的最大好处就是显存占用会进一步下降从而让原本无法在本地运行的大模型也有机会在有限显存的设备上完成部署。8bit 量化与前面几种浮点数不同在 bitsandbytes 中使用的 8bit 量化方式是 INT8 量化。INT8 属于8 位整数8-bit Integer表示也就是说每个数值只使用8 个比特来存储总共能够表示 个不同的离散数值。比如这里显示的 10001001 其实就等于值 137。通常在有符号整数的情况下INT8 的取值范围为-128 到 127。本质上就是把第一个比特变为负数其他都保持正数。比如这里同样是 10001001那得到的值就是 -119 了。相比 FP32 每个数需要 32 位来表示INT8 只需要 8 位因此单从存储角度来看理论上显存占用可以降到原来的1/4。不过INT8 和 FP16、BF16 最大的不同在于它不再使用浮点方式直接表示小数而是使用有限的整数区间去近似原本连续的浮点数值。这意味着原始模型中的权重或激活值不能直接原样存成 INT8而是需要先经过一个“映射”过程把浮点数压缩到 INT8 的整数范围中在真正计算时再根据对应的缩放参数将其近似还原。这个过程其实就是量化最核心的思即用更少的比特去近似表示原本更精细的数值。因此INT8 的优势非常明显它能够大幅降低模型存储成本并且在许多硬件平台上还能带来更高的推理效率所以在大模型部署中非常常见。尤其是在推理场景下INT8 往往是一种兼顾性能、显存和效果的平衡方案。当然它的代价也同样存在那就是由于整数表示的离散程度远高于浮点数模型在量化后会不可避免地产生一定的量化误差。如果这种误差较小那么模型整体效果通常仍能保持较好但如果压缩得过于激进或者某些层对精度特别敏感就可能导致输出质量下降。假如我们在模型推理中要使用bitsandbytes的量化我们需要先下载该库pip install bitsandbytes在 bitsandbytes 中使用 INT8 量化时通常通过 BitsAndBytesConfig(load_in_8bitTrue) 来完成配置并在 from_pretrained() 加载模型时通过 quantization_config 参数传入。这样模型会先以 fp16 的方式进行加载然后再在推理时进行量化从而在大幅降低推理显存占用的同时尽量保留关键计算部分的精度from transformers import AutoModelForCausalLM, **BitsAndBytesConfig**quant_config BitsAndBytesConfig(**load_in_8bitTrue**)# 不需要在写入 dtype 参数了model AutoModelForCausalLM.from_pretrained( model_path, **quantization_configquant_config,** device_mapauto, local_files_onlyTrue, trust_remote_codeTrue)除了最基础的load_in_8bitTrue官方还提供了一些常见配置参数动态平衡 CPU 和 GPUllm_int8_enable_fp32_cpu_offload如果显存还是不够可以启用CPU offload。我们可以使用 llm_int8_enable_fp32_cpu_offloadTrue 来进行实现from transformers import BitsAndBytesConfigquant_config BitsAndBytesConfig( load_in_8bitTrue, llm_int8_enable_fp32_cpu_offloadTrue)这个参数的含义是允许部分权重放到 CPU 上并且这些被 offload 到 CPU 的权重会保持 FP32而不是 8-bit。 这适合显存比较紧张、但又想尽量跑更大模型的场景。跳过某些模块不做 INT8 llm_int8_skip_modules有些模型的某些层对量化比较敏感这时可以跳过指定模块quant_config BitsAndBytesConfig( load_in_8bitTrue, llm_int8_skip_modules[lm_head])这表示例如 lm_head 不参与 8-bit 量化而保留原精度。这个配置属于进阶调参项通常在模型兼容性或效果调优时使用。Transformers 的量化配置类中支持这类参数。需要注意的是官方文档强调8-bit 权重本身不能像普通全精度权重那样直接训练但你可以在其上训练额外参数比如 adapter / LoRA 这类附加参数。4bit 量化4-bit 量化是进一步压缩模型权重的一种低比特方案。在 bitsandbytes 中它通常并不是简单意义上的普通 int4而是基于 FP4 或 NF4 等 4-bit 量化类型来实现。其中 NF4 还是 QLoRA 中非常常见的一种数据类型。相比 8-bit 量化4-bit 能带来更激进的显存节省效果因此在显存非常有限的情况下尤其有价值但与此同时也往往意味着对模型效果的影响可能更明显使用方式和配置通常更复杂一些对底层库、量化配置以及硬件环境的要求也可能更高。尽管如此4-bit 量化依然是当前大模型轻量化中的重要方案尤其是在 QLoRA 和本地低成本部署场景中应用非常广泛。官方文档中也指出bitsandbytes 的 4-bit 线性层是 QLoRA 方案的重要基础。这部分内容将在后续 LoRA 微调章节会进一步讲解。在实操中最常见的写法就是通过 BitsAndBytesConfig(load_in_4bitTrue) 在模型加载时把线性层替换成 bitsandbytes 的 4-bit 量化层from transformers import AutoModelForCausalLM, BitsAndBytesConfig**quant_config BitsAndBytesConfig(load_in_4bitTrue)**# 不需要在写入 dtype 参数了model AutoModelForCausalLM.from_pretrained( model_path, **quantization_configquant_config,** device_mapauto)但实际使用时通常不会只写 load_in_4bitTrue而是配合 bnb_4bit_quant_type、bnb_4bit_compute_dtype、bnb_4bit_use_double_quant 等参数进一步控制量化格式与计算精度。官方文档中把这套 4-bit 路线主要放在 QLoRA 语境下介绍也就是“模型 4-bit 量化 LoRA 微调”import torchfrom transformers import AutoModelForCausalLM, BitsAndBytesConfigquant_config BitsAndBytesConfig( load_in_4bitTrue, # 开启 4bit 量化 bnb_4bit_quant_typenf4, # 量化类型nf4 / fp4 bnb_4bit_compute_dtypetorch.bfloat16, # 计算时使用 bf16 bnb_4bit_use_double_quantTrue # 开启双重量化)model AutoModelForCausalLM.from_pretrained( model_path, quantization_configquant_config, device_mapauto, local_files_onlyTrue, trust_remote_codeTrue)bnb_4bit_quant_type这个参数决定 4-bit 的量化数据类型。官方支持两种其中官方文档特别提到 NF4 是一种更适合“权重接近正态分布”场景的 4-bit 数据类型因此在 QLoRA 场景里很常见。NF4NormalFloat4一种专门为接近正态分布的权重数据设计的 4-bit 量化数据类型通常在 QLoRA 场景下表现更好。FP4Float4一种标准的 4-bit 浮点量化格式。bnb_4bit_compute_dtype这个参数控制的是虽然权重是 4-bit 存储但实际计算时用什么精度来算是可以自行来进行设定的。比如可以设成官方文档说明这个计算精度可以和输入类型不同例如输入可能是 FP32但计算可以设成 BF16 来提速。torch.float32torch.float16torch.bfloat16bnb_4bit_use_double_quant这个参数表示开启 nested quantization / double quantization双重量化也就是对第一次量化得到的一些量化常数再次量化以进一步节省内存。虽然对于 4bit 或 8bit 量化而言其能够大大节省显存的消耗但是推理速度上量化后可能反而会更慢主要原因是推理时还会有反量化、混合精度计算、离群值处理等额外开销而 4bit 比 8bit 更激进因此常常又会更慢一些。所以假如要速度快又节省显存的话比较推荐使用 FP16 或 BF16 的方案会更合适一些。显存消耗对比那我们可以和前面 fp32fp16 和 bf16 一样去看一下 8bit 量化和 4bit 量化后的显存消耗。这里只需要结合前面 AI 写好的函数将代码改造成下面这样即可def main(): model_path rD:\微调与部署\qwen devices get_cuda_devices() ifnot devices: print(当前环境未检测到 CUDA GPU无法统计显存峰值。) return # 1) 模型加载阶段峰值含权重搬到 GPU reset_peaks(devices) tokenizer AutoTokenizer.from_pretrained(pretrained_model_name_or_pathmodel_path, use_fastTrue,local_files_onlyTrue) quant_config BitsAndBytesConfig(load_in_8bitTrue) # 不需要在写入 dtype 参数了 model AutoModelForCausalLM.from_pretrained( model_path, quantization_configquant_config, device_mapauto, local_files_onlyTrue, trust_remote_codeTrue ) sync_all(devices) report_peak_allocated(模型加载阶段, devices) # 2) 生成阶段峰值更贴近推理峰值含激活/KV cache reset_peaks(devices) user_prompt 你是谁 messages [{role: user, content: user_prompt}] # 关键用 Qwen3 的 chat template 生成“模型真正想要的输入文本” text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue, enable_thinkingFalse, # 先关闭 thinking输出更直接 ) inputs tokenizer([text], return_tensorspt).to(model.device) with torch.inference_mode(): generated model.generate( **inputs, max_new_tokens60, ) sync_all(devices) report_peak_allocated(生成阶段含 KV Cache, devices) new_tokens generated[0][inputs[input_ids].shape[1]:] answer tokenizer.decode(new_tokens, skip_special_tokensTrue).strip() print(\n 模型回答 ) print(answer)if __name__ __main__: main()此时我们使用 8bit 量化的情况下虽然模型加载阶段显存使用和 16bit 的差别不大但是在推理生成阶段还是降低了一倍 模型加载阶段 峰值显存peak_allocated GPU 0: peak_allocated 1.373 GB 生成阶段含 KV Cache 峰值显存peak_allocated GPU 0: peak_allocated 1.056 GB 模型回答 我是一个AI助手我帮助你解决问题和提供支持。你可以告诉我你需要什么帮助我会尽力为你服务。这个在 4bit 量化的时候就更明显了我们只需要把 quant_config 调整一下quant_config BitsAndBytesConfig( load_in_4bitTrue, # 开启 4bit 量化 bnb_4bit_quant_typenf4, # 量化类型nf4 / fp4 bnb_4bit_compute_dtypetorch.bfloat16, # 计算时使用 bf16 bnb_4bit_use_double_quantTrue # 开启双重量化 )虽然模型加载阶段的峰值显存也是 1.4 GB 左右但是推理生成阶段直接降到了 0.822 模型加载阶段 峰值显存peak_allocated GPU 0: peak_allocated 1.377 GB 生成阶段含 KV Cache 峰值显存peak_allocated GPU 0: peak_allocated 0.822 GB 模型回答 我是AI助手我是一台基于大语言模型训练出来的智能助手。我能够帮助你回答问题、提供信息、进行对话并且支持多语言交流。如果你有任何问题我非常乐意为你提供帮助从这里我们也可以看出4bit 和 8bit 量化主要是在推理生成阶段大幅度降低了显存的消耗能够让我们在更小的模型中进行推理。总结本节内容围绕大模型量化与精度控制展开核心目的是帮助大家理解当本地部署大模型时面对显存不足的问题并不一定只能依赖“换更大的显卡”来解决更常见、也更具工程价值的思路是通过降低数值精度或使用低比特量化在有限硬件条件下让模型尽可能顺利地运行起来。首先从理论层面来看我们明确了量化的本质它不是改变模型学到的知识而是改变这些知识的存储与计算方式。通过将原本高精度的浮点表示压缩到更低精度的数据格式中我们可以有效减少显存占用和内存带宽压力但与此同时也必须接受一定程度的量化误差。因此量化本质上始终是在**“资源节省”与“效果保真”**之间寻找平衡。在具体原理上我们介绍了几组重要概念对称量化与非对称量化前者实现更简单、计算更高效后者对数据分布的适应性更强权重量化与激活量化前者更常见、更容易落地后者更复杂通常只在极致优化场景中才重点考虑训练后量化PTQ与量化感知训练QAT前者更适合部署阶段直接使用后者则更适合对低比特性能有更高要求的训练场景。在实际工程中我们又从两条最常见的优化路径展开了实操说明。第一条路径是通过dtype控制模型加载精度也就是在FP32、FP16、BF16之间进行选择。这里的关键不只是“谁占显存更少”更重要的是理解它们在动态范围、表示精度和数值稳定性上的差异FP32最稳定、最准确但显存消耗最大更适合做基准测试或排查数值问题FP16显存占用更低推理速度潜力更高但数值范围较小更容易出现溢出和下溢BF16在显存上与 FP16 相近但由于拥有和 FP32 一样的指数位因此往往能提供更好的数值稳定性是当前很多大模型推理和训练中非常值得优先尝试的精度方案。第二条路径则是使用bitsandbytes做更进一步的低比特量化也就是通过8bit / 4bit的方式进一步压缩权重存储。其中8bit 量化是一种比较常见的折中方案通常能在显存节省和模型效果之间取得较平衡的结果4bit 量化更激进显存节省效果更明显但同时也意味着更高的量化误差风险、更复杂的配置方式以及更高的兼容性要求。尤其需要强调的是显存更低并不一定代表推理更快。在真实部署中4bit 和 8bit 量化虽然能明显降低模型加载时的显存占用但由于还会涉及反量化、混合精度计算、离群值处理等额外开销因此推理速度有时反而可能比 FP16 / BF16 更慢。也正因为如此在很多本地部署场景下如果你的主要目标是让模型先跑起来、尽量省显存那么 8bit / 4bit 往往非常有价值如果你的目标是兼顾速度与较好的稳定性那么 FP16 或 BF16 通常会是更实用的选择。总的来说这一部分内容想传达的核心思想可以归结为一句话量化与精度控制并不是“有没有必要学”的附加知识而是本地部署大模型时最基础、最关键的工程能力之一。只有真正理解了不同数值格式和量化方式的原理、优缺点与适用场景我们在面对显存不足、速度不理想、效果下降等实际部署问题时才能不只是“试着改参数”而是能够有依据地做出合理的技术选择。后续当我们继续进入 LoRA、QLoRA、模型微调与更大规模模型部署时这部分关于FP32 / FP16 / BF16 / INT8 / INT4的理解也会成为非常重要的前置基础。说真的这两年看着身边一个个搞Java、C、前端、数据、架构的开始卷大模型挺唏嘘的。大家最开始都是写接口、搞Spring Boot、连数据库、配Redis稳稳当当过日子。结果GPT、DeepSeek火了之后整条线上的人都开始有点慌了大家都在想“我是不是要学大模型不然这饭碗还能保多久”我先给出最直接的答案一定要把现有的技术和大模型结合起来而不是抛弃你们现有技术掌握AI能力的Java工程师比纯Java岗要吃香的多。即使现在裁员、降薪、团队解散的比比皆是……但后续的趋势一定是AI应用落地大模型方向才是实现职业升级、提升薪资待遇的绝佳机遇这绝非空谈。数据说话2025年的最后一个月脉脉高聘发布了《2025年度人才迁徙报告》披露了2025年前10个月的招聘市场现状。AI领域的人才需求呈现出极为迫切的“井喷”态势2025年前10个月新发AI岗位量同比增长543%9月单月同比增幅超11倍。同时在薪资方面AI领域也显著领先。其中月薪排名前20的高薪岗位平均月薪均超过6万元而这些席位大部分被AI研发岗占据。与此相对应市场为AI人才支付了显著的溢价算法工程师中专攻AIGC方向的岗位平均薪资较普通算法工程师高出近18%产品经理岗位中AI方向的产品经理薪资也领先约20%。当你意识到“技术AI”是个人突围的最佳路径时整个就业市场的数据也印证了同一个事实AI大模型正成为高薪机会的最大源头。最后我在一线科技企业深耕十二载见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事早已在效率与薪资上形成代际优势我意识到有很多经验和知识值得分享给大家也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我整理出这套 AI 大模型突围资料包【允许白嫖】✅从入门到精通的全套视频教程✅AI大模型学习路线图0基础到项目实战仅需90天✅大模型书籍与技术文档PDF✅各大厂大模型面试题目详解✅640套AI大模型报告合集✅大模型入门实战训练这份完整版的大模型 AI 学习和面试资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】①从入门到精通的全套视频教程包含提示词工程、RAG、Agent等技术点② AI大模型学习路线图0基础到项目实战仅需90天全过程AI大模型学习路线③学习电子书籍和技术文档市面上的大模型书籍确实太多了这些是我精选出来的④各大厂大模型面试题目详解⑤640套AI大模型报告合集⑥大模型入门实战训练获取方式有需要的小伙伴可以保存图片到wx扫描二v码免费领取【保证100%免费】